Skip to content

Releases: cortexkit/aft

v0.19.2

Choose a tag to compare

@github-actions github-actions released this 04 May 22:45

What's new in v0.19.2

🐛 Bug fixes

Foreground bash hang on Windows (issue #26)

AFT's foreground bash was inheriting stdin from the long-running bridge process — and the bridge's stdin is the JSON-RPC protocol pipe from OpenCode. Any child process that tried to read from stdin (PowerShell Read-Host, git/npm credential prompts, package-manager confirmations, etc.) would block forever waiting for input that never came, manifesting as the 65s bridge transport timeout reported in the issue.

Background bash had Stdio::null() since day one — foreground bash didn't. That asymmetry produced the bug.

Fix in crates/aft/src/commands/bash.rs:

  • Detach foreground bash's stdin with Stdio::null() (matches background bash and OpenCode's native bash)
  • Pass -NonInteractive to PowerShell on Windows so prompts fail fast instead of hanging

Closes #26.

Windows ARM64 support (Prism emulation)

Three coordinated changes in @cortexkit/aft-bridge so the AFT plugin works on Windows ARM64 via Microsoft Prism (the built-in x64 emulator):

  • platform.ts: map win32-arm64 → win32-x64 so the resolver and downloader pick the x64 binary that runs cleanly under Prism. We don't ship a native ARM64 build.
  • downloader.ts: route through the shared platformKey() helper so cache-miss downloads also pick up the new mapping.
  • onnx-runtime.ts: align ONNX Runtime arch with the AFT binary's arch (not Node's process.arch). Node correctly reports arm64 but our x64 aft.exe under Prism panics on dlopen of native ARM64 ONNX. The map keeps ONNX in lockstep with the resolved binary.

Verified live on Windows 11 ARM64: aft.exe loads x64 ONNX Runtime, builds the semantic index, and serves all 16 tools.

🧪 Tests

New Windows E2E harness

Real-bridge end-to-end test for Windows (tests/windows-e2e/). Runs OpenCode + a mock LLM inside a Parallels VM against the just-built aft.exe and plugin dist, exercising the full plugin → bridge → child-process flow that the Linux Docker harness can't cover. Includes a dedicated Scenario 2b regression test for issue #26 — invokes Read-Host and asserts bash returns within its own timeout (no bridge transport hang).

22/22 PASS on Windows 11 ARM64.

Linux Docker E2E fixture fix

The Docker E2E mock-server was using the pre-v0.18 aft_outline({ directory: ... }) API; v0.18.x renamed it to target. The first scripted turn was silently failing, the bridge never spawned, and the harness's lenient exit-code checks masked it. Updated to use target — Docker E2E now goes from 16/17 → 18/18 PASS.

v0.19.1

Choose a tag to compare

@github-actions github-actions released this 04 May 18:38

Highlights

This release is a focused stability and ergonomics pass. Two reliability fixes deserve special attention if you're using experimental.bash.background or semantic_search:

  • Background bash completion reminders no longer drop after the first wake. The wakeFiredThisIdle gate suppressed every completion after the first one in any idle window — empirically about 1 in 5 reminders. Subsequent completions now each fire their own reminder, debounced through the existing coalescer.
  • Semantic Index no longer reports "failed" when ONNX downloads successfully. A startup race could spawn the eager-warm bridge before ONNX Runtime download finished, leaving that bridge without ORT_DYLIB_PATH. Eager-warm now waits up to 60s for ONNX resolution before spawning, so semantic search is "ready" on the same plugin start instead of needing a second restart.

Changes

Bridge package (@cortexkit/aft-bridge)

  • ONNX Runtime download now runs in the background instead of blocking plugin load. Plugin tools register immediately; semantic search lights up once the runtime resolves.
  • aft_zoom now returns plain-text formatted output across both OpenCode and Pi via shared formatZoomText. No more JSON-escaped newlines and quotes in agent-visible blobs.
  • URL fetch under Node v24 now correctly handles dual lookup callback shape with opts.all: true (Pi aft_outline(url:) was failing with ERR_INVALID_IP_ADDRESS). Bumped to undici v8 with surfaced underlying fetch causes.
  • New BridgePool.setConfigureOverride() API lets the plugin layer patch configure overrides on already-running bridges.

