Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0eb0545
chore(packaging): point the tap and the bucket at 0.38.2
aakashpawar1999 Sep 6, 2026
f4e555c
Merge pull request #106 from initorigin/chore/tap-0.38.2
aakashpawar1999 Sep 6, 2026
ee3f3e9
chore(deps): pin io-harness 0.82.0 and enable otel, mcp-server and co…
aakashpawar1999 Sep 6, 2026
fe25166
feat(context): the ceiling is the model's real window, not a flat 24,000
aakashpawar1999 Sep 6, 2026
6756feb
feat(approval): the modal says what it takes, and the whole change ca…
aakashpawar1999 Sep 6, 2026
9940219
feat(undo): the rewind asks, and no keystroke arms it
aakashpawar1999 Sep 6, 2026
0dc9975
feat(palette): a command line typed into the palette runs
aakashpawar1999 Sep 6, 2026
89851bb
test(pty): drive the binary at a real terminal, and fix what that found
aakashpawar1999 Sep 6, 2026
c1839f0
fix(interrupt): a stopped turn leaves a record in the transcript, not…
aakashpawar1999 Sep 6, 2026
f2482d3
feat(status): the footer says how much of the bill was cached
aakashpawar1999 Sep 6, 2026
124e204
feat(skills): io skill add takes a folder, with its companions
aakashpawar1999 Sep 6, 2026
f33ec58
feat(events): a picture the agent was handed gets a row, and the cach…
aakashpawar1999 Sep 6, 2026
b03d361
feat(events): a command a turn runs costs one row, not four
aakashpawar1999 Sep 6, 2026
f6230d0
feat(exec): a headless run says what it did, on stderr
aakashpawar1999 Sep 6, 2026
88b9d23
feat(otel): a run exports as OpenTelemetry spans, and io says only wh…
aakashpawar1999 Sep 6, 2026
c482502
feat(codeact): a turn can write one contained program, and the transc…
aakashpawar1999 Sep 6, 2026
23553cb
feat(mcp): io mcp serve offers this install's tools to another agent
aakashpawar1999 Sep 6, 2026
2c9a568
test(docs): no page promises a streaming thought or a fallback of 24,000
aakashpawar1999 Sep 6, 2026
82279dd
feat(context): the collapse lever is visible when it is in force
aakashpawar1999 Sep 6, 2026
3c5bc4f
feat(contain): /contain on offers a fan-out instead of naming four keys
aakashpawar1999 Sep 6, 2026
b424b63
feat(setup): /setup runs the wizard in the session
aakashpawar1999 Sep 6, 2026
b4e088c
feat(acp): the editor door holds several sessions and reopens an old one
aakashpawar1999 Sep 6, 2026
bbbfff7
docs(release): the changelog and the contract for 0.39.0
aakashpawar1999 Sep 6, 2026
9dd3293
fix: the review's findings, and the splash card that contradicted the…
aakashpawar1999 Sep 6, 2026
8129035
docs: reattach the doc comments this release spliced onto their neigh…
aakashpawar1999 Sep 6, 2026
7bddf6e
test(live): the mask arm needs a run opened, not a classifier's judge…
aakashpawar1999 Sep 6, 2026
910ab8b
test(live): the ceiling a real run announces is the model's window
aakashpawar1999 Sep 6, 2026
7ffb798
fix: pass the pty size as a raw pointer
aakashpawar1999 Sep 6, 2026
a60f1c9
Merge pull request #107 from initorigin/feat/0.39.0
aakashpawar1999 Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,166 @@ All notable changes to this project are documented here. The format follows

## [Unreleased]

## [0.39.0] - 2026-09-06

No keystroke loses your work, a running turn shows more of what it is doing, and
everything io-harness has shipped is reachable from the interface it has.

### Fixed

Three ways a session could lose what you had done, all found in one field test on
2026-09-05. These are behaviour changes rather than corrections, which is why this
is a minor release.

