Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
97 changes: 96 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,100 @@ All notable changes to this project are documented here. The format follows

## [Unreleased]

## [0.40.0] - 2026-09-07

Every key io documents can be set from either door, and nothing decides on your
behalf.

### Upgrading

Two shipped interactions change. Neither is a correction you will not notice.

- **An approval now needs `Enter`.** `y`, `a` and `n` move the highlight to that
answer; nothing resolves until you press `Enter`, which is the same two-step the
arrow keys have always used. It costs one keystroke and it buys the thing that
was wrong: a letter inside a sentence can no longer decide a write. Typing "no,
not that one" at an approval used to deny with the `n` in "no" and leave the rest
of the sentence in the composer, which was then sent as a prompt.

- **A gated run that wrote `max_steps = 1000` now takes a thousand steps.** It took
forty. `[run] max_steps = 1000` is io-cli's own floor written down, so it was
indistinguishable from having written nothing and the gated cap applied anyway.
If you have a CI job that wrote a thousand and relied on the forty-step cap to
bound it, that job now runs to a thousand.

### Fixed

- **Thirteen `[app.io-cli]` keys are settable from the shell at last.**
`io config set` wrote a string wherever a number or a flag belonged, for every
key `kind_of` did not name — and the cost is not the key. io-harness reads
`[app.io-cli]` as one opaque value, so one bad value fails the **whole section**
and the session silently reverts theme, keybindings, containment, gates and
routing to defaults behind a single warning line. The thirteen are
`reference_catalogue`, `prices.models`, the five `[app.io-cli.browser]` keys and
the six `[app.io-cli.containment]` ceilings.

`reference_catalogue` is the sharpest of them: 0.39.0 shipped it as the way to
turn its new network call off, and left it unsettable.

0.38.1 fixed this class of defect for `max_total_agents` and the fix did not
generalise, because nothing in the suite walked from the settings struct to the
catalogue — a list cannot notice what is missing from it. That walk exists now,
and it found a fourteenth key while it was being written.

**Two keys of `[app.io-cli.browser]` are held to the file you own.**
`binary` names a program to execute and is written by hand rather than offered
at either `/config` door; `args` is that program's argument vector — where a
proxy carrying credentials or a loaded extension goes — and is settable only
with `--scope user`. io-harness refuses a whole top-level `[browser]` section
from any file inside the workspace for exactly this reason, and cannot apply the
rule to `[app.io-cli]`, which it reads as one opaque value.

- **`max_total_duration` is settable at all.** It deserializes as a `Duration`, so
what the file has to carry is `{ secs = N, nanos = 0 }` and no scalar you type is
a value for it. `io config set app.io-cli.containment.max_total_duration 3600`
takes the seconds and writes the table.

- **`/undo` forgets the thought as well as the files.** The whole-turn undo cleared
the status, the fleet and the seen set and never touched the transcript's own
memory, so `/expand` still printed the undone turn's reasoning — the one thing
you undo a turn to be rid of, kept by the act meant to remove it.

- **The `/config` scope picker offers only the scopes a key can be written to.** A
widening value is refused from any file inside the workspace, so offering
`io.toml` and `io.local.toml` was offering two rows whose write io-harness
refuses — and it refuses the whole file, not the key.

### Added

- **ACP carries an image and an embedded resource.** `image` and `embeddedContext`
are declared, so a conforming editor will offer both. An image reaches the model
as an attachment on that one turn, through the same door `/attach` uses; a
`resource` block is folded into the prompt with its uri, which is how an editor
hands over the file you have open. A block that cannot be taken is reported back
as a message rather than dropped. `audio` stays false and will until io-harness
carries audio.

### Changed

- **The pin moves to io-harness 0.83.0.** `sandbox.allow_network = true` reaches
the macOS backend on a proxied run — which is every real run — so a widened
sandbox widens, and `git_log` on a repository with no commits answers in its own
words instead of with the error git also returns for "not a git repository".
Both were reported from this product and neither was this product's to fix.