Plugin behavior

  • bash is now only hoisted when at least one of experimental.bash.rewrite, experimental.bash.compress, or experimental.bash.background is enabled. With all three off, the host's native bash tool is used unmodified. This avoids surprising users who have AFT installed but haven't opted in to bash hoisting.
  • Background-bash anti-polling guidance: tool descriptions and reminder text now describe completion delivery without prescriptive "end your turn" copy. bash_status polling responses now repeat the same reminder rather than nudging the agent into a polling loop.
  • Bash rewrite footer is now terser: Prefer 'read' tool over bash. instead of the previous verbose explanation.
  • Watcher invalidation now ignores read-only metadata events (AccessTime, Permissions, Ownership, Extended). Read-only tools like Biome lint no longer invalidate trigram and symbol caches.

Logging

  • Plugin log no longer renders LSP errors with four nested [aft] tags. slog_*! macros and 14 source files were scrubbed of inlined prefixes; env_logger handles the outer tag exclusively.
  • LSP spawn failures are now cached per (server_kind, workspace_root). One bad workspace root no longer logs an error on every edit — failed spawns log once per session.

Repo dogfooding

  • Root tsconfig.json plus bun-types and typescript devDeps so scripts/**/*.ts get full LSP coverage when working on the repo itself. (No user-facing impact.)