- **An approval is a modal, and it says so.** Every key that was not `y`, `a`, `n`,
an arrow or `Enter` was ignored in silence — so an operator who began typing a
sentence saw their words go nowhere with nothing on screen saying the interface
was waiting for one of three keys. The answers row now leads with `press y, a or
n` when the last keystroke was refused, in a word rather than a colour, and
clears on the next key it does take.

- **You can read the whole change before approving it.** The diff was cut at
whatever the viewport had, with `⋯ N more lines` on the last row: approving a
write whose end you cannot see is approving the elision. `Up`, `Down`, `PageUp`,
`PageDown` and `Home` move it under a header that stays put, and none of them
can answer anything.

- **The `+`/`-` header counts the diff drawn under it.** io-harness measures the
fragment an edit replaced while the hunk is the whole file's diff — correct for
a library, wrong for a header sitting on top of the change it claims to count.
The field test met the pair as `+3 -9` above an edit the transcript recorded as
`-9 +10`.

- **Undo asks, and no keystroke arms it.** `Esc` was bound to `esc esc`: the first
press armed and wrote what it would undo into the scrollback, the second acted.
Pressed to dismiss a picker, that footer is a line that has already scrolled and
the second press is not consent. The binding is one chord now and it raises
`/undo`'s own confirmation, which waits for an answer and names what would go
back. **If you have rebound `rewind`, the shipped default has changed from `esc
esc` to `esc`.**

- **A stopped turn leaves a record.** `Ctrl+C` wrote its ending to the footer,
which is gone at the next keystroke, so a turn stopped after doing work left no
sign in the scrollback that it had ever started. It is committed to the
transcript now and says what was kept. The two-press contract is unchanged: the
first asks io-harness to stop at the next step boundary, the second stops now.

- **`Enter` runs a command line you typed.** `/effort high` matched no palette row
— the row is `/effort` and the argument is not part of it — and `Enter` did
nothing at all, so a line copied from these guides sat behind `No row matches`.
It now runs, on that one `Enter`.

- **The session lock followed the store.** Two `io` processes with different
`IO_CONFIG_HOME`s refused each other, because the lock file was keyed on a
session id that is only unique within one `runs.db` while living in one fixed
directory. Found by the new pty harness on its first run.

- **The banner dates what it says.** The card goes into the terminal's own
scrollback and no later frame can reach a row of it, so after `/model` it went
on naming the model the session started with while the footer named the one the
next turn was going to — two model names on screen and nothing saying which.
Its three facts now sit under `opened with — the status line is current`. The
policy and the workspace are dated by the same caption, for the same reason:
both move mid-session and neither could be corrected either.

### Added

- **`io exec` without `--json` says what it did.** One line per tool call and one
per refusal, on stderr; stdout still carries the agent's reply and nothing else.
The README has promised that this door shows what it refused since it was
written, and it was true only in a session and in the JSON stream.

- **The footer separates cached input from fresh.** `52k tok · 44k cached`. The
running figure read as though every re-sent catalogue were paid for in full;
four one-word turns moved it from 8.1k to 52k at a spend of $0.0001 each.

- **A picture the agent was handed gets a row**, naming where it came from — an
MCP reply, a browser screenshot, its own `view_image`, or your attachment — its
type and its size. Something entered the model's context and nothing said so.

- **A command a turn runs costs one row, not four.** The sandbox created, ran and
torn down lines are gone; that the command was contained is on the status line
already.

- **`/setup` runs the wizard here** instead of telling you to leave and run `io
setup`. The theme applies at once and the rest arrives with your next message;
a changed provider or credential still needs `io` restarted, and it says so.

- **`io skill add` takes a folder**, with its companion files, which is the layout
io-harness has always discovered.

- **`/contain on` offers a fan-out** — four agents, two at once, one deep, and a
token ceiling — instead of naming four keys you would have to choose values for.

