From 1132a6d7500575440ffae50037c0893d957ab5c9 Mon Sep 17 00:00:00 2001 From: Emal <71031983+emal-avala@users.noreply.github.com> Date: Mon, 4 May 2026 07:35:13 -0700 Subject: [PATCH 1/4] chore(release): bump version to v0.21.1 --- crates/lib/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 2b24e21..0ff709f 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "agent-code-lib" -version = "0.21.0" +version = "0.21.1" edition = "2024" description = "Agent engine library: LLM providers, tools, query loop, memory" readme = "../../README.md" From 8df0c8f851486b1a8fe4217476d9aefb34d2db8e Mon Sep 17 00:00:00 2001 From: Emal <71031983+emal-avala@users.noreply.github.com> Date: Mon, 4 May 2026 10:11:35 -0700 Subject: [PATCH 2/4] chore(release): prepare v0.21.1 --- Dockerfile | 2 +- crates/cli/Cargo.toml | 4 ++-- crates/eval/Cargo.toml | 2 +- npm/package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 66da5af..e686182 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /build COPY Cargo.toml Cargo.lock ./ COPY crates/ crates/ -RUN cargo build --release --locked +RUN cargo update --workspace && cargo build --release --locked # Runtime stage FROM debian:bookworm-slim diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 8b118e4..8b6d947 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "agent-code" -version = "0.21.0" +version = "0.21.1" edition = "2024" description = "An AI-powered coding agent for the terminal, written in pure Rust" license = "MIT" @@ -16,7 +16,7 @@ name = "agent" path = "src/main.rs" [dependencies] -agent-code-lib = { path = "../lib", version = "0.21.0" } +agent-code-lib = { path = "../lib", version = "0.21.1" } # CLI clap = { version = "4", features = ["derive", "env"] } diff --git a/crates/eval/Cargo.toml b/crates/eval/Cargo.toml index 2a3cf58..7b65554 100644 --- a/crates/eval/Cargo.toml +++ b/crates/eval/Cargo.toml @@ -10,7 +10,7 @@ name = "eval_runner" path = "src/main.rs" [dependencies] -agent-code-lib = { path = "../lib", version = "0.21.0" } +agent-code-lib = { path = "../lib", version = "0.21.1" } # Async runtime tokio = { version = "1", features = ["full"] } diff --git a/npm/package.json b/npm/package.json index acda036..c53e0e7 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@avala-ai/agent-code", - "version": "0.21.0", + "version": "0.21.1", "description": "AI coding agent for the terminal. Built in Rust.", "license": "MIT", "repository": { From c2d1348cc54353e71d2c2b32e0c0e4675a244c71 Mon Sep 17 00:00:00 2001 From: emal Date: Mon, 4 May 2026 14:19:35 -0700 Subject: [PATCH 3/4] chore(release): stamp CHANGELOG for v0.21.0 and v0.21.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per RELEASING.md step 3, the original v0.21.1 prep PR bumped versions without stamping the CHANGELOG. v0.21.0 itself was tagged off-spec: the version bump never landed in main and no CHANGELOG entry was written. v0.21.0 is preserved per the rollback policy ("don't delete the tag — downstream may have pinned it"); this commit retroactively documents what's in it. CHANGELOG additions: - v0.21.0 — full backfill covering 20+ PRs across new tools (cron, remote-trigger, Brief/Config/McpAuth), accessibility-aware theme picker + first-run onboarding + OSC 11 detection, plugin marketplace MVP, output styles, team-memory layer, settings migrations, bash hardening, task variants, plus the security fixes from the multiple codex review rounds (validate_input pre-hook, mode-preserving atomic writes, defense-in-depth path validation, fail-closed McpAuth). - v0.21.1 — version sync; source-equivalent to v0.21.0, restoring Cargo.toml/Dockerfile/npm to the right version string. - Comparison links updated for both new entries. --- CHANGELOG.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f88b813..26ac137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 *No changes yet.* +## [0.21.1] - 2026-05-04 + +### Fixed + +- **Version sync** (#270): the v0.21.0 tag was applied before the workspace version bump landed in `main`, so `crates/{lib,cli,eval}/Cargo.toml`, `Dockerfile`, and `npm/package.json` continued to read `0.20.0`. v0.21.1 corrects the drift; source-equivalent to v0.21.0. + +## [0.21.0] - 2026-05-04 + +### Added + +#### New model-callable tools + +- **Cron and remote-trigger tools** (#254): `CronCreate`, `CronList`, `CronDelete`, and `RemoteTrigger` expose the existing `crates/lib/src/schedule/` storage and executor as model-callable surfaces. Routine names are validated at the store layer (defense-in-depth, not just at the tool boundary); `RemoteTrigger` runs in its own process group via `setsid` so grandchildren get reaped on timeout/cancel; writes go through `O_NOFOLLOW`-style atomic rename so a pre-planted symlink can't redirect writes outside the schedules dir. +- **Brief, Config, McpAuth tools** (#259): `Brief` files structured research handoffs with attachment-path canonicalization (rejects `..` traversal, symlink escape, bidi/zero-width/BOM/format codepoints in title/question/context). `Config` reads/writes a deliberately narrow allow-list of user-tunable settings via `crates/lib/src/config/supported_settings.rs`; security-sensitive sections (`permissions.*`, `security.*`, `sandbox.*`, `hooks.*`, `mcp_servers.*`, `*api_key*`) are rejected with separator-aware lowercase normalization. `McpAuth` re-triggers the auth flow for a configured MCP server, fail-closed on `McpAuthKind::Unknown`. + +#### First-run onboarding and theme system + +- **First-run onboarding** (#265): welcome banner with an ASCII monogram, 7-option theme picker (`Auto` + dark/light × `{default, colorblind, ANSI-only}`), and a live diff preview that re-renders in the highlighted theme's colors as the cursor moves. First-run sentinel at `~/.config/agent-code/.onboarding-complete`. Skips automatically when stdin/stdout aren't both TTYs (`--prompt`, `--serve`, `--acp`, subcommands). `/theme` reuses the same picker mid-session. +- **Accessibility-friendly themes**: `dark-colorblind`/`light-colorblind` on the Okabe-Ito palette safe for protanopia/deuteranopia/tritanopia, and `dark-ansi`/`light-ansi` restricted to the 16 standard ANSI codes for terminals without truecolor. +- **OSC 11 terminal theme detection** (#268): `Auto` mode queries the terminal background via `OSC 11 ?` and classifies dark vs light using the BT.709 relative-luminance formula (`L = 0.2126·r + 0.7152·g + 0.0722·b`, threshold > 0.5). Eliminates the silent fallback to dark on terminals (Ghostty, kitty, Alacritty, recent iTerm2) that don't set `COLORFGBG`. DA1-sentinel (`CSI c`) pattern terminates the OSC query batch without arbitrary timeouts. `is_tty()` gate prevents poisoning headless surfaces. + +#### Plugin marketplace and extensibility + +- **Plugin marketplace MVP** (#262): native `plugin.toml` loader, plus adapters for two adjacent plugin formats — claude-style (`.claude-plugin/plugin.json` with sibling `commands/`, `agents/`, `skills/`, `hooks/` dirs and `.mcp.json`) and codex-style (`config.toml` with `[mcp_servers.*]` entries plus `prompts/*.md`). New `/plugin {list, install, enable, disable, remove}` commands. Built-in plugin slot (currently empty). Per-project enable/disable persists to `/.agent/plugin-settings.toml` via the new atomic-write helper. +- **Disk-loaded output styles** (#256): markdown style files at `/.agent/output-styles/*.md` and `~/.config/agent-code/output-styles/*.md`. Frontmatter schema: `name`, `description`, optional `applies_to`. Disk styles override built-ins on id collision; `applies_to: [main|subagent]` filters at apply time; subagent style propagation via `AGENT_CODE_DISK_OUTPUT_STYLE` env var. Prompt cache key includes a 12-byte SHA-256 prefix of style content so in-session edits invalidate correctly. +- **Team-memory layer** (#257): version-controlled `/.agent/team-memory/` directory, read-only to the model and writable only via the explicit `/team-remember` slash command. The "read but not silently write" invariant is enforced through `PermissionChecker` (which now carries `project_root`), blocking `FileWrite`/`FileEdit`/`MultiEdit`/`NotebookEdit` and Bash redirections from team-memory paths. `MEMORY.md` link targets are validated against traversal and symlink leaves; `rebuild_index` produces byte-stable output. + +#### Configuration and infrastructure + +- **Settings migrations framework** (#255): forward-migration runner with numbered migrations (`crates/lib/src/config/migrations/`), wired into `Config::load_inner` for every layer (user, project, project-local). Atomic writes via `tempfile::NamedTempFile` with mode preservation (0600 secrets stay 0600), parent-dir fsync on POSIX, `MoveFileExW`-backed atomic replace on Windows. Profile loader (`services/profiles.rs`) routes through migrations. Seeded with `v0→v1` (stamp version) and `v1→v2` (consolidate `api.token` into `api.api_key`). +- **`Tool::validate_input` trait method** (#259): runs before `PreToolUse` hooks and `check_permissions`, so disallowed tool inputs short-circuit before any audit log entry or permission prompt fires. `ConfigTool` overrides it for the allow-list preflight; `BriefTool` overrides it for control + invisible-format codepoint rejection. +- **`config::atomic::atomic_write_secret`** (#259): shared mode-preserving atomic-write helper used by ConfigTool, plugin settings, the onboarding sentinel, and other secret-bearing files. +- **`config::agent_config_dir()`** (#267): cross-platform helper that honors `XDG_CONFIG_HOME` on every platform (falling back to `dirs::config_dir().join("agent-code")`). All 15 call sites of `dirs::config_dir()` rerouted through it; fixes Windows behavior where `dirs::config_dir()` ignores `XDG_CONFIG_HOME`. + +#### Task model + +- **Task model variants** (#263): `TaskKind` enum (`LocalShell | LocalAgent | LocalWorkflow | MonitorMcp | RemoteAgent | Dream`), tagged `TaskPayload`, per-kind `TaskExecutor` trait + registry. `LocalShellExecutor` wraps the existing path; `LocalAgentExecutor` delegates to `AgentTool` (no duplication); `RemoteAgentExecutor` stubs to `RemoteTrigger`; `LocalWorkflow`/`MonitorMcp`/`Dream` are `NotImplemented` with TODOs. Surfaced in `/tasks`, `TaskList`, `TaskGet`. + +#### Roadmap + +- **Phase 8** (#253, #260, #266): "Extensibility, Surfaces, and Orchestration" added to `ROADMAP.md` covering plugin marketplace, output styles, bundled skills, settings migrations, cron tools, multi-surface host, swarm mode, additional model-callable tools, bash tool hardening, fork/resume, team-memory, service fill-in, task variants, cloud-runtime mode, and the new 8.15 theme-detection follow-ups (8.15.1 OSC 11 detection landed; 8.15.2-5 still open). + +### Changed + +- **Bash tool: unified protected-path enforcement** (#258): split safety logic into named modules (`bash_security`, `command_semantics`, `read_only_validation`, `sandbox_decision`, `sed_edit_parser`, `sed_validation`) and added `bash/protected_paths.rs` — a single destination-operand checker covering writers (`cp`, `mv`, `tee`, `dd`, `install`, `ln`, `rsync`, `truncate`), all redirection forms (`>`, `>>`, `&>`, `2>`, etc.), recursive shells (`bash -c`, `sh -c`, `eval`) up to depth 3, and an interpreter heuristic (`python -c`, `node -e`, `perl -e`, `ruby -e`). Closes the gap where non-sed writers could bypass the FileEdit permission gate. +- **`dangerouslyDisableSandbox`** (#258): no longer skips destructive-pattern or protected-path validation. The flag now affects only whether the command runs inside the sandbox vs outside, not whether validation runs. +- **Read-only validation handles redirections** (#258): output redirections (`>`, `>>`, heredoc-to-file, process substitution) now classify as `Mutating` regardless of the base command. +- **Sed parser handles attached and clustered forms** (#258): `sed --in-place=.bak`, `sed --expression=ARG`, `sed -eSCRIPT`, `sed -fFILE`, and clustered short flags (`-Ei`, `-nEi`, `-Eie`) all route through the FileEdit permission path. +- **Concurrent config writes serialized** (#259): read-modify-write critical sections take an exclusive `fs2`-backed lock on a sibling `settings.toml.lock` so racing writers can't lose updates. +- **Test environment isolation** (#259): shared `EnvGuard` / `ENV_LOCK` in `crates/lib/src/test_support.rs` for any test that mutates `HOME`, `XDG_CONFIG_HOME`, or other env vars; replaces ad-hoc local locks across tests. + +### Fixed + +- **Windows compile**: `child_pid` binding is now cross-platform (#261); `backup_rotation_does_not_run_when_atomic_write_fails` is `#[cfg(unix)]`-gated (#264). +- **Windows runtime tests** (#267): profile loader test uses shared `EnvGuard`; sed-validation deny-substring uses platform-appropriate path separators; brief attachments test uses `C:\Windows\System32\drivers\etc\hosts` instead of `/etc/hostname`; `XDG_CONFIG_HOME` honored on Windows via the new `agent_config_dir()` helper. +- **Symlink TOCTOU in atomic config writes** (#259): unguessable temp file names via `tempfile::NamedTempFile::new_in` defeat planted-symlink attacks against deterministic `.tmp` paths. +- **File mode leak** (#255, #259): config rewrites preserve the original file mode (or default to `0o600` for new secret-bearing files), so a `0o600` settings file can't become world-readable on rewrite. +- **Missing parent-dir fsync** (#255, #259): atomic writes now `fsync` the parent directory after `rename` for crash-durable replacements on POSIX. +- **Non-deterministic memory merge** (#257): `merge_scoped_files` replaced `HashMap::into_values()` with `BTreeMap` plus an explicit `(scope_priority, path)` sort, so the resulting `memory_files` Vec is byte-stable across loads. +- **Path traversal in cron tools** (#254): `cron_delete`, `remote_trigger`, and the underlying `ScheduleStore::path_for` validate routine ids; `validate_schedule_name` is the single source of truth at the store layer. +- **Path traversal in team-memory delete** (#257): `delete_team_memory` validates the filename and runs `ensure_path_within` (canonicalize + prefix check) before any fs op. +- **`MEMORY.md` link traversal** (#257): `load_referenced_files` rejects `..` segments, absolute paths, and symlink leaves so a poisoned team-memory index can't read files outside the memory dir. +- **Profile loader bypassed migrations** (#255): `services::profiles::load_profile` now routes through `migrations::load_and_migrate_toml`, so old `/agent-code/profiles/.toml` snapshots with `api.token` are upgraded and rewritten on disk. +- **Output styles**: + - **Resolution order** (#256): `/output-style` checks the disk registry before built-in aliases, so a project `default.md` actually overrides the built-in. + - **`applies_to` enforcement** (#256): a style with `applies_to: [subagent]` no longer leaks into main-agent prompts. + - **Cache busts on edit** (#256): prompt cache key includes a content hash, so in-session edits to the active style file are picked up by `/reload`. + - **Subagent propagation** (#256): `Agent`-tool spawned children inherit the active disk style via `AGENT_CODE_DISK_OUTPUT_STYLE`; previously the subagent half of `applies_to` was dead at the subprocess boundary. + +### Security + +- **Team-memory read-only invariant enforced** (#257): the predicate was previously cosmetic — `is_team_memory_path` was only consulted in the background extraction skip path, while `FileWrite`, `FileEdit`, `MultiEdit`, `NotebookEdit`, and Bash redirections could all silently write to `/.agent/team-memory/`. Now blocked at the permission layer. +- **Bash bypasses for protected dirs** (#258): non-sed writers (`cp`, `mv`, `tee`, `dd`, etc.), shell redirections, recursive shells, and inline interpreter source can no longer reach `.git/`, `.husky/`, `node_modules/`, or system paths. +- **`McpAuthKind::Unknown` fails closed** (#259): a future auth marker that's parsed but unsupported now returns `ToolResult::error`, not a non-error result the model could be tricked into trusting. +- **Pre-validation runs before audit hooks** (#259): inputs that fail `Tool::validate_input` are rejected before `PreToolUse` hooks see them, so audit pipelines no longer log disallowed `Config` keys or attachment-traversal attempts. +- **Broader Unicode rejection in user-facing inputs** (#259): bidi controls (`U+202A`–`U+202E`), zero-width chars (`U+200B`–`U+200F`), BOM (`U+FEFF`), and Unicode format codepoints rejected in `Brief` title/question/context to prevent rendered-output spoofing. + +### Removed + +- *(none)* + ## [0.20.0] - 2026-04-27 ### Added @@ -311,7 +392,9 @@ Initial public release. - **Cross-platform support**: Linux (x86_64, aarch64) and macOS (x86_64, Apple Silicon) - **Installation methods**: cargo install, Homebrew tap, curl script, prebuilt binaries -[Unreleased]: https://github.com/avala-ai/agent-code/compare/v0.20.0...HEAD +[Unreleased]: https://github.com/avala-ai/agent-code/compare/v0.21.1...HEAD +[0.21.1]: https://github.com/avala-ai/agent-code/compare/v0.21.0...v0.21.1 +[0.21.0]: https://github.com/avala-ai/agent-code/compare/v0.20.0...v0.21.0 [0.20.0]: https://github.com/avala-ai/agent-code/compare/v0.19.0...v0.20.0 [0.19.0]: https://github.com/avala-ai/agent-code/compare/v0.18.0...v0.19.0 [0.18.0]: https://github.com/avala-ai/agent-code/compare/v0.17.0...v0.18.0 From f0a50395b682a02f7568a8cf99116252fff06852 Mon Sep 17 00:00:00 2001 From: emal Date: Mon, 4 May 2026 15:34:58 -0700 Subject: [PATCH 4/4] fix(release): commit Cargo.lock with bumped versions; drop cargo update from Docker build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous PR #270 commit added `cargo update --workspace` ahead of `cargo build --release --locked` in the Dockerfile because the bumped crate versions (0.20.0 → 0.21.1) had not been re-resolved in Cargo.lock, so a plain locked build failed. That defeated the point of a locked release build: every image rebuild of the same source SHA was free to pull different transitive versions, and could fail outright on upstream index churn or network state. The right move is to update and commit the lockfile in the release commit, not at image-build time. - Re-resolve Cargo.lock locally so it matches the bumped 0.21.1 workspace versions plus the libc add already in main. - Restore the Dockerfile to a single `cargo build --release --locked` step. Image rebuilds now use the reviewed lockfile bytes for bit stability. --- Cargo.lock | 5 +++-- Dockerfile | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac972a2..823ef4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "agent-code" -version = "0.20.0" +version = "0.21.1" dependencies = [ "agent-code-lib", "anyhow", @@ -22,6 +22,7 @@ dependencies = [ "dirs", "futures", "indicatif", + "libc", "predicates", "pulldown-cmark", "ratatui", @@ -61,7 +62,7 @@ dependencies = [ [[package]] name = "agent-code-lib" -version = "0.20.0" +version = "0.21.1" dependencies = [ "anyhow", "async-trait", diff --git a/Dockerfile b/Dockerfile index e686182..66da5af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /build COPY Cargo.toml Cargo.lock ./ COPY crates/ crates/ -RUN cargo update --workspace && cargo build --release --locked +RUN cargo build --release --locked # Runtime stage FROM debian:bookworm-slim