Polish

  • Discord badge and section-nav link in README pointing at the cortexkit Discord (https://discord.gg/DSa65w8wuf).

Known issues

  • Issue #26 (Windows bash transport timeout at 65s) is not addressed in this release. Windows e2e harness work is still in progress; a fix will ship once we have real Windows reproduction in CI.

Full Changelog: v0.19.0...v0.19.1

v0.19.0

Choose a tag to compare

@github-actions github-actions released this 04 May 07:48

First release after v0.18.4, with substantial improvements across plugin reliability, performance, and tool surface.

Critical fixes

  • Background-bash idle wakes preserved provider prefix cache — synthetic prompts now reuse the last assistant's {providerID, modelID, variant} so cached prefixes survive across turn-end notifications, ignored messages, and configure-warning deliveries.

Performance

  • Incremental semantic refresh — only re-embed files whose mtime+size changed instead of rebuilding the entire embedding set on every restart. On real OpenCode sessions, this collapses cold-restart cost from minutes to milliseconds.
  • Symbol cache disk persistence — per-project symbol tables now persist under <storage_dir>/symbols/<project_key>/symbols.bin. ~91% cache hit rate on second restart instead of full re-parse every spawn.
  • Async configure warningsconfigure returns immediately. File-walk + language detection + missing-binary detection happen on a background thread and stream back as configure_warnings push frames.
  • Home-directory bridge hang fix — FSEvents watcher attach moved off the configure foreground. Launching OpenCode from $HOME no longer triggers a 30s configure timeout that wedges the bridge in a respawn loop.
  • Bash default timeout 30s — foreground bash now defaults to 30s with a workflow hint that tells the agent to use bash({ background: true }) for longer commands.

Pi: URL support for aft_outline and aft_zoom

Pi now mirrors OpenCode's URL-fetching behavior for remote docs.

  • aft_outlinetarget accepts http:// / https:// URLs. Auto-detected by URL prefix.
  • aft_zoom — new optional url parameter (mutually exclusive with filePath). Multi-symbol zoom routes through the cached fetched copy.
  • Same DNS-pinned, SSRF-guarded fetcher OpenCode already uses. Cached under <storageDir>/url_cache/ with a 1-day TTL.
// Pi
aft_outline({ target: "https://platform.claude.com/docs/en/build-with-claude/prompt-caching.md" })
aft_zoom({ url: "https://...", symbol: "1-hour cache duration" })

Tool surface refinements

  • aft_outline unified target — file path, directory path, URL, or array of paths in one parameter (auto-detected). Replaces the previous mutually-exclusive filePath / files / directory shape.
  • aft_delete takes files: string[] — pass { files: ["a.ts", "b.ts"] } for any number of files including one. Returns honest partial-success reporting when some deletes fail.
  • [cmpaft] compressed-output marker — replaces the verbose (compressed by aft) suffix on bash output compression. Saves tokens on every compressed result the agent sees.
  • Pi background-bash completion via steer — completion now reaches the agent between its tool batch and the next LLM call instead of waiting for full turn completion.
  • Workflow hints in system prompt — AFT plugins now inject token-efficient workflow guidance (aft_outlineaft_zoom chains, URL fetching, long-running command patterns) into the agent's system prompt. Conditional on the actual configured tool surface.

Bug fixes (audit batch)

  • configure_warnings session routing — async push frames now carry session_id end-to-end so multi-session OpenCode delivers warnings to the correct client.
  • Semantic refresh data preservation — transient embed-backend errors no longer drop existing cache entries or update file mtimes. Old embeddings stay until the next successful extraction.
  • Prewarm generation guard — background prewarm threads use generation-guarded set_project_root / load_from_disk so a stale thread can't repopulate the symbol cache after reconfigure.
  • Semantic addition detection — refresh now handles all cases (added/changed/deleted) and returns a RefreshSummary. Previously, newly added files weren't picked up between restarts.
  • aft_outline directory honest signalingwalk_truncated, complete, and skipped_files fields are preserved end-to-end.
  • apply_patch diff size gate — raised from 100 KB bytes to 5000 lines so large source files (e.g. 114 KB / 3084 lines) get proper diff rendering in the UI.

Architecture

  • @cortexkit/aft-bridge shared package — bridge transport, pool, downloader, resolver, platform mapping, ONNX runtime, and URL fetcher all moved into one shared package. Both @cortexkit/aft-opencode and @cortexkit/aft-pi consume from it. Single source of truth for transport behavior.
  • URL fetching consolidatedfetchUrlToTempFile, cleanupUrlCache, and _isPrivateIpv4 are now exported from @cortexkit/aft-bridge so OpenCode and Pi share one DNS-pinning and SSRF-guarding implementation.

Install

bunx --bun @cortexkit/aft setup

If you've already installed:

bunx --bun @cortexkit/aft doctor

— the doctor tool will detect the new version and prompt to clear caches if needed.

Upgrading

OpenCode and Pi plugins both pin to @cortexkit/aft-bridge@0.19.0 exactly. Existing installations pull in the new bridge automatically on next package update; no config changes required.

Full changelog: v0.18.4...v0.19.0

v0.18.4

Choose a tag to compare

@github-actions github-actions released this 02 May 08:10

v0.18.4

Security & correctness audit (29 fixes)

A full-codebase council audit found and fixed 29 issues across Rust and both plugins.

P0 — Security

  • DNS-pinning SSRF: URL fetch now pins resolved IPs before following redirects (#1)
  • Bash permission-scan parse failure no longer bypasses the permission gate (#2)
  • Shared stdout BufWriter for background bash frames prevents interleaved output (#4)
  • Dangerous env vars (LD_PRELOAD, DYLD_INSERT_LIBRARIES, etc.) blocked from bash rewrites (#5)
  • Private IPs rejected in semantic search base_url at configure time (#25)

P1 — Correctness

  • LSP didOpen notification now uses the validated path after delete_file (#6)
  • aft_outline directory mode skips symlinked directories to prevent loops (#7)
  • Search index cache files now include a CRC32 integrity check (#8)
  • Background bash tasks detach cleanly on SIGTERM/SIGINT so completions survive bridge restart (#9)
  • Ambiguous move_symbol now fails with a clear error instead of silently picking one (#10)
  • appendContent (edit append mode) runs syntax validation after write (#11)
  • Active bridge lookup scoped by project root, not global (#13)
  • OpenCode pool canonicalizes keys with realpathSync to avoid duplicate bridges (#14)

P2 — Robustness

  • Background bash temp files include task ID for easier debugging (#12)
  • Commands report complete: true/false honestly per the tri-state protocol (#16)
  • Transaction rollback failures use a distinct rollback_failed error code (#17)
  • grep passes leading-dash patterns to rg via -- to prevent flag injection (#18)
  • Bash rewrite regex patterns capped at 10 KB to prevent ReDoS (#19)
  • Glob edit_match checkpoints include the request ID for uniqueness (#20)
  • Bash permission paths canonicalized before lookup (#22)
  • Large file ranged reads no longer rejected — clamps to file length (#23)
  • Batched aft_zoom surfaces partial failures per symbol (#24)
  • Background task IDs use OS entropy (getrandom) — format is now bgb-<8hex> (#26)
  • File watcher filters by exact path component, not substring (#28)
  • bash_status now includes stderr_path for inspecting background task stderr (#29)
  • Pi bash hoisting decoupled from read hoisting (#30)
  • Pi config migration preserves block comments (#31)
  • RPC client retries on stale port file (#32)

Workflow hints — system prompt injection

Both OpenCode and Pi now inject a short ## Prefer AFT tools for token efficiency block into the agent system prompt. Sections are conditional on the registered tool surface:

  • Web/URL accessaft_outline({ url })aft_zoom({ url, symbol }) instead of fetching whole pages
  • Code explorationgrep/aft_searchaft_outlineaft_zoom instead of chains of read calls
  • Relationship questionsaft_navigate (callers, impact, trace_to, trace_data) instead of grep + read chains (shown only at tool_surface: "all")
  • Long-running commandsbash({ background: true }) + bash_status (shown only when experimental.bash.background is enabled)

Always-on, no config toggle needed. Irrelevant sections are omitted automatically.

Bug fixes

  • Windows build: signal_hook gated behind cfg(unix) — Windows x64 binary now builds correctly
  • Test files missing /// <reference path="../bun-test.d.ts" /> directive fixed

v0.18.3

Choose a tag to compare

@github-actions github-actions released this 01 May 14:51

What's Changed

Bug Fixes

  • Background bash statustimed_out tasks now display correctly; the plugin was checking for "timeout" while Rust serializes as "timed_out"
  • Pi bash wake messagessendUserMessage now passes { deliverAs: "followUp" } to prevent "Agent is already processing" rejections mid-turn
  • HTML aft_zoom section content — heading symbols now return the full section body down to the next sibling heading, not just the heading line itself
  • Pi bash rendererrenderResult now shows last 25 lines of output, matching Pi's built-in bash behavior
  • handle_append syntax validationappendContent now calls real syntax validation instead of hardcoding syntax_valid: true
  • Glob edit_match formatting — glob edits now use the full auto_format pipeline; the separate write_format_only path is removed
  • formatter_timeout_secs config — the timeout override now flows correctly from both plugin config loaders into Rust configure
  • Pi formatter result fields — hoisted mutation results now expose formatted and formatSkippedReason to agents
  • formatter_excluded_path skip reason — paths excluded by scoped formatter config are now distinguished from generic formatter errors

Logging

  • Rust session-id logging — all Rust command-side log lines now include [ses_xxx] for correlation; background threads capture session_id at spawn time
  • Plugin per-request session-id — OpenCode and Pi plugin tool dispatch logs carry session IDs via sessionLog/Warn/Error()
  • Test log isolationbun test now writes to aft-plugin-test.log instead of the live aft-plugin.log to prevent test noise in production logs
  • RPC call/result noise removed — sidebar and /aft-status polling no longer floods the plugin log

Audit fixes (P0/P1)

  • Stdout write race — watchdog thread and main loop now share a properly serialized output path
  • delete_file LSP path — uses validated canonical path for LSP notification instead of raw user input
  • move_symbol ambiguous result — returns success: false for ambiguous symbol matches instead of misreporting success
  • atomic_write temp name — includes task ID to prevent concurrent write collisions
  • OpenCode pool realpathSyncnormalizeKey() now resolves symlinks like the Pi pool, fixing duplicate bridge entries on macOS /var/private/var
  • Random slug collision — background task IDs now use 64-bit entropy instead of 32-bit

v0.18.2

Choose a tag to compare

@github-actions github-actions released this 30 Apr 18:36

Bug fixes

TUI plugin failed to load on npm installs (sidebar + /aft-status missing)

The published v0.18.0 and v0.18.1 npm tarballs were missing src/logger.ts,
which is a transitive dependency of src/shared/rpc-client.ts and several
other shared modules used by the TUI plugin. When OpenCode loaded the TUI
plugin from the npm package, the import chain failed with module-not-found,
silently disabling the entire TUI plugin entry — both the AFT sidebar slot
and the /aft-status command disappeared.

Local dev installs (file:// or workspace path) were unaffected because
src/logger.ts was always present on disk regardless of the package files
manifest. The bug existed since the sidebar was first added in v0.18.0.

Fix: src/logger.ts is now included in the published files array so
all transitive imports resolve when the package is installed from npm.

v0.18.1

Choose a tag to compare

@github-actions github-actions released this 30 Apr 18:01

Full Changelog: v0.18.0...v0.18.1

v0.18.0

Choose a tag to compare

@github-actions github-actions released this 30 Apr 17:04

Highlights

Hoisted bash (experimental)

AFT now optionally replaces the built-in bash tool with a richer implementation. All three modes are opt-in via project or user config:

  • experimental.bash.rewrite: truecat, grep, find, sed, ls calls are automatically rewritten to AFT counterparts (faster, structured output). Falls through to real bash when the rewrite would fail (e.g. paths outside project root).
  • experimental.bash.compress: true — output compressors for git, cargo, npm, bun, pnpm, pytest, tsc, plus a generic ANSI/dedup/truncate fallback.
  • experimental.bash.background: truebash({ background: true }) returns a short bgb-<8hex> task ID. New bash_status and bash_kill tools manage long-running tasks. Tasks survive AFT restarts (disk-persisted) and completions are delivered via in-turn hooks and turn-end wake.

TUI sidebar

A live sidebar now shows the AFT badge, version, search index status, semantic index status, and on-disk sizes for both indexes. Refreshes every 1.5 s.

Config schema update (auto-migrated)

Old key New key
experimental_search_index search_index (graduated to top-level)
experimental_semantic_search semantic_search (graduated to top-level)
experimental_lsp_ty experimental.lsp_ty
experimental_bash_* experimental.bash.*

Run aft doctor to confirm the migration.


Bug fixes

  • aft_refactor corrupted export keywordmove, extract, and inline now correctly preserve the export on the source file and don't leak it onto the next declaration. Inline call-site indentation is no longer doubled.
  • apply_patch is now per-file commit — successful files are kept even if one hunk fails elsewhere. *** Add File: rejects overwriting an existing target. Fuzzy tab/space indent tier added. Move-hunk rollback fixed.
  • ast_grep_replace — anonymous $$$ in rewrites is now rejected at request time (was silently emitting $$$ literally). Replaces every match per file (was first-match-only). Dry-run shows the actual diff.
  • aft_import organize preserves named-import aliases ({ stdin as input }) and per-name type markers.
  • Glob edit_match restores the checkpoint when any edited file fails syntax validation.
  • transaction — batched workspace/didChangeWatchedFiles notification now fires once per transaction, not once per edited file.

LSP

  • Config-file edits (package.json, tsconfig.json, Cargo.toml, lockfiles, etc.) now send workspace/didChangeWatchedFiles so phantom Cannot find module 'bun' errors clear after multi-file patches.
  • LSP notifications fire after transaction validation, not before.

Reliability

  • Bridge wire-format collision fixed — bash, sed, grep no longer break when their params contain command/method fields.
  • Background bash: process-group kill, byte-safe truncate, session-scoped completions, 30-min default timeout.
  • Bridge crash errors now appear in plugin logs only — agent-facing messages stay clean.
  • Symlink chain escape in validate_path fixed for Linux (single-hop check missed multi-hop chains).
  • Windows build: terminate_pgid import and call gated behind #[cfg(unix)].

Internal

  • 1064 → 1182 tests across Rust and plugins.

v0.17.3

Choose a tag to compare

@github-actions github-actions released this 28 Apr 09:55

Bug fixes

Stop returning stale LSP diagnostics after edits

The post-edit LSP diagnostics path could return entries from before the edit. Concrete repro from a real session: an agent's edit added 4 function call sites that used previously-unused imports — the edit response then reported "declared but never read" for those exact 4 imports. The agent thought it had broken something it had just fixed.

The fix is layered:

  • Version-aware freshness check. When an LSP server publishes diagnostics, the post-edit wait now requires publishDiagnostics.version to match the document version we just sent. Servers that publish without a version (some still do) fall back to an epoch-delta check against a pre-edit snapshot. Either way, pre-edit cached entries can no longer be returned as fresh.
  • Multi-server completion. The wait now waits for all expected servers (TypeScript + ESLint + Biome co-servers, for example), not just the first one to publish. Servers that don't respond before the deadline appear in lsp_pending_servers. Servers whose process exited mid-edit appear in lsp_exited_servers.
  • Default wait_ms raised to 3000ms. The previous 1500ms was too tight for tsserver re-analysis on monorepo files (real-world: 2–5s). Still capped at 10000ms.
  • Honest empty-publish handling. When a server publishes diagnostics: [] for the post-edit version, that's now correctly classified as fresh-and-clean instead of getting confused with timeout silence.

Honest tri-state reporting on edit responses

Edit responses (write, edit, apply_patch) now always include lsp_diagnostics, lsp_complete, lsp_pending_servers, and lsp_exited_servers when diagnostics: true was requested — even when the diagnostics array is empty. Before, an empty array was silently dropped from the response, so agents couldn't tell "checked clean" from "nothing happened."

Edit responses no longer carry LSP fields at all when diagnostics weren't requested (Oracle pre-release fix — preserves the no-diagnostics-requested response shape).

OpenCode plugin write/edit handlers now surface lsp_pending_servers / lsp_exited_servers to the agent as inline notes, so silence isn't mistaken for "all clear":

Note: LSP server(s) did not respond in time: typescript. Diagnostics may be incomplete; rerun lsp_diagnostics later for a fresh check.

Tighter complete calculation in lsp_diagnostics

Standalone lsp_diagnostics file mode no longer marks push_only servers as fresh without proof. With the default wait_ms=0, no waiting happened — so reporting complete: true against pre-existing stale cache was misleading. Now push_only only contributes to complete when wait_ms > 0 AND a publish actually arrived during the wait.

Multi-root server-key handling: publishDiagnostics events now correctly carry the workspace root instead of an empty PathBuf, fixing a latent bug that could mix up entries across multi-root sessions.

Internal

  • Per-server epoch snapshots before didChange enable deterministic freshness detection
  • 5 new integration tests cover post-edit freshness, multi-server completion, root-aware storage, empty-publish-as-clean, and version-mismatch rejection (the last one uses an env-toggled stale-version mode in the fake LSP server)
  • Tri-state reporting contract documented in ARCHITECTURE.md and the Response doc comments

Upgrade

{
  "plugin": ["@cortexkit/aft-opencode@latest"]
}

Or run bunx --bun @cortexkit/aft@latest doctor to verify your install.

v0.17.2

Choose a tag to compare

@github-actions github-actions released this 28 Apr 08:19

Full Changelog: v0.17.1...v0.17.2