- **`/context` says when `[run] collapse` is shortening entries**, and how much
survives. That lever has worked since 0.38.2 and appeared on no surface.

- **`io mcp serve`** offers this install's own tools to another agent over MCP on
stdio, under this install's policy, saying which tools it serves and which
eleven it does not.

- **`[codeact]`** lets a turn write one contained program instead of a chain of
tool calls. Absent, nothing changes: the tool catalogue is byte for byte what
0.38.2 sent.

- **`[otel]`** exports a run as OpenTelemetry spans. It says what it configured
and never that anything arrived, because io-harness accounts for a dropped
batch in a log rather than in a value.

- **`io acp` holds several sessions and answers `session/load`**, so an editor can
open two conversations and reopen yesterday's. Both were named limitations in
0.36.0 and 0.38.0.

### Changed

- **The context ceiling is the model's real window, and reading it contacts one
more host.** Every release before this one assembled inside a flat 24,000
tokens on every provider `io` could build, whatever model was configured —
io-harness had the whole sizing path but nothing answered it
(initorigin/io-cli#105, io-harness#266). The pin moves to io-harness 0.82.0,
which gives each provider a source, and **io-cli turns the reference catalogue
on by default for the Anthropic and OpenAI providers**. OpenRouter needs no
opt-in — its catalogue is its own endpoint.

Read this if you upgrade and nothing else. Three consequences, all real:

- **A run costs more per step.** It stops throwing history away at 24,000 and
sends what the model holds. That is the point, and it is not free.
- **One more host is contacted, once per process, before the first step** — and
because io-harness authorises every endpoint a provider declares, an egress
policy that denies it **refuses the run** rather than skipping the lookup. The
refusal names the key that turns it off.
- **A local endpoint now reports a much smaller ceiling than a hosted one.**
That is io-harness being careful about a runtime that publishes nothing, not
`io` shrinking.

`[app.io-cli] reference_catalogue = false` restores the previous behaviour for
those two providers. `[run.context] max_tokens` still sets the ceiling yourself
and still wins over everything. Which catalogue is read is
`[app.io-cli.prices] source_url`'s question, unchanged since 0.24.0.

- **`source: "fallback"` no longer means 24,000.** io-harness 0.82.0 splits it
into one default for a remote endpoint and a much smaller one for a loopback
endpoint. Nothing io-cli ships quotes a number for that rung any more, and a
gate keeps it that way.

### Added

- **`/context` says which rung decided the ceiling**, as a sentence rather than
as io-harness's bare word: what set the number, and what to do if it is an
assumption. `EventKind::ContextCeiling` has carried this since io-harness
0.81.0 and io-cli discarded it.

- **`[app.io-cli] reference_catalogue`**, documented in `docs/CONTRACT.md` and in
the configuration guide. Absent is `true`.

- **`otel`, `mcp-server` and `codeact` are enabled on the io-harness
dependency.** Each adds no crate — `cargo tree --depth 1` is still exactly ten
names — and **enabling `codeact` hands the agent no tool**: `run_program` is
advertised only where a `[codeact]` section configures it, so a session with
none sends the catalogue 0.38.2 sent, byte for byte. That is worth saying
because `media` behaved differently in 0.9.0.

## [0.38.2] - 2026-09-06

The rest of what the 2026-09-05 field test found, and the io-harness pin that turned out to carry
Expand Down Expand Up @@ -3590,6 +3750,7 @@ client, tool, sandbox, policy engine or session store of its own.
- No test in this release asserts on wall-clock time.

[Unreleased]: https://github.com/initorigin/io-cli/compare/v0.38.0...HEAD
[0.39.0]: https://github.com/initorigin/io-cli/compare/v0.38.2...v0.39.0
[0.38.2]: https://github.com/initorigin/io-cli/compare/v0.38.1...v0.38.2
[0.38.1]: https://github.com/initorigin/io-cli/compare/v0.38.0...v0.38.1
[0.38.0]: https://github.com/initorigin/io-cli/compare/v0.37.0...v0.38.0
Expand Down
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "io-cli"
version = "0.38.2"
version = "0.39.0"
edition = "2021"
# io-harness declares 1.95, set by `libsqlite3-sys` calling the std `cfg_select!`
# macro from its build script. Nothing here can be lower than what the harness
Expand Down Expand Up @@ -63,7 +63,24 @@ path = "src/lib.rs"
# still correct about what this *renderer* can be handed; it is simply no longer
# load-bearing on the build. Turning `barcode` back off is what would restore
# it, and `documents` would then have to be spelled out as its other four.
io-harness = { version = "0.81", features = ["media", "browser", "documents"] }
# **`otel`, `mcp-server` and `codeact` are on from 0.39.0, and every one of them
# is claimed by io-harness to add no crate.** `otel = []` writes OTLP over the
# `reqwest` and `serde_json` already in the default build; `mcp-server =
# ["tokio/io-std"]` is two more types inside a name already here, the JSON-RPC
# framing hand-written precisely to avoid the nine packages `rmcp`'s server half
# pulls in; and `codeact = []` turns nothing on at all, because its interpreter
# is a host binary discovered the way a browser is and never downloaded. The
# claim is verified against `cargo tree --depth 1` with all six features on
# rather than believed from the harness's manifest — see `tests/dependencies.rs`,
# which asserts the ten names in both directions.
io-harness = { version = "0.82", features = [
"media",
"browser",
"documents",
"otel",
"mcp-server",
"codeact",
] }
# **`tui-textarea` is gone as of 0.18.0, and it is why this pair was frozen.**
# The comment that stood here said ratatui stayed at 0.29 and crossterm at 0.28
# because the composer was built out of that widget and the 0.30 line carried
Expand Down Expand Up @@ -186,3 +203,24 @@ image = { version = "0.25", default-features = false, features = [

[dev-dependencies]
tempfile = "3"
# **The second dev-dependency this crate has ever had, and it exists so that four
# keystroke contracts have an instrument at all.** The three ways 0.38.2 could
# lose an operator's work by a keystroke were all found at a real terminal and
# none of them was visible to the 2,000-odd offline tests: `src/main.rs` is what
# decides them and nothing under `tests/` links it, and the recorder backend
# every other test uses is not a tty. `openpty` is thirty lines over a name that
# is already in the transitive tree; `portable-pty` was the alternative and it
# brings a dozen packages on Windows into a repository whose dependency argument
# is that the set is small and enumerated. Unix only — the pty arms are compiled
# out on Windows and `tests/pty.rs` says so out loud rather than reporting a skip
# that reads like a pass.
#
# **It is declared here and not under `[target.'cfg(unix)'.dev-dependencies]`,
# deliberately.** That is where it belongs by platform and it is exactly where
# `tests/dependencies.rs` refuses to let it go: a target-gated dev-dependency is
# a test-only crate arriving where N1's count cannot see it, and the gate has
# said so since before this crate had a second one. The cost of declaring it
# unconditionally is that Windows compiles a crate it never links; the benefit is
# that `cargo tree --depth 1` prints the honest set. The platform gate lives on
# the code instead, as `#[cfg(unix)]` in `tests/pty.rs`.
libc = "0.2"
12 changes: 6 additions & 6 deletions Formula/io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ class Io < Formula

on_macos do
if Hardware::CPU.arm?
url "https://github.com/initorigin/io-cli/releases/download/v0.38.1/io-0.38.1-aarch64-apple-darwin.tar.gz"
sha256 "ba2cc2b821c238686c835a886b5eb4eb392365b87873ffcd5960ef6560aa73d0"
url "https://github.com/initorigin/io-cli/releases/download/v0.38.2/io-0.38.2-aarch64-apple-darwin.tar.gz"
sha256 "78ee8d07d9219aa91d1aca28fcabc3d09fc5ee4c57b3b539909fb7bb5d5721d7"
end

if Hardware::CPU.intel?
url "https://github.com/initorigin/io-cli/releases/download/v0.38.1/io-0.38.1-x86_64-apple-darwin.tar.gz"
sha256 "2d273e01d430de2d6f4d70f44c393d282646f421724fa78b5f4d023008d5efe7"
url "https://github.com/initorigin/io-cli/releases/download/v0.38.2/io-0.38.2-x86_64-apple-darwin.tar.gz"
sha256 "587a5d14c0820db3e66780eab506eee912574ecd84940a4ed3adac370ce8133a"
end
end

Expand All @@ -47,8 +47,8 @@ class Io < Formula
# rather than a URL that 404s halfway through an install.
on_linux do
if Hardware::CPU.intel?
url "https://github.com/initorigin/io-cli/releases/download/v0.38.1/io-0.38.1-x86_64-unknown-linux-musl.tar.gz"
sha256 "2e99e143d1c0803d7e07e005146395e82d7ed20a4d883fc4bbb27be634d4040e"
url "https://github.com/initorigin/io-cli/releases/download/v0.38.2/io-0.38.2-x86_64-unknown-linux-musl.tar.gz"
sha256 "6052126fab14221ae76806cd2d8f290441c4eb279c0788da5dcc6a90eee75045"
end
end

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ posture on the next.](assets/screenshot.png)
| **Approvals in place** | A write stops the run and shows the diff it proposes; `y`, `a` or `n`, answered where it was asked |
| **A fan-out you can watch** | Contained turns spawn children under one shared ceiling; `Ctrl+F` shows the tree and what it is costing |
| **Your file, in force** | Every section of `io.toml` bounds a session turn as it bounds `io exec`; the budgets in force are on the status line with what is left of them, and `/status` commits the whole state — policy layers, sandbox backend, caps, budgets, connections — into the scrollback |
| **Undo** | `Esc Esc` at an empty prompt rewinds the last turn — its files, its memory and the conversation head |
| **Undo** | `Esc` at an empty prompt asks whether to rewind the last turn — its files, its memory and the conversation head |
| **Conversations that survive** | `/resume` reopens an earlier session and answers whatever its last run stopped on, `/fork` continues from an earlier turn, `/clear` starts fresh without leaving |
| **A paused run, answered** | A question, a plan or an interrupted call is decided where it was left and the run carries on from the step it stopped at — from a session, or from a script with `io resume` |
| **Everything it asks, in one place** | Several questions asked at once arrive as one overlay, answered one at a time and sent together; an offer may explain itself and preview what taking it would do; `Space` marks where a question takes more than one answer |
Expand Down
8 changes: 4 additions & 4 deletions bucket/io.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"version": "0.38.1",
"version": "0.38.2",
"description": "Terminal agent that shows what it may do, what it spends and what it refused",
"homepage": "https://github.com/initorigin/io-cli",
"license": "Apache-2.0",
"architecture": {
"64bit": {
"url": "https://github.com/initorigin/io-cli/releases/download/v0.38.1/io-0.38.1-x86_64-pc-windows-msvc.zip",
"hash": "f3cb3c0d6b31a8d37d9603e9faf1b38a8056c1558f0299cf2b6ac4eab689cbbf",
"extract_dir": "io-0.38.1-x86_64-pc-windows-msvc"
"url": "https://github.com/initorigin/io-cli/releases/download/v0.38.2/io-0.38.2-x86_64-pc-windows-msvc.zip",
"hash": "be8ea3dc313635a86909cd0cc386130102cda97bcabb78bc0e5986ec688a89f8",
"extract_dir": "io-0.38.2-x86_64-pc-windows-msvc"
}
},
"bin": "io.exe",
Expand Down
Loading