- **Four sentences that were false are corrected, and each has a gate.** The commit
identity a repository with none of its own will carry is named
(`io-harness agent <[email protected]>`) rather than left to be discovered
in `git log`; the forty-step gated cap is documented instead of contradicted; the
`--json` stream says that `reasoning` arrives after the step whose tokens it
explains; and `Cargo.toml` says what its dependency gate actually asserts.

- **`io.local.toml` has been widening-checked since io-harness 0.74.0, and three
pages still offered it as the place to put a widening value** — including
`skills/io-permissions.md`, which is what an agent reads. The refused list is
thirteen (key, value) pairs across twelve keys, not the five those pages named.

## [0.39.0] - 2026-09-06

No keystroke loses your work, a running turn shows more of what it is doing, and
Expand Down Expand Up @@ -3749,7 +3843,8 @@ client, tool, sandbox, policy engine or session store of its own.
- There is no crates.io publish and `cargo install` is not an install path.
- No test in this release asserts on wall-clock time.

[Unreleased]: https://github.com/initorigin/io-cli/compare/v0.38.0...HEAD
[Unreleased]: https://github.com/initorigin/io-cli/compare/v0.40.0...HEAD
[0.40.0]: https://github.com/initorigin/io-cli/compare/v0.39.0...v0.40.0
[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
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

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

19 changes: 13 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "io-cli"
version = "0.39.0"
version = "0.40.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 @@ -69,11 +69,18 @@ path = "src/lib.rs"
# ["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 = [
# is a host binary discovered the way a browser is and never downloaded.
#
# **That claim is checked at its source, and the sentence that stood here said
# something else (0.40.0).** It said the claim was verified against `cargo tree
# --depth 1` by `tests/dependencies.rs` — which parses this file and `Cargo.lock`
# and never runs cargo, and `--depth 1` prints io-cli's own DIRECT dependencies,
# which is exactly where a transitive crate would not appear. What is asserted is
# what can be: `f8_the_pinned_harness_features_this_crate_turns_on_name_no_dependency`
# reads the `[features]` table of the io-harness version `Cargo.lock` pins and
# holds those three to naming no `dep:`. The ten-name allowlist is a separate gate
# in the same file, asserted in both directions.
io-harness = { version = "0.83", features = [
"media",
"browser",
"documents",
Expand Down
7 changes: 7 additions & 0 deletions docs/CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ deleted that was not already copied. This does nothing at all when `$IO_CONFIG`

## Limits that hold today

**A gated headless run with no budget stops at forty steps and exits `3`.** A `[app.io-cli.gates]`
criterion takes away every bound io-cli's own floor of a thousand is safe because of — io-harness
hands a failed criterion back as a reason to keep going, a run with no budget is measured against
nothing, and headless has no operator — so a run that set no `max_steps`, `max_duration_secs` or
`max_tokens` is bounded at forty. Anything you wrote beats it, **including `max_steps = 1000`**,
which through 0.39.0 was indistinguishable from having written nothing.

**A marketplace, and an entry inside one, resolve against `github.com` and no other host.** A
marketplace is named `<owner>/<repo>`; an index entry naming a repository elsewhere has its url
re-derived to that shape, and a url that is not two ordinary path segments on that host is refused
Expand Down
9 changes: 9 additions & 0 deletions docs/RELEASE_PROCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ irreversible in front of something reviewable.
`brew` reads a tap's **default branch**, which is `develop`, so the tap is live when that pull
request merges and needs no `main` merge of its own.

**It is a step of the release, not an errand after it, and 0.39.0 is why this paragraph
exists.** That release shipped on 2026-09-06 and both files still named 0.38.2 — so for
everyone installing through `brew` or `scoop`, the release notes described a version they could
not get, and `release.yml`'s note that the tap follows "shortly after" was untrue for anyone
reading it. The release is not finished until this pull request is merged and a `brew install`
resolves to the version the notes name. `tests/packaging.rs` asserts the two files agree with
each other and name a released version; it cannot assert they name the *newest* one, because
the tree that would have to know is the tree that has not shipped yet.

## What the workflow builds

`release.yml` builds four targets — `aarch64-apple-darwin`, `x86_64-apple-darwin`,
Expand Down
Loading