# Changelog

## [Unreleased]

## [0.15.2] - 2026-08-25

### Changed

- Version 0.15.1 was tagged but never published: release automation failed while deriving release notes, before any package reached npm. Everything listed under `## [0.15.1]` below ships in this release.

## [0.15.1] - 2026-08-25

### Added

- Added `/language [en|ko]`, the interactive slash command for the persisted `ui.language` selection. Without arguments it reports the current language; with a canonical code, locale tag (`en-US`, `ko-KR`), endonym (`한국어`), English name, or common aliases (`eng`, `kr`, `kor`) it persists the canonical `en`/`ko` value through settings and confirms in the selected language. An unsupported value is rejected with the available list and changes nothing. See `docs/ui-language.md`.
- Added a persisted, owner-directed `ui.language` selector for human-facing settings chrome, with deterministic English fallback and an independently authored initial Korean catalog. Commands, flags, environment variables, canonical values, JSON, and other protocol output remain unchanged. Localization-boundary guidance was informed by the unofficial, unaffiliated MIT-licensed community patch [`yazzang-homelab/gajae-code-ko@aff1bfa`](https://github.com/yazzang-homelab/gajae-code-ko/commit/aff1bfa097789a8e2f13349da3c7f99b008d93b6). (#4919)
- Added frictionless interactive onboarding for unresolved users and `/tutorial`. GJC now performs bounded, local metadata discovery across known coding-agent roots, requires corroborating activity within 90 days before inferring a workflow, falls back to manual guidance when evidence is insufficient, persists only a derived profile and completion decision, discloses unavailable sources, and previews the guided migration result before a single explicit apply or experienced-user skip.
- vLLM servers are now discovered implicitly at `http://127.0.0.1:8000/v1`, the same zero-config UX as Ollama, LM Studio, and oMLX. The existing descriptor lane remains authoritative so implicit discovery does not race or replace authenticated/custom vLLM discovery evidence. Credentialless discovery is loopback-only; trusted shell/user-env `VLLM_BASE_URL` plus `VLLM_API_KEY`, or an explicit `models.yml` provider entry, supports remote servers. Adds `discovery.type: "vllm"`.
- SGLang servers are now discovered implicitly at `http://127.0.0.1:30000/v1`, the same zero-config UX as Ollama, LM Studio, oMLX, and vLLM. The existing descriptor lane remains authoritative so implicit discovery does not race or replace authenticated/custom SGLang discovery evidence. Credentialless discovery is loopback-only; trusted shell/user-env `SGLANG_BASE_URL` plus `SGLANG_API_KEY`, or an explicit `models.yml` provider entry, supports remote servers. Adds `discovery.type: "sglang"`.
- Standalone GitHub release binaries now publish `gajae-release-binaries-v1.json` and `gajae-release-binaries.sha256` so installers and `gjc update` can verify downloads.

### Changed

- End-user install and `gjc update` are standalone-binary-first and do not require Bun. `scripts/install.sh` / `install.ps1` default to the current platform GitHub release (stable, `--channel nightly`, or `--ref <tag>`), never detect or download Bun, and keep `--source` as an explicit development path that requires an already-installed Bun. `gjc update` resolves channels from GitHub releases, atomically replaces a matching binary, migrates package-manager shims to a user binary path instead of overwriting them, and refuses to self-overwrite source checkouts or `dev:link` executables.
- The five `macOS Local (oMLX)` presets are retuned from same-machine throughput measurements (#4871). All presets now use one role-effort ladder — critic and architect `high`, planner `medium`, executor and default `low` — and pick each preset's model by measured local throughput: `fast` keeps the 4-bit and `balanced` the 8-bit Qwen 3.6 35B A3B MoE quants (93.5 / 71.1 tok/s measured), `quality` keeps the 8-bit MoE for default/executor/planner/architect and routes only the critic to the official dense `Qwen3.8-27B-8bit` checkpoint (public SWE-bench/agent scores still favor dense for criticism), and both `abliterated` presets move to the faster `Qwen3.8-27B-Uncensored-MLX-4bit` (19.8 tok/s measured, ahead of the Abliterated 4-bit/6-bit quants on every measured axis). Preset display names now state the measured throughput instead of the memory-tier hints.

### Fixed

- Onboarding language detection (`/tutorial`, first-run onboarding) no longer misreads English or Korean transcripts as French, Spanish, or German. Word evidence for space-delimited languages is matched on token boundaries instead of substrings — `le`/`la`/`el` used to match inside `file`, `please`, `class`, and `help`, so plain English handed French a majority — Korean, Japanese, and Chinese are identified by script instead of particle substrings, script counts and word hits share one ranking so two Hangul or Han characters cannot override a clearly English transcript, a claimant script needs two characters before it may claim mixed han text so one stray Hangul or kana glyph cannot erase dominant han evidence, a language must lead the runner-up outright to beat the OS locale, and an explicit `ui.language` selection outranks both. `/language` also accepts locale tags such as `en-US` / `ko-KR` and now rejects hostile inputs that used to resolve to inherited object properties (`__proto__`, `constructor`) instead of canonical `en`/`ko`.
- `ask` question wording and option labels no longer use the post-resample U+2014 display-text exemption because accepted calls enter durable session history and deep-interview rounds; genuinely display-only tool fields retain the existing exemption (#4926).
- Gajae Pet now treats forwarded `LC_TERMINAL=iTerm2` and `TERM_PROGRAM=iTerm.app` values as probe hints over SSH, enabling iTerm2 only after an active OSC 1337 File-capability reply. Generic SSH, spoofed/noninteractive/CI environments, and unmanaged tmux/screen/zellij nesting remain on the text fallback; verified Kitty and Sixel keep precedence. (#4911)
- Interactive terminal completion now publishes the local `agent_end` boundary before waiting for the ordered coordinator sidecar write, so slow or lock-hostile WSL drvfs mounts cannot leave the foreground activity indicator and composer busy after a turn has completed. Coordinator persistence ordering and extension delivery remain unchanged. (#4741)
- One unresponsive session host can no longer own SDK Router startup. A host whose listener still accepts while its loop never serves answers nothing, and `SessionRouter.start()` awaited the initial attach pass, so startup cost the sum of every such session's request budget: measured at 40.7s of a 40.9s startup for a single spinning host, against the 10s budget `gjc sdk session list` gives startup — every SDK CLI call on that agent directory failed with `SDK session Router startup timed out` while eleven healthy sessions had attached in under 200ms combined. `start()` now bounds the initial pass (5s) and lets it finish in the background, and an attach replay whose frame was dispatched and left unanswered (`uncertain_after_send`) no longer runs the 3-retry ladder against a peer that cannot answer any of them. Per-request replay budgets are unchanged, so a host that is legitimately slow — settling a cancellation grace, for example — is not cut off; the periodic 2s reconcile keeps converging stragglers. Measured on the same workspace: startup 40.9s to 5.1s.

- Telegram polling no longer wedges behind a deleted private-chat `forum_topic_created` update. Definitive missing-topic `400` responses from the adoption picker now consume the stale update, remove only its matching pending-topic sidecar, and let the ordered poll offset advance; rate limits, network/server failures, authentication failures, and ambiguous responses remain retryable. (#4904)
- Quota and rate-limit exhaustion now keep AuthStorage's already-computed unblock instant on the terminal provider error as `retryable at <ISO-8601>`. All-row exhaustion still surfaces immediately with no wait and no extra retry loop (#4908).
- ACP sessions re-register reverse providers when a permission lease expires or the same client reattaches. Guarded tools no longer fail permanently with `no permission provider is connected` after the reverse-lease TTL/reclaim grace lapses on a still-living session (#4909).

- `gjc --worktree` now runs the declared Bun, npm, or pnpm frozen install in workspace launch worktrees instead of linking the origin checkout's `node_modules`. Workspace imports and install mutations are bound to the worktree's own lockfile and checkout, including reuse of worktrees carrying the legacy source-tree link (#4620).
- Custom OpenAI-compatible models now expose and send reasoning levels only when their transport capability is known or explicitly opted in. Arbitrary proxies no longer inherit a universal `reasoning_effort` assumption from `reasoning: true`; `compat.supportsReasoningEffort: false` keeps the selector, persisted effort controls, and outbound payload aligned, while bundled OpenRouter, OpenCode, Kilo, and other audited provider routes retain their existing reasoning controls (#4903).
- Late conventional MCP connections publish into the active session registry through an ordered, failure-isolated chain: concurrent late snapshots can no longer interleave a stale tool list, one failed publication no longer kills every later one, and mixed plugin + conventional sessions re-seal the fixed connection set once the last conventional server settles instead of staying reloadable.
- User skill discovery now follows the active `agentDir` profile, matching MCP and customization writers instead of silently reading the default home-relative profile. The bundled `deep-interview` trigger no longer claims explicit third-party Ouroboros commands.
- `gjc setup hermes` no longer hardcodes the Hermes MCP client timeouts. New `--timeout` / `--connect-timeout` flags (whole seconds, 1–3600, exit 2 on anything else) override the 180/60 defaults, and `--install` now preserves existing numeric `timeout` / `connect_timeout` values from a GJC-marked block per field when the flag is omitted — a hand-set `timeout: 900` survives re-installs instead of being reset to 180 (#4878). The managed setup signature no longer pins those two operator knobs (hand-tuning keeps the block managed and `--check` no longer flags timeout drift), and unmarked blocks keep the existing refusal and `--force` full-overwrite behavior. Docs/help now state these are host MCP client call budgets — not GJC turn deadlines and not the coordinator per-call caps (`watch_events` 30000 ms; `await_turn` 30 minutes). Upgrading a pre-#4878 install: a block still matching its stored signature is re-signed on the next `--install`; a hand-tuned legacy block is refused with a dedicated stale-signature error pointing at `--force` (which preserves the tuned values); `--profile-dir` targets holding an older operator-instructions render need one `--force` because that file's digest pins its exact content. Unpreservable values (non-numeric or out of range) fall back to defaults with an explicit warning instead of being written through.

- Bumped Telegram daemon durable authority to generation 176 after the SDK lifecycle changes modified protected broker declarations. Older generation-175 daemons are now unattachable by this host, preserving fail-closed discovery and launch ownership across rolling upgrades (#4668).
- Gajae Pet now stays reachable in tmux and image-protocol-free SSH clients through a bounded two-row text-cell rendering. The fallback derives conservative Block Elements from the active skin, uses truecolor when available and ANSI-256 otherwise, reserves no extra transcript rows, and retains the pixel renderer unchanged for supported Kitty, Sixel, and iTerm2 terminals (#4867).
- `gjc setup hermes --gjc-command` now accepts the full server command instead of always appending `mcp-serve coordinator` (#4877). Omitted or single-token values stay executable-only and render byte-identically to previous output (`gjc`, `/opt/gjc` + `args: [mcp-serve, coordinator]`); a multi-token value is the complete command, split quote-aware (single/double quotes, backslash escapes) into controller argv and rendered verbatim with nothing appended — `--gjc-command 'python3 /tmp/gjc-wrapper.py'` now emits `command: python3`, `args: [/tmp/gjc-wrapper.py]`, exactly the shape a wrapper that already execs `gjc mcp-serve coordinator` needs instead of the doubled argv tail it previously received. Unbalanced quotes are rejected with an explicit error; the value is tokenized, never shell-evaluated. Setup signatures are computed from the rendered block, so existing installs and idempotent re-installs remain valid.

- Corrected the session-state lock recovery contract documented for 0.15.0: valid dead regular state owners are reclaimed automatically, while foreign, unqualified, malformed owners and post-crash atomic transition directories remain fail-closed for explicit operator recovery.
- Python kernel reuse is once again keyed on the settings values that shape a kernel, not on `Settings` object identity. Binding built-in executors to session settings (#4826) made `scopedSessionId` suffix each kernel key with a per-instance counter, so two logical sessions in one process — which resolve the same shell configuration and therefore spawn byte-identical kernels — each started their own kernel, and disposing one shut down a kernel the other still owned. The scope is now a fingerprint of the resolved shell config (shell, args, environment), so equivalent settings share one retained kernel while genuinely different shell environments stay isolated.
- Flat Telegram private-chat idle markers now carry a short per-session tag (`🟢 Agent idle · <tag>`), because the flat fallback has no topic to identify which session went idle (#4855). Asks and threaded/topic delivery are unchanged, preserving the identity-once contract from #981; the tag is the last six characters of the session id and no full session id is ever rendered. The discarded `sessionTag` parameter threaded through `buildRedactedAction`, `notificationActionPayload`, and `NotificationPresentationEngine` — computed but never consumed anywhere — was removed.

## [0.15.0] - 2026-08-22

### Added

- Added `router.SessionRouter.generationStatus(sessionId, endpointGeneration)`, a public credential-free exact-generation reconciliation predicate for managed SDK consumers. It returns deterministic `current`, positively evidenced `retired`, live-generation `replaced`, or fail-closed `unknown` results without exposing endpoint URL/token credentials, process IDs, locators, or private Broker payloads. Close/delete proof is durable across Router and Broker restart within the bounded session-index retention policy; unavailable/incomplete reconciliation, expired history, ambiguous authority, and detected generation reuse remain unknown (#4848).
- The bundled `autoresearch` skill now cooperatively drives missions through GJC goal mode: after spec or cold-intake persistence, the agent creates or resumes a matching inline goal so the continuation loop nudges the mission to a structured verdict receipt. Active or paused goals owned by another objective suppress creation and remain untouched; inconclusive means `status.disposition: "inconclusive"`, while only `status.disposition: "conclusive"` requests mission auto-clear after goal completion. Manual clear, handoff, and cancel require the agent to drop only the mission's own nonterminal goal when the runtime permits it; generic goal guards, aborts, and handoffs can leave state open, so failed steps are surfaced and retried on re-entry without rewriting completed goals as dropped. Verdict writes are append-only and not idempotent, so uncertain retries must inspect the latest receipt before issuing another verdict. Instruction-only: no runtime code, config keys, guards, or `ask` blocking.
- Added an SDK `automationTools` option for host-owned `browser` and `computer` implementations. External automation retains built-in activation and provenance, receives the normal abort signal, works independently of the default browser/platform gates, and fails closed on custom, extension, or MCP name collisions (#4809).
- Added opt-in Task autorouting that derives deterministic `fast`, `balanced`, and `strong` model tiers from declared provider priority. Tier generation is catalog-scoped and auth-independent, `/routing` opens the smart-routing setup panel, and bounded preflight evidence records candidate skips and acceptance outcomes.

### Changed

- `macos-omlx-quality` now routes every role through `omlx/Qwen3.6-35B-A3B-8bit` instead of the BF16 checkpoint, retaining the profile's higher reasoning-effort ladder while reducing the local model footprint and improving measured generation throughput on its M5 Max 128 GB target. The preset group label now uses the branded `macOS Local (oMLX)` casing throughout the public profile picker.
- Upgrade note: restart the SDK broker after upgrading (`gjc sdk` callers pick this up automatically on the next launch; an already-running daemon does not). This release adds the `model.resolve` broker operation, and the broker discovery record carries no package-generation fence — a daemon left running from an earlier version keeps serving and answers the new operation with `unknown_operation`, so coordinator `model` pins (`gjc_coordinator_start_session`) fail until it is replaced. Verified against a live daemon: the failure is loud, no session is created, and one broker restart clears it.
- Closed the remaining Sentry triage approval races: `gh` is resolved only from trusted system paths, acknowledgement rechecks and records under the creation lock, pending-create reconciliation uses a per-attempt nonce with compare-and-clear, and malformed timestamp suffixes fail ingestion before rendering. Regression coverage pins the retry/apply interleaving so a newer unresolved create cannot be cleared by an older `--retry-pending` request.

- The SDK session index now self-repairs on append when it finds a corrupt suffix, instead of throwing `Cannot append to corrupt session index log` until an operator runs `gjc gc --repair-session-index`. The inline path reuses the same quarantine-backed repair (evidence preserved under `sessions/quarantine`, valid prefix republished), so a single poisoned row — e.g. a stale long-lived broker signing a `lifecycle_terminal` event against an outdated `indexSeq` — no longer wedges every subsequent session launch in that agent dir; the manual `gc` flag remains for non-converging damage.
- Breaking: removed `task.autorouting.preset` and the autorouting preset layer. Preset-only autorouting configuration is inactive until tiers are generated from smart-routing setup; public routing contracts no longer expose preset/source fields, and routing notes contain tier, fallback, and resume components only.
- Autorouting selectors now use one shared provider-qualified grammar and 256-character bound across runtime validation, generated tiers, routing evidence, and JSON Schema. Settings-derived tiers report provenance only when the provenance is valid; malformed provenance fails closed as hand-authored tiers.

### Fixed

- Cleanly exiting foreground turns now settle on filesystems such as CephFS that reject Linux `renameat2` exchange/no-replace. State owners rewrite their identity-verified, machine-qualified records into reusable PID-1 compatibility tombstones, while transition ownership uses atomic `mkdir`/`rmdir` with a validated sidecar; no live release unlinks a pathname that a successor can race. Foreign, unqualified, malformed, and all post-crash state/transition owners remain fail-closed for explicit operator recovery.
- Telegram inbound reaction acks now stay serialized per update id when an earlier ack settles while a later one is still in flight. `InboundReactionSequencer` deleted the per-update chain unconditionally on settle, so a fast `accepted` ack settling dropped the tail an in-flight `consumed` transition had installed, and the next transition for that update chained onto a fresh promise and ran concurrently — letting a replayed queued marker race the terminal consumed marker the sequencer promises can never be overwritten. The chain delete is now identity-guarded, matching the router's other serialization tail-maps, and tombstone eviction tracks the maximum terminal update id so reverse-order effect completion cannot grow the retained map without bound.
- The Sentry triage approval store (`scripts/sentry-crash-issues.ts`) now works on Windows without weakening its local approval boundary. Windows store directories, files, and lock metadata receive and verify native protected owner-only DACLs bound to retained filesystem identities instead of relying on synthetic POSIX mode bits; reads use an identity-checked handle, so reparse or pathname substitution fails closed. Lock acquisition now publishes a fully initialized candidate through the native atomic no-replace rename primitive instead of classifying Windows `EPERM` from ordinary directory replacement, stale owners are displaced only after definitive process absence, and cleanup uses an exact native directory-tree snapshot so a successor lock cannot be removed through the stale-owner pathname.
- `Esc` abort with a cleanup budget now actually recovers the session when a provider stream ignores its abort signal. After the budget fired and the agent was force-aborted, the abort still awaited the aborted-turn drain, which waits on the wedged prompt's in-flight count — a count that only drops when the wedged `agent.prompt(...)` settles, which force-abort does not make happen. The abort therefore never released its unwind, and every later prompt, message, and abort waited on that unwind forever: a permanent input lockout announced as "forced session recovery". Forced recovery now abandons the exact prompts that were in flight when it fired (tracked by identity, since prompts overlap and do not settle FIFO): the aborted-turn drain and the agent_end defer/flush gates stop waiting on abandoned prompts, the forced terminal `agent_end` publishes instead of parking behind them, and an abandoned prompt that does settle later still cleans up after itself. A bounded abort that piggybacks on an earlier unbounded abort's unwind also no longer discards its own budget: it races the shared unwind and forces the same recovery on the first abort's behalf, so a user `Esc` issued while an extension- or tool-initiated abort is wedged can still recover the session.
- Terminal `agent_end` now retires the foreground `Working…` indicator even when the mutable session streaming flag settles late. The terminal event remains authoritative across later background-task refreshes, while the next `agent_start` re-enables normal streaming activity.
- Bash tool globs with a trailing separator (`pattern*/`) now match directories only, and literal components after a glob (`*/name`) now require the resulting path to exist, matching bash/POSIX pathname expansion. The vendored shell's expander appended post-glob literal components (including the empty component a trailing `/` splits into) without any filesystem check, so `LICENSE*/` matched the plain file `LICENSE`, `ls -d */` received file arguments and exited 1, `for d in */` iterated over files, and `*/absent` fabricated paths that were never on disk. Directory checks follow symlinks (matching bash `*/` semantics) while literal-suffix existence uses lstat (dangling symlinks still match); patterns whose last component is itself a glob are unchanged since their matches come from directory reads.
- Workflow handoffs out of autoresearch now transit properly: `/skill:deep-interview`, `/skill:ralplan`, and `/skill:ultragoal` chain from any live autoresearch phase (`intake`/`research`/`verdict`), and ralplan's `final` phase chains via its manifest terminal states; ralplan/ultragoal live phases still require the explicit write-to-handoff step. `gjc autoresearch clear` remains the finalize-only exit.
- The coordinator idle reaper now judges idle eligibility against the durable WAL's turn activity watermark (`recovery.prompt_watermark_at`, falling back to the session's creation stamp for turn-less sessions) instead of the projection session-state stamp. Projection repair (and any failed close admission) rewrites that state file with a fresh `now` stamp, which reset the idle clock after every failed `session.close` and deferred the reaper's same-key retry by a full idle TTL. The reaper retry contract (`coordinator-reap:<session>:<incarnation>` key reuse) holds again, and an ephemeral session older than the TTL whose last turn completed recently is no longer reapable between turns; the projection stamp remains the fallback only for sessions whose WAL cannot be read. The `stop-session` and `send-prompt-concurrency` lifecycle suites were also re-based onto the post-#4731 durable layout (canonical WAL + namespaced projections; broker `session.create` mocks echo `coordinatorSidecarKeyId`), restoring them to the required CI set (#4835).
- Breaking: autoresearch spec intake is now the explicit `intake` verb (`gjc autoresearch intake --spec <path>`; the bare `--spec` flag form still works). The ambiguous `handoff` verb token is rejected with disambiguation hints instead of silently becoming a cold-intake goal — workflow handoff lives at `/skill:<callee>` (or `gjc state autoresearch handoff --to <callee>`).
- Subagents now start correctly on Bun 1.4 when extension tools provide custom renderers. `RegisteredToolAdapter` installs its renderer adapters before proxying the definition, avoiding a write through the proxy's getter-only property while preserving the no-renderer fallback path.

### Fixed

- Added a fail-closed recursive-deletion boundary to test preloads: recursive cleanup now derives the real home from the account database rather than mutable HOME/TMPDIR values, accepts the supported `/tmp` and `/var/tmp` fixture roots only when they are disjoint from that home, refuses roots, shallow or symlink-escaping targets and unowned paths, and determines Darwin case handling from filesystem evidence; HOME-sensitive fixtures use the safe cleanup contract and static scan, while runtime interception aborts unsafe Bun test processes without touching the target (#4794).
- `sdk session tail --until-idle` now reconciles retained replay and live lifecycle events that carry a `(generation, seq)` position by that canonical order instead of arrival order, so a retained terminal event from an earlier turn no longer completes a newer turn that is still active, and a terminal event observed live is no longer undone by replayed history arriving after it. Positioned event items are emitted canonically ahead of an arrival-ordered segment of events carrying no position; such an event stays visible but cannot supersede a positioned turn state, including for live Router frames without a publication position. Different lifecycle kinds claiming one `(generation, seq)` now fail closed with `protocol_error` instead of letting arrival order decide, and that conflict outranks an otherwise successful idle or close completion. A canonical position must be a pair of non-negative safe integers, checked on the raw replayed event-ring row before projection: a row stating either coordinate property must state both validly, so claiming one coordinate only, or a null, negative, fractional, non-finite, or unsafe-integer coordinate, fails closed with `protocol_error` rather than being silently treated as unpositioned. Only a row stating neither coordinate is unpositioned. Retained transcript entries now deduplicate independently of event-ring rows, so a transcript row projecting the same kind and position as a real event no longer suppresses that event's lifecycle or conflict evidence. `session status <sessionId> <opRef>` remains the lossless authority for a specific prompt operation; a bare live tail otherwise remains attached until its timeout unless the session closes.

- `gjc plugin uninstall --dry-run` no longer performs the uninstall it was asked to preview. `plugin.ts` parsed `--dry-run` but `handleUninstall` never received it, so a preview removed the plugin through all three uninstall paths and reported `Uninstalled` — most visibly `gjc plugin uninstall <name>@<marketplace> --scope user --dry-run --json`, which deregistered the plugin and deleted its cache. Preview is now a separate read-only API per path rather than an early return inside the mutating one, so under `--dry-run` the handler only ever reaches code that cannot write: `previewGjcBundleUninstall`, `MarketplaceManager.resolveUninstallTarget`, and `PluginManager.previewUninstall`. Each resolves and validates exactly what the real uninstall would act on — GJC registry entry and installed-root containment, marketplace target lookup with scope disambiguation and cache-path validation, npm package-name validation — so a preview refuses whatever the real uninstall refuses. The uninstall entry points keep their existing signatures and result shapes.
- A `gjc plugin uninstall --dry-run` for *any* target no longer migrates the GJC plugin registry. GJC-bundle classification runs before a target is selected, so it read the registry for every uninstall target — including npm and marketplace names that never reach the GJC path — through a migrating read that persists legacy-root discovery under the scope lock. With a legacy bundle present at `<cwd>/.gjc/gjc-plugins/<name>` (files on disk, no registry entry), a dry run for an unrelated npm name created and wrote `registry.json` in project scope. Preview classification now uses `readEffectiveRegistryUnpersisted`, which applies legacy discovery and entry migration in memory only: the preview still sees the same entries the real uninstall would act on, without persisting them and without taking the lock (acquiring it would itself create the scope root and a lockfile). Real uninstalls keep the migrating read.
- An unreadable (corrupt) GJC scope registry now makes `gjc plugin uninstall` fail closed for *any* target, dry-run or real. Classification previously skipped an unreadable scope as non-ownership and fell through to the marketplace/npm branches, so a corrupt registry that actually owned the requested name could preview — and the real command could then remove — a same-named marketplace or npm plugin the user never targeted. The refusal is a typed error naming the unreadable scope with a repair hint; nothing is removed while ownership is unknown. The exported mutating GJC uninstall API keeps its historical `invalid_target` result for a malformed registry; only the read-only preview surfaces the internal `registry_unreadable` signal the CLI fails closed on.
- `gjc plugin uninstall <name>@<marketplace> --json` now emits JSON instead of the human `✔ Uninstalled …` line, which the marketplace branch printed even in JSON mode.
- Fixed long skill-HUD entries corrupting bottom-pinned TUI row accounting by emitting at most two independent width-bounded rows and preserving severity glyphs through narrow truncation.
- Image generation now receives the owning session's settings through direct, TUI, tmux, and coordinator/sub-session tool contexts, so `modelRoles.image` resolves for Codex image models without consulting mutable global state (#4826).

- SDK `goal.list/get` no longer reports `resource_gone` when an in-flight session's live goal projection is temporarily unavailable. It recovers the latest authoritative goal mode state from the current session branch after runtime recreation or session replacement, while goal-less sessions return an explicit `no_active_goal` diagnostic instead of being confused with snapshot-store loss (#4824).

- `/logout` (and `gjc accounts logout`) now remove stored API-key credentials, not just OAuth rows. The interactive logout and the CLI only enumerated `oauth` inventory, so API-key logins (OpenCode Go/Zen, Cursor, Venice, DeepSeek, …) were rejected with `API-key credentials are not managed here`; both paths now remove stored credentials of either kind.
- Sanitized autorouting selectors and routing-summary attributes before persistence and `noEscape` prompt interpolation, including control characters and Unicode line separators. Generated tier scope now retains multimodal text-capable models while excluding only image-generation-only catalog entries.
- Autorouting preflight now uses a staged publication when durable session/artifact authority exists and an explicit artifact-only acceptance path when it does not, so a valid synchronous Task without a child session file is not rejected at the acceptance fence.
- Queued SDK prompts now retain their dispatch-time ownership across selection fences instead of reclassifying from a contradictory later streaming snapshot. Fresh promotion, earlier follow-up ordering, and terminal-abort cancellation are reachable again, while `/btw` test fixtures now model the user-drainable queue count required by the current empty-submit contract.
- Coordinator stop and idle-reap now initialize canonical namespace state before reading durable deletion recovery. Fresh or upgraded projection-only sessions were misreported as `state_corrupt` before the broker close was attempted, and completed deletion receipts were excluded from the idempotent missing-session lookup; both paths now preserve strict malformed-state rejection while allowing safe cleanup and replay.
- Fixed provider safety-stop classification being lost before session persistence (#4777). The managed provider-envelope boundary now preserves only the allowlisted `provider_safety_stop` kind, typed safety stops remain terminal even with transport facts on a multi-model fallback chain, and the regression e2e test is selected both by the focused affected-path route and exactly one normal coding-agent shard.
- A forged `errorKind: "provider_safety_stop"` label can no longer suppress model fallback (#4777 review). Terminal safety-stop authority is now adapter-minted (module-scoped provenance set by the first-party anthropic/openai/google adapters when they parse a structured refusal) rather than carried on message data: the loop strips unauthenticated labels at the stream exit, the managed discard gate re-verifies identity, and transport facts never upgrade provenance, so a compromised provider that names the typed kind on an error envelope degrades to an ordinary fallback-eligible failure on every status (400 and 429 both keep advancing the chain). Authenticated envelopes remain single-dispatch terminal with the manual-switch hint intact, and persisted/reloaded sessions retain the legacy display classification without retaining live terminal authority, so restart/replay stays fail-closed.
- The provider safety-stop minting path is now package-private and capability-branded; public AI imports and structural custom-stream payloads cannot forge terminal authority. Trailing stream completion now sanitizes the final message before managed-shell rebuilding when no `done` or `error` event was emitted, preserving fallback for forged labels (#4777 review).
- Retained-publication acquisition diagnostics now name the stage that actually refused instead of always claiming a failed open, and the native errno vocabulary is closed over the actionable `open(2)` refusal set (#4764). `nativeRetainedObstruction()` rendered every unmatched reason as "could not be opened", so a `broker.json` FIFO — which opens fine `O_RDWR | O_NOFOLLOW | O_NONBLOCK` and then refuses `read_to_end` with `WouldBlock` — was reported as an open failure that had in fact succeeded, `clone`/`metadata` refusals of already-open descriptors were reported as opens, and `unsupported-platform` read as an open error. The structured native reason now drives the prose: `errno-`/`io-` render as open refusals, `read-<kind>` as a read failure, `clone-`/`metadata[-kind]` as inspection failures of an already-open object, `unsupported-platform` as unimplemented authority, and any future reason falls back to a stable "withheld publication authority (reason)" sentence instead of misnaming the stage; the structured reason is preferred whenever the native message parses, so the observed-state fallback is never entered for a stage-named refusal. The Rust `errno_name()` vocabulary adds `EAGAIN`, `EMFILE`, `ENAMETOOLONG`, `ENFILE`, `ENOMEM`, `EOVERFLOW`, and `EPERM` alongside the existing six — descriptor exhaustion and LSM/seccomp denial no longer collapse to `UNKNOWN` — while every unlisted errno still renders as `UNKNOWN` and no errno number or OS string ever leaves the native layer.
- `move_session` no longer lets a cwd transition commit inside an in-flight tool's execution (#4629). The previous fence re-checked the cwd generation and then yielded, so a move that started inside a tool's first `await` retargeted work already admitted against the launcher root: bash computed `commandCwd` from the live session cwd only after `expandInternalUrls`, running a root-A command in root B. Relative-path tools now hold a shared read lease on the session cwd for their whole execution (`SessionManager.runWithCwdReadLease`), and writers drain outstanding leases before committing, so the cwd observed at admission stays authoritative until the tool finishes. Announced writers also block newly arriving readers, so a stream of tool calls cannot starve a queued move, and async bash jobs stay bound to the cwd they were admitted for.
- `move_session` re-roots the state the model is shown at the new cwd (#4629). The volatile per-turn message reported the post-move cwd while attaching the launch-bound workspace tree, and subagents launched at the live cwd inherited startup `contextFiles`, `skills`, and `workspaceTree` — pairing the new cwd with the retired root's files and project instructions, which drives wrong path selection rather than merely stale display. Project context files and skills are now re-discovered after a committed move, the workspace-tree service resolves the live cwd per scan instead of capturing the launch root, the cached tree is retired so the next turn re-scans, and the system prompt is rebuilt from the live cwd.
- `move_session` now completes every fallible step before committing (#4629). Process chdir, capability reset, LSP shutdown, and plugin/MCP/Python authority rebinding previously ran *after* `moveTo` committed and the one-move budget was consumed, with rebind failures swallowed — so the tool could reject after the session had already moved, or succeed holding launch-root tool authority. Those steps now run ahead of the commit and restore launch-root authority on failure, so a rejected rescope leaves the session exactly where it was and does not consume the one-move budget.
- `move_session` now verifies process-cwd identity against the pinned directory handle (#4629). The no-follow handle was only ever `stat()`ed while the commit and `setProjectDir` resolved the target *name*, so a replacement after the last comparison escaped the validated descendant — the confinement the tool exists to enforce. Node exposes no `fchdir`, so the post-`chdir` cwd's inode identity is now compared against the pinned handle and the chdir is rolled back on mismatch.
- Process-cwd authority is now an explicit claim rather than an inference (#4629). `move_session` decided it owned the process cwd when `process.cwd() === session.cwd`, which two sessions launched at the same root both satisfy, so rescoping one called process-global `setProjectDir`, `resetCapabilities`, `shutdownAllLspClients`, and plugin-cache clearing underneath its sibling — reaching real execution through consumers such as browser tab work. `SessionManager.claimProcessCwdOwnership` grants the claim to one live manager; a session without it moves its own cwd and leaves process-global state untouched.
- Conventional MCP autoload now reads the user scope from the agent directory instead of a home-relative `<home>/.gjc/agent` path (#4767). Every writer and denylist reader already resolved user scope through `getMCPConfigPath("user")` (the agent directory), while native discovery derived it from the load context's home, so the two disagreed the moment an agent-directory profile was in play: `GJC_CODING_AGENT_DIR=<profile> gjc mcp add <name>` wrote `<profile>/mcp.json` and reported the server as loaded by ordinary sessions at startup, but startup read `~/.gjc/agent/mcp.json` — the profile's own registrations never loaded and the default profile's servers loaded into the profile instead. `loadAllMCPConfigs` accepts an `agentDir`, sessions created with their own `agentDir` pass it, and the `disabledServers` denylist follows the same scope, so discovery, `gjc mcp add`, the `/mcp` wizard, and `gjc customize doctor` all name one file. This also restores isolation for the MCP autoload suites, which established their temp user scope with `setAgentDir()`: after the trusted-home provenance rework their `os.homedir()` mock no longer reached discovery, so on a developer machine the red-team suite read the real `~/.gjc/agent/mcp.json` and in CI it found nothing.
- Accepted SDK prompt controls now receive a bounded zero-progress deadline at durable acceptance or promotion, renew every correlation attached to a shared run, fence expiry before late lifecycle adoption, and reconcile `agent_failed` through the typed extension event. The `goal.list/get` goal-less response remains an explicit `no_active_goal` diagnostic page; integrations using `onQueuedPromoted` must handle both `startsOwnRun` dispositions.
- Immediate `turn.prompt` after `turn.abort` acknowledgement now starts exactly one successor turn instead of being silently consumed by aborted-turn teardown. Abort unwind no longer classifies the successor as steering into the dying loop, delayed `agent_end` is paired with the aborted invocation rather than the successor, and abort acknowledgement waits for that serialized terminal transition — no client-side sleep (#4749). Retention is bounded in the other direction too: abort stays terminal across overlapping aborts. Every abort request advances an abort-admission epoch synchronously, including one that shares an already in-flight unwind, so a prompt admitted between two aborts is refused as a cancelled preflight instead of refreshing its generation and starting work the user already aborted twice. An abort sharing another abort's unwind also applies its own request-scoped effects (preflight cancellation, and abort visibility so a later real abort is not silenced by an earlier `silent: true` abort). A prompt admitted after an abort settles still starts a normal successor turn.
- Esc/Ctrl+C now recover a WSL/basic-terminal session whose busy indicator outlived its turn (#4741). Both the global interrupt/clear listener and the editor escape handler treated any mounted working loader as unconditionally cancellable work: after a completed turn left the loader mounted, every press ran a no-op abort, consumed the key, and reset the escape gestures, so the composer stayed `working [esc]`/`Fetching … [esc]` and Esc, Ctrl+C, and Ctrl+X never did anything (Ctrl+Z suspend/resume was the only escape). The loading branch now stops the stale indicator via the shared activity-indicator stop and lets the key fall through to idle semantics when there is no pending submission (including a started one still inside prompt preflight, exposed through the new `hasPendingSubmission()` context query), no queued steering/follow-up/compaction messages, and the session is neither streaming nor compacting; active work (streaming, queued messages, pending optimistic or started-preflight submission) still aborts exactly as before. Recovery gates on drainable queues only: the new `AgentSession.drainableQueuedMessageCount` counts exactly the steering and follow-up entries that `clearQueue()`/`popLastQueuedMessage()`/`getQueuedMessageEntries()` return, whereas the aggregate `queuedMessageCount` also counts hidden next-turn context that no key press can drain and that deliberately survives turn completion (a `todo_write` failure reminder queued with `deliverAs: "nextTurn"` and no `triggerTurn`) — gating on the aggregate left a permanently nonzero count that reproduced the same lockout while idle. Hidden next-turn ordering and delivery are unchanged; recovery neither clears nor delivers those entries. The same correction applies to the two adjacent gates whose handlers are also visible-queue-only: `app.message.sendNow` no longer advertises itself when only hidden context is queued (its only outcome was "No visible queued message to send"), and an empty submit while streaming no longer aborts the live turn to flush a queue that holds nothing drainable.
- Bare-default Codex and Anthropic provider-overload retries now honor the configured retry ceiling instead of entering the unbounded transient path, and every replay still requires a clean retry scope after extension lifecycle handlers participate.
- `gjc accounts` command errors no longer escape as uncaught exceptions in text mode. `accounts pin` resolves its target through `resolveOAuthPinTarget`, which throws a typed `OAuthCredentialSelectorError` for user-correctable selector problems (API-key rows, active overrides, disabled or missing accounts, ambiguity), and `runAccountsCommand` rendered `AccountsCommandError` only in `--json` mode — text mode rethrew everything, so even the command's own "Provider … is not configured; no pin was written" surfaced as a stack trace plus a `gjc-crash.log` entry. Selector failures now map to `AccountsCommandError` with the message preserved (so `--json` reports `accounts-error` instead of `internal-error`), and text mode prints one clean stderr line with exit code 1 instead of an uncaught exception.
- Reverted the SDK broker package-generation fence (#4862 and its follow-ups). Broker discovery no longer refuses reuse, retires, or signals a live broker because its published `packageGeneration` / `packageVersion` / `installationIdentity` differ from the caller's, so `gjc sdk …`, ACP, and coordinator callers stop failing with `SDK broker package generation … does not match expected generation …`. An in-place upgrade needs the broker stopped once (`bun run restart:sdk-broker` from a source checkout, or stop the published `broker-internal` pid) instead of an automatic retirement handshake.
- Fixed interactive steering stalling during post-prompt unwind when the terminal transcript message is a bash or Python execution; the auto-continue gate now treats all three terminal roles consistently and regression coverage pins delivery for assistant, bash, and Python tails (#4739).
- Restored live subagent progress in the `subagent await` panel through a bounded public DTO. #4748 dropped the `progress` field believing it exposed model-generated deltas and tool output to the parent model; the renderer needs live status, tool names, output counts, fast mode, and retry state, but raw progress must not enter tool-result, ACP, or telemetry envelopes. The await signature now tracks only those approved fields and ignores countdown-only churn, so the panel updates without exposing model deltas, tool arguments, arbitrary output, or nested task payloads.
- Dev CI no longer re-runs the entire code-validation matrix when a pull request body is edited. The verdict line lives in the PR body, so every review verdict update fired a `pull_request: edited` event; with `cancel-in-progress: true` that cancelled the run already validating the identical tree and re-queued the whole matrix against it. Cancelled runs surfaced as confusing downstream failures rather than clean cancellations -- a job would report `Artifact not found for name: dev-affected-plan-<run>` while that artifact existed and its producer had succeeded. `edited` stays in the trigger list because `pr-contract-bootstrap` must re-check the body, but it is now the only job that runs for it; every code-validation root carries an explicit guard and the remaining jobs inherit it through their dependency on a skipped plan.
- Dev CI no longer re-runs the entire code-validation matrix when a pull request body is edited. The verdict line lives in the PR body, so every review verdict update fired a `pull_request: edited` event; with `cancel-in-progress: true` that cancelled the run already validating the identical tree and re-queued the whole matrix against it. Cancelled runs surfaced as confusing downstream failures rather than clean cancellations -- a job would report `Artifact not found for name: dev-affected-plan-<run>` while that artifact existed and its producer had succeeded. `edited` stays in the trigger list because `pr-contract-bootstrap` must re-check the body, but it is now the only job that runs for it; every code-validation root carries an explicit guard and the remaining jobs inherit it through their dependency on a skipped plan. Body-only edited runs are also partitioned into their own concurrency group so they cannot cancel the validation run already covering the identical tree, and the exemption is keyed on a body-only payload -- `edited` also fires for title and base changes, and a base retarget can be the first validation event for a new head/base pair.
- Retained-publication refusal diagnostics now carry the native acquisition object and reason into the surfaced error before any later pathname inspection. POSIX acquisition rejects non-regular owner records before granting authority, while fallback probes are labeled as current observed state and escape the complete Unicode control/format surface.
- Coordinator `gjc_coordinator_start_session`, `gjc_delegate_plan`, and `gjc_delegate_execute` now accept an optional `model` argument that pins an explicit `provider/model` for the started session with the same grammar as `gjc --model` (#4707). An external orchestrator could previously select only a model *profile* through `mpreset`; writing a model name into the prompt is not model selection, so a reserved start could never prove it was on the requested model (every Cursor id — `cursor/claude-fable-5-xhigh`, `cursor/composer-2.5`, `cursor/default`, … — worked from the CLI but not through Coordinator start). The pin is resolved through the same staged selector resolver the CLI uses (against the full registry, not the authenticated-only subset, so a valid id is never rejected up front for a missing local credential) and fails closed with the CLI's `Model "..." not found. Use --list-models` error before any broker mutation or idempotency record — no session is ever created on a different model. The resolved pin threads through the SDK lifecycle launch request as `modelId` and the session host applies it exactly like a CLI `--model` selection, so when both `mpreset` and `model` are given the explicit model wins, matching `gjc --mpreset <p> --model <m>`. The delegate tools' previous prompt-metadata `model` hint is upgraded to this real pin, and reusing a delegate session with a conflicting pin is rejected like the existing `mpreset_conflict`. The effective precedence is `model` > `mpreset` > configured `modelProfile.default` > role/resume/default. The pin is a guarantee rather than a preference: the coordinator validates against its own registry while the child owns the registry that actually serves requests, so on drift (a model removed, a provider disabled, an extension that failed to register) the child now fails session construction before readiness and before any profile application, disposes the partial session, and reports an error naming the pinned selector — previously the discarded fallback warning let startup profile application activate `modelProfile.default`/`mpreset` while the coordinator still reported the requested pin. Coordinator validation also refreshes its registry per request (offline, from the on-disk discovery cache) instead of caching one snapshot for the process lifetime, so ids added or removed after an earlier pin are judged against current contents.
- Custom-provider `apiKeyEnv` credentials sourced from the trusted agent `.env` now follow rotations on the next request instead of remaining pinned to the process-start value. Long-running sessions previously kept sending a revoked OAuth access token after an external account synchronizer atomically replaced the agent `.env`; the registry now re-reads only that trusted regular file and updates or removes the config credential override, failing closed for a symlink or read error after agent ownership is established. Presence in the agent file makes it authoritative over an older value inherited from the launching shell, while names absent from the agent file keep their existing environment precedence. Explicit `apiKey` values remain authoritative when both forms are configured, and caller-project `.env` files remain excluded from credential resolution.
- Coordinator event journal rows can now be pushed to one opt-in webhook (#4706). External orchestrators that cannot stay attached to `gjc_coordinator_watch_events` long-poll (a 300s `await_turn` timeout is not session death) had no push of **existing** journal rows; they can now set `GJC_COORDINATOR_MCP_EVENT_WEBHOOK_URL` to receive each row as an authenticated POST whose body is the exact native `watch_events` record — same `seq`, same stable `id`, at-least-once so sinks dedupe on `id`. The feature is env-only and default-off (no MCP tool can set or read it), destinations are allowlisted (`https:` anywhere, `http:` loopback only, no redirects), the bearer token comes from a secret file path rather than env, an optional session-id scope restricts delivery to authorized sessions, and delivery runs through a durable per-row outbox off the journal append path with bounded attempts, exponential backoff, and a bounded request timeout — a dead sink never delays or rewrites terminal turn/session persistence. The five `GJC_COORDINATOR_MCP_EVENT_WEBHOOK_*` variables resolve through the trusted credential environment (`$credentialEnv`, the same provenance as the crash-relay DSN) rather than raw `process.env`, so a checkout's `.env` cannot select the egress destination or the token file. `watch_events` long-poll is unchanged and remains the source of truth; `gjc coordinator doctor` reports the resolved webhook state.
- Extension activation is now transactional. `pi.registerFlag(..., { default })` and `pi.registerProvider(...)` used to mutate the shared `ExtensionRuntime` state directly with no rollback, so a factory that threw midway was discarded while its side effects leaked: the flag default stayed readable via `getFlag`/`getFlagValues` and the provider registration stayed queued for the ModelRegistry drain in `sdk/session.ts` and `runListModelsCommand`, activating providers from an extension that never activated. Each factory invocation now stages its shared-state writes in an `ExtensionActivationScope` (stage → factory completes without throwing → commit into the shared runtime); rollback discards the staged writes so a failed extension leaves no flag default and no provider registration behind, and earlier extensions' committed state is untouched. After commit the shared runtime is authoritative for `getFlag`, so runtime-side writes (CLI flag overrides, a later extension's committed default) stay observable to retained extension API objects exactly as before the transaction (#4718). Commit itself is transactional: prior flag entries and the provider-queue length are journaled before publication, so a throw partway through commit is undone before it escapes and the scope only becomes terminal once publication fully succeeds — a failed extension leaves nothing behind even when the failure happens during publication.
- A content-free Anthropic capacity overload no longer ends the turn under the default retry configuration. Anthropic can answer with its typed `overloaded_error` as a statusless stream error, and session retry already classifies that as transient, but the bare-default admission list only covered watchdog timeouts and the Codex `server_is_overloaded` event — so the turn surfaced the raw provider envelope and went idle, leaving the operator to resend or switch models by hand for a failure the provider says to retry. The admission now also accepts Anthropic's own overload code, recognized by parsing the error envelope and requiring both the outer `type` and the nested `error.type` to match exactly. Nothing else changes: the attempt must still carry no assistant text, thinking, or tool call and no conflicting transport facts (a status-bearing or otherwise typed failure keeps failing closed), overload prose alone can never authorize a replay, and the existing capped exponential backoff and `retry.enabled: false` opt-out are untouched.
- File tools no longer lose a just-written path or leave a 0-byte target when a write fails (#4734). `writethroughNoop` and LSP writethrough now publish through a sibling temp + rename (`writeFileAtomically`) so a permission/IO error cannot truncate the destination; `EACCES`/`EPERM`/`EROFS` surface as an actionable `ToolError` that says the original file is unchanged. Read tries the ACP `readTextFile` bridge when disk stat misses, and bridge failures fail closed: only an explicit `transport_unavailable`/`bridge_unavailable` code falls back to the agent host's disk, while structured denials and raw OS errno values such as `EPERM` do not, so a local read cannot bypass a remote client's access decision. Successful writes invalidate `fileReadCache`. Compaction-state now lists recent successful `write`/`edit`/`apply_patch`/`ast_edit` paths so a long-session compact does not silently drop in-flight file-tool context. This is independent of Windows directory-fsync `EPERM` (#4457) and of workflow-validation compaction (#4560).
- Closed exact-head review findings on the #4734 atomic write path: the session-local trust boundary is now validated **before** any parent directory is created (a dangling symlink inside a trusted root resolves outside it, so creating parents first materialized an attacker-selected tree outside the sandbox before publication was refused, and the boundary is re-checked after `mkdir -p` follows existing symlinked ancestors); the publication parent is pinned by device/inode rather than realpath string, so a parent unlinked and replaced by a different directory at the same path is detected instead of published into; and the Windows in-place sharing fallback revalidates destination inode identity before mutating by pathname, refusing with `destUnchanged: true`/`not_published` when a concurrent writer substituted a successor during rename backoff. Read's ACP bridge fail-closed policy is now documented accurately: only explicit `transport_unavailable`/`bridge_unavailable` codes fall back to disk, never structured denials or raw OS errno.
- Hardened the #4734 atomic write path after review: LSP writethrough awaited its `BunFile` write again (an unawaited call escaped the surrounding `try`, so a rejecting write was recorded as published and surfaced as an unhandled rejection), the Windows in-place sharing fallback now writes replacement and rollback bytes at absolute position 0 (`handle.writeFile()` resumes from the handle offset, so a partially accepted replacement left interleaved bytes while the result still reported `destUnchanged: true`), and the module contract no longer claims crash atomicity it does not provide. Publication is documented as last-writer-wins in `docs/tools/write.md`: identity is revalidated before the rename, but `rename(2)` commits against the pathname.

- Fixed idle ACP sessions burning sustained CPU with no active turn (#4689). Every attached session's SessionRouter ran a 2s reconcile that unconditionally re-acquired the machine-global index lock and re-read, re-parsed, and re-checksummed the entire session index, then re-projected every historical row — O(total index history) per 2s tick per live session, forever, scaling a fresh session from ~2% (small index) to a sustained 30-70% of a core on an aged machine and degrading concurrent session creation through lock contention. The index now carries a change stamp (size/mtime/ctime over log + snapshot) so a poller proves "nothing changed" with two stats and reloads append-only changes through the tail reader, and the router's idle tick is just that stamp check plus a cheap transport-revive no-op: the full attach/retire body runs only on index changes, pending adoptions, or a 30s liveness sweep (well inside the index's own 2x60s heartbeat-freshness window). Measured idle CPU on a 15k-row index drops from a sustained ~30% to ~1% mean, with prompts unaffected (the pre-send reconcile is unchanged work when state actually changed). Because a running chat daemon builds its own SessionRouter, a pre-upgrade owner would keep the old hot polling loop: the Telegram daemon generation advances to 172 and its serving epoch to 88 (generation alone does not force replacement), and Discord/Slack generations 67/70 fence the same rollout.
- Runtime skill discovery now scans `skills.customDirectories`. Session startup already loaded those directories through `loadSkills`, but `discoverRuntimeSkills` and `findRuntimeSkillByName` searched only the canonical project and user roots, so a configured custom skill was invocable by exact name yet absent from every `skill_discovery` search -- usable only by someone who already knew it existed. Both discovery entry points now scan the configured directories at user level (so project-scoped queries exclude them), deduplicated and tilde-expanded the same way `loadSkills` does. Naming a directory is explicit consent, so custom directories are not gated on `skills.trustUserSkills` -- matching the startup rule -- while the `skills.enabled` master switch still suppresses them.
- A broker that cannot retain its own publication now names the object that withheld authority. The native layer opens `sdk`, `sdk/broker.lock`, `sdk/broker.lock/owner.json`, and `sdk/broker.json` no-follow and reports every refusal as one opaque `Retained broker publication authority is unavailable.`, so `gjc sdk` died with nothing to act on and the precondition could only be learned from the native source — a shared multi-account layout that symlinks the agent directory's `sdk` entry crashed every broker start this way. The failure is still fatal and still rolls back its publication; it now appends the first obstruction (missing entry, symlinked entry, wrong file kind, unreadable entry, or a non-fixed-width `heartbeatAt`) ahead of a bounded agent directory, so the named object survives the 512-character startup-failure reason, and stays verbatim when every precondition holds so a named condition is never invented. Each object is probed with the native's own access mode — the lock record read-only, only the published record read/write — and a file kind is only ever named through the open the native itself refuses, so a layout the native accepts is never reported as an obstruction; the published record is read through the descriptor the no-follow open already verified, never reopened by name. When rollback fails too, the aggregate message now carries the acquisition diagnostic, since the durable startup-failure marker persists only that message.
- Retained broker publication probing now opens POSIX objects non-blocking, diagnoses exact-buffer malformed records, escapes control and bidi characters in persisted agent-directory diagnostics, and covers native-rejected wrong-kind objects without inventing a condition the native layer accepts.
- `subagent await` now ends its wait when the busy session queues a user steer, so the message is handled at the tool boundary instead of after the full wait window; the awaited children keep running.
- Crash relay provenance now rejects repository-declared credential and agent-directory overrides even when dotenv expansion changes their runtime values, preserves trusted XDG state while refusing checkout-controlled XDG state, and accepts maximum-sized legacy index entries during relay-watermark upgrade (#4715).
- A subagent whose provider stream fails with a self-declared `errorKind: "local_buffer_overflow"` is no longer reported to the parent as a staging-buffer overflow (#4618). The agent runtime now derives the terminal message's `errorKind` and `bufferOverflow` from one identity check, so a forged label reaches neither `TaskResultReceipt.preview` nor `errorSummary` and the receipt falls back to the ordinary provider-error text instead of pointing the reader at the wrong subsystem.
- Hardened the parent-facing local-failure boundary for subagent receipts (#4618). The executor now runtime-validates the structured `bufferOverflow` diagnostic before interpolating anything — closed `stage`/`exceeded` literals only, finite non-negative safe-integer counters, and internal consistency with the cap claimed to have tripped — so a foreign provider payload that smuggles the field through a message snapshot degrades to a fixed neutral sentence instead of embedding arbitrary text in `TaskResultReceipt.preview`/`errorSummary`. `local_snapshot_failure` and plain `local` failures likewise render fixed sentences rather than regex-redacted free-form child message text (redaction never covered prompt text, tool arguments, or file content). The render path is pinned end to end by a genuinely tripped staged-buffer overflow carried through the executor boundary, the async job/snapshot chain, and both streaming and cached renderers with per-line width/tab bounds.
- Fixed the reusable composer being terminally disposed at the remaining overlay-open paths: the generic selector boundary (`SelectorController.showSelector`, reached by bare `/login` and the `/debug` entry), the queued-message selector (`app.message.dequeue` / `alt+up` / `alt+down`), the jobs overlay (`alt+j` / `/monitors`), the tasks pane (`alt+t`), the OAuth API-key paste input, the `/mcp add` wizard, and the `/share` loaders, plus the `/debug` log + raw-SSE viewers. Each open called `editorContainer.clear()` with the live composer attached, so the first round-trip silently killed the editor's tab-width listener and runtime tab-width changes stopped re-deriving composer layout. Every open now detaches the reusable editor before clearing — the `Container.detachChild` detach-then-readd reuse contract (#4657). The `/debug` log viewer now binds its async completion to the exact composer child and `editorContainer` mount revision captured before log I/O, so a stale completion cannot misidentify a newer RawSSE/Input owner or reclaim the same composer after an intervening overlay cycle; the RawSSE/Input race regressions prove that it discards stale viewers instead of clearing and disposing whatever UI now owns the container.
- Added maintainer-side issue filing to the Sentry crash triage script (`scripts/sentry-crash-issues.ts`), restored behind an operator approval model (#4716). The script's earlier review history established that the `gjc.fingerprint` tag is stamped client-side with the public DSN ingestion key and is forgeable, so no fingerprint alone can ever authorize a write: `--apply` now files only rows whose exact reviewed content is recorded in a local single-use approval store (`~/.gjc/sentry-triage-approvals.json`, 0600), written by `--approve <batch-digest>` after a human reviews the dry-run listing. Each approval manifest binds repository, org, project, immutable Sentry group id, fingerprint, and a canonical rendered-content digest, is recomputed at `--apply` time and rejected on drift, and is consumed on use — so a later row from a different upstream context or with different rendered content can never inherit an earlier approval. Issue-body marker hits remain untrusted candidates that suppress nothing until `--acknowledge` binds the operator's decision to the exact issue URL, with successful local creations tracked as a separate `filed` idempotence record; the final duplicate-check-plus-create runs inside a lock-directory critical section so two concurrent approved invocations cannot double-file, and same-fingerprint group collisions stay withheld for manual reconciliation. Upstream rows failing ingestion validation are now reported as a distinct incomplete-run condition (non-zero exit) instead of being miscounted as groups without fingerprints.
- Hardened the Sentry triage approval boundary after review: lock takeover/release is owner-token checked, filed manifests remain idempotent across local-store crashes, public issue matches require explicit URL acknowledgement, GitHub child processes receive no Sentry credentials and have their host pinned to github.com (an inherited `GH_HOST` override can no longer redirect issue lookups, creates, or the credential to an arbitrary host), saturated pages and malformed tags fail closed, approval succeeds with a zero exit when durable, and the file-backed store has atomic/fsync and integration coverage. An `--apply` attempt that reaches `gh issue create` and never confirms a canonical issue URL — timeout, thrown error, non-zero exit, or malformed stdout — is now treated as an unresolved remote commit rather than a safe retry: the in-flight record it writes is read back inside the same creation lock, and the row is withheld from every later `--apply` because GitHub's marker search is eventually consistent and therefore cannot prove the issue was not created. Reconciliation is explicit — `--acknowledge <url>` for the issue that did land, or `--retry-pending <fingerprint>` after confirming none did, which clears the record only for the exact currently rendered manifest so a drifted row cannot inherit it.
- Fixed notify recovery feeding native exact-unlink exchange debris back through the exchange, which made a fresh notification daemon spin at ~100% CPU and accumulate unbounded `.gjc-delete-notification-endpoint-*` and `.gjc-exact-unlink-placeholder-*` artifacts (#4701). The native exchange always detaches a target into a fresh quarantine destination and leaves durable retained records, so recovery's endpoint scan re-classified those `.json` quarantine targets as live endpoints and re-quarantined them, while the debris sweep removed each aged stub by re-running the same exchange — manufacturing fresh quarantine/placeholder debris every pass and re-attacking a live endpoint publication. Recovery now treats native exchange debris paths as non-endpoint (never routed through the exchange) and `sweepNotificationDebris` removes positively-stale inert debris by a direct identity-checked unlink (re-verified at mutation time so a live successor is retained, never unlinked). Live endpoints stay published and matchable, health's endpoint census is no longer inflated by debris, and repeated recovery is a fixed point.
- Fixed ralplan run fragmentation by writer cwd: `gjc ralplan` and `gjc ralplan --write` now accept an additive `--worktree-root <path>` that binds run state, stage artifacts, the `index.jsonl` ledger, HUD state, stuck markers, and review-budget accounting to one canonical git worktree regardless of the writer's ambient cwd (#4693). Without the flag, cwd-based behavior is unchanged. An explicit target is canonicalized and must be an existing git worktree root (missing, non-directory, non-git, and subdirectory targets fail before any filesystem mutation); explicit-target mode requires exact canonical `worktreeRoot` equality with the seeded run's `repository_binding` — a linked worktree that merely shares `commonDir` is rejected — and refuses to write where no seeded run state exists, so a role lane can never stamp fragmented authority in a second `.gjc` tree. A relative `--artifact` file is still resolved from the invoking cwd, and the bundled ralplan skill now instructs Planner/Architect/Critic/revision/disposition/post-interview/ADR/final writes to propagate the seed receipt's `repository_binding.worktreeRoot`. This removes the false terminal `PLANNING-STUCK` a dispatcher-cwd re-review hit when its local ledger lacked the target worktree's `revision` opener.
- Windows ACP `session.create` no longer leaks `terminal_uncertain` when a lifecycle child publishes ready/endpoint evidence and then exits before the broker's final live admission (#4709). The ready-then-exit tolerance and rollback-receipt relaxation are win32-only through a testable host-platform seam; other platforms keep the prior fail-closed `terminal_uncertain` path. The typed broker/ACP code is `ready_then_exited` so MCP launch remapping cannot misattribute it as an MCP unavailable failure. A session host that rejects its liveness watcher after readiness writes no startup receipt, so the exact ready-plus-death classification is never preempted; a host that tore down gracefully is proven ready through the broker's own host registration when its endpoint file is gone. The ready-authority probe is typed (`matched`/`absent_indexed`/`absent_unindexed`/`not_published`/`malformed`/`io_error`): the broker-index fallback fires only on a proven-absent (ENOENT) endpoint, and a corrupt or unreadable endpoint surfaces as the new honest terminal `endpoint_unreadable` instead of a false `spawn_failed` claim about the child. Owned-artifact deletion re-checks the lifecycle marker immediately before unlinking the endpoint so a PID-reusing successor's endpoint can never be removed. Detached-host stderr is discarded by the OS (`stdio: "ignore"`): a parent-owned stderr pipe is not detached by `unref()` and would retain the broker process for the host's lifetime, and the host handles launch configuration and credential-bearing environment material, so its output is never captured, persisted, or included in caller-visible error strings. A dead child is never admitted as live.
- Added an agent-invokable session rescope: a `move_session` tool the model can call when a session launched from a broad multi-repo root has converged on one repository (#4629). It runs the same sequence as the text/ACP `/move` handler (`flush` → `moveTo` → `setProjectDir` → plugin/capability cache reset → SSH tool refresh), so tool path resolution, the bash default cwd, and relative-path resolution all follow the move. The tool is exposed through an optional `ToolSession.rescopeSessionCwd` accessor wired only for top-level sessions (`taskDepth 0`) without a restricted bash surface — subagents and read-only profiles never see it — and rejects non-directory targets without moving. Unlike the user-driven `/move`, the model-invoked accessor only narrows: the canonical (realpath) target must be a strict descendant of the canonical current cwd; the committed move revalidates a no-follow directory identity so a swapped symlink cannot escape; SessionManager serializes model and SDK/ACP moves and dispose joins the in-flight transition; restored-or-live workflow skills refuse the move; and cwd-capturing MCP/custom/Python/project authority is revoked and recreated at the target. Like the user-driven `/move`, the volatile workspace-tree snapshot itself still follows the launch cwd until #4628 lands.
- Handled tool failures are now recorded and relayed alongside fatal crashes. A tool that throws and is caught never reaches the `uncaughtException` path, so those errors were invisible to both `gjc crash list` and the upstream relay. They are captured at `finishExecuteToolSpan`, which already holds the live `Error` with an intact stack, and only when `status === "error"` with a non-empty stack -- aborted calls, blocked calls, and non-`Error` throws are excluded because without a stack the v1 fingerprint degrades to `<no-app-frame>` and unrelated failures collapse into one group. Hooking `logger.error` was rejected for the same reason: nearly every call site passes `String(error)` or `error.message`, so the stack is already gone. Handled errors use their own files rather than the fatal store, since they are high-volume and would otherwise evict the rare fatal records under a shared cap; record format, redaction, fingerprint, and the `sanitizeExternalCrashV1` egress contract are reused verbatim. Upstream they differ only by `level` (`error` rather than `fatal`), and fatal signatures relay first so a noisy handled class cannot starve them at the per-run cap.
- Closed remaining crash-relay egress and durability holes from #4658/#4698: automatic fatal and handled stores resolve through a non-XDG trusted agent path so a checkout `.env` that sets `XDG_STATE_HOME` and creates `$XDG_STATE_HOME/gjc` cannot feed forged state; the relay comparison watermark follows journal append order independently of display-time `lastSeen`; legacy `relayedAt` indexes keep coverage across upgrade and downgrade/re-upgrade; a 2xx POST whose local watermark append fails is a failed send and retries with the same event id.
- `gjc config set` and `gjc config reset` now fail instead of reporting success when the setting cannot be durably written to disk.
- Fixed a steer sent while a tool was running leaving the session idle with nothing but `Operation aborted`. Interrupting an in-flight tool aborts the run's signal without ending the agent loop, so the loop unwound the tool and then drained the queued steering inside that same aborted run: the steer was delivered as a user message, the turn opened on the already-aborted signal, and the provider rejected it before the first token. The operator saw the tool marked `Tool execution was aborted.`, their steer echoed back, and then a bare `Operation aborted` with the session going idle — the instruction was consumed and answered by nothing. An aborted run now hands queued steering back and ends, so the existing resume path starts a fresh run and delivers it, which is what already happened when no tool was in flight. The phantom empty assistant turn is gone with it. The same guard covers the tool-execution steering drain (the immediate-interrupt path), which could otherwise reopen a born-aborted turn when the interrupt landed while the steer-interrupted tools unwound, and the tool-execution steering check no longer consumes steering once the run's signal is aborted — a tool task still unwinding could otherwise dequeue steering the drain had just handed back, orphaning it in an execution result nobody reads.
- Fixed broker-managed SDK prompts that run longer than the process-local submission TTL (`PROMPT_SUBMISSION_TTL_MS`, 5 min) reaching durable `terminal_ok` without their `agent_end`/`agent_failed` ever entering the positioned event ring (#4691). `terminalizePrompt()` committed the durable outcome and marked the submission terminal, then `emitPromptLifecycle()` ran `cleanupPromptRecords()` first, which age-evicted the just-terminalized record, so publication returned before the correlated terminal frame was emitted and attached subscribers (and replays) never saw it. Age-based and capacity eviction now apply only after terminal publication has settled (`delivered`) or for fatal transport-level closures, which never publish by design; records stranded without a runtime or after a publication throw are expired immediately with their dedupe tombstone so bounded retention is preserved. Regression coverage pins past-TTL terminal delivery, the mid-terminalization cleanup race, exactly-once re-drive, and registry health after a past-TTL terminal.
- Session-scoped reasoning (`Shift+Tab`, `/effort`) now survives the next user prompt when the default fallback chain carries an explicit suffix such as `xai/grok-4.6:high`. Re-resolving that chain used to re-apply the suffix and snap the live effort back to `:high` before the request left, so the UI could show `xhigh` while the turn ran at `high`.
- Session thinking scope now distinguishes operator-chosen effort from model defaults (#4695). `thinking_level_change` entries carry operator-intent provenance set only by the control surfaces (`/effort`, Shift+Tab); appends driven by a model's own `defaultLevel` on model switch, temporary model switches, context clears, or re-applies after model cycling no longer mint `session` scope, so a suffixed default-chain entry such as `xai/grok-4.6:high` is no longer suppressed when the operator never chose an effort.
- Session restore now applies the same chain-resolution thinking-level rule as the two per-turn re-resolution sites. The restore site still wrote the resolved selector's suffix level through `setThinkingLevel`, which appended a stray `thinking_level_change` entry; that entry flipped the restore recompute's `hasThinkingEntry` probe and restored the wrong effort (a session with no thinking history of its own came back at the switching session's level instead of `defaultThinkingLevel`). The block is removed in favor of the unconditional recompute, so all three sites share one rule (#4697).
- A Round-0 deep-interview `ask` whose `deepInterview` object is present with topology identity (`round: 0`, `component: "review-topology"`, `dimension: "topology"`) but omits required topology fields (`ambiguity`, `intent_contract`) is now rejected before coercion with a targeted correction naming the omitted fields and the exact `intent_contract` shape, instead of generic zod issues plus a full payload echo. The incomplete object is not a retired-pair recovery candidate, so it fell through to schema validation whose message named neither the contract nor what a corrected retry must contain — and a metadata-only retry repeated the same invalid shape (#4649). Recovery stays fail-closed: `intent_contract` is never synthesized (items and affirmative labels are the locked-intent evidence), and the recorder still locks intent only on an affirmative user answer. Valid contract-only Round 0 and post-Round-0 payloads validate unchanged.
- Added an opt-in crash upstream so local crash signatures can be aggregated across installs, which the agent-dir-scoped index could never answer on its own. `crashReport.upstream` (default `off`) plus `crashReport.upstreamDsn` (or `GJC_CRASH_SENTRY_DSN`) gate a hand-rolled Sentry envelope POST; no DSN literal is compiled into the binary, so a build has no destination to fall back to and `off` costs one settings read and no IO. The relay never runs on the fatal path — a crashing process still performs exactly one `O_APPEND` journal write, and relaying happens at the next startup after index compaction, bounded to 8 signatures per run with a 10s timeout. A Sentry SDK is deliberately not used: SDK defaults attach breadcrumbs, environment, and argv, which would defeat the point of the outbound sanitizer. Every crash-derived field must pass `sanitizeExternalCrashV1` and a refusal drops that signature outright rather than falling back to a less-sanitized payload; the emitted payload is a fixed key set (`event_id`, `timestamp`, `platform`, `level`, `logger`, `release`, `environment`, `fingerprint`, `exception`, `tags`, `extra`, `sdk`) with `user`, `server_name`, `contexts`, `breadcrumbs`, `request`, `modules`, env vars, argv, and hostname structurally absent. The gjc fingerprint is sent as Sentry's `fingerprint` array so grouping is ours rather than Sentry's heuristics — one upstream issue per gjc signature, verified by two events with disjoint stack frames merging into a single group. `gjc crash relay` exposes the same batch as a loud, non-zero-on-refusal command. A new `relayed` journal event stamps `relayedAt` monotonically so re-runs do not resend unless `lastSeen` advanced, and `relayedAt` is deliberately not an input to index eviction. The `gjc crash report` GitHub flow keeps its per-invocation, digest-confirmed consent boundary unchanged; the two egress channels are separate with separate rules.
- The builtin `python` tool is now a mission-free persistent per-session REPL. It no longer requires an active autoresearch mission, owns its kernel as `python:<session-id>` (distinct from the eval kernel), always runs in the session cwd, and appends every execute call to an append-only transcript at `.gjc/_session-{sessionid}/ipykernels/{datetime}-{kernelid}/transcript.jsonl` (rotated per real kernel lifetime via a new acquisition-time `onKernelStart` executor callback). The `clear` action and per-agent isolation are preserved; the managed research-venv seeding is dropped.
- `gjc autoresearch` is now ledger-only: the `report` verb is removed (explicit unknown-verb rejection, exit 2), the mission-notebook concept is deleted (`src/autoresearch/{python-tool,session,report}.ts`), CLI `clear` no longer disposes any kernel, and the `kernel_cleared` ledger event is renamed `mission_cleared`. Ledger verbs keep their existing mission guards unchanged.
- `gjc team` worker auto-checkpoints no longer commit and merge root-level worker runtime state (`.gjc/state/**`, e.g. SDK broker endpoints like `.gjc/state/sdk/<session-id>.json` and settings migration markers) into the leader repo's default branch. The checkpoint classifier's protected prefixes now cover both GJC runtime roots — `.gjc/_session-*/` and `.gjc/state/` — while user-owned `.gjc/` content (config, agents, skills) stays eligible as reviewable worker work. The worker-runtime-state e2e guard now asserts absence at the actual leader merge-target path instead of an unrelated session-scoped path, and matcher boundary cases (`.gjc/state` bare entry vs `.gjc/state-*` siblings) are pinned (#4603).
- Terminal `provider_safety_stop` errors now surface a bounded diagnostics hint after the raw provider refusal in the TUI assistant-message render and text-mode (`gjc -p`) stderr: safety stops are often specific to the (model, context) pair, the accumulated context does not need to be discarded, and the session continues after a manual model switch. When the default role's configured `modelRoles` chain contains another model that the authoritative model selector resolver accepts (`allowInvalidThinkingSelectorFallback: false`, so malformed suffixes like `:bogus` fail closed while route-suffixed IDs keep their exact-ID semantics) and that is not the model that refused, the hint names it and shows the canonical `/model <provider/model>` command; otherwise it falls back to static `/model` guidance. Purely presentation-only: the stop stays terminal, no second provider dispatch, retry, fallback switch, quarantine, or state mutation occurs (#2069/#2077 invariants preserved), the raw refusal text is retained verbatim, alternates are never claimed to be guaranteed, and unrelated error kinds get no hint (#4650).
- A gjc session running inside a Herdr pane no longer disappears from Herdr's agent list — permanently, until restarted — the first time it shells out to `gjc`. Herdr's pane variables are inherited, so a nested invocation (an agent running `gjc doctor`, a scripted `gjc -p`, any child that creates a session) claimed the very same `custom:gjc` authority on the parent's `HERDR_PANE_ID`, and on exit released it and cleared the pane title. Herdr keeps the accepted sequence per source as a monotonic watermark, and since 0.14.1 each process seeds its own from the wall clock, so the child's watermark sat minutes ahead of the parent's counter — which advances by one per state transition and can never catch up. The pane lost its agent row and its title while the session was still running, and every later `working`/`idle` report was silently dropped. The process that installs the reporter now stamps `GJC_HERDR_PANE_OWNER=<pane-id>:<pid>` into its environment; descendants inherit it, see the pane already owned, and report nothing at all. A claim naming a different pane is ignored rather than trusted, so a marker inherited across panes cannot silence a legitimate session.
- Codex-backed web search and OpenAI hosted image generation now sanitize OS-derived `User-Agent` components, preventing non-ASCII Android kernel release names (e.g. `4.4.302-Minimal™-EAS-QTI_Haptic-R26`) from being rejected as invalid HTTP headers before the request is sent — the same crash class fixed for the Codex model provider.

- The Herdr pane ownership marker is now versioned and bound to the installing process's OS incarnation plus a process-local token. Descendants remain silent only while the recorded owner is live; stale or reused-PID markers are reclaimed, same-process environment spoofing is discarded, and release clears only the installer's own claim.

- ACP `session/new` can reduce latency by overlapping the active-provider walk with model-catalog continuation pages. The provider enumeration (`providers.list/active`) starts only after the first `models.list/current` page has resolved: assembling that page is what finalizes host-side credential state (expired OAuth credentials are refreshed or disabled while it is built, and continuation pages replay the frozen revision), so the provider snapshot can never mix pre- and post-refresh credential state with the catalog it filters. The saving is limited to the actual overlap and is zero for a single-page catalog; the dominant catalog enumeration itself is unchanged and remains per-session (#4682).
- A resident blob that goes missing on a path that still fails closed now says which session it belonged to and leaves a record. `ResidentBlobMissingError` already received `sessionId` and `sessionFile` from every construction site but rendered neither, so an abort surfaced a bare 64-hex hash naming no transcript to inspect and not even identifying the resident cache as the subsystem; the message now carries both. The three remaining throw paths (staged-reference verification before commit, cold-spill promotion, and materialization under the fail-closed policy) also emitted nothing at all, while the *non-fatal* legacy image resolvers warn on every miss and the demotion salvage reports each placeholder it substitutes: the recoverable cases were observable and the turn-killing ones were not. Each fail-closed throw now logs one bounded line naming the phase, kind, hash, and session, and the resident-transition materialize lane now actually supplies the binding it was already given everywhere else — its inline stores were the one construction that dropped `sessionId`/`sessionFile`, so its record would have logged both as undefined. The corrupted boundary one lane over — a staged sentinel whose ref does not even parse — failed just as closed with a bare `Error` that named no ref, kind, or session and left no record; it now logs the same style of bounded record (with the ref truncated, since a corrupted boundary is exactly where unbounded input appears) and throws a message carrying the binding. Placeholder substitution stays silent on that channel, since it is self-evidencing in the transcript and already has its own report.
- Fixed an SDK broker that stops publishing while its process stays alive blocking every client permanently (#4704). The publication tick is a chain of awaited IO -- the machine-global session-index lock, the retained heartbeat write, the host checkpoint -- and a fence is only reachable from an observation that returned or an error that was thrown, so a stall that does neither armed neither the loss nor the ambiguity deadline. The broker then held its port and its lock with a published heartbeat aging far past its TTL, and peers refused to reclaim a lock whose owner pid is alive, so every `paseo run --provider gjc`, `gjc sdk`, and ACP launch failed with `Broker lock is held by a live owner (pid N)` until the process was killed by hand (observed: 13.6 hours, after a `session index "heartbeat checkpoint" still holds the index lock after 10s` warning that escalated to nothing). Liveness is now measured from the last *successful* publication rather than from being scheduled: a broker that has published nothing within `max(60s, heartbeatTtl * 4)` terminates itself as `lost-root`, so the dead-owner reclaim from #3963 hands the lock to its successor. The check runs before any await in the tick and on every tick even while an earlier one is still in flight, so a stalled chain cannot starve the path that ends it, and ticks no longer stack behind a stalled predecessor. The retained heartbeat's positional write and its `fsync` also moved off the JS thread onto the libuv blocking pool (`heartbeatAsync`/`syncAsync`, replacing the synchronous bindings): both are unbounded, and running them on the JS thread let a wedged filesystem stop the timers, signal handlers, and broker completion along with the write they were blocking -- a process that could not even notice it had stopped publishing. The retained handle is split accordingly, so shutdown is not merely moved behind the same stall: the single-writer lock is taken only on the pool, while `observe()` and `close()` need the descriptors rather than the write ordering and take a lock held for a pointer read alone. Closing therefore detaches immediately -- an unresolved worker keeps its own reference, observes the closed flag, and returns without committing a heartbeat against authority the owner has already given up -- so `lost-root` completion finishes and the lock stays reclaimable by the successor while the stalled write is still outstanding.
- The per-turn project context now carries the host's local date, clock time, UTC offset, and IANA zone (`Today is 2026-08-19 (Wed), the local time is 21:04 UTC+09:00 (Asia/Seoul)`) instead of `new Date().toISOString().slice(0, 10)`. The agent previously received a bare UTC calendar date with no clock and no zone, so it reported every timestamp it read from files, logs, git, and APIs in UTC — forcing users outside UTC to convert by hand — and was a full day wrong for east-of-UTC users during their local morning (00:00–09:00 KST rendered as the previous date). The same block now instructs the agent to convert timestamps explicitly identified as UTC to that local zone while preserving timestamps that already include an offset or timezone. Rendering is derived from a single `Intl.DateTimeFormat` pass over one resolved zone, with deterministic UTC fallback for invalid clock values, so date, clock, offset, and zone can never disagree, and the vestigial UTC `date`/`dateTime` fields are dropped from the cached stable system prefix, which never rendered them. The zone follows runtime process configuration and may be influenced by project environment settings; it is display context, not trusted location evidence. Full IANA-zone output can reveal geographic information, and date/time overrides are accepted only in the strict derived formats used by deterministic tests.

### Fixed

- A gjc session no longer disappears from Herdr's sidebar when the Herdr server is replaced under it. A server restart or `herdr update --handoff` starts with an empty agent registry, and the reporter deduplicates against the last state it sent, so a session waiting at its prompt stayed invisible until the user typed into it. The reporter now watches the pane's API socket — replacement rebinds the same path to a new inode — and re-reports its current state and last title when that happens.

## [0.14.2] - 2026-08-20

### Added
- Added an opt-in crash upstream relay for cross-install aggregation. Handled tool errors are captured and relayed to a configurable Sentry upstream; relayed-upstream state transitions are journaled and indexed; and the relay is hardened across the trust boundary (no unsigned provenance), delivery watermarks (no replay races), egress and durability (bounded queues, LRU-bounded handled dedupe, restored fatal-occurrence journaling), and recovery/provenance paths closed by exact-head review. The generated config schema was regenerated after the global-only relay settings. Off by default; enabling requires explicit operator opt-in.
- Added a manual-switch hint after provider safety stops: when a provider halts a turn for a safety reason, the session now surfaces that the operator can switch models/providers manually instead of leaving the stop unexplained (#4650, #4653).
- Shift+Tab effort cycling now wins over profile suffixes, and operator-intent effort is distinguished from model-default appends, so explicit effort choices are no longer silently overridden at restore time (#4695, #4700).

### Fixed
- Session lifecycle events no longer disappear when a prompt's post-prompt resource lease cannot be acquired. Assistant and tool messages still append to the canonical transcript while stale events remain fenced to the current run, preventing long tool loops from billing turns that are invisible and unrecoverable (#4788).
- Fixed a self-locking managed-session scope that eventually forced repeated emergency heap compaction, which users experience as a long session abruptly losing its earlier context. Two defects combined into a deadlock: the per-mutation receipt reconciliation counted **every** dirent in the bound directory against `REPLACEMENT_CLEANUP_RECEIPT_SCAN_LIMIT` rather than the receipts it actually reconciles, and the remnant reaper that is the only mechanism able to shrink such a directory was scheduled *after* that scan. On macOS every managed replacement leaks zero-byte write-protocol remnants (no retained directory authority), so a long-lived scope drifted past the limit, began throwing `managed_replace_cleanup_receipt_limit_exceeded` on every mutation, and could never reach the cleanup that would have fixed it. Tool-output eviction persists through that same store, so each eviction failed closed and retained its original output in memory until the non-disableable emergency floor cut the session (observed in one scope: 50,003 dirents of which 47,043 were inert zero-byte remnants and **zero** were receipts, 55 failed evictions, and 9 heap compactions in a single day). Reaping is now scheduled before reconciliation, and the scan limit counts only receipt entries, so unbounded receipt growth is still rejected while an inert-remnant backlog resolves itself instead of wedging the scope permanently.
- Fixed worker runtime state under `.gjc/state` being swept into leader-repository auto-checkpoints during team runs; checkpoint classification now excludes it (#4603).
- Fixed ralplan explicit-target worktree handling end to end: runs bind to the explicit `--worktree-root` instead of fragmenting by writer cwd, a trailing `--worktree-root` with no operand is rejected, HEAD objects are verified and explicit-target writes contained, mutation fails closed on review findings, and `.gjc` is confined before migration and fails closed without procfs.
- Fixed SDK broker liveness (#4704): a broker that stops publishing while its process stays alive is now terminated; the retained broker heartbeat and watchdog observation run off the JS thread; shutdown and observation no longer hold the retained writer lock; stale watchdog observations are fenced during teardown; startup admission reopens after authority recheck; and retained work is fenced across broker restarts.
- Fixed agent steering losing or racing user input: a steer that interrupts an in-flight tool is delivered, the remaining abort-race gaps in steer-after-tool-interrupt are closed, and a live subagent await ends when user steering arrives instead of hanging.
- Fixed rotating agent environment credentials not being reloaded and hardened the rotation path so concurrent readers never observe a partially written credential set.
- Fixed the config CLI reporting success when a setting was not durably saved, and made retargeted CLI persistence fail closed instead of writing to an unintended target.
- Fixed session robustness: a fatal resident blob now names the session and leaves a record; the corrupted-sentinel lane the strict check walked past is closed; and the transition materialize lane is bound and the dead invalid-ref guard dropped.
- Fixed notification/herdr integration: recovery no longer re-quarantines native exchange debris, debris cleanup binds to native identity, pane authority binds to process identity so a nested gjc cannot release its parent pane's agent authority, and the native lint gate is satisfied.
- Fixed `/usage` not surfacing cached usage for API-key credentials in `getCachedUsageReport` (#4686), and made the account-inventory usage tests hermetic on credential-free hosts (#4663).
- Fixed the auth gateway rejecting explicit `null` fields on openai-chat requests (#4667).
- Fixed Grok Build turns stalling by giving the provider the same 300s idle window other long-turn providers use.
- Fixed skill discovery ignoring `skills.customDirectories`; configured directories are now scanned (#4713).
- Fixed the escaped-non-ASCII em-dash exemption applying beyond display-only tools (#4627).
- Fixed deep-interview Round 0 ask calls omitting required intent metadata, which produced HTTP 400 rejections on strict providers (#4651).
- Fixed SDK terminal-in-progress prompt submissions being dropped past the delivery TTL.
- Fixed postmortem tests writing into the real crash store; they now isolate into a fresh store.
## [0.14.1] - 2026-08-18
- Fixed a resident-cache demotion aborting the turn it was supposed to rescue. The demotion is the salvage that runs *because* the cache already failed, so it materializes a store that is by definition missing blobs — yet it was the one materialize call site that never passed `missingPolicy`, inheriting the fail-closed default. It therefore threw `ResidentBlobMissingError` on the very entry it existed to rescue, the transition never committed, the store stayed broken, and every later turn repeated the same throw out of the `message_end` handler. That handler only `logger.warn`s, so the operator saw a bare `Operation aborted` with every pending tool marked `Tool execution was aborted.` and nothing naming a cache, a blob, or an errno — permanently, until the session was restarted. The salvage now substitutes the same placeholder the persistence path already uses, which never emits a `blob:sha256:` ref, so the invariant the fail-closed policy protects is preserved while the session stays usable. Unrecoverable substitutions are counted in `residentBlobPlaceholderCount` and logged once each with their kind, hash, and the demotion's errno, so silent content loss is visible rather than inferred.
- Parent task receipts now surface a terminal subagent `local_buffer_overflow`/`local_snapshot_failure` actionably instead of the generic `Task failed; error recorded.`: the executor retains a bounded, redaction-safe `localErrorSummary` (kind + summary) and the receipt preview, `errorSummary`, subagent tool output, and await renderer all name the local kind and carry the diagnostic so a staging-buffer failure can no longer be misread as a provider or context-window problem. Overflow summaries are built ONLY from the agent runtime's structured, identity-checked `bufferOverflow` shape (closed-vocabulary stage/`exceeded` literals and numbers) — never from the free-form `errorMessage`, which a foreign self-labeled error could fill with arbitrary text; a self-labeled overflow without the shape degrades to a neutral sentence. Renderer guidance is kind-conditional (staging limit that reproduces on re-issue vs. serialization defect that is safe to retry) (#4618).
- Fixed the resident-cache GC reaping the cache of a *running* session, which made long-lived sessions abort every turn with an unexplained `Operation aborted`. The owner lease stores a process start time purely to detect PID reuse, and derived it from `ps -o lstart=` — a zoneless local wall clock rendered from `/etc/localtime` by `ps`, then bound to a zone by `Date.parse` in the reading runtime. Those are two independent zone resolutions: pinning `LC_ALL`/`LANG` stabilized the *format* but not the *instant*, so a reader whose runtime resolved UTC while `ps` rendered UTC+9 computed a start time nine hours off for the same live PID, declared the owner reused, and deleted its instance directory. Observed in the wild: a test process reaped the live instance directories of two running sessions in the same sweep (captured at 32,400,000 ms of skew, exactly the host offset). Each victim then lost the externalized bodies of its large `edit` snapshots, and every later turn died materializing them. The probe now pins `TZ=UTC` on the `ps` child and parses the render explicitly as GMT, so the value is absolute in any reader; leases carry `startTimeBasis: "utc"`, and a lease written before the pin leaves reuse unproven instead of stale, so upgrading cannot reap a live owner once on the way through.
- A resident text cache demotion now names the OS failure behind it. `ResidentCacheTrustError` lifts the wrapped errno into `causeCode` and a bounded, single-line `causeSummary`; `SessionManager` logs both on `Resident cache trust rejection` and reports `residentCacheDegradedCauseCode` in observability stats; the managed-sidecar disposal warning gains the path-free `causeCode` only, so the cache path it deliberately withholds stays withheld. Previously the record carried `reason` alone, which collapses distinct failures: `blob_create_failed` reads identically whether the instance directory vanished under a live store (`ENOENT`), the process exhausted descriptors (`EMFILE`), or the tree turned read-only (`EACCES`). That mattered in practice — long-running sessions that had externalized a large edit snapshot began aborting every turn once their cache went missing, and the demotion record could not distinguish that from a hostile-path rejection without attaching a debugger to a process that was already failing.
- Fixed `/usage` and `/usage check` omitting provider limits for stored OAuth accounts. Cache-only snapshots now use the provider's resolved base URL when reading usage, and explicit checks render the successful probe report directly instead of depending on a cache-key-identical readback (#4634).
- `/usage` shows quota resets again. Canonicalizing multi-account management (`364f14022`) rewired the interactive `/usage` handler from the graphical panel to the account-inventory text view, and that view rendered only `label: N% used (M% left)` — no bars, no reset countdown — leaving the command unable to answer when a quota comes back and stranding `handleUsageCommand`/`renderUsageReports` as unreachable code. Plain `/usage` in the TUI renders the panel again, sourced from the same cache-only inventory snapshot the text view reads, so the cache-only contract is preserved and no fetch or probe is reintroduced; `/usage check` keeps the text path, where the per-credential health verdict is the point. Account rows on every surface (TUI, ACP, Telegram) now carry `resets in <countdown> (<absolute time>)`, and the panel itself gained multi-account reset lines, hour-precision countdowns past 48h (`6d 14h`, previously rounded to a bare `7d` at anywhere from 6.6 to 7.4 days), and set-aware account-label truncation so pooled credentials sharing a domain no longer collapse into identical columns.
- A Round-0 deep-interview `ask` whose `deepInterview` object is present with topology identity (`round: 0`, `component: "review-topology", "dimension": "topology") but omits required topology fields (`ambiguity`, `intent_contract`) is now rejected before coercion with a targeted correction naming the omitted fields and the exact `intent_contract` shape, instead of generic zod issues plus a full payload echo. The incomplete object is not a retired-pair recovery candidate, so it fell through to schema validation whose message named neither the contract nor what a corrected retry must contain — and a metadata-only retry repeated the same invalid shape (#4649). Recovery stays fail-closed: `intent_contract` is never synthesized (items and affirmative labels are the locked-intent evidence), and the recorder still locks intent only on an affirmative user answer. Valid contract-only Round 0 and post-Round-0 payloads validate unchanged.
- Added an opt-in crash upstream so local crash signatures can be aggregated across installs, which the agent-dir-scoped index could never answer on its own. `crashReport.upstream` (default `off`) plus `crashReport.upstreamDsn` (or `GJC_CRASH_SENTRY_DSN`) gate a hand-rolled Sentry envelope POST; no DSN literal is compiled into the binary, so a build has no destination to fall back to and `off` costs one settings read and no IO. The relay never runs on the fatal path — a crashing process still performs exactly one `O_APPEND` journal write, and relaying happens at the next startup after index compaction, bounded to 8 signatures per run with a 10s timeout. A Sentry SDK is deliberately not used: SDK defaults attach breadcrumbs, environment, and argv, which would defeat the point of the outbound sanitizer. Every crash-derived field must pass `sanitizeExternalCrashV1` and a refusal drops that signature outright rather than falling back to a less-sanitized payload; the emitted payload is a fixed key set (`event_id`, `timestamp`, `platform`, `level`, `logger`, `release`, `environment`, `fingerprint`, `exception`, `tags`, `extra`, `sdk`) with `user`, `server_name`, `contexts`, `breadcrumbs`, `request`, `modules`, env vars, argv, and hostname structurally absent. The gjc fingerprint is sent as Sentry's `fingerprint` array so grouping is ours rather than Sentry's heuristics — one upstream issue per gjc signature, verified by two events with disjoint stack frames merging into a single group. `gjc crash relay` exposes the same batch as a loud, non-zero-on-refusal command. A new `relayed` journal event stamps `relayedAt` monotonically so re-runs do not resend unless `lastSeen` advanced, and `relayedAt` is deliberately not an input to index eviction. The `gjc crash report` GitHub flow keeps its per-invocation, digest-confirmed consent boundary unchanged; the two egress channels are separate with separate rules.
- Managed-fallback sessions now steer escaped-non-ASCII retries instead of re-issuing them blind, and bound them. The agent loop's `escaped_arguments_discarded` outcome reports whether the discarded attempt still lacked a transient recovery instruction, and the session's retry continuation attaches exactly one such instruction (naming the `\uXXXX` defect and demanding literal UTF-8) through the new `transientRecoveryMessage` prompt option, so the deterministic Hangul-escaping failure observed on `ask` payloads is corrected on the retry instead of exhausting the budget every turn. Because those retries are deliberately un-charged (the defect is not provider evidence), each continuation is a fresh loop with a fresh in-loop resample budget and the fallback chain never exhausts on them — so a deterministic escaper previously looped forever under managed fallback (measured: 2,940 provider calls in 5s before an external timeout). The session now bounds escaped retries per logical run (steered retry + blind retry, reset each user turn) and fails closed through the terminal exhaustion message. The instruction is transient: sent only to the provider, never committed to durable history, never riding a later request; the terminal per-call rejection stays fail-closed.
- Fixed resume listing scaling its read-syscall count with total transcript bytes. The trailing `header_patch` scan walks back to BOF whenever `cwd`/`title` stay unresolved (#3633), which is the common case because only `/rename` and workspace moves ever emit a patch; because the scan borrowed the caller's 4 KiB prefix buffer, that walk cost one `read` per 4 KiB of every candidate transcript on each `--resume`, `--continue`, and picker open. The scan now owns a 64 KiB buffer, so the same bytes are covered in ~16x fewer syscalls. Measured on a real 31-session workspace holding 105 MB of transcripts (largest 41 MB): 25,715 reads / 61.9 s before, 1,652 reads / 0.5 s after, with all 24 recovered titles unchanged. Buried-title recovery, the bytes examined, the `header_patch` marker prefilter, and listing results are unchanged.

- Fixed Telegram forum topics freezing after the identity header: an attached, trusted session whose topic-host lease expired (20 s `HEARTBEAT_TTL_MS`) could never renew it, because `renewActiveTopicLeases` only renewed sessions that already passed the trusted-lease gate, so every later `turn_stream`/`context_update`/tool frame was rejected pre-send with "trusted attachment lease is stale" and the topic never updated again (#4647). A live attachment that still owns its exact logical session and holds an authorized recovery lease may now re-arm its own expired host lease — from the ownership heartbeat and once more before the publication gate — mirroring `acquireLease` admission (expired-but-owned active lease, or a same-owner resume inside the disconnect-grace window, which also covers the incident's persisted `disconnect_grace` record). Dropped sessions, closed endpoints, foreign lease owners, archive-fenced/inactive topics, malformed bindings, and cross-session ownership checks all still fail closed. Daemon generation bumped 169→170.
- A tool call the agent loop refuses now reports why it was refused. The loop attaches its own failure envelope (`{ failureKind }`) in place of the tool's details, and the TUI still dispatched that envelope at the tool's renderer, which owns only its own detail shape: `search_tool_bm25` threw on `details.tools`, `task` printed `Task result details unavailable`, `resolve` printed `Failed: pending action`, and `write` painted its success card — in every case the rejection text (for example the `\uXXXX`-escaped-arguments rejection that ends a Korean `task` call) never reached the screen. Such a result now renders the same error card a tool without a renderer already produces: the failed status line plus the reason. Results a tool produced itself keep their renderer, including `todo_write`'s own `failureKind`.
- `SdkClient` requests accept `beforeDispatch`/`onDispatch` boundary callbacks (`SdkRequestOptions`), giving dispatch-aware consumers a synchronous post-send boundary without owning the raw transport lifecycle (#4640). `onDispatch` fires immediately after the frame is handed to the socket — never before — with the exact request identity (`frame.id`), `connectionId`, and transport generation; a throwing observer cannot displace settlement, so the request still settles through its response, deadline, or `uncertain_after_send` retirement on transport close. `beforeDispatch` runs before the write and its throw aborts the dispatch with nothing on the wire (no sent record, caller's own error, retryable). This replaces the only previous alternative — a raw `send()` + `onFrame()` request that could never settle on a close after handoff and waited for its own timeout — while keeping pending-request ownership inside the client.
- Discovered oMLX models now keep thinking metadata (`reasoning: true`, `supportsReasoningEffort`, `thinkingFormat: qwen-chat-template`) so `macos-omlx-*` role suffixes (`:low`/`:medium`/`:high`) survive clamp and reach oMLX as `chat_template_kwargs.reasoning_effort`.
- Added built-in `MACOS LOCAL (OMLX)` model profiles (`macos-omlx-fast`, `macos-omlx-balanced`, `macos-omlx-quality`, `macos-omlx-abliterated-fast`, `macos-omlx-abliterated-balanced`) for oMLX local inference on Apple Silicon Macs with native full context support and single-LLM thinking effort role mappings to eliminate model swap latency.
- Fixed an HTTP 400 that killed every deep-interview session on the `google-antigravity` provider before the first assistant turn. The Round-0 topology `ask` schema pinned `round` with `z.literal(0)`, which zod serializes as `const: 0` and the Cloud Code Assist normalizer rewrites to a numeric `enum: [0]` — a shape CCA rejects (`TYPE_STRING`). `round` is now pinned with an integer range `[0, 0]` instead, so the wire schema carries `type: integer` with the bounds spilled into the description (the same treatment `ambiguity` already gets) and no numeric enum remains. Runtime contract unchanged: only `0` validates (#4606).
- The terminal-app integration docs now cite the upstream work that backs each support rating: Gajae Code is proposed for Paseo's in-app ACP provider catalog ([getpaseo/paseo#3471](https://github.com/getpaseo/paseo/pull/3471)) and for Orca's built-in agent registry ([stablyai/orca#15025](https://github.com/stablyai/orca/pull/15025)), while T3 Code has no GJC harness and the integration shape is under discussion upstream ([pingdotgg/t3code#7290](https://github.com/pingdotgg/t3code/discussions/7290)).
- The README controller section is now generic and SDK-first: it is titled for OpenClaw / Hermes / Grokbot / your own bot, and the copy-paste bootstrap prompt drives GJC through the broker-bound `gjc sdk session` CLI and the bundled [`sdk-skills/`](https://github.com/Yeachan-Heo/gajae-code/tree/main/sdk-skills) procedures instead of the Coordinator MCP setup. The prompt now names the real surface: `list`/`inspect`/`raw query` discovery, `session.create|fork|resume|close` lifecycle with an idempotency key, `send --op-ref` plus `status` reconciliation (one fresh op-ref per logical prompt; `unknown` is uncertainty, not proof of non-execution), `tail --until-idle`, and the `ask.answer` / `workflow.gate_answer` control allowlist. Coordinator MCP is kept as the pointer for event-driven fan-out across worktrees.
- Every session start under a non-writable cwd (e.g. a Windows console defaulting to `C:\Windows\System32`) no longer dies with an uncaught `EPERM` before any output: `FileGateStore.beginRuntimeInstance` no longer flushes at construction when the store holds no gates and no counters (the runtime instance id rides along with the first real mutation, preserving the documented lazy first-write contract), and `flushState` now runs `mkdirSync` inside the typed write boundary so a genuinely unwritable directory surfaces as `GateStoreWriteError` instead of a raw `ErrnoException` escaping the store abstraction (#4568).
- Fixed ACP `session/new` failing with `lost exact Router authority` for every symlinked workspace cwd. The broker's session index stores the lifecycle caller's lexical cwd in `locator.repo` (`reconcileReadyScope` re-scopes only that field) while `locator.stateRoot` stays the host process's physical path, because the host derives it from `process.cwd()`, which resolves symlinks; `SessionRouter#readEndpoint` derived the expected state root from `repo` and compared spellings with a plain `path.resolve` equality, so a symlinked cwd (`/home/jun/desk -> /data/Lina-Desk`, macOS `/var -> /private/var`) made the scope test fail on every reconcile, the adopted attachment was retired, and the ACP agent tore down a healthy host after its publication poll expired. The scope test now compares path identity through `resolveEquivalentPath` — the same symlink-equivalent comparison `sameResumeLocator` and the index fence-row predicate already use — so equivalent spellings resolve to `default`/`chat` and the attachment publishes.
- A cold-spilled tool call whose payload can no longer be recovered no longer breaks the whole session. `rehydrateColdSpillRef` reports an unreadable, hash-mismatched, or unparseable blob by returning the human-readable `[Cold-spill blob unavailable: …]` sentence, and that string landed directly on `toolCall.arguments`. Providers forward it verbatim — Anthropic serializes it into `tool_use.input` and rejects the entire request with `tool_use.input: Input should be a valid dictionary` — so one missing blob made a compacted session permanently unresumable, with an error naming a message index rather than the lost payload. Rehydration now enforces the object invariant at the session boundary and degrades just that call to the existing `incompleteArguments` / `"malformed"` contract, preserving the recovered text under `recoveryNotice`, so the agent loop refuses that one call with retryable guidance while the rest of the transcript still loads.
- `/copy` no longer throws when the most recent assistant turn carries a persisted `toolCall.arguments` of `null` (written by an earlier cold-spill eviction path). The last-bash-command lookup read `arguments.command` unguarded, the same shape that made compaction pruning fatal.

- The `/model` selector no longer freezes the UI thread for hundreds of milliseconds on open, selection, tab refresh, and every catalog change. Canonical catalog queries rebuilt a 4k-entry candidate-key set (and a full provider policy + catalog order) once per canonical record — 1,243× per query — and the selector then re-resolved every record through the same per-record path again. `getCanonicalModels` now builds the variant filter plan once per query, and a new batch `getCanonicalModelSelections` resolves every record with one shared candidate set, policy, and catalog order; the selector and `--list-models` consume the batch form. On the bundled 4,276-model catalog with credentials: canonical listing 348ms→1.5ms, batch resolution ~700ms→7.5ms, post-selection refresh 351ms→40ms, per-catalog-change refresh 345ms→40ms. Per-record `resolveCanonicalModel` semantics are unchanged and pinned by a whole-catalog equivalence test.
- Provider tabs in the `/model` selector no longer force a network round-trip on every visit. The selector now refreshes the selected provider with the same cache-respecting strategy the rest of the app uses (`online-if-uncached`), and configured discovery now publishes a credential-safe SHA-256 provenance fingerprint of the reusable effective discovery request context (credential evidence, normalized endpoint, non-secret effective request headers, and the semantic request shape: discovery type, provider api, per-prefix api routing, models.dev catalog key) with its cache rows; secret-bearing header and endpoint query/userinfo contexts persist only sanitized model metadata with secret-independent one-shot provenance and cannot reuse cached discovery — previously the row stored discovered model ids without provenance, so every network-strategy refresh treated its own cache as suspect and re-fetched on each provider-tab visit. A provider tab whose discovery cache is fresh now renders instantly with zero fetches (the discovery state also reports the cache row's real fetch time instead of "now"); stale (24h TTL), missing, or request-context-changed caches (credential, endpoint, or e.g. a tenant/project header) still re-fetch, pinned by a zero-fetch revisit test, a constant-credential header-change invalidation test, and fetch-count probes. Raw header values are never persisted or logged — only the one-way digest is stored.
- The `/model` selector and `gjc --list-models` no longer surface another discovery context's cached models when a provenance-forced refetch fails. Stale-while-error fallback previously merged cached dynamic rows unconditionally on a failed/absent fetch; now those rows only serve while the cache's provenance fingerprint still matches the current request context, so a credential, endpoint, tenant-header, or request-shape change whose validation re-fetch errors (e.g. a 503) fails closed — the foreign rows are omitted from the catalog and the discovery state reports the error instead. Same-context stale-while-error and explicit `offline` reuse remain supported. The synchronous constructor-time cache load applies the same provenance gate, so a rebooted registry cannot briefly expose mismatched rows before refresh. A failed fetch with no prior row now persists a non-authoritative tombstone that preserves a concurrent insert (`ON CONFLICT DO NOTHING`) and a same-context row is downgraded only by compare-and-swap on its exact contents, so a concurrent foreign writer can never be overwritten or last-writer-win; the retry cadence for those non-authoritative rows follows the standard 5-minute backoff instead of a network attempt on every provider-tab visit. Opening the `/model` picker in an offline pass now also clears previously published dynamic models for discovery contexts that can never be cache-reused (secret-bearing headers, endpoint query/userinfo, malformed URLs), so another context's rows cannot linger in the live catalog until the next successful online discovery; bundled/static/custom models with the same id are preserved.
- Configured-discovery providers whose cache row is served at startup (constructor load, fresh-cache revisits) now re-derive the provider transport override — including non-secret request headers such as `OpenAI-Organization` — from the same source the live publish path uses. Persisted cache rows deliberately store sanitized model metadata without headers, so without this the models surfaced from a warm cache lost their provider headers until the next successful online discovery.
- Re-added the `commandcode-goat` model profile changelog entry (default GLM-5.3 / execution DeepSeek V4 Flash / planning Kimi K3 / criticism GLM-5.2 / architecture DeepSeek V4 Pro) under Unreleased; the rebase had dropped it.
## [0.14.0] - 2026-08-17
- Documented how to run GJC inside external agent shells. `README.md` gains a support-rated integration table for [Paseo](https://paseo.sh) (★★★★★ — `gjc setup paseo` writes a conformance-tested ACP provider), [Orca](https://onorca.dev) (★★★★ — GJC runs as a custom CLI agent per worktree), and [T3 Code](https://t3.codes) (★★★ experimental — no GJC harness exists upstream yet), and [`docs/terminal-app-integrations.md`](../../docs/terminal-app-integrations.md) carries the per-host setup, verification, cancel-semantics, and troubleshooting detail. The T3 Code row is deliberately marked unsupported rather than advertising an install command for a bridge that does not exist.

- Session endpoints hosted on the notification-adapter transport now deliver every ring-retained session event live to attached SDK subscribers as the same positioned `event` envelope (`generation`/`seq`) that `event_replay` returns, sent per connection over the validated directed leg with the same capability gating replay applies. Previously the live leg only pushed raw side-channel frames — the native broadcast enum reduced non-native kinds (including terminal `agent_end` lifecycle) to empty `unknown` frames, and correlated lifecycle reached only the submitting connection — so an already-attached direct SDK subscriber could observe a later positioned event, including a turn's terminal lifecycle, only by issuing another replay. Each connection's directed writer now bounds queued host frames to the replay-ring capacity; a lagged subscriber rejects additional best-effort live sends and recovers through replay (including the existing sequence-gap contract) instead of growing an unbounded backlog. Ring persistence, replay ordering, event positions, correlated requester delivery, and native notification frames are unchanged.
- Persisted edit results no longer inline complete pre/post-edit file snapshots. `EditToolDetails.oldText`/`newText` (and each `perFileResults[]` copy) larger than 16 KiB are replaced at the session-persistence boundary by a fixed-size digest receipt (`oldTextDigest`/`newTextDigest`: UTF-8 byte length + SHA-256) plus an externalization marker, so a tiny `apply_patch` to a large file costs bytes proportional to its diff instead of ~2x the file size in the managed transcript (#4566). Live in-process results still carry full bodies for ACP `diff` ToolCallContent and editors; sub-16 KiB snapshots persist verbatim; diffs, paths, ops, first-changed-line, diagnostics, and meta are unchanged. A regression suite pins bounding, inline sub-cap behavior, multi-file copies, and the near-limit committed-edit durability contract.
- Near-limit managed appends are now a typed, deterministic outcome instead of a silent recovery or an unclassified abort. When a live append crosses the 128 MiB managed per-file cap, the existing full-rewrite recovery runs and is then verified: if the recovered transcript still cannot hold the entry (or the rewrite itself hits `content_too_large`), the append throws `SessionNearLimitAppendError` carrying structured fields (`code: "near_limit_append"`, entry/live/cap bytes, whether the entry is retained in memory) and a message stating whether the committed edit's receipt is preserved and how to continue (`/compact` or `gjc export`). `AgentSession` maps it to a structured tool-result outcome instead of a generic fatal `SessionAppendPersistenceError`, so a committed source mutation can no longer lose its receipt silently (#4566).
- Telegram notification delivery now carries an explicit per-update inbound acknowledgement contract: user messages are acked `accepted` at session preflight acceptance (before the turn starts, so a fast turn can no longer out-race the pending-update registration), late admission failures ack `rejected`, and genuinely discarded frames ack `dropped`. Policy-suspended control commands are deferred to activation instead of being acked as dropped, per-update reaction transitions are serialized with terminal states monotonic (a slow queued 👀 can no longer overwrite a later ✅), and retraction sends the empty reaction list the Bot API requires. Daemon generation bumped 167→168. (#4528)

- Managed fallback local snapshot failures now surface their one producer-boundary diagnostic immediately instead of re-issuing the identical request up to three times. The failure still never charges the provider fallback chain, advances models, or mutates credentials.

### Added

- Ultragoal boundary validation is now risk-proportional and runtime-authoritative (#4560): a deterministic applicability policy selects expensive boundary lanes from trusted change-set facts, plan shape, open review blockers, and a runtime-computed source-basis digest exposed by `gjc ultragoal quality-gate source-hash`. Low-risk single-goal boundaries may omit redundant cleaner and architect review only through an exact runtime-validated `validationLaneSelection` proof; QA, targeted verification, frozen `sourceHash` binding, receipts, provenance, and join-before-repair remain mandatory. Workflow-enforcement changes classify themselves as high-risk, untracked files are content/identity hashed without following symlinks, and CI changed-path metadata is bound only when the inspected Git root is the authoritative `GITHUB_WORKSPACE`, so outer workflow metadata cannot poison an independent nested/test repository. High-risk, multi-goal, computer/shared-registry, migration, security, native, SDK/public-contract, agent-wire, incomplete, and untrusted changes retain the full cohort. Terminal-critic reuse additionally requires the current and prior cohort hashes to match the authoritative digest.

### Changed

- Ralplan now performs a persisted material-intent reconciliation stage before Architect/Critic consensus (#4560), asking only about unresolved decisions that can change objective, scope, non-goals, acceptance criteria, architecture, or verification. Material corrections revise the Planner artifact before review; the post-consensus interview is retained as a delta gate for assumptions introduced by review.
- Hardened the #4560 risk-proportional validation and compaction recovery surfaces after review. Multi-goal risk is now derived from the durable aggregate shape rather than remaining goals, so a two-goal run stays high-risk through its final aggregate boundary; security classification adds `packages/ai/src/auth-storage.ts`, `runtime-mcp/oauth-flow.ts`, and `commands/auth-broker.ts` plus filename-marker detection so credential surfaces cannot grade low-risk wherever they live; `gjc ultragoal quality-gate lane-selection` exposes the authoritative computed proof so a reduced cohort is obtainable without running the omitted lanes, and architect evidence is no longer demanded when the verified selection omits that lane. Ralplan recovery resumes a planner-only run at intent reconciliation instead of consensus review; recovery verifies every path component with no-follow semantics and hashes the same bytes it projects; and checkpoint change-set capture re-checks a repository state witness so a snapshot raced by concurrent mutation fails closed as incomplete.
- Compaction recovery for active Ralplan and Ultragoal runs now reloads a bounded structured projection from canonical durable workflow state instead of relying on summary prose alone (#4560). Active Ralplan consensus restores its confined, digest-verified Planner/revision artifact and exact review/revision/reconciliation action; Ultragoal restores goals, blockers, joined cohort evidence, and next action. Continuation enforces scope reload, latest-user-intent precedence, explicit classification of scope expansion, unchanged-basis rerun avoidance, and zero-progress escalation counted once per actual compaction; malformed, stale, tampered, paused, terminal, and unrecognized state safely retains the existing generic behavior.

- Managed fallback local buffer overflows (`local_buffer_overflow`) now surface immediately with the original local diagnostic instead of entering the bounded `unknown` retry class: re-streaming the same request reproduces the same oversized response, and a local staging failure must never charge or advance the provider fallback chain, emit `model_fallback_switched`, or rotate credentials.

- Fixed Windows startup lock starvation in the SDK session index (#4544): a live detached broker holding `index.jsonl.lock` across a wedged Windows sync-family await exhausted every later launch's full 600-attempt lock budget. OS process-incarnation probes (which can spawn `powershell.exe` on Windows) now run before the machine-global index lock is taken in the heartbeat checkpoint pass, the conditional-unregister pass (now routed through the shared slow-operation choke point), and the own-pid registration derivation; the PowerShell fallback is time-bounded (`timeout` + `SIGKILL` enforcement); locked index transactions log an actionable slow-operation warning after 10s naming the exact operation; and lock exhaustion errors now identify the live owner (pid, liveness, lock path) instead of a bare attempt count. The heartbeat pass also rechecks probe freshness after the locked replay on the monotonic clock: a pid reused while the replay re-reads the log fails closed (no heartbeat this cycle) instead of checkpointing the wrong host, and a backward wall-clock step (NTP slew, manual fix, VM restore) cannot defeat the bound. A lock record carrying a foreign `owner_host_id` (shared-volume topic registry) reports its owner host with unknown liveness instead of probing a coincidental local pid. Stale-lock safety is unchanged: a proven-live owner's lock is never stolen.

- Fixed Windows session-index reads paying a `powershell.exe` spawn per dead registration: the incarnation probe only fences PID reuse for a live process, so `projectIdentity` no longer probes pids already observed dead. With dozens of stale registrations in `index.jsonl`, every `listSessions`/checkpoint read previously took seconds to tens of seconds (and stalled gjc startup past the session-extension 30 s watchdog); the projection is unchanged. (#4594)

- SDK terminal prompt, skill, and correlated-steer reconciliation records no longer expire after 15 minutes. Canonical status and bounded result content remain queryable across durable restart until the existing 256-record-per-kind oldest-terminal-first capacity bound evicts them; active records, selectors, response shapes, first-terminal-wins settlement, ordered non-replay behavior, and client-reference release on capacity eviction are unchanged (#4547). The age window was also removed from the SDK-only session host's prompt/skill reconciliation, which serves `turn.result`/`turn.prompt_status`/`turn.skill_status` for sessions without the notification-adapter surface, so every public reconciliation surface now retains terminal records until capacity eviction only.
- Fixed ACP `session/delete` retries so a durable artifact `cleanup_pending` result remains authoritative instead of re-closing the already-terminated session and misclassifying an unreaped Linux zombie as unverifiable SIGKILL uncertainty. Broker close now recognizes an identity-matching zombie as exited without weakening PID-reuse checks, and repeated deletes resume the authorized cleanup receipt idempotently.

- Added `/extensions`, an interactive project/global `.gjc` manager for skills, hooks, MCPs, and explicit Claude Code/Codex imports with redacted previews and atomic rollback.

### Added
- Added implicit discovery and keyless local support for oMLX (`http://127.0.0.1:8080/v1`) with `OMLX_BASE_URL` and `OMLX_API_KEY` configuration.

- Gajae Pet now renders in iTerm2 through a bounded inline GIF protected from ordinary TUI redraws by a reserved raster lease, with the same composer-side layout and lifecycle cleanup guarantees used by Kitty and Sixel.
- Added the bundled `ouroboros` dark theme, translating the official navy, teal, green, and gold identity into terminal-safe semantic colors while retaining the pet's vivid `#AEE80E` lime and `#7092BE` cool-scale accents. Live previews now recolor the open `/theme` selector and Settings theme submenu instead of leaving their construction-time theme visible, and confirmation consistently settles the preview into the active appearance mapping.
- Added the 16×16 `Ouroboros` terminal pet: a vivid lime snake with a cool `#7092BE` underside that rests in a soft coil, blinks, flicks its tongue upward, and occasionally sobs with `><` eyes. Its signature flex rolls the same silhouette through a symmetric circle into an exact 180-degree heart pose, blinks a small pink heart twice, and reuses the authored frames in reverse; agent work enters and exits a stable six-frame infinity loop through explicit unwind transitions. Pet skins now own their frame registry, source resolution, idle loop, work transitions, work loop, and signature burst; a saved skin removed by a later installation falls back to RedGajae while an explicit `off` remains off.
- `subagent await` now emits periodic liveness even when the awaited subagent's rendered-state signature is stable, so a healthy long wait is never indistinguishable from a hung session. A bounded liveness interval (floor 15s, derived from `heartbeat_ms`) force-re-emits `onUpdate` through the existing update channel, preserving immediate emission on genuine progress changes. Long silent `sleep N` (N ≥ 120s) in bash now appends a one-time advisory notice recommending `subagent await` or `job poll` instead — purely advisory, never blocking (#4465).

### Changed
- Updated every bundled GLM model profile (`glm-eco`, `glm-medium`, and `glm-pro`) from ZAI GLM-5.2 to GLM-5.3.
- `browser` Chrome profile mode (`app.browser: "chrome"`) now defaults an omitted `path` to installed Chrome/Chromium and an omitted `profile_directory` to `"Default"`. `user_data_dir` remains explicit and must be non-default because Chrome 136+ disables remote debugging for default Chrome data directories; Stable/Beta/Dev/Canary/Chromium roots (including trusted Linux environment, Flatpak, and Snap defaults), symlink aliases to them, and non-Chrome browser executables are rejected with remediation instead of timing out or risking cross-brand profile access. Profile and Windows executable discovery use trusted environment sources that ignore repository `.env`, platform path semantics are preserved, executable/profile-root canonicalization races stalled filesystem work against cancellation, the canonical Snap Chromium launcher is admitted only when it resolves to `/usr/bin/snap`, and wrapper-launched Linux Chrome processes are reused through an abort-aware asynchronous `/proc` scan only after kernel executable identity plus exact profile/loopback-CDP guards pass. Cancellation propagates before, during, and after CDP probing, and omitted profile names render as `Default` in the TUI.
- Breaking: replaced the bundled `team` workflow and `gjc team` command with the research-only `autoresearch` workflow and its session-scoped mission ledger. `gjc rlm` is removed; existing Team/RLM automation must move to the supported workflow and approval-gated execution surfaces.
- Defense-in-depth: when an Anthropic-origin assistant transcript message carrying directly adjacent `thinking`/`redacted_thinking` blocks is persisted, a single bounded warn is emitted per session manager instance — but only in development/test builds, never in production. The diagnostic names only the envelope shape (block count, adjacency presence, provider), never raw thinking text, signatures, redacted payloads, or transcript-path metadata. Storage is never mutated — the send-boundary collapse remains the wire source of truth; this is a read-only observation that helps surface upstream producers of the rejected shape (#4443).
### Fixed
- Coordinator durable writes now tolerate only Windows directory-handle `EPERM`/`EACCES` at the directory open/sync barrier. File fsync, atomic rename, cleanup, and all other filesystem failures remain fail-closed; on Windows, the renamed directory entry has weaker crash durability because the operating system does not support the directory barrier.
- The Herdr integration no longer disappears from Herdr's sidebar after a session restarts in the same pane. Herdr keeps the highest report sequence it has accepted per source on the pane's terminal, and that watermark outlives the gjc process that set it, so a counter restarting at zero made every report of the next session look stale: the pane showed no agent at all until the new session happened to out-count its predecessor, which for an idle resumed session could be never. Lifecycle-state and pane-title sequences now start from wall-clock milliseconds (with room for 1000 reports per millisecond), matching what Herdr's own bundled integrations do.
- Ouroboros now keeps its own authored base, idle, work-transition, work, and burst frames across Sixel, Kitty, and iTerm2 rendering. Marked iTerm pet drag paths are also discarded in Bash and Python composer modes instead of being inserted literally.
- iTerm2 pet animation now stays silent while the TUI is temporarily stopped for an external editor or todo edit, preventing raster writes from corrupting the child terminal session. Its unavailable-capability deadline now begins after iTerm probing starts rather than during initialization work.
- Forced Kitty Gajae Pet graphics remain unavailable inside tmux, screen, and zellij; forced Sixel and verified iTerm2 transport keep their existing behavior.
- iTerm2 Gajae Pet rendering now uploads an animated GIF only when its meaningful state changes and holds a raster lease across ordinary TUI redraws, preventing work-animation flicker and transient composer-background frames.
- Dragging an iTerm2 Gajae Pet image no longer pastes its temporary named image pathname into the composer. Pet frames identify their generated image by name, and the paste controller consumes only that marked pathname while preserving native iTerm dragging and all other image-path pastes.
- Gajae Pet now keeps its intended two-row art footprint in iTerm2 across terminal font, Retina, and cell-geometry changes instead of rendering as a fixed undersized pixel box.

- Post-merge repair for #4542: `CHAT_DAEMON_GENERATIONS.discord` 64→65 and `.slack` 67→68 so the `SessionRouter` initial attachment replay change is generation-fenced for already-running Discord and Slack daemons. The semantic guard manifest is regenerated with the corrected generation and declaration digests.
- Cursor split responses now preserve canonical provider order across pre-admission artifact spilling, so a server-side tool result cannot be persisted after the assistant continuation that follows it. The canonical admission reservation is owned by each emission's own handler, so a host bridge or replay emitting the same `message_end` event object twice can no longer overwrite the reservation and deadlock every later canonical admission (#4536).
- Uncontended canonical `message_end` admissions no longer cost a microtask: the admission lane keeps a synchronous fast path when its predecessor slot is already released, restoring synchronous visibility of persisted appends for external emitters (successor-finalization and deep-interview continuation flows) while FIFO admission still holds under a genuinely in-flight predecessor (#4536).
- SDK `session.prompt`/`steer` dispatches that arrive while a default-model selection already owns the admission fence are now preserved as queued work instead of failing: the shared ingress awaits the selection fence, keeps same-session prompt entry fail-fast, and only private scheduled continuations (auto-compaction/queue continuation) bypass the fence with an explicit reentry capability. Queued dispatches reserve follow-up order before durable acceptance so a later plain prompt cannot overtake an earlier follow-up behind the same fence, and continuations parked behind a pending selection fence are tracked as settlement work so `waitForIdle` cannot report completion while the continuation is still waiting (#4519).
- Default-model selection now reserves a causal fence before credential probing, so an already accepted prompt preflight cannot be overtaken and a selection accepted first blocks later prompt preflight through durable publication. The fence does not hold session admission across `waitForIdle`, allowing inherited auto-compaction continuations to obtain prompt admission; same-session reentrancy still fails fast, successor sessions remain protected, and disposal deterministically drains accepted selections while rejecting queued prompts without unhandled rejections (#4519).
- AgentSession now observes provisional tool-call updates through a private, turn-bounded identity channel, preserving cancel-and-submit visibility and exactly-once streaming edit guards across cloned events, delayed handlers, and terminal interleavings.
- Ordinary sessions no longer import or execute Claude Code and Codex directory hooks as competing runtime authorities. Runtime hook discovery is fail-closed to canonical native `.gjc/hooks/` providers while explicit configured paths, constrained plugin hooks, and foreign-provider import/diagnostic discovery remain available (#4516).
- Telegram/Slack/Discord outbound publications no longer freeze after a session-host rehost. A rehosted fleet re-attaches every session in one reconcile pass, and each attachment's initial `event_replay` was awaited inside the serialized `#reconcileTail`, so one slow replay (up to its full retry budget) wedged all later reconciles and the sends funneling through them; leases and inbound polling stayed green while delivery died until daemon restart. Reconcile-driven attachments now publish immediately and run initial replay on the attachment's ready tail (matching the reconnect path); `start()` still drains those tails so bootstrap callers observe replay completion. Replay ordering, generation fences, cross-session isolation, and provider hooks are unchanged (#4527).
- Automatic session retry now honors the Anthropic transport's first-event attempt ceiling: a full-window request at or above 1 MB stops after the initial upload, while a smaller request can be attempted at most twice even when the global retry setting is higher. Attempt events and terminal elapsed diagnostics use the actual bounded ceiling. Credit: @probepark (#4464).
- A manual `retry()` after an exhausted provider first-event ceiling now clears that stale ceiling before re-attempting, so a later transient failure on the reissued turn consumes the ordinary retry budget instead of being exhausted by the prior turn's ceiling. Verified by a discriminating regression test that fails when the ceiling is retained (#4464).
- `gjc_coordinator_stop_session` no longer reports `close_failed` after a successful DR-1 terminal close. Reap now proves the same retained session is `terminal` and non-`live` for the exact `workspace/generation/incarnation` before completing local cleanup; rotated generation, different incarnation, ambiguous, still-live, and `terminal_uncertain` remain fail-closed (#4431).
- The crash signature index can no longer quarantine itself. `parseCrashIndex` rejects an entry whose retained count exceeds its lifetime count, and compaction produced exactly that whenever the crash log still held more identity-bearing records for a signature than the journal had counted — which happens whenever a fatal's journal append fails or spends the per-process latch while its crash-log record is still written. The next read then discarded the whole index, taking every signature in it. The retained count is now held at the journaled lifetime.
- Crash records whose event-journal append was lost are reportable again. Compaction recovers only never-indexed, structurally complete v1 records whose marker fingerprint recomputes from their own diagnostic text, trying only marker-proven reconstructions for the v1 header's ambiguous colon names, multiline messages, arbitrary multiline stacks, and separate serialized object payload. Record ids are deduplicated for recovery and retained counts; recovered ids remain durable until delayed journal events arrive; whole rotated-journal digests make publish-before-delete replay idempotent beyond the 256-id window; and timestamp ordering controls first/last-seen plus latest record metadata. Reported or acknowledged signatures evicted from the bounded index stay retired while their records survive: confirmed log pruning drops obsolete tombstones, uncertain crash-log or sidecar reads fail closed, retirement evidence publishes before the main index that depends on it, and replayed journal batches heal an interrupted stale tombstone without repeating counts. The separate ledger is bounded by the maximum distinct records in the bounded log rather than the live-index entry cap, so sequential retirements cannot exhaust admission (#4478, reported with measured field evidence by @yazzang-homelab).
- The mid-run context maintenance contract suite no longer uses real-time `Bun.sleep(10)` to settle persistence after externally emitted events. A new public `AgentSession.awaitSessionSettlement()` deterministically awaits every in-flight agent-event handler (including the synchronous canonical persistence each one performs) and the durable transcript flush queue, replacing the ambiguous fixed-duration sleep whose adequacy depended on system load. Concurrent-state observation helpers now use event-loop tick yields (`Bun.sleep(0)`) instead of paced real-time sleeps. The maintenance outcome contract (`not-needed | pruned | compacted | promoted | failed | aborted`), publication failure semantics (fail-closed `artifact_persistence`), rollback, and canonical-output retention are unchanged (#4476).
- `sdk-adapter-dispositions.test.ts` no longer exceeds the CI 300s file-timeout with `exit=0`. Measurement proved all 489s was genuine per-fixture runtime (291 production SDK host startups at ~1.7s each), not leaked timers/sockets/workers — the process exits ~200ms after the last test. The three machine-adapter cohorts (MCP, ACP, daemon CLI) are now split into sibling files so each runs under the unchanged timeout with byte-identical coverage; the chat-adapter loops stay in the original file (#4475).
- Print mode now requests a governed process exit after successful session teardown, so completed one-shot runs cannot be pinned by residual runtime handles; both stdout and stderr are drained first and the recorded exit code is preserved.
- `todo_write` no longer rejects a positional task handle without saying how to address a task. Callers reach for `id`/`index` because the tool result renders todos as a list, and `todo-write.ts` already had a message for exactly that confusion, but for model-issued calls raw validation rejects the unknown key before `execute` runs (only bridges that invoke a tool directly, such as `eval`'s `callSessionTool`, still reach it), so the model only saw `rejected key: "id"` next to the legal-key list and had to guess the replacement (each retry discards the whole batch, since rejection is atomic). The positional-handle family (`id`, `ids`, `index`, `taskId`, `task_id`), including one nested under `task` or `content`, now carries the same correction the executor gives: tasks have no id or index, target one with `task` set to its exact content or a whole phase with `phase`. A correction shared by several rejected keys is stated once, and raw diagnostics retain only eight keys with a deterministic omitted-key count. The executor comment claiming those keys are stripped before it runs was stale and is corrected.
- Post-merge repair for #4401/#4399: `CHAT_DAEMON_GENERATIONS.discord` 63→64 and `.slack` 66→67 so the shared `SessionRouter` attachment lifecycle changes from #4401 (`#attach`, `#createAttachedClient`, `#publishAttachment`) are properly generation-fenced for Discord and Slack daemons, not just Telegram. The semantic guard manifest is regenerated.
- Chat daemons can no longer block session opens. SDK session startup awaited Telegram/Discord/Slack daemon ownership before publishing the core endpoint — with multi-second spawn/reload/reservation-lock budgets on that path, a wedged or blocked Telegram daemon stalled or hard-failed every ACP/MCP `session/new` ("Telegram daemon ownership is blocked"). Daemon ownership is now acquired in a detached background task strictly after core publication: daemon slowness, crash, wedged lock, or a blocked identity degrades only notification delivery (adapters stay withheld until ownership settles) and never session lifecycle.
- `bun test` no longer writes into the live `~/.gjc/agent`. Test suites resolved the real agent dir by default, leaking fixture daemon diagnostics, transition markers, and unlink placeholders into operator state — accumulated leaked markers wedged the live Telegram daemon's dead-owner lock recovery (`left-contended`). The shared test preload now isolates every test process into a fresh temp agent dir unless an explicit non-default `GJC_CODING_AGENT_DIR` pin is present.
- `gjc notify recovery` now sweeps inert notification-dir debris (detached transition-marker quarantines, exact-unlink exchange placeholders, crashed-writer `.tmp` staging files) with positive staleness proof, and the Telegram daemon runs the same sweep non-blockingly at startup, so the notifications directory can no longer grow unboundedly and slow every endpoint scan. `--force-daemon-lock` — previously a no-op that only changed the diagnostic wording — now actually detaches a provenance-less (unparseable) steal marker older than the transition grace window through the identity-bound primitive, giving a wedged recovery a real operator escape while valid and young markers stay untouchable.
- Chat-provider ownership is now proved against the full, non-secret provider identity and re-proved whenever it changes. The identity fingerprints every provider credential (never raw values or lengths) and covers Telegram token/chat, Discord token/application/guild/channel, Slack bot+app token/workspace/channel and its authorized actor, per-provider enablement, the Telegram activation marker, and — only where a chat daemon is effective, because it is part of that daemon's durable identity — redaction and verbosity. Acquisition is single-flight and key-bound, so an outcome proved for one configuration can never authorize another, and a late or superseded outcome can never downgrade a runtime whose adapters are already active. On an identity change the runtime withholds its local adapters before re-proving, and re-proof is provider-neutral: a Discord-only or Slack-only change takes the same path. None of it is ever awaited by session lifecycle. Note that an already-attached external Discord/Slack daemon keeps its SessionRouter attachment through the window; fencing that requires a Router-level chat-attachment authority boundary and is tracked as a follow-up.

### Added
- The Grok Build provider (`grok-build`) fallback catalog now includes `grok-4.6`, so it can be selected when the proxy exposes that model. It uses xAI's documented 500k context window and $2/$0.50/$6 sub-200k pricing; `xhigh` is its reasoning-effort ceiling while Grok 4.5 remains capped at `high`. The `grok-4.6-latest` alias resolves to Grok 4.6 and `grok-build-latest` remains intentionally mapped to Grok 4.5. The effort allowlist accepts only exact model IDs or hyphenated variants, preventing similarly named models from retaining unsupported reasoning payloads.
- Documented the shipped multi-account UX: `/usage` is cache-only by default with `/usage check` for explicit sequential probes; `/login` supports OAuth account selection, **AUTO**, and **Add new account**; `/logout` supports selective local OAuth removal; and `gjc accounts list|check|pin --persistent|logout` exposes safe inventory, checks, global pins, and local removal.
- `/theme <name>` now switches the theme immediately without opening the selector: the name is validated against built-in and custom themes, persisted to the detected `theme.dark`/`theme.light` slot, and applied to the running session in one step. Bare `/theme` keeps the existing live-preview selector, and an unknown name is rejected with the list of available themes.
- Added built-in `grok-46-{eco,medium,pro}` role profiles using the existing xAI OAuth/subscription provider, and updated `/model` so direct xAI Grok 4.5/4.6 assignment prompts for a supported reasoning effort instead of retaining `(inherit)`. Grok 4.5 offers `low`/`medium`/`high`; Grok 4.6 adds `xhigh`.
- The model selector and argument-based `/model` assignment now require an explicit supported reasoning effort when assigning any reasoning-capable model to DEFAULT, regardless of provider. The selected `:effort` is persisted in `modelRoles.default`, while non-reasoning assignments and provider-general role-less temporary switches remain direct.
- `gjc setup paseo` registers GJC as an ACP provider in [Paseo](https://paseo.sh), bridges Paseo's orchestration skills into GJC skill discovery, seeds only empty orchestration role mappings, diagnoses the installation with `--check`, and rolls itself back with `--remove`. Paseo owns its own config files and exposes no lock API or reload verb, so every write is conservative: a round-trip fidelity self-check refuses to touch a file GJC cannot reproduce byte-for-byte, a compare-and-swap guards publication, backups land beside the original at mode 0600 (`~/.paseo/config.json` holds a credential, which never reaches stdout, stderr, `--json`, or a diff), and a durable credential-free intent record makes an interrupted run recoverable. `--remove` deletes a key only when GJC's own provenance ledger recorded creating it *and* the value still matches what GJC wrote, so a hand-authored or user-edited entry always survives. `~/.agents/skills` is treated as read-only, and the Paseo daemon is never restarted automatically -- `--check` reports `stale` and leaves the restart to you.
- `가재씨` is now installed alongside `gjc` as a Korean launcher alias. Typing `가재씨` runs Gajae-Code identically to `gjc`; it is a package-owned bin entry created by npm/Bun during install with no shell alias or dotfile edit required. Supported on Linux and macOS (UTF-8 locales); on Windows the shim is created but invoking it from `cmd.exe` depends on the console's active code page (#4363).
- `GJC_WORKTREE_DIR` now selects where `gjc --worktree` creates its launch worktrees. The bucket was derived entirely from the repository path (`<parent>/<repo>.gajae-code-worktrees`) with no override, so a machine that already keeps worktrees under its own convention accumulated a second bucket beside the first. The value is a path template: `{repo}` expands to the repository directory name so one exported value stays repo-scoped, a relative value resolves against the repository's parent directory (`{repo}.worktrees` adopts an existing sibling bucket), and an absolute or `~/`-prefixed value is used as given. Unset or blank keeps the previous default. See [`docs/environment-variables.md`](../../docs/environment-variables.md#6-storage-and-config-root-paths).
- Added `/credential` (alias `/account`), a slash command that switches a running session's active stored OAuth account for a provider mid-session, independent of quota state. With no argument it lists every provider's stored accounts and marks the one this session is currently using; `/credential <selector>` (`email:<addr>`, `id:<n>`, `account:<id>`, `project:<id>`, optionally `provider/<selector>`) switches to a specific row, inferring the provider from the current model when possible. This is a manual switch distinct from the existing automatic quota-triggered credential fallback — it works even when nothing is exhausted. Refuses to switch when a stronger override (`--credential`, `--api-key`, or a config-sourced `models.yml` `apiKey`) already owns the provider's credential selection, and switching to a still-blocked row safely falls back to a usable account instead of forcing another failed request. See [`docs/custom-providers-and-multi-account.md`](../../docs/custom-providers-and-multi-account.md#switch-accounts-mid-session).
- `gjc mcp add` registrations are now consumed by ordinary standalone sessions at startup (conventional autoload). Top-level sessions (`gjc`, `gjc --tmux`, print/text/json modes) discover enabled servers from GJC's own native scopes only — project `.gjc/mcp.json` and user `~/.gjc/agent/mcp.json` — with deterministic precedence (project scope wins name collisions; plugin-bundle MCPs override conventional entries). A server is skipped when marked `enabled: false`, listed in either scope's `disabledServers`, or marked `autoload: false`; project scope loads by default and is disabled only by an explicit `mcp.enableProjectConfig: false` setting. New `--no-mcp` flag opts one session out (mutually exclusive with `--mcp-config`, which remains an isolated exact-file mode that replaces autoload). Claude Code/Codex MCP files are explicit import sources into `.gjc` through a new bounded normalization layer (`normalizeClaudeMcpJson` / `normalizeCodexMcpToml` / `validateMCPCompatServer`), never implicit runtime authorities, and foreign user-home configs (`~/.claude`, `~/.codex`) are never read. `gjc mcp list` now reports source scope, path, and a deterministic runtime status (`autoload` / `autoload-off` / `disabled`) with secrets still redacted, and new canonical scope-aware read/write APIs (`runtime-mcp/scope-config.ts`) back both the CLI and the upcoming `/extensions` surface. Subagents inherit the parent's MCP tools without duplicating server processes or owning cleanup, and `/mcp reload` re-discovers native scopes in sessions without plugin-bundle MCPs (#4284, part of #4283).
- MCP remote (HTTP/SSE) servers now support the Model Context Protocol 2026-07-28 stateless specification ("MCP v2") alongside a bounded, observable legacy compatibility path. Each server accepts a protocol preference (`auto` | `2026-07-28` | `legacy`, default `auto`) across config, capability, and Claude/Codex import mapping. In `auto`/strict mode the client probes optional `server/discover` and applies deterministic, downgrade-safe era detection: it never falls back to legacy on auth, issuer/audience (RFC 9207), resource-audience (RFC 8707), SSRF/DNS/redirect, malformed-response, or other security/protocol-integrity failures — those fail closed. Modern requests are fully stateless (no `initialize`, `Mcp-Session-Id`, sticky session, standalone GET stream, or `DELETE`): every request carries required `_meta` (protocolVersion, clientInfo, clientCapabilities) and mirrored `MCP-Protocol-Version`/`Mcp-Method`/`Mcp-Name` headers, with `x-mcp-header` tool-parameter mirroring (tools with invalid annotations are excluded rather than breaking the catalog). Multi Round-Trip Requests (`input_required`) are recognized on `tools/call`/`prompts/get`/`resources/read`, bridged to GJC's structured ask surface, and retried exactly once under a stable correlation id with verbatim `requestState` echo and a fresh JSON-RPC id. Catalog freshness honors server `ttlMs`/`cacheScope` hints with per-connection invalidation. One authoritative secret-free protocol observation model (`MCPManager.getProtocolObservation(s)`) exposes preference, negotiated era/version, downgrade reason, and feature lifecycle for `/extensions` (#4291) and customization doctor (#4288) consumption. stdio servers keep the legacy handshake unchanged (#4292, part of #4283).
- Added three provider-agnostic Muse Spark 1.2 presets: Muse Spark across all roles, plus DeepSeek and Luna executor variants.
- Added `--prefer-credential <selector>`, a soft stored-OAuth-credential preference for multi-account provider pools (`email:<addr>`, `id:<n>`, `account:<id>`, `project:<id>`, or `provider/<selector>`). Unlike `--credential` (a hard pin that never rotates and fails the session on exhaustion), a usable preferred credential is placed ahead of the provider's normal ranking, and a content-free quota/rate-limit failure rotates it out to another active credential immediately and keeps the fallback sticky for the rest of the session. Mutually exclusive with `--credential` and `--api-key`; an unqualified selector must resolve to exactly one active OAuth provider or startup fails asking for an explicit `provider/<selector>` prefix, and a resolved model from a different provider than the preference fails closed instead of silently stranding it. See [`docs/non-compaction-retry-policy.md`](../../docs/non-compaction-retry-policy.md#preferred-credential-quota-fallback).
- When gjc runs inside a Herdr pane (`HERDR_ENV=1`), lifecycle state is reported through Herdr's custom-integration API (`pane report-agent --source custom:gjc`), so the pane is recognized as the `gjc` agent in Herdr's sidebar and workspace rollups with `idle`, `working`, and `blocked` (ask tool) states, and the authority is released on exit. The herdr binary is resolved from `HERDR_BIN_PATH` or PATH only, every invocation is timeout-bounded and detached, and the reporter is a no-op outside a Herdr pane (`#4318`, contributed by @ox8884).
- The Herdr integration now also reports the session title as display-only pane metadata (`pane report-metadata --source custom:gjc --title`), so panes are told apart by what they are working on instead of showing an identical `gjc` label each. Herdr draws it on the pane border and exposes it to the sidebar as the `pane` token. The title follows every rename, session switch, and resume, is collapsed to a single sanitized line (control characters removed, bounded to 120 characters, surrogate-safe truncation) so a model-generated name can never inject terminal escapes, and is retracted when the reporter releases the pane so a returning shell does not keep the finished session's title. Sessions that have no name yet report nothing and keep the pane's previous title (`#4402`, contributed by @nahyeongjin1).
- Hook discovery now records one canonical six-kind normalization contract while preserving each runtime's real authority and semantics. Native directory modules are validated before import and adapted into the session `ExtensionRunner`; Claude/Codex directory providers remain import and diagnostic sources. Constrained plugin batches validate atomically before registration and route after-phases to the post-tool path without gaining blocking authority, unsupported aliases and malformed matchers fail closed with stable diagnostics, and Codex-managed command timing/trust/redaction remain explicitly provider-owned.

- Crash records now carry a stable, versioned fingerprint (`gjc-crash-record.v1 fp:<32hex> fpv:1 id:…`), and the new `gjc crash list` / `gjc crash report` commands turn those signatures into an assisted, preview-first bug report. Nothing is transmitted before an explicit per-invocation confirmation of a digest-pinned snapshot: crash text is rewritten by the strict `sanitizeExternalCrashV1` contract (paths, URLs, credentials, ANSI/bidi/zero-width controls, pid and exact timestamps removed, fail-closed on scanner uncertainty), `gh` is first executed after consent, the duplicate search is `--repo` pinned and candidate-only, and "+1" comments carry a per-issue idempotency stamp. Records written before this release carry no identity line and are not reportable. A rate-limited, local-only startup line (`crashReport.nudge`, default on, at most once per 24h) reports that unreported signatures exist. See `docs/crash-reporting.md`.
- GJC plugin bundle manifests now accept the Claude Code/Codex-familiar `mcpServers` map alias and normalize transport-relevant, end-to-end representable fields into the canonical `mcps` array (per-server `type` maps to `transport`; `command` implies `stdio`, a bare `url` implies `http`), so canonical and alias manifests compile to byte-equivalent normalized surfaces. Ambiguous or unrepresentable aliases (`mcp`, a top-level `skills`/`agents`/`commands`/`slash-commands`, Claude Code-shaped or hybrid hooks, transport-incompatible MCP fields, or `mcpServers` entries with `env`/`auth`/`oauth`/`headers`/enablement controls) now fail with targeted migration diagnostics naming the canonical form and the loose `.gjc/` surface, instead of generic unknown/forbidden-key errors. Protected workflow-skill and role-agent invariants, collision authority, MCP security policy, constrained hooks, and appendix authority are unchanged and apply to normalized entries (`#4287`).
- `docs/gjc-plugins.md` now contains a decision table for canonical project/user `.gjc` loose customization (`.gjc/mcp.json`, `.gjc/hooks/pre|post/<tool>.ts`, `.gjc/skills/<name>/SKILL.md`, `.gjc/extensions/<name>/`) versus distributable plugin bundles, and identifies Claude Code/Codex layouts as explicit `/extensions` import sources rather than parallel runtime authorities (`#4287`, `#4291`).
- Filesystem skill discovery is now **on by default**: a valid `SKILL.md` in project `.gjc/skills` or a canonical user location (`~/.gjc/agent/skills`, plus legacy user roots) is advertised and invokable in a normal session with no configuration ceremony (#4285).
- Project and user skill trust is now explicit and bounded through `skills.trustProjectSkills` / `skills.trustUserSkills` (both default on) instead of the implementation-named `enablePiProject` / `enablePiUser` booleans, which remain as deprecated aliases (#4285).
- `gjc skills discover` lists filesystem-discovered skills per scope with actionable diagnostics (protected-name collisions, shadowing, include/ignore/disable filters, invalid frontmatter, missing descriptions) (#4285).
- Project `.claude/skills` and `.codex/skills` (and their user-home counterparts) are recognized as explicit import sources: discovery surfaces each convention skill with the exact copy command that enables it in `.gjc/skills`, so a skill placed in a documented Claude Code/Codex location is discoverable in a normal session while GJC never silently loads another host's files (#4285).
- Added `gjc customize doctor`, a read-only provenance and status report over every discovered customization surface (MCP servers, skills, hooks, custom tools, extension modules, slash commands, plugin bundles). For each item it reports the source class (canonical `.gjc`/bundled, registered convention, Claude Code/Codex import candidate, or plugin), effective precedence and shadowing, runtime status, a bounded reason code (`invalid-config`, `disabled-*`, `shadowed-by-precedence`, `quarantined`, `policy-blocked`, `source-ignored`, …), remediation commands, and trust notes. The command reuses the canonical discovery registry and the exact session-startup consumers (`loadSkills`, `loadSlashCommands`, `loadAllMCPConfigs`, the plugin-bundle registry) instead of re-implementing discovery, so the report cannot drift from what sessions actually load — including the #4285 invariant that bundled workflow skills always win over same-name filesystem copies. It never executes hooks, never connects MCP servers, and redacts secret-shaped MCP fields (args, env values, headers, auth, endpoint userinfo/query) in both text and `--json` output (#4288).

### Changed
- The four bundled GJC workflow skills (`deep-interview`, `ralplan`, `team`, `ultragoal`) are now always authoritative: a filesystem or caller-supplied skill sharing one of those names can never replace the bundled definition, and the collision is diagnosed by discovery (#4285).
- Duplicate skill names now resolve with documented, deterministic precedence: project scope beats user scope; within project scope the `.gjc/skills` directory nearest to the working directory wins; within user scope, canonical `~/.gjc/agent/skills` beats legacy roots. Claude/Codex convention copies never participate in runtime precedence — they are import sources. Shadowed duplicates produce warnings instead of silent skips (#4285).
- Skills without a parseable frontmatter block or without a `description` now produce actionable scan diagnostics instead of silent skips (#4285).
- Documented the skill surface in `docs/skills.md` (locations, install examples, trust/disable, precedence, diagnostics) and updated the skill-template docs and README to the zero-config install flow (#4285).
- The root `artifacts/` directory is now fully untracked: test evidence, QA reports, red-team output, and provenance snapshots are no longer committed. The root `.gitignore` ignores `/artifacts/`, and a new gate test (`scripts/artifacts-tracked-policy.test.ts`) enforces zero tracked files under that path. The single deterministic test fixture (`issue-3670-anthropic-cache-eval.json`) moved to `packages/ai/test/fixtures/` where it belongs, and the stale `ci-dev-affected` behavioral-owner mapping that referenced its pre-rename path (`architecture-2383-eval.json`) is corrected (#4420).
- ACP `session/cancel` again aborts only the active turn by default. The ACP surface's C04 terminal-abort scope had been set to `owned` so an external client that ends a run also stopped exact owned subagents and background tasks; the default is reverted to `turn` to match the SDK `turn.abort` default and other ACP clients' cancel behavior. Owned termination remains available as an explicit opt-in via `_meta.gjc.abortScope: "owned"` on the cancel notification or `GJC_ACP_ABORT_SCOPE=owned` in the agent environment, and invalid values now fail safe to `turn` instead of `owned`. Paseo keeps owned cancels by setting `GJC_ACP_ABORT_SCOPE=owned` in its provider config env with no source changes.
- `/model <preset>` and `/model gajae-code/<preset>` now activate a known model profile immediately instead of failing with "Unknown model". Unknown names still fall through to model resolution, and `/model <role> <preset>` keeps assigning a model to the named role.
- Browser `act` and `run` responses can now surface an opt-in (`open(..., { diagnostics: true })`), bounded mailbox of page exceptions and `console.error` metadata. Entries carry only kind, timestamp, origin-only URL, line/column, and an allowlisted built-in error class; path segments, query strings, messages, arguments, values, and stacks are never retained. Serialization is byte-bounded with an explicit truncation marker.
- A generated, host-neutral `sdk-skills/` bundle now provides namespaced external-agent skills for direct GJC SDK session discovery, approved trusted-local operations, and TypeScript/Python script authoring, with deterministic drift checks and credential-safe fail-closed templates. The bundle carries a versioned `manifest.json` (format version 1) so installed bundles fail closed on missing or unsupported layouts, and the skill prompts are authored as static Markdown sources under `scripts/gjc-sdk-skills/prompts/` that the generator copies and validates.
- `GJC_SESSION_CONTEXT_BUDGET_BYTES` now overrides the synchronous session-context materialization budget that backs `SessionContextTooLargeError` (previously hardcoded to 64 MiB, now a 512 MiB default). The override parses fail-closed: only a canonical positive-integer value is honored, while empty, non-numeric, negative, zero, safe-integer-overflowing, or above-ceiling (8 GiB) values fall back to the 512 MiB default with a warning, so a malformed value can never silently raise or lower the memory guard. Raise it above your measured session size to suppress the preflight, or lower it to restore the old tight bound.
- Custom OpenAI-compatible providers can opt into `/fast` forwarding with `compat.supportsServiceTier: true`; first-class OpenCodex discovery enables it automatically so client `service_tier: "priority"` reaches an OpenCodex proxy running in passthrough (`Auto`) mode.
- `/import-session <transcript-file> [--provider codex|claude]` imports an explicit Codex CLI rollout transcript, Claude Code transcript, or claude.ai conversation export into a new GJC session with reconstructed, redacted continuation context. Detection is content-based with a fail-closed `--provider` override; provenance (provider, format, source session id, source SHA-256, counts, import timestamp) is persisted on the new session, unmappable records are quarantined with digests instead of being silently dropped, secrets are redacted fail-closed, and oversized transcripts are bounded with a deterministic head/tail elision marker. The source file is only read, never modified (#3709).

### Fixed
- STT and OAuth selector animation frames now use the TUI's layout-only render path, avoiding unchanged transcript reconstruction while those local controls animate. The autoresearch overlay no longer starts its 80 ms spinner timer for static completed results and stops the timer as soon as a running experiment settles.
- Managed session persistence no longer crashes with an unhandled `ENOENT: no such file or directory, open '<session>.jsonl'` when dev's identity-bound persistence state still expects a transcript that has disappeared. Full rewrites now recreate only a confirmed-missing predecessor from the complete resident transcript, while identity mismatches still fail closed so a concurrent successor cannot be overwritten. Hot appends recover the same missing-file case through a complete rewrite rather than publishing only the new records and creating a truncated transcript. Main/release builds were unaffected because they still used unconditional `replaceSync` / `appendSync`; the regression was introduced by dev's later exact-identity persistence hardening.
- Task-launched canonical subagents no longer receive an ACP exact-config `toolsOnly` MCP manager that they are forbidden to reuse. Planner, Architect, Critic, and Ralplan consensus lanes now start normally while reusable plugin MCP managers continue to inherit across sub-sessions.
- Telegram notification failures are now contained behind an independent provider subscription: Telegram can cancel only its own cursor and cleanup scope, Router authority replacement/stop no longer awaits provider hooks, topic persistence strips SDK endpoint identity, reconnect preserves topic/cursor continuity, and only explicit terminal archive authority can retire a Telegram topic (#4399).
- Managed output publication no longer freezes the resident event loop when a no-replace rename stalls in the kernel (#4394). The async `publishManagedFileNoReplace` chain (staging write/fsync, no-replace rename/link publication, destination verification, directory fsync) now runs on the libuv blocking pool through async `FileHandle` operations and the new `renameNoReplacePathAsync`/`linkNoReplacePathAsync` native boundaries, so a kernel-blocked `rename(2)` — e.g. into an oversized APFS directory namespace on macOS — blocks one pool thread instead of starving every timer, sibling subagent, and `Bun.sleep`-based subagent await timeout in the process; a hung publication degrades to one unresolved receipt while the child `.jsonl` transcript remains the source of truth. Atomic no-replace semantics, staging identity verification, owner-only security checks, and crash-recovery behavior are unchanged, and the synchronous publication path is untouched. Scrubbed write-protocol remnants are now also reaped inside per-session descendant directories, not only at managed scope resolution: each bound `ManagedSessionDescendantStore` schedules a throttled (60 s), serialized, batch-yielding best-effort reaper before mutations, bounding the unbounded remnant growth (tens of thousands of zero-byte dirents on platforms without retained authority) that made the kernel hang probable in long orchestration runs.
- Chat daemon ownership probes now treat only `ESRCH` as proof that an owner exited. Permission errors such as `EPERM` remain fenced as live or indeterminate, preventing Discord and Slack startup from reclaiming a live owner's lock and spawning a competing daemon.
- Dev CI coding-agent shards preserve file-level state boundaries without Bun 1.3.14 `--isolate`: each deterministic shard enumerates its test files and runs every file in a fresh root-cwd `bun test` process with the repository preload and private HOME/XDG/TMP/GJC roots, while aggregating every failed file. This removes both isolate's preload/segfault startup failures and the shared-process contamination that followed its removal (leaked spies, fake timers, config roots, SDK endpoints, broker state, and native/worker lifecycle), while retaining eight-way shard parallelism and bounded per-file process cleanup (#4378).
- Overlapping ACP `session/cancel` requests are now aggregated so any acknowledged attempt resolves the waiter immediately, the last failing attempt resolves false, and the aggregate is only re-armed when an entire wave fails without any acknowledgement. A failing attempt that followed an acknowledged one previously cleared the resolved-successful aggregate, so a later cancel installed a fresh unresolved promise whose prompt-preflight rejection awaited past the grace bound. The terminal-abort steering snapshot lifecycle is also turn-bound: capture, discard, and rebind use the captured turn key so a replay/prompt race cannot leak entries, and notification-path rebind covers the full-bus terminalization path where the requester's queued prompt wins during the abort.
- Telegram forum topics now receive a fresh identity header when an asynchronously generated session title is persisted, so the daemon renames the active topic without depending on `agent_end` winning a race against title generation. The observer is runtime-scoped, deduplicates unchanged titles, and is stopped before session authority is released.
- A terminal Coordinator session (`completed`, `errored`, `stale`) no longer reports `ready_for_input: true` while `live: false`. Both the coordinator durable state writer (`writeSessionStateUnlocked`) and the runtime sidecar (`basePayload`) computed `ready_for_input` as `state === "ready_for_input" || state === "completed"`, so a completed session published contradictory lifecycle fields (`state: completed`, `live: false`, `ready_for_input: true`). Downstream clients treated the dead session as input-ready and kept incrementing its elapsed timer indefinitely. `ready_for_input` is now `true` only for `ready_for_input`; every terminal or dead state reports `false`. The runtime sidecar validation mirror (`validPreviousRuntimeStatePayload`) enforces the same invariant, so a stale pre-fix `completed + ready_for_input: true` marker is rejected instead of silently preserved. Terminal transitions now also stamp an authoritative `ended_at` timestamp, exposed through `publicCoordinatorSessionState`, so downstream consumers receive a terminal time to freeze timers against (#4351).
- `ensureBroker` now surfaces the real detached-broker startup-failure reason again instead of the generic "exited before publishing discovery" fallback (#4340). The durable startup-failure marker writer in the `sdk broker-internal` entrypoint was dropped during an earlier merge, so nothing ever wrote the marker `ensure.ts` reads, and every startup failure fell back to the bounded generic message regardless of the real cause (e.g. an unsupported `index.snapshot.json`). The marker writer is restored on both `broker.start()` rejection and the race-loser clean-exit path, and the marker is now bound to the pid of the exact spawned child: `ensureBroker` only trusts a marker written by that exact child, so a stale marker left by a prior spawn or a foreign broker racing the same agent dir can never be misattributed to a different spawn's failure. The bounded generic fallback remains for when no valid current marker exists.
- The SDK broker's Windows process-liveness probe no longer opens a console window on every poll. `runProcessIncarnationCommand` spawned `powershell.exe` without `windowsHide`, and on Windows 11 — where the default terminal delegation hands every new console to Windows Terminal — each probe therefore created a visible terminal window that stole focus. The PowerShell path runs whenever the native reader cannot bind the target pid, so one dead or inaccessible pid turned the broker's ~2s liveness polling into a continuous window flash for the life of the process. Every other internal spawn in the repo already passed `windowsHide: true`; this one did not.
- The SDK broker's Windows process-liveness probe no longer opens visible PowerShell/conhost windows when the native binding authoritatively reports an absent process (#4362, #4367). The broker calls `processIncarnation(pid)` every ~5 s during `#watchPublication → #checkpointSessionHeartbeats → checkpointLiveHeartbeats`; the native Rust binding (`Process.fromPid().incarnation`) reads the process creation-time FILETIME via kernel `OpenProcess` + `GetProcessTimes` without spawning any subprocess. When the native binding returns `null` (the authoritative absent-process result), the code previously fell through to spawning `powershell.exe` with `Get-Process -Id <pid>`, which on Windows 11 allocates a console that Windows Terminal renders as a brief visible window flash. Since PowerShell's `Get-Process` uses the same `OpenProcess` path, it cannot recover an incarnation the native binding already failed to read — the spawn was pure waste. `processIncarnation` now returns `undefined` immediately when `fromPid` returns `null`, treating the PID as unresolvable (identical to the prior PowerShell-failure outcome). The PowerShell fallback is preserved when the native binding throws or returns a non-canonical incarnation, so binding/permission/unknown errors still get the fallback path. The dead-registration sweep (60 s) reaps the row once `process.kill(pid, 0)` returns `ESRCH`, converging state correctly. Daemon generations bumped (Telegram 159, Discord 62, Slack 65) because the generation guard tracks the `processIncarnation` declaration body digest; the daemon lifecycle contract itself is unchanged.
- An acknowledged ACP `session/cancel` can no longer leave the original `session/prompt` unsettled by a wedged client transport. The bounded cancelled-settlement and prompt-rejection paths published the idle `session_info_update` before resolving/rejecting the prompt promise, but `sessionUpdate` awaits the transport write — a client that stops draining the stream (e.g. force-cancel in progress) blocks it, so `waiter.resolve`/`waiter.reject` never ran and the client's turn stayed pending forever (`cancelAgentRun: acknowledged turn still active after timeout`), with the next prompt colliding with the stale foreground turn. Settlement now happens first and the advisory idle publication is detached (`void`), so exactly one bounded `{stopReason:"cancelled"}` result settles the prompt, the running phase is still released (`gjcRunning:false`), the next prompt is accepted, and a late terminal stays fenced. The prompt-control rejection path now also tombstones its settled correlation and releases the phase like every other settlement path (#4324).
- Telegram notifications are delivered again, and in threaded mode they always use threads. Two defects stacked on top of the session-index fix below. First, `sdk/bus/index.ts` derived a session's `telegramTopicsEnabled` from how it was launched (`lifecycleStartupCapability !== undefined || isTelegramOrchestrationSession(process.env)`), so every ordinary interactive session declared itself topic-ineligible; the daemon (which always runs with `requireTelegramTopicEligibility: true`) rejected that identity header, never created a forum topic, and had nowhere to deliver — while still reporting healthy attachments. Eligibility now follows configuration through the new `isTelegramSessionEligible(cfg)` (Telegram configured and effectively enabled), and the launch-provenance predicate `isTelegramOrchestrationSession` is removed. Second, topic admission itself no longer consults the session's self-declaration at all: a still-running session started by an older build declares `false` forever, and refusing it silently downgraded all of its notifications to flat chat-root delivery. In threaded mode the daemon now always uses threads, keeping only the fail-closed fence that requires a loaded topic registry before it can create or reuse a topic. Daemon generation 156.
- Managed session appends no longer crash with `managed_replace_failed:io_error` when a transient Windows file-sharing conflict (another handle denying delete sharing on the session transcript) blocks the exact replacement for a moment (#4330). The native destination open retries the conflict for a bounded ~3 s and the append completes once the holder releases; a persistent conflict surfaces the specific `managed_replace_failed:sharing_violation` with the underlying hex Windows status attached, and nothing is retried after any namespace mutation, so no append can be duplicated or corrupted.
- Telegram (and other chat daemon) notifications work again for interactive sessions. The direct-session GC fence row `main.ts` appends under the agent dir (`host_registered`, `endpointGeneration: 0`, no endpoint) no longer counts as an authority-fencing root in the broker session index, so a session's real endpoint root is no longer forced to `ambiguous`/`live: false`. The post-0.13.1 symmetric ambiguity fencing (#4281) had marked every interactive session ambiguous, SessionRouter refused to attach any session, the notification daemon idle-exited with zero attachments, and broker heartbeat checkpointing skipped every row. The exemption is scoped to that exact bookkeeping shape: an unproven generation-0 `lifecycle_terminal` claim, a malformed generation, and every other unresolved root still fence (C5/C6), and surviving-authority selection is unchanged.
- `gjc sdk session send --wait` no longer overruns its own wait window. Each `turn.result` poll passed no timeout, so after the session reply budget widened in #4258 a wedged status reply could hold a single poll for 40s past a 30s requested wait; every poll now carries the remainder of that window. The whole poll — not just its post-connect reply — is bounded by the remaining window, so a reconnecting transport cannot dispatch past the deadline, and a poll the window itself ends is reported as the documented `wait_timeout` outcome with the last observed status instead of a transport error. The `readCompleteQ12Snapshot` budget is also described accurately: it bounds pagination (when a page may start, and the reply budget that page gets), not attachment resolution or transport reconnect, and the duplicate #4258 entry that a rebase left under `Added` is removed (#4258).
- Scheduled goal continuations now reuse an inherited active session admission, or queue through the same admission gate when no admission is inherited. Emergency compaction can therefore continue exactly once without colliding with an already-processing turn or deadlocking behind that turn's post-prompt settlement.
- Restored task details with a missing result collection now render an explicit pending or unavailable state instead of throwing on `results.length` and falling back to a generic component render error.
- Durable terminal-scope retention no longer deletes a transitional `no_effect_reserved` reservation that shares an evicted completed row's idempotency key pair. The retention bound chose eviction candidates from completed rows only, but applied the resulting key-pair set to every non-pending row, so the reservation an in-flight abort still had to finalize could be dropped; the mirrored asymmetry in the evicted-key collector could also tombstone a reservation and replay uncertainty over a transaction that was still open. Both now apply the same completed-row rule — neither pending markers nor transitional reservations are evictable or tombstonable. A surviving reservation keeps its key pair present, so the completed peer sharing that pair mints no tombstone — losing that peer's replay authority is the lesser harm. The 256-reservation and 4096-tombstone caps, oldest-first eviction, and FIFO tombstone expiry are unchanged, and the bus and SDK-only host session runtimes now write durable terminal state through one shared retention bound instead of eight hand-rolled copies.
- A chat daemon no longer reports an authorized SDK command as rejected when the transport only proved uncertainty after the frame was sent. `uncertain_after_send` now classifies as an ambiguous delivery, so the effect journal records `uncertain` and the operator is never told a prompt failed while the session may already be running it.
- Router attachment identity is now derived in exactly one place (`sessionAttachmentAuthorityId`). The digest binds the endpoint URL/token alongside session, generation, pid, and endpoint mtime, so a caller that persists an attachment authority id can re-derive it instead of hand-rolling a copy that silently stops matching.
- Resident session-cache disposal no longer retains a predecessor's cache directory when the cache root's permissions widen after adoption. The retained parent descriptor is still revalidated for identity (substitution, symlink, or foreign directory fails closed and stays retryable), but a widened mode on the same owned root no longer turns disposal into permanent retention of resident session bytes.
- The broker's dead-registration sweep no longer re-reaps rows it already unregistered, and reaps at most 64 registrations per pass. Since DR-1 keeps an unregistered row listed (terminal, not live), every sweep re-appended an unregistration for every dead row: the index grew without bound and the broker held or re-took the shared session-index lock continuously, so unrelated `gjc` launches failed after exhausting their lock-retry budget.
- Repairing an SDK session index now applies the configured retention policy to the republished snapshot. A repair of a long-lived index previously restored an unbounded snapshot that every later locked transaction had to re-parse, so one broker could pin CPU and hold the shared session-index lock long enough to fail unrelated `gjc` launches.
- The built-in `claude-opus` model preset now falls back deterministically to `anthropic/claude-opus-4-6` when the authenticated Anthropic catalog does not expose Opus 5, while continuing to prefer Opus 5 when available (#4251).
- `gjc update` now performs durable managed-notification recovery only after its replacement and installed-runtime verification succeed: force-stop, canonical daemon restart, then notification recovery run serially before local default-skill refresh. Complete provider credentials count even when the global delivery switch is disabled; incomplete, quarantined, and project/session-only configuration never triggers the lifecycle (#4305).
- Dev CI now runs coding-agent shards from the repository root so the root `bunfig.toml` preload remains cwd-independent, nested managed task children use their canonical provider scope for async-job ownership without rewriting persisted logical headers, bounded TaskTool replay observes the production SDK/manager seams, and the jobs overlay interaction fixture initializes the real theme contract (#4293).
- The legacy `agentDir/settings.json` source is now retired (`.bak`) only after the combined migration commits: a malformed `agent.db` row that aborts the load keeps the source discoverable for the next load instead of stranding its only copy.
- An unreadable project ownership marker (`.gjc/state/settings.json.migrated-keys`, e.g. EACCES/EISDIR) now aborts the project migration instead of treating it as empty and reimporting a stale retained value.
- Strict resume rejection now tears down derived state without mutating the rejected transcript, and releases the persistence writer it opened. A confirmed close is reported as successful cleanup even when the writer rethrows a queued drain failure, a quarantined `close_unknown` outcome is surfaced to the caller as an `AggregateError` alongside the resume error, and a certified pre-dispatch failure retains the writer under an unreferenced retry that releases the descriptor, resident session state, and managed authority once the close reaches a terminal state.
- Same-directory managed session opens now retain directory and transcript identity from admission through later persistence: existing bounded resumes reject deletion or replacement without persistence, close-time rewrites and appends cannot mutate a successor, and initially missing targets use retained no-replace publication with post-publication identity validation and sidecar initialization only after acceptance.
- `session.delete` now terminalizes a fully scrubbed transcript correctly instead of reporting `cleanup_pending` for a POSIX exchange-placeholder that carries `payloadDurable: true`. The native protocol always retains a zero-length scrubbed placeholder after destroying the payload; the TS classification now accepts that placeholder as terminal (matching `managed-session-storage.ts`'s `exactUnlinkCompleted`) instead of requiring its absence. A retained successor or unknown path still stays `cleanup_pending`. The `sdk-machine-lifecycle-topology` test ingress was also updated from the retired `sdk session raw global` to the correct `daemon session global` CLI (#4273).
- `SdkClientError` now carries `reconnect` diagnostics on every reconnect-cycle termination: attempts consumed, the configured attempt budget, elapsed wall-clock, and whether the cycle ended from attempt exhaustion, an elapsed deadline, or cancellation. A long-lived ACP session budget truncated by an unrelated wall-clock deadline was previously indistinguishable from a host that consumed every retry, and closing during a backoff sleep escaped as a bare teardown error with no attribution at all. `details` keeps its existing meaning — the terminating transport error — so consumers reading it directly are unaffected.
- macOS computer-use permission failures now distinguish a real Screen & System Audio Recording denial from a capture failure after a successful preflight, include the current launcher identity in diagnostics, and preserve TCC grants across GJC ad-hoc rebuilds with a stable designated requirement. Accessibility errors now carry the same launcher/relaunch guidance.
- Vendor-separated model profiles now delegate. A profile that maps `executor` or `planner` to a provider other than the `default` role exists to spend the other vendor's quota, but the strong "delegate by default" system-prompt directive is gated behind `task.eager`, whose default is `false` — so the main agent kept implementing everything itself and burned its own provider's quota with no warning. Such a layout now enables eager delegation, including when the profile is activated mid-session through `/model` or the model selector (which also activates the `task` tool under `tools.discoveryMode: all`). An explicit `task.eager` value in config still wins, and `gjc config doctor` reports an explicit `task.eager false` next to vendor-separated workers as an advisory. Bare provider-agnostic aliases and single-vendor profiles are unaffected.
- Telegram topic archives now settle in a private Threaded Mode chat. The archive dispatched `closeForumTopic`, which Telegram rejects outside supergroup forums (`400 … the chat is not a supergroup forum`), so an orphaned private-chat topic could never be archived: the stale thread stayed visible in the chat and the durable `archive_pending` record blocked the session from reusing or recreating a topic after resume. A private paired chat now archives through `deleteForumTopic` (the only archive operation Telegram offers there); supergroup validation forums keep the non-destructive `closeForumTopic`. `TOPIC_ID_INVALID` — Telegram's definitive answer for a topic that was already deleted by hand — now settles the archive instead of retrying forever. Durable archive retries are drained again by a periodic sweep; the drain was dropped in the router refactor, leaving `archive_pending` jobs parked until the next same-session event that never came.
- SDK workflow-gate recovery now survives a direct-control transport failure during shutdown. When `resolveGate` throws after the broker has already accepted an answer, the catch path re-checks durable completed/accepted-incomplete state and attempts recovery before falling back to quarantine — so a gate that was accepted but whose transport response was lost is no longer stranded. The `AgentSession` emitter fence is moved after `session_shutdown` emission so admitted resolutions can drain during the shutdown window, and both the gate-resolution drain and `session_shutdown` extension handlers are bounded (5 s and 60 s respectively) so a stuck emitter/controller or extension cannot hang shutdown indefinitely (#4047).

- ACP `session/new` no longer fails with an uncertain-after-send internal error against a cold session host. Session-scoped commands were dispatched through `SessionRouter` on the SDK transport's one-shot default response deadline (10s), which the first `models.list/current` (Q10) outruns whenever profile-provider credential collection has to refresh several OAuth providers — the reply landed after the deadline (measured 10002ms against an 11s answer), the outcome could then only be reported as uncertain after the frame was sent, and the ACP agent discarded the session it had just created while the immediate retry answered in ~260ms. Router-dispatched session requests now wait for a reply on the SDK's existing round-trip budget (40s, the same `2 * HEARTBEAT_TTL_MS` the session reconnect budget and prompt watchdog already use), while any caller-supplied timeout — coordinator prompt acknowledgement, lifecycle requests — still wins. Connect, reconnect, and lifecycle deadlines are unchanged. Other Router-dispatched surfaces (ACP prompt/query, chat-daemon `/sdk` commands, `gjc sdk session`, SDK MCP, non-prompt coordinator operations) intentionally inherit the same reply budget, so a connected but wedged host is reported after 40s instead of 10s. Two callers are deliberately excluded because they own a tighter promise: `turn.abort` keeps the one-shot deadline, since ACP awaits that acknowledgement before it can arm its 5s settlement grace and a cancel must stay responsive; and each `readCompleteQ12Snapshot` page now carries what is left of the coordinator's 5s snapshot budget, which was previously checked only between pages while the page request itself carried no deadline (#4258).
### Changed
- macOS now emits the terminal BEL for completion, approval, and ask notifications by default when `notifications.terminalBell` has not been configured; an explicit setting still controls the behavior.
## [0.13.3] - 2026-08-15

### Added
- Added Grok 4.6 to the bundled Grok CLI model catalog with bounded reasoning-effort parsing.
- Added built-in `grok-46-{eco,medium,pro}` role profiles using the existing xAI OAuth/subscription provider, with supported reasoning-effort selection for Grok 4.5/4.6.
- Added three provider-agnostic Muse Spark 1.2 presets: Muse Spark across all roles, plus DeepSeek and Luna executor variants.
- Added the bundled Ouroboros theme and pet, including transactional theme selection and RedGajae fallback (#4468).
- Gajae Pet now renders in iTerm2 using inline PNG frames, with the same reserved composer-side layout and lifecycle cleanup used by Kitty and Sixel (#4499).

### Fixed
- Managed sessions recover from `content_too_large` by rewriting live in-memory entries, and proactively compact before the managed transcript limit (#4411).
- Managed output publication no longer freezes the resident event loop when a no-replace rename stalls in the kernel; async publication uses blocking-pool native boundaries and per-session stores reap scrubbed protocol remnants (#4396).
- Print mode now requests a governed process exit after successful session teardown, so completed one-shot runs cannot be pinned by residual runtime handles; both stdout and stderr are drained first and the recorded exit code is preserved.
- `todo_write` no longer rejects a positional task handle without saying how to address a task. Callers reach for `id`/`index` because the tool result renders todos as a list, and raw validation rejects the unknown key before `execute` runs; positional-handle keys now explain that tasks are addressed by exact `task` content or by `phase`.
- Escaped-non-ASCII tool-call turns in managed fallback are retried on the same model without charging the fallback chain or advancing it — the wire defect is a sampling accident, not provider evidence — bounded to two consecutive retries before the run terminates (#4515).
- Image generation now redacts API keys, bearer tokens, JWTs, and other credential formats from provider error messages and surfaced metadata, bounds provider response bodies at 64 MiB before buffering, and cancels rejected bodies instead of retaining unbounded streams (#4302).
- A transient Windows `invalid_request` during managed replacement-receipt reconciliation is now resolved by capturing both the pending and canonical receipt paths: success when another reconciler already moved the pending receipt, fail-closed `managed_replace_cleanup_receipt_invalid` for conflicting destinations, byte-identical copies, or missing receipts (#4373).
- Status-line scratch roots are resolved at render time instead of process start, so path classification follows the active environment when `HOME`/`TMPDIR` are rewritten after module caching (#4452).

## [0.13.2] - 2026-08-13

### Added
- Added a portable Stream Deck integration for cmux sessions, including a plugin manifest, reusable profile pages, action icons, installation scripts, and a guided setup document (#4308 by @Yeachan-Heo).
- `/theme <name>` now switches the theme immediately without opening the selector. The name is validated against built-in and custom themes, persisted to the detected `theme.dark`/`theme.light` slot, and applied to the running session in one step (#4429 by @Yeachan-Heo).

### Fixed

- Anthropic thinking blocks emptied by `clear_thinking` now drop stale signatures before replay, preventing every subsequent request from failing with an invalid thinking-block signature (#4247, reported by @probepark).
- Coalesced double-Esc and triple-Esc input now emits individual Escape presses under tmux and SSH, while split ambiguous Escape sequences stay buffered until a continuation or flush timeout resolves them (#4312 by @Yeachan-Heo).
- Apple Terminal.app now retains its default keyboard mode when Kitty keyboard support is unavailable, preserving Korean/Hangul IME composition (#4297 by @Yeachan-Heo).
- Managed output publication no longer freezes the resident event loop when a no-replace rename stalls in the kernel. Async publication now uses asynchronous file operations and native blocking-pool rename/link boundaries, while per-session stores reap scrubbed protocol remnants on a throttled, serialized schedule (#4396 by @Yeachan-Heo; fixes #4394).
- The Telegram notification daemon now contains transient Windows state and ownership-lock read failures during steady heartbeat renewal instead of terminating its run loop; staging files are cleaned in a `finally` block, and only a proven ownership mismatch stops the owner (by @Yeachan-Heo; issue #4200).
- Coordinator MCP now answers the standard `ping` keepalive with an empty result instead of `method not found`, preventing liveness clients from reconnecting repeatedly (#4412 by @developjik).
- Task-launched canonical subagents no longer receive an ACP exact-config `toolsOnly` MCP manager that they are forbidden to reuse, restoring Planner, Architect, Critic, and Ralplan consensus launches (#4419 by @chlee1001).
- The SDK broker's Windows process-liveness probe no longer opens a console window on every poll; its PowerShell fallback now passes `windowsHide` so a dead or inaccessible process cannot trigger a continuous focus-stealing terminal flash (#4346 by @Dayoooun).
- Foreground Bash calls that finish before their auto-background deadline now cancel that deadline instead of leaving a sleep behind that keeps compiled print-mode processes alive (#4256 by @probepark).
- Prompt suggestions now remain visible after cancelling another interactive action and returning to the composer (#4347 by @thegreatesthoneybee).

## [0.13.1] - 2026-08-11

### Added
- Terminal receipt state and correlated steering are now durable. Prompt reconciliation tracks whether a terminal turn produced reportable output (`present`/`missing`/`unknown`), failing closed as `receipt_missing` when a completed turn has no final response. `turn.steer` with a `clientRef` hashes the validated text (SHA-256) and durably reserves dispatch before queueing, deduplicating replays, rejecting conflicting text, and settling to `accepted`/`rejected`/`uncertain`. The new `turn.steer_status` query (Q31) looks up durable steer status by `clientRef` or canonical `commandId`/`turnId` pair. Steer delivery is uncertain after process restart; settled records share the 15-minute/256-record retention bound.

- `/model <preset>` and `/model gajae-code/<preset>` now activate a known model profile immediately instead of failing with "Unknown model". Unknown names still fall through to model resolution, and `/model <role> <preset>` keeps assigning a model to the named role.
- Browser `act` and `run` responses can now surface an opt-in (`open(..., { diagnostics: true })`), bounded mailbox of page exceptions and `console.error` metadata. Entries carry only kind, timestamp, origin-only URL, line/column, and an allowlisted built-in error class; path segments, query strings, messages, arguments, values, and stacks are never retained. Serialization is byte-bounded with an explicit truncation marker.
- A generated, host-neutral `sdk-skills/` bundle now provides namespaced external-agent skills for broker-bound GJC session discovery, approved trusted-local operations, and TypeScript/Python script authoring. Its templates invoke `gjc sdk session` without discovering endpoint records or creating transport clients; deterministic drift checks and a versioned `manifest.json` keep the generated bundle closed.
- `GJC_SESSION_CONTEXT_BUDGET_BYTES` now overrides the synchronous session-context materialization budget that backs `SessionContextTooLargeError` (previously hardcoded to 64 MiB, now a 512 MiB default). The override parses fail-closed: only a canonical positive-integer value is honored, while empty, non-numeric, negative, zero, safe-integer-overflowing, or above-ceiling (8 GiB) values fall back to the 512 MiB default with a warning, so a malformed value can never silently raise or lower the memory guard. Raise it above your measured session size to suppress the preflight, or lower it to restore the old tight bound.
- Custom OpenAI-compatible providers can opt into `/fast` forwarding with `compat.supportsServiceTier: true`; first-class OpenCodex discovery enables it automatically so client `service_tier: "priority"` reaches an OpenCodex proxy running in passthrough (`Auto`) mode.
- Coordinator session state now carries a bounded tool-activity snapshot of what a session is running right now, surfaced through `gjc_coordinator_read_status` and every other `session_state` projection (issue #4195). It reports a per-session sequence, the last activity time, a canonical built-in tool label (anything unproven reads as `custom`), phase, outcome, elapsed time, the exact number of in-flight calls, and a list of the newest 8 of them. The label follows the tool object the agent loop actually dispatched to, so a custom tool that takes a built-in's name mid-run is never published under the built-in's label. A settled session always reports zero active tools. The snapshot carries no tool arguments, results, command text, paths, output, model text, raw call ids, or credentials, and a snapshot that does not match the exact published shape — or that contradicts the lifecycle state published beside it — is withheld rather than repaired.

### Changed
- SDK core now exclusively owns managed session lifecycle and external attachments through Broker lifecycle services and `SessionRouter`; provider daemons receive opaque capabilities only, lifecycle-equivalent adapter controls fail closed, and the former public stdio/socket, standalone TypeScript, and Python raw endpoint clients are removed.
### Fixed
- A long-running managed session that grows its transcript to the 64 MiB per-file storage limit no longer permanently fails with `Extension sendUserMessage failed: content_too_large`. The emergency compaction system now includes a transcript-file-size floor (48 MiB, 75% of the managed per-file cap) so a growing session compacts before hitting the wall. When the limit is reached anyway (compaction disabled, a very large tool result, etc.), the active-session append path recovers via a full-rewrite that writes only the live in-memory entries, shrinking the file below the limit instead of permanently poisoning the session (#4408).
- A Telegram topic registry poisoned by v0.12.12–v0.12.17 (an archive begun during a disconnect grace window persisted `archive_pending` together with a stray `disconnectGraceExpiresAt`) no longer bricks the shared topic authority forever. The parser now tolerates the stray grace deadline on archive-family non-grace records (`archive_pending`, `archive_exhausted`, `inactive`) — the field is inert there and dropped on load, so the next persist heals the durable snapshot — instead of rejecting the whole registry, which made every authority read and CAS write fail (empty in-memory registry, duplicate topics, and on pre-0.12.18 daemons a startup crash loop). Non-archive states still reject the stray field, disconnect-grace records still require both the deadline and the orphan timestamp, and malformed values are still rejected.
- Foreground Bash calls that finish before their auto-background deadline now cancel that deadline instead of leaving an uncancellable `Bun.sleep()` behind. In print mode the losing sleep kept the compiled CLI alive for the full command timeout after Bash and subagent work had already completed, which appeared as a shutdown hang with no active Node handles.
- A cancelled persistent Bash shell that is still unwinding no longer forces same-key follow-up commands onto temporary one-shot shells. The retired shell is removed by identity, its persistent successor is installed immediately, and late retirement cleanup cannot delete that successor.
- Bash shell sessions are now closed rather than merely aborted when they are retired, so a completed command no longer keeps its native shell retained for the rest of the process. This covers shutdown cleanup, one-shot invocations, and shells retired after a cancellation or timeout — the retired shell is never reused, so it is released instead of held.
- A just-created or just-registered SDK session no longer reads as not-live (failing immediate `session.close`/`session.delete`, endpoint resolution, and chat-daemon attachment) for up to one heartbeat interval: the host-written registration now counts as initial liveness evidence, aging out exactly like a heartbeat, with the OS process-incarnation match still required. The broker's shutdown-escalation identity proof also accepts the incarnation the index auto-stamps at registration, and ACP `session/list` no longer advertises or re-adopts closed/unregistered (DR-1 terminal) rows — repeated ACP deletes answer already-gone instead of escalating against a host that no longer exists.
- Insane public-route search now cancels and rejects response bodies larger than 1 MiB instead of buffering unbounded feed, HTML, or JSON payloads.
- Telegram forum-topic ownership is now limited to coordinator/lifecycle-managed sessions; ordinary sessions can still use local SDK or other notification providers without creating Telegram topics.
- The Telegram notification daemon no longer terminates with an uncaught `EPERM` when publishing its heartbeat sidecar while an external Windows file lock (antivirus, indexer) briefly holds the destination. The rename is retried a bounded number of times under the ownership-lock fence, a still-failing publication is contained as a diagnostic-logged transient (the next heartbeat cycle republishes), and only a proven ownership loss stops the daemon; the stale-writer fence and staging-temp cleanup are preserved on every path (#4200).
- `/fast` status, model badges, and task/subagent badges no longer show `⚡` for providers that silently drop unscoped priority intent. Indicators now follow wire-effective built-in support or an explicit `compat.supportsServiceTier` capability instead of treating every provider as fast-capable.
- The broker's dead-registration sweep now requires positive proof of host death (ESRCH, or a readable OS process incarnation that differs from the recorded one) before unregistering a session. Under heartbeat-based liveness a registered host whose heartbeat had not yet been checkpointed read as not-live and could be reaped while still running; a stale or missing heartbeat is no longer grounds for a reap, and terminal registrations are never re-reaped.
- Option+Q verification now supports normal macOS Terminal.app profiles and fails with an actionable Settings > Profiles > Keyboard > Use Option as Meta key diagnostic when the profile cannot forward Option as Meta/Esc.
- Enabling Speech-to-Text from `/settings` now checks and configures its local dependencies immediately instead of deferring setup until the first recording. Setup progress is shown in the status line; failures disable STT again rather than leaving an unusable enabled toggle. Setup also reports platform-correct recorder commands, detects Ghostty's `macos-option-as-alt` requirement, and documents the Ctrl+P command-palette fallback, permissions, models, troubleshooting, and remapping.
- Queued-message selection now accepts the actual macOS Terminal.app Option+Up/Down byte sequence from either physical Option key, enabling multi-message restore, delete, and reorder flows.
- Keybinding configuration now normalizes `option`/`meta` to Option and `command`/`cmd` to Command, while Terminal.app guidance covers both physical Option keys and explicit Command profile mappings.
- ACP prompts now settle when a terminal frame arrives before prompt acknowledgement with correlation split between the frame envelope and event payload. Live and deferred frames share strict ownership derivation, correlated nonterminals wait for exact acknowledgement ownership, and watchdog or cancel settlement no longer waits for a missing acknowledgement. Incomplete or foreign terminals emit structured error logs, unrelated session traffic cannot keep a wedged turn alive, and correlationless session events remain publishable without contaminating prompt-owned final-text state. Deferred terminal replay failures are contained by the session frame queue.
- Closing a managed session no longer crashes the process when its cold-history sidecar resident-cache directory has already been removed. `SessionManager.close()` released the sidecar cache through an unguarded `dispose()`, so a `ResidentCacheTrustError` escaped teardown as an unhandled rejection (`directory_unverifiable`) and took down the CLI. Disposing an instance directory that no longer exists is now a completed disposal rather than a trust violation — a present-but-untrusted directory still fails closed — and sidecar release both clears its state before disposing and downgrades any remaining trust failure to a warning, matching every other resident-store disposal site.
- `bun run restart:sdk-broker --close-session-hosts` no longer fails against session hosts whose workspace was deleted while they kept running. The teardown fence proved a pid's identity only from the spawn-time marker inside the session's own workspace state root, so deleting that workspace (a removed worktree, a cleaned scratch directory) destroyed the only evidence the broker would accept: `session.close` answered `close_refused` forever, the host stayed resident serving the source it started with, and every restart exited non-zero naming the same unkillable orphans. Hosts now publish their OS start incarnation into the broker-owned session index at registration — and reconciliation preserves it — so the pid stays provable after its workspace is gone, while a marker naming a different process still refuses the signal and an incarnation that no longer matches the live pid still fails closed. A host that dies before withdrawing its own registration is now retired by the broker on the same evidence its dead-registration sweep already uses, so a completed teardown is reported as one instead of surfacing as `endpoint_stale` or `terminal_uncertain`.
- Conversation-store lock cleanup now uses a best-effort close and ownership-checked unlink: a failed `close()` never skips removing the lock file (preventing a leaked well-formed lock that blocks every later waiter), while the unlink validates the lock identity so it can only ever remove the lock this holder wrote.
- Bounded cold-session resume now uses automatic size routing, keeps ordinary sessions eager, admits two-GiB transcripts with fork-header headroom, and reduces first-open work with reusable scanning, buffered sidecar writes, pressure-based GC, one full transcript JSON pass plus a bounded authenticated suffix read, owner-bound concurrent publication, fail-closed post-admission tamper handling, deferred secondary artifacts, and split reservation/residency/disk accounting.
- Session resume entrypoints now share size-aware bounded admission without weakening strict small-session validation, and hard-link fallback cleanup uses identity-bound native retirement instead of a pathname `lstat`/`unlink` pair.
- OpenCode Go Responses completions that carry a response id but no output items or token usage are now treated as typed retryable failures instead of successful turns; untyped proxy empty responses retain the existing context-overflow promotion and compaction path.
- Memory-pressure sweeps now request asynchronous garbage collection instead of forcing a stop-the-world collection on the main event loop, preventing periodic input and rendering stalls under the opt-in memory guard.
- `gjc gc --disk --prune` now fences the blob mark/sweep so a session transcript that keeps changing under the mark can never cause unsafe reclamation: each transcript's references are bound to a stat snapshot taken before and after its read, the store must be observed completely quiet across a quiescence window before the sweep may act, and the fence is re-verified immediately before every removal. A store that still moves withholds the sweep with `keep:withheld_evidence_incomplete: sessions_changed_during_mark` instead of reclaiming on evidence it could not prove stable (#4158).
- Concurrent interactive launches now atomically publish mode-600 core SDK discovery even when notification-daemon ownership is blocked, retry failed notification readiness without republishing the SDK endpoint, wait through bounded session-index contention with dead-owner recovery, and recover exact tmux-owner cleanup from competing valid terminal-verdict publishers.
- Follow-up queue auto-continuation now waits for compaction and foreground bash/eval work to settle, preventing queued prompts from starting a model turn concurrently with those operations.
- Scheduled continuations no longer route through `continueQueuedMessages()` merely because the last message is a non-assistant execution tail. The queue-consuming continuation is now used only for the explicit follow-up/steer drains (post-execution settlement, auto-compaction `queued_continue` recovery, and user-interrupt steering), so retry, fallback, auto-compaction, deep-interview, active-goal, TTSR, and reminder continuations keep `continue()` semantics and no longer fail with "No queued messages to continue" when no prompt is queued.
- Follow-up queue submissions now defer slash/skill text during foreground bash/eval work instead of invoking a competing custom-message turn.
- Follow-up queues now resume through a dedicated queued-message continuation after Python/Bash execution tails, avoiding an extra stale-turn replay.
- Post-maintenance continuation now resumes the current non-assistant tail when no queued messages exist, while consuming queued steering/follow-ups only when present; overflow recovery no longer fails with `No queued messages to continue`, and accepted queued successors reset fallback accounting exactly once.
- Superseded lifecycle SDK startups are now treated as expected cancellation during background notification reconciliation, preventing a red extension error while fail-closed `/notify on` admission and later same-session replacement recovery remain intact.
- Streamed edit preview coalescing now keys on the complete partial-JSON payload rather than its length, so same-size in-place argument replacements recompute and render while repeated payloads remain cached.
- CLI parsing now fails closed by default, while launch and ACP explicitly defer only their owned startup options; this preserves ACP-specific diagnostics, SDK startup forwarding, real ACP subprocess framing, and RLM typo rejection without exposing retired flags in root help or completion.
- Read-tool code summarization and ZIP extraction now run asynchronously instead of blocking the TUI event loop during structural parsing or decompression.
- `secrets.yml` entries with `mode: "replace"` and no explicit `replacement` now derive their substitute from a keyed, domain-separated HMAC-SHA256 construction over the process key instead of an unkeyed public `Bun.hash`. Derived replacements are no longer confirmable offline without the key (issue #4166); same-process determinism, same-length output, and alphanumeric character behavior are preserved, and explicit `replacement` values are unchanged. Note: derived replacements differ across processes/key rotations — set an explicit `replacement` when a stable value is required.
- Managed scope preparation now reaps scrubbed write-protocol remnants before the synchronous binding publication receipt scan. The POSIX exact-unlink fallback detaches and scrubs but never deletes, and macOS has no retained directory authority to complete deletion, so every managed replacement leaked zero-length `.gjc-exact-unlink-placeholder-*`, `.gjc-receipt-remove-*`, and `.gjc-exact-replace-destination-*` entries; scope directories grew without bound (observed at 83k dirents), degrading every per-mutation receipt scan and making an already-over-limit scope unable to reach its former post-publication self-healer. Preparation now removes zero-length, single-link remnants older than 15 minutes before that scan; non-zero displaced predecessors and detached receipts are retained as evidence, and in-flight protocol steps are protected by the age gate (#4303).
- Conversation store file locks now publish complete, synced metadata atomically and release idempotently: failed lock construction cleans its pending artifact, release cleanup is ownership-checked, and recovery reclaims only stale locks or provably abandoned locks from the same process isolate. Empty, malformed, and unproven legacy markers fail closed at the normal timeout; concurrent healers and predecessors cannot delete a successor lock.
- SDK lifecycle hosts now verify that their session endpoint file is removed after graceful shutdown, setting a non-zero exit code and emitting a diagnostic if the endpoint persists. This closes a gap where a `SIGTERM`/`SIGINT` stop could exit cleanly even though the host's endpoint JSON was still on disk, leaving a stale endpoint for broker discovery.
- Owned subprocess teardown now recognizes zombie-only Linux process groups without consuming its grace window, while failing closed on unreadable `/proc` evidence, guarding recycled process-group leaders with their spawn-time identity, and reporting an explicit incomplete-teardown outcome to MCP stdio callers. Recycled-leader guarding applies only where that identity is readable (`/proc`); on macOS and other POSIX platforms teardown still signals the owned group instead of refusing it as unverifiable.
- ACP clients can now map advertised `/skill:*` commands to canonical `skill.invoke` while binding the run to exact correlated prompt completion and cancellation ownership, and can answer deep-interview forms in headless lifecycle sessions. Lifecycle hosts initialize the shared theme before tools render and before the MCP readiness budget is calculated, preventing the pre-elicitation `theme.status` failure, and protocol form providers remain authoritative if a local `/notify on` registers an interactive source later.
- SDK session-index snapshot publication no longer fsyncs a read-only temp handle, which threw `EPERM: operation not permitted, fsync` during Windows startup whenever the broker published the session snapshot. Publication now goes through the same atomic write-and-sync helper the repair path uses (writable handle, rename, directory sync, and temp cleanup on failure), so Windows `FlushFileBuffers` succeeds and a failed publication no longer leaks the `.tmp` file (#4250).

### Added
- `gjc setup provider` now ships parameterized proxy presets: `--preset litellm` and `--preset openai-compatible-proxy` (aliases `litellm-proxy`, `openai-proxy`, `compatible-proxy`, `custom-proxy`) with a required `--base-url`, configurable `--api-key-env`, and live model discovery (#4123).
- New `modelProfile.proxyProvider` and `modelProfile.proxyMode` settings route built-in model-preset selectors through an authenticated OpenAI-compatible proxy (e.g. `xai/grok-4.3` → `litellm/xai/grok-4.3`). `fallback` preserves directly authenticated providers by default; `always` forces every proxy-routable built-in selector through the configured gateway. Routing fails closed for unconfigured or unauthenticated proxies and missing or ambiguous proxy models (#4123).

- `turn.abort` `mode:"terminal"` with `scope:"turn" | "owned"`: stops the root turn and blocks only its own continuation routes, while left-running owned work keeps running and its completions resume the agent as a fresh turn (`scope:"turn"`) or are settled/dropped (`scope:"owned"`). Durable idempotency via the reconciliation store, deterministic replay after restart/eviction, bounded reservations, and the private turn-continuation fence.

- SDK-only session hosts now publish their session ID and register their endpoint lifecycle with the broker, matching its identity and staleness fences. This restores broker/coordinator resolution for durable workflow-gate controls (`workflow.gates.list` and `workflow.gate_answer`) without relying on tmux pane input; broker unavailability leaves non-lifecycle local hosts usable and retries publication later.

- Python eval now settles managed-runtime provisioning before cancellation, retires cancelled initialization without sharing it with successors, and retains late kernels whose shutdown is unconfirmed for postmortem retry.
## [0.12.21] - 2026-08-09

### Fixed
- Strict session resume now sanitizes stale replay metadata in memory without mutating the selected transcript, then publishes the terminal resume breadcrumb only after the first durable user-visible write. Declined auto-continuations restore their deferred predecessor terminal instead of dropping client settlement evidence.
- Todo completion reminders now honor `todo.reminders.max` across an interactive turn rather than stopping after the first reminder.
- Hidden `nextTurn` messages that trigger a follow-up model call no longer deadlock the predecessor prompt during post-prompt settlement.

## [0.12.20] - 2026-08-09

### Fixed
- Parallel task children now use their durable parent/task ownership tuple as a stable provider-facing continuity identity, preventing nested managed children from contending for one websocket owner while preserving logical session headers, artifact hierarchy, detached-resume identity, and explicit provider-session overrides.
- Session contexts no longer retain materialized resident text after the backing cache disappears; subsequent reads rematerialize the existing public-safe missing-blob placeholder. Headless `--export` coverage now expects the established error for a nonexistent source file.
- SDK prompt reconciliation now preserves bounded safe provider failure codes after `agent_failed`, while retaining the fixed redacted wire/persisted message and recording a bounded local diagnostic.

- Shadow-mode cold-session builds now compare the actual sidecar-derived provider context against the authoritative eager context and expose check/mismatch counters in `/session`; corrupt provider metadata produces a counted mismatch without weakening transcript authority. Cold branch activation now resolves compacted 10k-entry branches through one bounded ordinal-index scan and one bounded transcript range read instead of scanning a dictionary partition for every ancestor, and cold dictionary lookup skips unrelated JSON decoding while retaining full partition-digest verification. Bounded first-open, exact-reopen, and lazy cold-entry paths apply the same stale OpenAI Responses replay-metadata sanitation as eager resume.
- ACP startup now rejects unrecognized command flags and incompatible terminal-auth modes through the normal CLI usage-error path, while preserving separate dash-leading system-prompt text.
- ACP now rejects `--tools` by presence. A comma-only filter such as `--tools ,,` normalized to an empty list, which passed the length-based startup check and then set `toolNames` to `[]` because an empty array is truthy, starting an ACP session with every tool disabled instead of failing closed.

- Ultragoal per-goal completion receipts no longer go permanently stale when a later `record-critic-verdict` call names that (already-`complete`, therefore immutable) goal's id — for example while re-confirming an earlier goal's evidence during a subsequent goal's terminal-critic gate. `critic_verdict` ledger rows are purely additive audit evidence that never mutate a goal's `goals.json` row, but the receipt-freshness check previously treated any ledger row carrying a goal's id as proof that goal had changed; one such `critic_verdict` row permanently staled the earlier goal's own per-goal receipt, which in turn blocked every later `final-aggregate` receipt that depends on it — with no actionable diagnostic identifying the actual cause. `critic_verdict` now joins `nudge` as a receipt-freshness bookkeeping event.
- `gjc --mode acp` no longer aborts on flags it advertises in `--help`. `parseArgs` never recognized `--extension`/`-e`, `--hook`, `--no-extensions`, `--no-skills`, or `--skills`, so every one of them landed in `unknownFlags`; once unknown flags became a hard ACP failure, launching ACP with `--no-extensions` crashed at startup with `Unsupported under SDK-backed ACP: extension flags`. All five are parsed again, so `--no-extensions` starts normally while `--extension`, `--hook`, `--no-skills`, and `--skills` are rejected under ACP by their own names — the rejection list in `resolveAcpStartupOptions` had been dead code because nothing ever populated the fields it tested. A genuinely unknown flag is now reported as `unknown flags: --typo` instead of being mislabeled an extension flag.
- Strict session resume now sanitizes stale replay metadata in memory without mutating the selected transcript, then publishes the terminal resume breadcrumb only after the first durable user-visible write. Declined auto-continuations restore their deferred predecessor terminal instead of dropping client settlement evidence.
- Todo completion reminders now honor `todo.reminders.max` across an interactive turn rather than stopping after the first reminder.
- A transient Windows `io_error` while detaching a managed replacement cleanup receipt now defers that receipt for later reconciliation instead of aborting the current session mutation. Invalid receipt and non-I/O cleanup failures remain fail-closed.

- Attached image placeholders such as `[image 1]`, including the `[image N] source="…"` pasted-path reference form, are now deleted atomically with Backspace when the cursor sits at the placeholder boundary or the end of the full reference, instead of being erased one character at a time.
- Root CLI help and shell completion now share the launch flag table instead of maintaining divergent copies, advertise the working `--fork` and `--worktree` options, omit retired extension/skill flags, and reject unknown options instead of silently dropping typos (#4023).
- SDK broker startup diagnostics (#3963): a detached broker that exits before publishing discovery now writes a durable bounded startup-failure marker under `sdk/`, and the caller surfaces a typed `broker_startup_failed` error carrying the exit code, reason, and a bounded stderr excerpt instead of a bare `code=0`.
- Image paste now defaults to Ctrl+V before Command+V on macOS (where terminals commonly consume Command+V), while Windows also accepts Ctrl+V and Alt+V; generated hotkey guidance reflects every platform chord.

### Added
- Added a deterministic quick-lane task classifier (`classifyQuickLane`) exposed through a read-only `gjc quick-lane classify` command. It is an auditable classification surface that performs no routing and is NOT wired into runtime routing: it only prints whether a task request carries quick-lane eligibility signals (explicit file paths, issue/PR numbers, named symbols, explicit tests, numbered steps, acceptance criteria, error references, code blocks, or an explicit `force:`/`!` override) or deep-lane exclusions (vague, ambiguous, exploratory, risk-sensitive, or multi-file/cross-contract scope), and it never alters deep-path defaults. The classifier is deterministic and fail-closed: any exclusion forces the deep lane even when a concrete anchor is present. The #3984 routing decision remains open.

- `session.list` now supports bounded pages with opaque continuation cursors and stable snapshot sequences. Internal ACP, coordinator, CLI, and broker-restart consumers drain every page so existing session discovery remains complete.
- Bounded-memory cold-session disk offloading is rollout-ready behind the `sessionMemory` settings namespace. `sessionMemory.mode` (default `"shadow"`) selects `off`/`shadow`/`enabled`: shadow measures without changing observable session behavior, and canary/default-on remain release-channel decisions rather than user-facing enum values. Fixed implementation budgets preserve the ≤64 MiB steady-state guarantee; transcript v5 stays authoritative and derived `.spill.*` sidecars remain disposable. Context materialization uses the typed `SessionContextTooLargeError` preflight, while `sessionMemory.contextOverflowRecovery` independently controls the async compact-once recovery path.
- Enabled explicit-path and managed-session resumes validate descriptor-bound commit metadata, base/tail hashes, terminal proof, and hot-record digests before retaining only the authenticated post-compaction tail. Managed TUI and nested review-subagent sessions read authoritative transcript ranges through retained/no-follow descriptor checks while keeping disposable sidecars in a verified per-process resident cache outside the managed tree. Fresh-process gates cover 120k compaction cycles, direct/captured forks, and one-million-record first build plus exact lazy reopen.
- Enabled canary sessions fail closed to shadow/eager behavior after repeated sidecar build failures or failed adoption, expose the live auto-disable reason through `/session`, and clear the session-local latch through the existing `off` rollback mode. Live `enabled` → `shadow`/`off` rollback keeps existing cold entries lazily readable for the current process; the next `off`-mode start ignores derived state and restores eager compatibility.
- `gjc team` no longer rejects every successful native tmux startup with `tmux_layout_postproof_failed` and then, after passing layout verification, `tmux_window_option_postproof_failed`. tmux reports `#{window_layout}` as an encoded layout string rather than the requested preset name, so Team now verifies the encoded readback and the non-mutating `main-vertical` pane geometry without replaying the readback. Window-option readback now also uses `show-window-options -v`; unlike `show-options`, native tmux does not support the quiet `-q` flag on that command.

## [0.12.19] - 2026-08-08

### Fixed

- Telegram topic-registry archive transitions remain parser-valid across durable-fence load promotion and failed archive-publication rollback. The initial generation-57 fix cleared `disconnectGraceExpiresAt` on direct archive transitions, but loading a `disconnect_grace` record superseded by a newer durable fence could still promote it to `archive_pending` while retaining the grace-only field, and rollback could restore `disconnect_grace` without restoring its required deadline. Both paths now preserve the state/field invariant with parser-roundtrip coverage. This entry also corrects the hotfix attribution omitted from the published 0.12.18 release notes. `DAEMON_GENERATION` bumped to 58.

## [0.12.18] - 2026-08-08

### Fixed
- Repeated provider idle-stream stalls now stop after `retry.maxRetries` instead of inheriting the unbounded transient-error retry path. A one-off stall still retries normally, while a persistently silent stream surfaces its error rather than resubmitting the same billable context indefinitely.
- SDK snapshot spill writes now retry once with a fresh temporary file when Bun reports `EBADF` during write or `fsync` under heavily contended descriptor teardown, and no longer fail when `close()` reports `EBADF` after a successful write and sync. The atomic writer removes the abandoned attempt before retrying, preserves primary I/O failures, accepts only the proven already-closed close case, and keeps every non-`EBADF` error or repeated descriptor failure fatal.
- Discord Gateway ingestion now decodes text and binary WebSocket payloads, resolves a thread's missing parent-channel metadata through Discord's REST API, and stops reconnecting after terminal authentication, sharding, version, or intent close codes.

## [0.12.17] - 2026-08-08

## [0.12.16] - 2026-08-08

### Added

- Added provider-agnostic model presets that resolve bare model-family aliases across authenticated bundled and custom providers while preserving concrete provider/model identity, session stickiness, and exact provider pins. Settings now includes a durable global Provider Priority Order editor, and the preset catalog includes a curated Open Weight Models group for GLM, DeepSeek, Kimi, Luna, and mixed-role combinations.
- `gjc gc` now reports managed session scope capacity when a scope is at or past 75% of the managed byte budget. A scope is snapshotted in full on every session start and fails closed once it exceeds the budget, but it is filled by GJC's own session records, so a working directory in sustained use can cross the limit with no prior signal — the first symptom is a launch that aborts. The probe is read-only and never fails a gc run: an absent, unreadable, or non-directory scope is reported as `unavailable`, an unreadable subtree is skipped so a partial walk still answers "am I near the budget?", and scopes below the threshold are omitted entirely so existing output is unchanged. `gc` still reclaims nothing here; the report names the scope path so stale session directories can be moved out by hand.
- `--clipboard-transport <auto|native|osc52|ssh>` and `--clipboard-ssh-host <alias>` CLI flags, plus persisted `clipboard.transport` / `clipboard.sshHost` settings (CLI overrides config, config overrides the `auto` default). `ssh` mode routes text copy/paste through `ssh -o BatchMode=yes -o ConnectTimeout=3 -- <host> pbcopy/pbpaste` via argv spawn (never a shell string) with a 5-second hard timeout covering the whole operation lifecycle, a 1 MiB payload cap enforced during streaming (never fully buffered before the check), fatal UTF-8 decoding on the inbound side (invalid bytes are rejected, not silently normalized to U+FFFD), and outbound NUL-byte/unpaired-surrogate rejection. Explicit `ssh` failures (nonzero exit, timeout, invalid host, oversize/invalid payload) surface a sanitized error and never silently fall back to native clipboard or OSC 52 — clipboard payloads are never written to logs or artifacts. `auto`/`native`/`osc52` behave exactly as before. New `app.clipboard.pasteText` action (no default key; command palette only, so it never collides with the existing image-paste binding) reads the configured `ssh` clipboard and inserts it at the cursor.
- `gjc sdk session` is the new broker-bound CLI for operating live SDK sessions: `list`, `inspect`, `send`, `status`, and `tail` plus the explicit `raw control|query|global` hatch. Every semantic verb resolves sessions through the SDK broker (validated endpoint identity, credentials used and never printed), output is a versioned credential-free row DTO, `send --wait` polls `turn.prompt_status` to a terminal state without ever cancelling, `tail` replays retained history from the durable checkpoint with `--strict` failing closed on `retention_gap`, and `--until-idle`/`--all-events`/`--cursor` bound and widen the follow. The removed `gjc daemon session` route is not aliased; the new surface is documented in `docs/sdk-session-cli.md` and referenced from `docs/sdk.md`.

### Changed

- Edit cards now keep live calls concise when collapsed and show per-file results, diff statistics, diagnostics availability, and full details consistently when expanded.

### Fixed
- SDK turn-result reconciliation now has one canonical content-bearing query with durable, UTF-8-safe bounded (16 KiB) terminal content. Prompt and skill terminal results remain correlated to their exact invocation and survive restart without retaining process-local revisions or cursors. Cursored continuation was removed as unreachable: the bounded content cap is well under both the response ceiling and the page target, so no revision is ever created; cursors are now rejected upfront instead of advertising a dead continuation path. Legacy status aliases delegate to the same authority, while provider query IDs and adapter dispositions remain unchanged.

- SDK snapshot shutdown now reserves a revision write synchronously before any asynchronous serialization work, so an immediate `close()` cannot begin terminal cleanup ahead of an in-progress spill write under heavily contended CI scheduling. The coordinator owner-intent negative matrix also receives the same 30-second contention budget used by other filesystem-backed integration cases; its assertions and four-case coverage are unchanged.
- Provider-agnostic preset ranking now uses AuthStorage's effective selectable credential type. Unusable stored command keys no longer misclassify OAuth-backed providers, and session-selected OAuth, fail-closed selectors, blocked/expired OAuth retry, and `openai-codex-device` aliases now follow the same precedence and normalization as request dispatch.
- Customizing one assignment in an active provider-agnostic model profile now concretizes every remaining bare profile alias before clearing profile ownership, matching profile deletion and preventing untouched roles from losing their equivalent-provider resolution semantics.
- Opening or refreshing the `/model` profile preview no longer passes the live canonical session ID while resolving role badges, so display-only alias resolution cannot overwrite the default model's provider affinity; credential-session ranking remains intact.
- SDK/ACP Q10 synthetic preset rows now resolve provider-agnostic defaults with the same authenticated equivalent-provider fallback as activation while using a read-only canonical scope; custom preset snapshots likewise keep credential context without mutating live provider affinity.
- Bare model selectors in custom profiles now distinguish configuration from authentication across both scalar and fallback-array forms: typos report unknown catalog models, while known aliases whose entire fallback array lacks usable credentials remain recoverable authentication errors with login guidance.
- Command-palette cycle availability checks and role metadata lookups no longer let read-only resolution mutate an active profile's canonical provider affinity; actual user-initiated cycling and fallback transitions still use the live canonical scope. Bare profile aliases now clamp explicit thinking suffixes against the authenticated concrete model while preserving the alias itself.
- Profile customization now snapshots canonical affinity before alias concretization and restores it if any later persistence step fails. Colon-bearing bare aliases keep their full route identity through equivalent-provider auth retry and diagnostics. Subagents inherit the parent credential-selection session separately from their child-owned provider transport/cache identity, aligning preflight ranking with request dispatch without sharing mutable provider state.
- SDK/ACP synthetic profile availability now validates every bare default and role assignment through authenticated preset-equivalent resolution, so partially available combined profiles are not advertised as selectable. Partial profiles grant equivalent-provider alias intent only to default, role, and role-agent assignments they actually own across task dispatch, cycling, compaction, resume, and fallback; pre-existing manual assignments remain exact.
- Credential-session identity is now propagated through profile catalog availability, startup/default resolution, metadata account UUIDs, temporary model preflight, auth-error invalidation, quota/auth rotation, compaction provenance, and `/btw` turns while provider transport/cache identity remains separate. Failed profile activation also restores the prior persisted resume-default selector or explicit absence.
- Profile activation/materialization, memory maintenance, interactive selectors, slash-command assignment, model-profile previews, and SDK extension contexts now use the same explicit credential identity as request dispatch; canonical session IDs remain reserved for sticky routing and provider transport/cache identities remain isolated.
- Task subagents and branch commit-message generation now inherit the parent's credential identity separately from logical/canonical state. Extension model preflight, custom tools (including image generation and web search), title/prompt maintenance, background ephemeral metadata, and profile materialization use the same credential scope. Preset landing auth now requires usable effective credentials rather than catalog presence alone.
- SDK tool sessions, deferred SDK selectors, slash-command aliases, memory role resolution, and provider-order status now carry credential identity. Provider alias invalidation updates canonical storage/backoff/session state, and preset landing availability is derived from actual credential-scoped `getApiKey` results (including keyless providers) rather than credential-type metadata.
- Prompt suggestions now remain visible after cancelling another interactive action and returning to the composer.

- `todo_write` and `ask` no longer reject valid calls before the tool loads. Both tools carried two independent copies of their raw-argument rules — one in the loaded tool, one in the cold descriptor registry that runs first — and the deferred copies had drifted: `todo_write`'s dropped the `content` synonym for `task` and the `complete`/`completed` aliases for `done`, accepted targetless `complete` entries the loaded tool rejects, and returned every rejection without its correction code, so the model saw a bare "raw arguments rejected before coercion" with nothing to fix and retried the same shape until the turn died. Both also rejected the harness's own injected `_i` intent field, failing any call carrying it with an unknown-root-key error the model could not repair. `todo_write` validation now lives in a single shared contract module (`tools/todo-contract.ts`) used by both paths, and both tools tolerate `_i` at the root while still rejecting genuinely unknown keys.
- A stalled ACP session is no longer unrecoverable. The SDK host drops a session whose client has not ponged within `HEARTBEAT_TTL_MS` (20s), but the ACP client inherited the transport's one-shot reconnect defaults — 3 attempts at a 25ms base backoff, a total budget of 175ms — so any event-loop stall long enough for the host to reap the session exceeded the client's entire retry window by two orders of magnitude and surfaced as a terminal `-32603 ACP session transport was lost: SDK WebSocket reconnect attempts exhausted`. Under machine load this killed long-running agent sessions outright while their processes stayed alive. The ACP adapter and the broker connection now share an explicit `ACP_SESSION_RECONNECT` budget derived from `HEARTBEAT_TTL_MS` rather than a magic number: backoff ramps 250ms → 500ms → 1s and holds at a 2s cap for 23 attempts (~41.75s), so the client outlives twice the host TTL while individual sleeps stay short enough to reattach promptly once the host answers again.
- Managed scope prepare and legacy-local resume no longer report success while group/other-readable descendants remain on disk (e.g. mode `0o036`/`0o644`): prepare uses a mode-only walk (not `snapshotManagedTree("")`, which races concurrent writers and broke `/move`) to detect drift, re-secures with apply+verify, and retries once; legacy-local capture uses the same resecure helper.
- ACP `session/request_permission` responses are now normalized from the spec-shaped `RequestPermissionResponse` (`{ outcome: { outcome, optionId } }`) into the SDK's flat permission-decision contract before reaching the permission provider. Standards-compliant ACP clients such as Paseo can now authorize permission-gated shell/eval and destructive file operations (`bash`, `monitor`, `eval`, `delete`, `move`, and `edit` only for delete/move operations) without an invalid-response failure; `write` and ordinary edits remain ungated. Nested and flat selected/cancelled responses are accepted by reconstructing the canonical SDK decision fields, while malformed or unknown decisions fail closed.
- `bun run restart:sdk-broker --close-session-hosts` no longer aborts the whole restart when one session host refuses to close. A host whose endpoint is unreachable and whose durable process identity cannot be verified — an orphan left behind by an earlier broker — made the broker reply `close_refused`, which surfaced as an uncaught `SdkClientError`, so the broker was never replaced and kept serving the source it started with: precisely the stale-code failure the flag exists to prevent, and worse than the broker-only restart because part of the teardown had already run. Every listed host is now attempted, the replacement broker starts regardless, and surviving hosts are named with their failure reason on stderr with a non-zero exit so a partial teardown is never reported as a clean one.
- An ACP turn can no longer outlive its own end and wedge the session. A prompt whose terminal frame carried no normalized outcome — what an SDK-side `terminal_uncertain` closure produces when agent-owned async work (subagents, IRC deliveries) outlives the turn — was rejected without ever publishing the matching `session_info_update`, so the client stayed in the `working` phase forever. `session/cancel` had the same open end: `turn.abort` was acknowledged but nothing settled the pending `session/prompt` when the aborted run never published a terminal, leaving the waiter pending, the composer spinning, and every later prompt refused with `conflict` (surfacing in Paseo as `cancelAgentRun: acknowledged turn still active after timeout` followed by a permanent `A foreground turn is already active`). Every prompt rejection now releases the running phase, and an acknowledged cancel settles as ACP's mandated `cancelled` stop reason once a bounded grace expires, with a real terminal still winning inside that grace.
- Intent tracing (`_i`) is no longer disabled on every headless surface. It was gated on `hasUI`, so an ACP, print-mode, or SDK-embedded top-level session ran a measurably different turn than the TUI on identical settings: the `_i` guidance line was dropped from the system prompt and the field was stripped from every tool schema, removing the model's pre-call intent statement and leaving ACP `tool_call.title` permanently falling back to `tool: <path>`. The original intent of the gate was to spare *sub-agents* the per-call token cost, so the omission now keys on canonical sub-sessions (`taskDepth`/`parentTaskPrefix`/`currentAgentType`) instead of surface shape. `tools.intentTracing` and `PI_INTENT_TRACING` are unchanged and still authoritative. **Breaking (SDK):** `resolveIntentTracingEnabled`'s second parameter is now `{ subSession: boolean }` instead of `hasUI: boolean`; a boolean is rejected at compile time rather than silently inverting behavior.
- A 61MB `bun build --compile` intermediate (`packages/coding-agent/.18c95f9fdbe9bff8-00000000.bun-build`) had been committed to `dev`, and five more sat untracked in the worktree. `bun build --compile` writes these hidden temporaries next to the entrypoint and leaves them behind when interrupted, and no ignore rule covered them. `*.bun-build` is now ignored and the committed copy is untracked.

### Added

- The SDK `models.list/current` (Q10) catalog now lists model profiles as synthetic `gajae-code/<profile>` entries (e.g. `gajae-code/codex-eco`), and selecting one through `model.set` (or the ACP Model picker) activates the profile for the live session only; global persistence remains the explicit `gjc --mpreset <name> --default` or TUI default-selection path. ACP/SDK clients such as Paseo can therefore offer presets like ordinary models; only availability-filtered profiles are advertised, the reserved `gajae-code` namespace fails closed on collision, and `config.patch` serializes with profile activation through the session admission boundary.
- Added first-class `cline-pass` and `commandcode-goat` provider presets with documented API endpoints, environment-variable credentials, non-hardcoded live model discovery from models.dev and the Command Code Provider API, and prefix-based Claude routing.
- Registered the `jetbrains-junie` provider in the famous-provider ordering and the `JUNIE_API_KEY` credential help so JetBrains AI (Junie) Claude models surface in `/model`, `--list-models` and `gjc --help` alongside the other first-class providers (#3626).
- Added lease-backed MCP connection pooling with typed recovery, shared HTTP/SSE sessions, per-lease callback demultiplexing, and authorization binding scopes that keep credential secrets out of pool keys.
- Added plugin registry v2 as the single execution authority for plugin tools, subskills, and prompt appendices, with digest verification at final use.
- Added module-trace and process-tree RSS verification harnesses for startup-memory regressions.
- Added `bun run clean` / `bun run clean:native` (`scripts/clean.ts`) to remove build output — `dist/`, `binaries/`, `coverage/`, stray `*.bun-build`, `*.tsbuildinfo`, and with `--native` compiled `.node` addons. Sources, `node_modules/`, `.gjc/` runtime state, and `artifacts/` test evidence are refused as targets rather than silently skipped, and `--dry-run` lists targets without deleting.
- Workflow settings (ralplan `maxIterations`/`autoHandoff`/`maxReviewPassesPerLane`, deep-interview `ambiguityThreshold`, ultragoal `nudgeBudget`) now resolve through one shared resolver: project `.gjc/config.yml` → user `<agentDir>/config.yml` → built-in default. `config.yml` is the ONLY settings surface — the legacy `settings.json` files' workflow keys are retired: the config-root `~/.gjc/settings.json` is migrated once into the default global agent `config.yml` and the source removed, while the project `.gjc/settings.json` is retained for its non-workflow settings (only its workflow keys are migrated into project `.gjc/config.yml` and no longer read). `config.yml` values previously ignored by the workflow runtimes now take effect (`gjc config set gjc.ralplan.maxIterations 7` is honored by ralplan); project configuration beats user configuration, fixing deep-interview's former user-YAML-first inversion.
- Codex GPT-5.6 OAuth context-window overrides are honored instead of silently re-clamped: explicit `modelOverrides.<id>.contextWindow` values survive the bundled 372K cap when positive and finite, non-positive values are ignored with a warning, and the precedence/fallback contract is codified in the Codex context-cap policy (#3987).

### Changed
- `edit.mode` now defaults to `auto`: the edit protocol is routed by the active model's family (GPT/Codex → `apply_patch`; Claude/DeepSeek/Qwen → `replace`; MiniMax/GLM/Kimi/Moonshot and unknown models → `hashline`), independent of which provider serves the model. Resolution precedence is `GJC_EDIT_VARIANT`/`PI_EDIT_VARIANT` env force, then a matching `edit.modelVariants` rule, then explicit non-`auto` `edit.mode`, then a model-catalog edit recommendation when the session exposes one, then the built-in family mapping, then the `hashline` fallback. Invalid forced env values fail fast and a matched-but-invalid `edit.modelVariants` value fails closed with a diagnostic instead of silently falling through. Persisted explicit `edit.mode` values and the `atom` → `hashline` migration are unchanged; `resolveEditModeDetails()` exposes the resolved mode with provenance (source, model, family, matched rule).
- Because `read`/`search` emit hash-line anchors and default line numbers only in `hashline` mode, `edit.mode: auto` now routes those display surfaces with the edit protocol: GPT/Codex (`apply_patch`) and Claude/DeepSeek/Qwen (`replace`) sessions no longer show hash anchors or line numbers in `read`/`search` output unless `readLineNumbers: true` is configured, while MiniMax/GLM/Kimi/Moonshot and unknown models keep the hashline display. `GJC_EDIT_VARIANT=hashline` restores the previous hashline read/search display exactly.

- Workflow skills are no longer implicitly auto-routed. The UserPromptSubmit keyword autoroute now matches only explicit `$`-prefixed tokens (`$deep-interview`, `$ralplan`, `$ultragoal`, `$team`); natural-language phrases ("don't assume", "consensus plan", "interview me", "coordinated team") and bare skill names no longer activate workflows. The system prompt now ranks explicit user intent above every routing heuristic, forbids workflow self-invocation and plan stacking, bans task-difficulty overestimation, directs the agent to offer heuristic workflow escalation (especially deep-interview for vague requirements) through the `ask` tool with an opt-out, and adds an `<engineering>` principles section.
- Registered `gjc.ultragoal.nudgeBudget` in the public settings schema (default 10, non-negative integer).
- The legacy config-root `~/.gjc/settings.json` workflow keys are migrated once into the environment-selected global agent profile's `config.yml` — `GJC_CODING_AGENT_DIR` / `PI_CODING_AGENT_DIR` when set, else the default `~/.gjc/agent/config.yml` — on the next global-scope load (absent-only, atomic marker, no-clobber `.bak`). Invalid strict ralplan legacy values keep the source active so `gjc ralplan` still fails loudly (exit 2); future-schema `config.yml` targets are never touched.
- ralplan settings are strict for all three keys: malformed or invalid explicit settings in any layer/format exit 2 (the former silent `maxIterations` fallback is removed). An invalid strict ralplan value in the target `config.yml` is repaired with a valid legacy value during migration.
- `config.yml` settings use the nested schema form; flat dotted keys are honored only in legacy `settings.json` files so every effective override stays manageable via `Settings`/`gjc config`.
- A strict ralplan key in a retained project `settings.json` that is shadowed by a valid project `config.yml` value is recorded as migrated (owned), so a later `gjc config unset` of that value falls through to the default layer instead of resurrecting the invalid legacy value (exit 2).
- Workflow commands inside an SDK session created with a custom `agentDir` (`createAgentSession({ agentDir })`) resolve the user/agent settings layer from that profile instead of the process-global default profile.
- Deferred notification adapters, native bindings, provider construction, tools, skills, eval, session artifacts, and history storage until their feature paths are used, reducing the CLI startup module graph without changing default behavior.

- Split SDK session hosting into a transport-neutral runtime and lazy notification adapters.
- `sticky-viewport-showcase.test.ts` runs in ~53s instead of ~192s, cutting the slowest CI test shard from ~369s to ~167s locally. Each of its 17 cases spawned a `bun` subprocess that re-rendered all 20 showcase frames (~9.4s per capture, ~187s of the file's ~192s) purely to obtain a pristine bundle it then corrupted. The bundle is now captured once and handed out as filesystem copies; the one case that must observe mutated `GJC_STICKY_VIEWPORT_ORACLE_COMMIT` state still captures for itself. Isolation is load-bearing and verified: sharing the directory instead of copying it fails 9 cases.

### Fixed
- Telegram topic archival no longer corrupts the shared topic registry when an active topic first enters disconnect grace. `beginArchive` changed `authorityState` to `archive_pending` but retained `disconnectGraceExpiresAt`, a field the persisted-state parser permits only for `disconnect_grace`; one closed or stale session therefore made the entire otherwise-valid registry unreadable, after which every daemon scan reported `shared topic authority unavailable` and no new session could create a thread or replay a message. Every transition out of disconnect grace now clears the grace-only deadline before persistence. `DAEMON_GENERATION` bumped to 57.

- Added dual execution/receipt terminal truth across SDK reconciliation, runtime sidecars, and coordinator turns. Empty terminal output now fails closed as `receipt_missing`, while version-1 records without additive receipt fields remain readable as `unknown`.
- Added retry-safe correlated `turn.steer` acknowledgement and Q30 `turn.steer_status`. GJC durably reserves a SHA-256 digest before dispatch, replays retained status without duplicate steering, and preserves uncertain delivery across restart without storing steer text.

- `notifications-topic-registry.test.ts` pins `DAEMON_GENERATION` at 54 after #3965 (was stale at 53; Dev CI run 31133356543).
- `smithery-env-trust.test.ts` no longer awaits hung probe pipes past a hard per-attempt deadline: minimal child env, `stdin: "ignore"`, SIGKILL + settled race, and up to 2 timeout-only retries. Fixes the inherited-config case that hit exactly 60001ms on Dev CI run 31133356543 after kill-at-45s left `Promise.all` on stdout/stderr unresolved. Assertions unchanged.
- A Telegram notification daemon that dies without a clean shutdown no longer keeps advertising itself as the ready owner. Ownership was surrendered only by `releaseDaemonOwnership`, which runs after a fully quiesced and fully persisted shutdown; an uncaught error, a failed final topic-registry persist, or a signal left `ownershipPhase: "ready"` and a matching ownership lock on disk, and every later reader attached to a process that no longer existed. Observed in the field: a daemon wrote one heartbeat 559 ms after readiness, died on an uncaught `shared topic authority unavailable` error, and was still recorded as ready eight hours and seventeen crashes later, with no notification delivered in between. The daemon-internal entrypoint now records `stoppedAt` on the way out — from its own `finally` and from a `postmortem` hook that also covers the fatal paths that call `process.exit()` without unwinding — fenced on full owner identity so a successor's state is never touched, and leaving the ownership lock for the existing reclaim path to adjudicate. `isFreshLiveOwner` already treats `stoppedAt` as disqualifying, so recovery no longer depends on a pid liveness check that a recycled pid can defeat.
- Under the default configuration (no explicit `retry.*` keys), message-only first-event stream timeouts — the wrapped `Error: Provider stream timed out while waiting for the first event`, the bare canonical form, and the per-provider `Anthropic stream timed out...` / `OpenAI responses stream timed out...` variants — were not retried once the run had observable activity (e.g. a prior tool execution in the same turn), so the turn surfaced the timeout instead of retrying like other provider non-critical errors. Content-free message-only watchdog prose is now admitted in the bare-default first-event retry gate like the typed path; visible content, conflicting structured facts, near-miss prose, extension-hook participation, Alibaba/Kimi terminal policy, and ollama-cloud bounded retry remain fail-closed.
- `smithery-env-trust.test.ts` warms the Bun probe child in `beforeAll` and kills stalled spawns at a 45s budget so the first case no longer absorbs cold-start compile cost into its per-test timeout under shard contention (observed 60001ms timeout after the 60s cap on #3969 exact-head CI). Assertions unchanged.
- `smithery-env-trust.test.ts` raises the per-test child-process timeout from 30s to 60s so CI contention cannot fail at the previous 30s cap (Dev CI run 31128319216 timed out at 30004ms).
- Headless coordinator `ralplan` approval gates no longer stall with `missing_runtime_turn` or reject a direct answer with `resource_gone`. Canonical subagents keep workflow-gate emitters local instead of replacing the endpoint owner, the coordinator projects the ask-shaped `ralplan/approval` contract, and provisional notification policy retains gates without publishing them until activation. Direct-control completion is fenced to the exact source generation and preserves exact terminal proof across replacement races.
- `smithery-env-trust.test.ts` now sets a 30s per-test timeout on all five child-process-spawning trust-boundary tests, preventing CI flake when the Bun child-process spawn + env-file-parse chain exceeds the default 5s budget under parallel shard contention (Dev CI run 31102063678).
- Fire-and-forget agent continuations (auto-compaction retries, queued follow-ups) racing a still-busy agent no longer spin on a fixed 100ms reschedule forever: they now back off exponentially (100ms doubling up to 5s) and give up after 50 attempts (~4 minutes) with an explicit warn, fixing a runaway loop observed as 10,742 reschedules over 21 minutes in a single session.
- A Telegram daemon no longer crashes when the shared topic authority is momentarily unavailable. The liveness heartbeat renewed a session's topic lease through a single-argument `.then()` whose rejection escaped to the process-level fatal handler (observed as repeated `Uncaught exception` / `Unhandled rejection: shared topic authority unavailable` deaths while the authority was down); it now reports the failure and retries on the next heartbeat. A startup topic-registry load failure is likewise reported and the daemon continues with an empty registry instead of dying before it starts serving. The four `catch { throw }` authority-failure sites now preserve the underlying error as `cause`, and the compensation fence stops retrying a failed persist every 250ms forever (it gives up after ~10s and lets the next scan/session pass retry), so a shutdown can quiesce instead of spinning. `DAEMON_GENERATION` bumped to 55.
- ACP `session/prompt` no longer hangs forever when the agent continues mid-prompt. Any continuation (todo reminder, TTSR resume, auto-continue) re-enters the agent loop and emits a second `agent_start`, which shifted the already-empty pending queue and overwrote the live prompt correlation with `undefined`. The prompt's `agent_end` then carried no correlation, `terminalizePrompt` never ran, and every ACP client (Paseo, Zed, JetBrains Air) waited until the 30-minute `sdk.promptDeadlineMs` before failing with `prompt_deadline_exceeded` — despite the answer having already streamed. `agent_start` now claims a pending correlation only when the session has no active one, and the todo-completion continuation holds the predecessor `agent_end` like every other continuation call site (#3949).
- ACP `session/prompt` no longer hangs forever when the agent continues mid-prompt. Any continuation (todo reminder, TTSR resume, auto-continue) re-enters the agent loop and emits a second `agent_start`, which shifted the already-empty pending queue and overwrote the live prompt correlation with `undefined`. The prompt's `agent_end` then carried no correlation, `terminalizePrompt` never ran, and every ACP client (Paseo, Zed, JetBrains Air) waited until the 30-minute `sdk.promptDeadlineMs` before failing with `prompt_deadline_exceeded` — despite the answer having already streamed. `agent_start` now claims a pending correlation only when the session has no active one, and the todo-completion continuation holds the predecessor `agent_end` like every other continuation call site (#3949).
- ACP skills, slash commands, and initial session state now actually reach the client. `session/new`, `session/resume`, and `session/fork` scheduled their bootstrap `session/update` notifications on a fixed 50 ms timer taken *before* the session-state queries ran, so on any host where those queries took longer the notifications overtook the response that carries the `sessionId` — naming a session the client had never seen, which clients drop. Measured against Paseo: `available_commands_update` arrived 27 ms *before* the `session/new` response, so the skill list was silently discarded. Bootstrap is now scheduled only once the response payload is ready, matching the ACP session-setup sequence, which permits updates before the response only for `session/load` (#3962).
- ACP clients can now render the user's own message and its image attachments. A live prompt turn emitted no `user_message_chunk` at all — that update only existed on the `session/load` replay path — so an attached image reached the model but never appeared in the client transcript. `session/prompt` now echoes the prompt's text and image content blocks as `user_message_chunk` before dispatching the turn (#3962).
- An oversize ACP prompt now fails with a typed, actionable error instead of killing the session. The SDK WebSocket server caps a request frame at 256 KiB (`REQUEST_FRAME_BYTES`) and answers an oversize frame with `CloseCode::Size`, which surfaced to clients as an opaque `-32603 connection_closed` mid-turn; a 244 KiB PNG reproduced it while a 59 KiB PNG succeeded. The prompt is now measured inside its real `control_request` envelope and rejected up front with `-32602` naming the actual and permitted size (#3962).
- ACP `session/cancel` and `session/close` now settle the pending `session/prompt` with `stopReason: "cancelled"` instead of surfacing a transport error. A cancel that landed during prompt preflight rejected with `busy`, and `session/close` rejected with `connection_closed`, both of which clients display as spurious errors for the user's own action. The spec requires agents to "catch these errors and return the semantically meaningful `cancelled` stop reason"; involuntary teardown still rejects (#3962).
- GJC no longer advertises `mcpCapabilities.sse`. The legacy MCP HTTP+SSE transport (spec 2024-11-05, deprecated) is not implemented: an `sse` config is routed to the Streamable HTTP transport, which never performs the required `endpoint`-event handshake, so a genuine legacy-SSE server offered by an ACP client could not connect. Locally configured `sse` entries still resolve through `createTransport` and keep working (#3962).
- Reverse ACP responses (`fs/read_text_file`, terminal output) are now measured as the full serialized frame rather than only the inner `result`. A near-limit result passed the 256 KiB check and then tripped the WebSocket total-frame ceiling, closing the whole session instead of raising the typed `payload_too_large` (#3962).
- `todo_write` now accepts the synonyms models actually emit instead of failing the tool call mid-turn: `complete`/`completed` for the `done` operation, and `content` for `task`. The operation sets a status spelled `completed` and stores the task as `content`, so the schema's own vocabulary invited both mistakes. The accepted key set and the requirement that a completion name a task or phase are unchanged (#3962).

- Managed session scope failures are no longer misreported as `binding_invalid`. An over-budget managed-tree snapshot now surfaces as `capacity_exceeded` carrying the native `content_too_large` message instead of pointing operators at a byte-for-byte canonical binding file, and `migration_busy` is preserved from every classification path rather than only one of the three.
## [0.12.15] - 2026-08-06

## [0.12.14] - 2026-08-06

## [0.12.13] - 2026-08-06

### Fixed
- ACP session configuration now emits the spec-defined `category` field on the Mode, Model, and Thinking select options (`mode`, `model`, `thought_level`), so standards-compliant ACP clients such as Paseo discover models, modes, and thinking levels instead of an empty model picker (#3922).
- The ACP session model catalog is now filtered to active providers via `providers.list/active`, falling back to the full catalog on older session hosts, so ACP clients no longer list models for providers without usable credentials (#3922).
- Workflow-gate asks (ralplan approval, deep-interview questions) now surface through the ACP permission channel when the client does not advertise ACP form elicitation, so plain ACP clients such as Paseo can answer selector gates; free-text asks remain unanswered and the richer `ui` channel stays preferred when advertised (#3925).
- ACP `session/prompt` no longer hangs forever when the agent continues mid-prompt. Any continuation (todo reminder, TTSR resume, auto-continue) re-enters the agent loop and emits a second `agent_start`, which shifted the already-empty pending queue and overwrote the live prompt correlation with `undefined`. The prompt's `agent_end` then carried no correlation, `terminalizePrompt` never ran, and every ACP client (Paseo, Zed, JetBrains Air) waited until the 30-minute `sdk.promptDeadlineMs` before failing with `prompt_deadline_exceeded` — despite the answer having already streamed. `agent_start` now claims a pending correlation only when the session has no active one, and the todo-completion continuation holds the predecessor `agent_end` like every other continuation call site.
- ACP clients can now render the user's own message and its image attachments. A live prompt turn emitted no `user_message_chunk` at all — that update only existed on the `session/load` replay path — so an attached image reached the model but never appeared in the client transcript. `session/prompt` now echoes the prompt's text and image content blocks as `user_message_chunk` before dispatching the turn.
- An oversize ACP prompt now fails with a typed, actionable error instead of killing the session. The SDK WebSocket server caps a request frame at 256 KiB (`REQUEST_FRAME_BYTES`) and answers an oversize frame with `CloseCode::Size`, which surfaced to clients as an opaque `-32603 connection_closed` mid-turn; a 244 KiB PNG reproduced it while a 59 KiB PNG succeeded. The prompt is now measured inside its real `control_request` envelope and rejected up front with `-32602` naming the actual and permitted size.
- ACP `session/cancel` and `session/close` now settle the pending `session/prompt` with `stopReason: "cancelled"` instead of surfacing a transport error. A cancel that landed during prompt preflight rejected with `busy`, and `session/close` rejected with `connection_closed`, both of which clients display as spurious errors for the user's own action. The spec requires agents to "catch these errors and return the semantically meaningful `cancelled` stop reason"; involuntary teardown still rejects.
- GJC no longer advertises `mcpCapabilities.sse`. The legacy MCP HTTP+SSE transport (spec 2024-11-05, deprecated) is not implemented: an `sse` config is routed to the Streamable HTTP transport, which never performs the required `endpoint`-event handshake, so a genuine legacy-SSE server offered by an ACP client could not connect. Locally configured `sse` entries still resolve through `createTransport` and keep working.
- Reverse ACP responses (`fs/read_text_file`, terminal output) are now measured as the full serialized frame rather than only the inner `result`. A near-limit result passed the 256 KiB check and then tripped the WebSocket total-frame ceiling, closing the whole session instead of raising the typed `payload_too_large`.
- `todo_write` now accepts `complete` and `completed` as aliases for the `done` operation. The status this operation sets is spelled `completed`, so models repeatedly emitted `op: "complete"` and hit a hard mid-turn tool failure; the operation vocabulary is otherwise unchanged and a completion still requires a task or phase target.

- `todo_write` now rejects malformed raw arguments with bounded, authority-controlled correction codes instead of a generic rejection: unknown root keys, unknown operation-entry keys, done/drop entries without a task or phase target, and unknown init list-entry keys each surface a fixed message naming the accepted shape without echoing the offending input, while recoverable payloads keep the passthrough/coercion path and the existing ask-tool codes are untouched (#3916).
- The Alibaba Token Plan onboarding preset and `alibaba-token-plan-qwen-deepseek` profile now reference the provider-supported `qwen3.8-max` model id instead of `qwen-3.8-max`, preventing the built-in profile from selecting an HTTP 400 unsupported model (#3909).
- Restored computer batch failure metadata, timeout handling, and coordinate bounds validation to match single-action dispatch.
- Preserved idempotent deletion of unknown SDK sessions and fixed concurrent `/notify on` startup after native loading became lazy.
- Kept deferred tool descriptors aligned with eager availability guards for headless asks, subagent checkpoints, IRC, GitHub, and cron.
- `/model` reasoning menu header now shows the highlighted reasoning level (not the model id), seeds the cursor from the role badge when re-editing the same model, and uses a provider-neutral label for `max` instead of "Opus maximum reasoning" (#3847).
- Resume listing now reverse-scans for buried but canonically valid `header_patch` titles, so a persisted manual title remains visible in the picker after later transcript growth instead of falling back to an empty/line-1 projection (#3633).
- Custom OpenAI-compatible models whose wire id is namespaced (for example `cline-pass/deepseek-v4-flash`) now inherit capability metadata from the bundled leaf model when `contextWindow` / `maxTokens` are omitted, instead of silently falling back to the generic 128K / 16K defaults. True unknown leaf ids still default; explicit limits remain authoritative (#3856).
- `gjc gc` file-lock discovery now budgets the walk **per lock root** and reports a hit entry cap as a **warning**, not a hard error. Truncating one root no longer skips the remaining roots, and a healthy run with only cap warnings exits `0` so scripts/cron/`&&` chains stay usable (#3852).

- `gjc models` is no longer treated as a free-form agent prompt. The mistaken subcommand spelling now routes to the existing `--list-models` listing path so a nested bash-tool invocation cannot recursively spawn unbounded GJC agents (#3857).
- Always-apply and rulebook rules are injected on the default system prompt path again. Discovery still loaded `.gjc/rules/`, `~/.gjc/agent/rules/`, and sticky `RULES.md`, but only `custom-system-prompt.md` rendered them, so normal sessions silently dropped the content while AGENTS.md in the same directory continued to work (#3859).
- Made Telegram reference-client capability diagnostics safe for TUI embedding.
- Custom `anthropic-messages` providers can now configure `compat.promptCacheMode` (`none`, `explicit`, or `automatic`) and `compat.supportsLongCacheRetention` at provider, model, and model-override levels. Canonical Anthropic defaults to automatic caching, while non-canonical Claude-family endpoints default to gateway-safe explicit block markers and can opt into top-level automatic caching when supported.
- A Telegram notification daemon whose reconciliation pass fails no longer exits. The pass persists through the shared topic authority, and a momentarily unavailable authority (lock contention or a rejected compare-and-set) rejected out of both the scan timer and the run loop into the process-level fatal handler, killing the owner. Every session topic was then left behind as an unarchived shell that answers nothing — including for sessions that were still live and lost their notifications. The pass now reports the failure and the next scan interval retries it; the queue-flush timer is guarded the same way.
- MiniMax M3 preset and profile ids canonicalized to `MiniMax-M3` (issue #3896): the `minimax` / `minimax-cn` onboarding presets and the `minimax-eco` / `minimax-medium` / `minimax-pro` builtin model profiles no longer reference the removed lowercase `minimax-m3` / `minimax-v3` first-class catalog ids.
- Deep-interview round identity and input caps are now Unicode-canonical. Question text, selected options, and custom input are canonicalized to NFC before hashing and persisting, so the same Korean answer arriving in decomposed form (macOS-sourced pastes and some IME/clipboard paths emit NFD) no longer produces a second `answer_hash` — the documented append-or-merge no-op holds, and intent-review approval evidence still matches the user's recorded answer. Free-text caps are measured on the NFC form, so decomposed Hangul is charged the same character budget as the identical composed text instead of 2–3 code points per syllable (#3871).
- Telegram notifications no longer disappear in a paired private chat whose bot has no Threaded Mode. Telegram answers `createForumTopic` there with `Bad Request: the chat is not a forum`, which was not recognized as a capability refusal, and the refusal verdict lived in caller-local flags — so every frame that joined the shared in-flight topic creation rethrew and its message (identity headers after `/resume`, asks, context updates) was dropped instead of being delivered flat. The rejection is now carried by typed errors that every awaiter of the same creation classifies identically, `the chat is not a forum` counts as a capability refusal, and a confirmed refusal is latched so later frames stop re-issuing a rejected `createForumTopic` per message.
- Slash commands now expand in non-interactive runs. `gjc -p "/init"` previously reached the model as the literal text `/init`, so no command body was injected, no file was written, and the model still answered as if the command had run. Print mode now loads the same bundled and file-based command list interactive mode uses before it prompts.
- `gjc plugin install <name>` now names the marketplaces that offer `<name>` when the npm resolution it falls back to fails, so a plugin name copied out of `gjc plugin discover` no longer dead-ends on a bare `install_failed`.
- A remote multi-select ask now shows what is already selected. The ask tool re-issues one remote request per toggle, but the request carried no selection state, so Telegram kept posting an identical prompt with no sign that option 1 had been picked — the checkbox rendering existed only for durable workflow gates. `AskAnswerRequest` now carries `multi` and the selected option labels, the notification bus publishes them as `selectedOptionIndices` with the `(N selected)` question prefix while keeping the ask tool's own Next/Done control, and pre-numbered options (deep interview) are renumbered once instead of rendering as `1. ☑ 1. …`.

## [0.12.12] - 2026-08-05

### Added

- Interactive turns now announce their state as an OSC 777 sequence (`notify;Terax;gjc;working|attention|finished`), so a hosting terminal can follow the agent without polling. Terminals that do not parse it discard it like any unknown OSC, and print/RPC mode stdout is untouched.
- Existing Slack threads can now be adopted through an opt-in prepare → bind → activate lifecycle. A prepared session (broker `readiness: "deferred"` / `gjc_coordinator_start_session` with `prepare_existing_thread`, or `GJC_NOTIFY_BIND_EXISTING_THREAD=1` for a manually started one) publishes discoverable endpoint authority while withholding readiness, so no stock root is claimed; `gjc notify bind-thread` then adopts the exact operator-supplied root through the running Slack daemon owner over a new per-request command channel — the CLI is never a direct mapping writer — and `gjc notify activate-thread` / `gjc_coordinator_activate_session` publishes the withheld readiness exactly once behind a gate that proves the daemon-owned mapping at that endpoint generation. Adoption posts zero replacement roots, is fenced on the exact session/generation/daemon-owner tuple, and is idempotent on exact retry. Every mapping mutation passes a two-sided final authority fence inside the store lock — authority is proven, commit authority is taken, and authority is proven again — so a session, endpoint generation, or daemon-owner tuple that rolls while commit authority is being taken leaves no mapping behind. `ChatDaemonRuntime` now drops internal control-plane frames (`session_prepared`, `event_replay_result`, `control_response`, `query_response`, `hello`) before any post, root, mapping, resume, close, or action mutation on both the live and replay paths, and correlates each delivered frame first: an event envelope, its `name`/`kind` aliases, and its payload are representations of one event, so disagreeing spellings, a duplicated `sessionId`/`generation` that is unequal or present-but-malformed, and a reserved lifecycle or control-plane identity carried on only one representation all make the frame inert before any mutation. Because the command channel proves correlation and never authorship, a reported `status:"ok"` that the durable conversation store does not corroborate is reported as `binding_outcome_unknown` instead of success; a storage failure raised after commit authority was granted — the mapping rename applies before its durability barrier — is reported as `binding_outcome_unknown` rather than as a definitive rejection an operator could retry against; and a timeout that wins the single-winner response claim is a definitive, mutation-free failure for that submission's race against a concurrent serve (not durable settlement across a crashed and resurrected daemon). Ordinary sessions keep the stock immediate ready/root behaviour.

### Added

- Added a verified, copy-installable `ooo` bridge example: `ooo interview` renders Ouroboros MCP questions in GJC, serializes startup and follow-up answers by session ID, cancellation-fences late settlement, disposes state on GJC session changes and `/clear`, drops queued predecessor-generation starts, releases dead transports and controls, honors `OUROBOROS_CLI`, and loads dependency-free in compiled binaries (#3803).
- Added a scheduled and manually dispatchable nightly deployment cycle that verifies the full `main` graph, stages one immutable source-bound prerelease version across npm/Cargo/native surfaces, publishes the complete package set under the npm `nightly` dist-tag without moving `latest`, and creates a matching GitHub prerelease with binaries and closed package evidence.

- `gjc update` now accepts `--channel stable|nightly` to switch release channels in place, and a new **Update Channel** settings entry (`startup.updateChannel`, stable by default) picks the default channel for both `gjc update` and the interactive startup update check. Channel resolution maps to the npm `latest`/`nightly` dist-tags without ever pointing nightly at `latest`, version comparison now orders nightly prereleases with real semver semantics instead of NaN-falling into a forced reinstall, and installed-version verification recognizes prerelease version strings reported by nightly binaries.

### Fixed

- Fixed deep-interview prompts exposing the literal argument placeholder.

- Automatic session retry now refuses to re-issue a request once the failed attempt carries observable assistant text, thinking, or tool-call content — including under explicit legacy `retry.*` settings. Content-free clean failures keep their existing bounded/unbounded policy; managed provisional discard, credential rotation, first-event timeout scope checks, and manual `/retry` are unchanged (#3791).
- Resuming a session whose transcript file is at or above the ~64 MiB managed-storage per-file bound no longer OOMs, stalls the process, or fails with a bare unhandled rejection. Resume/open now fail closed with a structured `oversized` reason (`SessionTranscriptOversizedError`) and recovery guidance before the full read/decode/parse path, instead of loading the entire file into memory. The bound matches the existing managed-artifact per-file limit and is not raised; sub-limit sessions resume unchanged (#3851).

- Parent sessions and their subagent trees now share one identity-authorized artifact manager across persistent and ephemeral operation. Non-persistent roots are retired on committed session transitions and terminal close, failed transitions retain predecessor ownership, and atomic numeric-ID claims prevent same-root managers from creating ambiguous artifact references (#3813).

- Telegram daemon restart now revokes every persisted callback alias before polling. Reconnecting sessions must replay a pending ask to receive fresh, owner-bound aliases; old controls remain stale, and their keyboards are best-effort terminalized when the original Telegram message id is available. Shutdown now fences new session messages and drains every admitted handler before final callback persistence and ownership release, preventing a successful send racing shutdown from publishing alias state after a successor takes ownership (#3727).
- Extension handler timeout signals now preserve lazy, live context accessors instead of eagerly snapshotting them. Model changes made through SDK controls are immediately visible to later context reads, and unused getters can no longer reject lifecycle emission before the runner's extension error boundary (#3817).
- Direct interactive launches inside tmux now bind automatic window renames to the originating pane's immutable pane/window identities and observed window index. If that binding changes before mutation, GJC preserves every window name instead of renaming whichever window became active (#3808).
- `gjc update` and the startup version check now resolve the npm registry from npm configuration — `npm_config_registry` or `BUN_CONFIG_REGISTRY` from the environment, a scoped `@scope:registry` key, and the user and machine-wide `.npmrc` files, including the credentials registered for that registry — instead of always querying `registry.npmjs.org`. On networks that mirror or block the public registry the check failed with an empty `Failed to fetch release info:` even though the install step, which already shells out to bun/npm and therefore honored the configured registry, would have succeeded. Repository-controlled configuration is excluded: a `.npmrc` in the current working directory is not read, the environment is read through `$credentialEnv`, and `npm_config_*` is ignored entirely when GJC is launched by an npm lifecycle, because npm synthesizes those variables from the project `.npmrc` with `${VAR}` already expanded. Credentials are never sent to a remote plaintext `http:` registry, including credentials embedded in the registry URL. Whichever manager will run the install decides Bun-vs-npm config priority, the machine-wide config path is derived from npm's own prefix rather than guessed, and keys inside an ini `[section]` are not treated as top-level config. A registry that is configured but unusable, and a config file that exists but cannot be read, fail loudly instead of silently falling back to the public registry; failures name the URL, the status, and the exact file or environment variable the registry came from — including the case where an intercepting proxy answers 200 with a non-JSON body — and credentials are stripped from a `https://user:pass@host` registry rather than printed with it. `bunfig.toml` is not read. (#3821)

### Fixed

- Telegram `notify setup` activation works when `notifications/` is a directory symlink (multi-account shared notification dirs). Transition-lock release previously rejected intermediate directory reparse points, left `telegram-daemon.steal` behind, and failed with "provisional ownership could not be retired safely" while durable settings remained armed. Only intermediate directory components are resolved before native exact unlink; the final basename is rejoined so final-component file symlinks stay `reparse_point` under native `AT_SYMLINK_NOFOLLOW`, including TOCTOU replacement after JS preflight (#3761).
- A failed `notify setup` no longer reports "Unable to persist and activate Telegram notification settings" when the durable configuration already carries the attempted bot token, chat id, and enabled state. The wording now follows the stored configuration, so it can no longer contradict a follow-up `notify status`; an operator who reads the failure as "nothing was saved" would otherwise leave Telegram armed for a token another poller may own. A commit that was entered and then failed while the stored configuration is also unreadable is reported as undecided, pointing at `notify status`, instead of guessing either outcome (#3761).
- Continuing a large managed session on Darwin now batches stale OpenAI Responses replay-metadata patches into one transcript append instead of performing one identity-verified whole-file replacement per patch. Interactive startup also renders before exact MCP connection and explicit `--mpreset` activation, gates every provider turn until both are ready, and refreshes models online only after the UI is usable, preventing `gjc -c` from remaining at `GJC warming workspace` with sustained CPU, multi-gigabyte RSS growth, or avoidable network waits (#3793).
- Slack Web API requests now use form encoding instead of JSON, preventing thread reconciliation through `conversations.replies` from failing with `invalid_arguments`.

- Managed replacement cleanup now migrates version-one receipts from earlier releases and recovers canonical exchange placeholders left by interrupted cleanup, so a stale receipt cannot permanently block the next managed session mutation with `managed_replace_cleanup_receipt_invalid`.

## [0.12.11] - 2026-08-03

### Fixed

- Side-effecting native macOS computer input now restores the global cursor after releasing held input on success, cancellation, supervisor rejection, and action failure. Batches containing input execute in one serialized native capture-to-restore transaction, while screenshot/wait-only operations remain cursor-neutral; capture/restore failures are reported distinctly without masking the primary action error, and global focus behavior remains unchanged (#3642, #3781).
- Managed session rewrites and authority-absent Darwin appends now use native identity-verified atomic replacement instead of deterministically failing with `managed_replace_exact_unavailable` or risking a torn JSONL tail; uncertain readable outcomes are re-fsynced and carried through ctime-bound strict adoption before recovery reports them durable. This unblocks Darwin compaction and session append durability when retained native authority is absent (#3742, #3760).
- `todo_write` recovery reminders now distinguish rejected payloads from runtime aborts, preserve the available cause, and require durable state reconciliation instead of incorrectly telling the agent to change a valid payload (#3743, #3760).
- Authority-absent Darwin `appendSync` regression coverage now exercises the replace-based race window (destination mutation during successor staging) instead of the retired in-place `O_APPEND` open path, and documents that ctime-only destination transitions are tolerated by exact replacement.
- The legacy interactive footer now uses the session manager's cumulative usage index, so completed task and subagent tokens, premium requests, and estimated costs are included exactly once instead of reporting only the parent agent's assistant messages.

## [0.12.10] - 2026-08-03

### Added

- `/login anthropic --manual` pairs by pasting the authorization code Anthropic shows in the browser instead of waiting on a `localhost:54545` callback the browser cannot reach. Complete it with `/login <code>`. Use it when gjc runs over SSH, in a container, or on a headless host; the default `/login anthropic` still uses the loopback callback. The flag is resolved before the paste fallback, so it is never mistaken for an authorization code, and providers without a paste-a-code redirect reject it explicitly instead of silently falling back.

### Fixed

- Composer Bash policy rejections now identify the active provider surface and direct Cursor Composer models to their native repository tools, enabling the agent runtime's bounded automatic recovery instead of leaving a blocked shell attempt as a terminal turn.
- The `AgentSession retry fallback > invalidates an auth-failed managed credential` test now uses a stored credential instead of a runtime-key override, matching the pin-guard behavior added in #3724 where `--api-key`/`--credential` pinned keys are never invalidated; the shared test fixture installed runtime keys as plumbing, which silently tripped the new guard and blocked the auth invalidation path.
- The Extension Control Center inspector no longer crashes when a narrow two-column layout leaves its preview pane fewer than two columns wide.
- Prompt-template positional arguments now preserve literal `$@` and `$ARGUMENTS` text instead of recursively expanding it during placeholder substitution.
- Native Windows session and GC commands now report the searched `psmux` / `pmux` / `tmux` provider set when no compatible multiplexer is available instead of leaking a literal `tmux` spawn error (#3688).
- Managed-session recovery now preserves committed mutation state and the actual Linux fallback primitive in native publish receipts, preventing unsafe retry classification after a post-link staging unlink failure (#3746).
- The system prompt now requires non-ASCII tool-input text to be written as literal UTF-8 rather than hand-spelled `\uXXXX` escapes, including JSON serialized into a string field, while leaving escapes that are intended source syntax alone. Models that hand-spell hex codepoints for CJK mis-type them, and each mis-typed escape decodes to a valid-but-wrong syllable, so Korean text in tool parameters silently arrives corrupted (anthropics/claude-code#83033).
- The `models.yml` validation error for a custom provider without a credential source now explains that `auth` selects only the auth scheme and lists the three corrective forms (`apiKeyEnv`, literal `apiKey`, or `auth: none`) instead of restating the rule that was already misread. `docs/models.md` documents the same contract in a table (#3738).
- Credential rotation no longer mutates a pinned credential, and no longer reports a rotation that did not happen. `#markFailedCredential` now applies its pin guard first and for every trigger class, consulting both the `--api-key` runtime override and the `--credential` runtime selector — previously the guard existed only on the quota path and checked only the API-key override, so a `--credential` pin could be rotated away from and the `auth` path could invalidate a pinned credential outright. Both paths now also require the re-resolved credential to actually differ before reporting a rotation: `invalidateCredentialMatching` reports that a row was matched and blocked, which is not the same as the session having moved to a different credential, and with a single-row pool it was true while nothing rotated. A terminal `forbidden` failure is now excluded from retry admission and makes no credential-state change at all. Finally, a rotation is only converted into a same-model retry when the fallback controller could actually be rewound; `restorePreviousEntryForRetry()` refuses once an entry's restore budget is spent, and ignoring that refusal left `activeIndex` on the next entry while the session still requested the previous model.
- Dead-owner notification recovery now preserves a machine-readable transition block, marker-age diagnostics, and safe force-recovery guidance without weakening ownership proofs (#3762).
- Detached SDK session hosts no longer outlive the broker that spawned them. A host whose broker died without teardown (crash, `SIGKILL`, restart without `--close-session-hosts`) previously stayed resident forever, holding its session's memory — hundreds of MB per orphan. Each host now polls the broker discovery publication and, after a bounded grace period with no live broker, disposes itself through the same graceful teardown a `SIGTERM` takes. A replacement broker resets the window, so hosts still survive ordinary broker restarts, and a transient discovery read failure is treated as ambiguity rather than proof of orphanhood.
- Syntax highlighting now recognizes special filenames such as `CMakeLists.txt`, `Dockerfile.*`, `Makefile`, and `.env.*` before generic filename extensions.
### Changed

- Updated the Cursor Eco, Medium, and Pro profiles from Composer 1.5 to distinct Composer 2.5 tiers: standard throughout for Eco, Fast on execution/review/design roles for Medium, and Fast throughout for Pro. Removed inert generic effort suffixes that the Cursor RPC could not transport.

## [0.12.8] - 2026-08-02
### Added

- Added the paginated public SDK query `providers.list/active` (Q29), returning deterministic, deduplicated `{ provider, connectionKind }` descriptors for locally eligible providers without exposing credentials or performing remote health probes.
- Added the opt-in Alibaba Token Plan Pro profile with `deepseek-v4-flash-0731:max` for execution and `glm-5.2:xhigh` for independent criticism, preserving the existing Balanced profile unchanged.

### Added

- Notification settings now expose first-class Telegram, Discord, and Slack configuration, desired-intent toggles, provider-local quarantine and repair guidance, explicit `keep | replace | remove` secret actions, provider-specific health/test diagnostics, and truthful saved-but-runtime-degraded outcomes. The global master preserves provider credentials and intent, `GJC_NOTIFICATIONS=0` suppresses only automatic generic-session admission, and blocked Telegram ownership uses an isolated chat-only endpoint so verified Discord or Slack siblings can continue without exposing the shared endpoint.
- `/import-session codex [session-id ...]` imports selected Codex histories, or every Codex history bound to the current workspace when IDs are omitted, into resumable native v5 sessions. Imports stream through bounded sanitization and quarantine, preserve source provenance, publish atomically and idempotently, support transcripts above 64 MiB through memory-guard checkpointing, and remain unavailable to ACP/remote clients.

### Fixed

- Windows automatic tmux resolution now selects `psmux` then `pmux` by canonical command order without rejecting distinct lower-priority aliases; it probes `tmux` only when neither named provider is available (#3725).
- CI failure extraction now aggregates Bun failure and suite-error summaries across every test invocation in a job log instead of silently using only the first summary.
- The GitHub status-line lookup now binds terminal links to positive PR numbers and canonical matching HTTP(S) pull-request URLs, rejecting ambiguous or control-bearing targets.
- Fork-context subagents no longer inherit the parent's provider continuity identity. Each child session now presents its own `session_id`/`prompt_cache_key`, so concurrent subagent fleets stop colliding on session-owning upstream transports (`owner_busy` websocket-to-HTTP fallbacks) and keep per-worker cache affinity. Explicit `providerSessionId` overrides and serial same-session continuity are unchanged.
- Fork-context subagents no longer inherit the parent's provider continuity identity. Each child session now presents its own `session_id`/`prompt_cache_key`, so concurrent subagent fleets stop colliding on session-owning upstream transports (`owner_busy` websocket-to-HTTP fallbacks) and keep per-worker cache affinity. Because `providerSessionId` scopes sticky credential selection as well as prompt-cache affinity, each child also selects its provider credential independently instead of following the parent's sticky choice. Explicit `providerSessionId` overrides and serial same-session continuity are unchanged.
- Ordinary `ask` selectors now bound long question premises and page through every premise row without skipping rows hidden by overflow indicators (#3675).
- First-event timeout retries now require a typed, content-free failure from the current clean attempt scope, preventing prior or stale extension activity from suppressing or admitting a later request (#3553).
- Managed session preparation now preserves native `content_too_large` storage failures as `artifact_capacity_exceeded` instead of misreporting `binding_invalid: prepare:store`.
- The issue-1979 Korean prose wrap test now cleans up inherited multiplexer env vars (`TMUX`, `TMUX_PANE`, etc.) so it deterministically exercises the plain-terminal render path regardless of the CI runner's terminal session (#1979).
- The SDK operation inventory now classifies the local-only `/import-session` seam as a locked exclusion and regenerates the committed matrix, fixing the shard-5 `accepts the committed generated matrix` gate failure introduced by the Codex import command (#3714).
- The model selector's assignment menu now shows the model each role currently resolves to (`Set as EXECUTOR (Executor) — now: anthropic/claude-haiku-4-5`), distinguishing an unset default, a role that inherits the default, and a configured-but-unresolvable selector. Previously the role rows were unlabeled, so the only way to learn a role's model was to scan the whole 800+ entry model list for role badges.
- Standalone `AGENTS.md` ancestor discovery now bounds directory traversal, per-file reads, and aggregate instruction bytes while surfacing content-free omission warnings (#3722).

## [0.12.7] - 2026-07-31

## [0.12.6] - 2026-07-31
### Added

- Added the bundled `lunamaxxing` OpenAI Codex profile, mapping every role to GPT-5.6 Luna with medium default reasoning, xhigh executor reasoning, and maximum planner/critic/architect reasoning.

### Fixed

- Managed session publication now works on filesystems that implement no `renameat2` rename flags at all. NFS rejects both `RENAME_NOREPLACE` and `RENAME_EXCHANGE` with `EINVAL`, so publishing a migration receipt failed the whole resume with `Could not open managed session: invalid_request`. The no-replace publish now falls back to `linkat(2)`, which fails with `EEXIST` on an occupied destination and therefore carries the identical no-overwrite guarantee; the fallback is authorized only by a pre-mutation missing-primitive outcome, so a publish that may have committed is never retried under a second primitive. The staged descriptor is retained across publication and the staging link is removed only after it is released, because unlinking a still-open name on NFS silly-renames it and would leave a second link on the published inode.
- Settings now requests a repaint after asynchronous GJC bundle and plugin views rebuild, so loaded content and mutation results appear without an extra keypress (#3643).
- `todo_write` now rejects unsupported operation keys and treats a bare `done` or `drop` as an error instead of completing or abandoning every task (#3640).
- Deferred `agent_end` publication again settles public session readiness before slow extension handlers finish, while retaining exact cancellation leases through queued extension delivery and draining that delivery before session shutdown.
- Ultragoal validation-batch hydration now fails closed unless deferred and final-close evidence exactly matches a complete authoritative cumulative Git/CI inventory and durable batch tuple. Explicit malformed, partial, unknown, reordered, or stale receipt data is rejected; Git path capture is byte-safe, NUL-delimited, and includes untracked files; incomplete capture conservatively requires computer-control QA; shared settings and tool registries cannot use partial diffs to bypass that suite; validate/checkpoint replacement hydration is identical; and current/replacement receipts are byte-bound to ledger payloads (#3541).
- Fixture quality gates that complete intermediate Ultragoal stories now write file-backed adversarial artifact proof; skill-state hooks and computer red-team fixtures match the unconditional adversarial path check so #3543 CI stays fail-closed without weakening hydration exactness (#3543).
- Runtime settings reconciliation now validates every `web_search.fallback` entry against the declared provider enum instead of accepting unsupported or non-string array items (#3601).
- Ultragoal critic-gate, dogfood, review, durable-completion, and runtime test suites now pin `CI_DEV_CHANGED_PATHS` hermetically in their setup/teardown. Their temp checkpoints live inside the enclosing git work tree, so the CI planner's changed paths (which include computer control surface paths on branches that touch them) previously leaked into the computed change set and falsely triggered the mandatory computer red-team suite (`COMPUTER_REDTEAM_CASE_MISSING: … must include kill-switch-bypass`). The production kill-switch-bypass gate is unchanged; only the test fixtures now isolate their own contract from the host branch's diff (#3533).
- Ultragoal critic-gate, dogfood, review, durable-completion, and runtime test suites now relocate temp dirs to `os.tmpdir()` (outside the enclosing git work tree) and pin `CI_DEV_CHANGED_PATHS` to a non-computer test path. The prior in-repo temp dirs caused `computeCheckpointChangeSet` to return `captureIncomplete=true` under parallel shard load (git command timeouts), which unconditionally triggered the mandatory computer red-team suite even when no computer surface was touched. The production kill-switch-bypass gate is unchanged; the `.tmp-*` gitignore entry prevents in-repo test artifacts from polluting untracked-file inventory (#3533).
- Telegram topic delete settlement is now fence-epoch bound, two-phase, and durably route-atomic. `TopicRegistry.settleDelete` requires the caller's dispatched authority epoch to still equal both the record's own epoch and the session's current epoch, so a held earlier delete can no longer settle a newer scan/close-started fence for the same session and topic and release its quarantine; it now removes the record but deliberately *retains* the topic-id quarantine and returns a settlement token instead of publishing routes, so no colliding survivor becomes routable and no settled id becomes adoptable while the clear is still only in memory. `commitSettledDelete` publishes the rebuilt inbound routes and releases the quarantine only after the durable topic-state persist resolves, and `rollbackSettledDelete` undoes a failed persist as a compare-and-set that applies only while the post-settlement state is still exactly current, so a stale rollback can no longer resurrect a deleted record over a newer fence. A refused settlement returns no token and is therefore structurally incapable of being rolled back. Authority-epoch advancement is routed through a single saturating helper capped at `Number.MAX_SAFE_INTEGER`, and settlement fails closed (keeping the fence) on a non-safe-integer, negative, or already-saturated epoch instead of settling against an unsound comparison. Telegram's first create-compensation path now marks compensation complete only after that durable clear commits, so a failed persist leaves the fence supervised rather than stranding a cleared memory state against a `delete_pending` disk state.
- The Telegram notification self-heal reaper now reclaims abandoned publication staging files in the agent `notifications/` directory. `writeJsonAtomic` stages a sibling `<name>.<pid>.<epoch-ms>.<suffix>.tmp` and renames it over the destination; if the staging write or the rename fails, or the process dies between the two, that temp is never published and never read again. No prefix in the reaper's leak-artifact list claimed `.tmp`, so one unreachable file accumulated per failed attempt — permanently, across the roots registry, daemon state, callback aliases, seen-update ids, and the topic registry snapshot. This is most visible where a rename-blocking condition persists (a Windows `EPERM` from an antivirus or indexer holding a handle, `EACCES`, `EIO`, `ENOSPC`). Reaping is shape-matched and still bounded by the existing five-minute mtime grace window, so a temp that an in-flight publication is still staging is never removed, and reclaiming it here also recovers temps orphaned by a crash, which no writer-side unwind can reach.
- The Telegram notification self-heal reaper now reclaims abandoned publication staging files in the agent `notifications/` directory. `writeJsonAtomic` stages a sibling `<name>.<pid>.<epoch-ms>.<suffix>.tmp` and renames it over the destination; if the staging write or the rename fails, or the process dies between the two, that temp is never published and never read again. No prefix in the reaper's leak-artifact list claimed `.tmp`, so one unreachable file accumulated per failed attempt — permanently, across the roots registry, daemon state, callback aliases, seen-update ids, and the topic registry snapshot. This is most visible where a rename-blocking condition persists (a Windows `EPERM` from an antivirus or indexer holding a handle, `EACCES`, `EIO`, `ENOSPC`). Reclaiming it here also recovers temps orphaned by a crash, which no writer-side unwind can reach. Removal is fenced rather than age-only: the reaper parses the publisher PID out of the temp's own name and removes it only when that publisher is *provably dead*, so a live or slow publication keeps its staged temp however old it is, and an indeterminate liveness probe or an unparseable claim retains the file. A proven-dead temp is still bounded by the existing five-minute mtime grace window, and the deletion itself is bound to a no-follow identity capture (`dev`+`ino`+`size`+`mtime`+content digest, single-link regular files only) executed through the exact-unlink native, so a symlink is never followed and a temp replaced between capture and delete is refused instead of destroying the successor.
- The Telegram notification self-heal reaper now handles abandoned publication staging files in the agent `notifications/` directory. `writeJsonAtomic` stages a sibling `<name>.<pid>.<epoch-ms>.<suffix>.tmp` and renames it over the destination; a failed staging write or rename, or a process death between those steps, can leave an unreachable file. The reaper parses the publisher PID from the temp name and acts only when that publisher is provably dead and the existing five-minute mtime grace window has elapsed. Deletion is bound to a no-follow identity capture (`dev`+`ino`+`size`+`mtime`+content digest, single-link regular files only) through the exact-unlink native, so symlinks and same-name replacements are retained. A terminal native removal is reported as reclaimed; a typed `cleanup_pending` result is instead reported as skipped, leaves the bytes visible under a recognized exact-unlink placeholder, and later scans preserve that placeholder without pathname churn rather than claiming false removal.

### Fixed

- Canonical wrapped first-event timeouts now continue the same clean turn through bounded retries and configured fallback rotation, while preserving replay-safety, cancellation, provider-terminal policies, exact attempt diagnostics, and task/subagent retry-status truth (#3553).
- Runtime skill discovery now preserves a candidate when its exact skill name appears as a query token, so additional task-specific terms no longer discard an explicitly named skill.
### Fixed

- Managed-session deletion now immediately continues a descriptor-authorized POSIX artifact detach through exact payload scrubbing before retiring the transcript. Durable direct and replay cleanup preserve substituted successors, while fork regressions use an explicit snapshot barrier instead of scheduler timing.
- Managed-session migration keeps a definitely live holder exclusive beyond the 60-second lease without timer-dependent self-fencing, permits immediate successor acquisition only after explicit release or proven process death, and surfaces capacity/busy startup failures through fixed path/content-redacted guidance (#3508).

## [0.12.5] - 2026-07-30
### Fixed

- ACP and SDK broker session deletion no longer promotes a non-empty retained artifact quarantine to transcript deletion. `cleanup_pending` keeps transcript and exact quarantine authority across retries and restarts while payload bytes survive; root-only transcript preauthorization remains replay-bound and is revalidated after ledger persistence immediately before mutation, while ordinary completion still requires an empty identity-bound root or `artifacts_removed`.

## [0.12.5] - 2026-07-30

## [0.12.4] - 2026-07-30

## [0.12.3] - 2026-07-30

### Fixed

- The release cut no longer deletes previously released changelog headings whose body is empty. `releasedChangelogContent` ran `removeEmptyVersionEntries` before transforming `## [Unreleased]`, so cutting a release replaced the prior empty semver heading instead of inserting the new one above it — v0.12.2 dropped the `## [0.12.1] - 2026-07-29` heading from four package changelogs. Released sections are now immutable history and are always preserved.

## [0.12.2] - 2026-07-30

### Added

- `gjc ultragoal quality-gate init` scaffolds a multi-surface quality-gate template (`--surface` repeatable, `--out` required) so agents can fill evidence once and use read-only `quality-gate validate` multi-error diagnostics instead of discovering missing fields one checkpoint at a time (#3474).
- Ralplan can persist a typed `disposition` stage (`ralplan.review_conflicts.v1`) when Architect and Critic prescribe incompatible actions on the same plan target. Writes fail closed until every conflict has an explicit disposition, and source receipts must resolve against the same-pass Architect/Critic rows in the run index (#2902).
- Published bounded, redacted, hash-bound sealed perf-corpus memory evidence and an output-free replay notebook. The authenticated analysis identifies sustained heap growth on the `agent-session` and `tui` surfaces while keeping RSS/native allocation and p95 claims explicitly out of scope.
- The interactive terminal’s responsive IRC/todo work-lane contract now covers exact narrow/wide geometry, requested versus effective IRC visibility, direct-root pin ordering, todo lane bounds, remapped IRC toggles, and live composer shortcut hints.
- Managed-session startup now preserves bounded Windows ACL and identity failure classifications in path-redacted recovery guidance without broadening permissions, elevation, or unsafe fallback.

### Fixed

- Managed session forks no longer fail with `managed_nested_path_unsupported` on platforms without retained root authority. Nested reads verify each intermediate directory component as a real same-device directory instead of being rejected outright, restoring artifact copying during `fork()` and `moveTo()`.
- Terminal input now normalizes Option/Meta navigation and psmux modified-Enter encodings through the native key parser, keeping legacy, Kitty CSI-u, and modifyOtherKeys behavior consistent.
- Ultragoal CLI replay no longer executes model-authored test source or trusts `replaySafe: true` as arbitrary command authority. Runtime replay is limited to the pinned Bun runtime for `--version` and literal `-e "console.log(...)"`; shells, interpreter code strings, path-qualified executables, tests, install/publish/network/git mutation commands, and arbitrary argv are rejected. Replay cwd/artifact files are realpath-confined, ambiguous rows fail closed, stdout and stderr are checked, and POSIX timeout cleanup signals the process group. Structured test-report fallback remains deliberately unsupported pending a separate trusted-provenance design (#3533).
- Ultragoal CLI replay evidence now reads the replay file referenced by an `executorQa.artifactRefs` entry whose `kind` is `cli-replay`. Inline, nested, and file-backed replay forms are disambiguated explicitly; mixed or malformed rows fail closed (#3533).

- Broker artifact cleanup no longer promotes a non-empty `cleanup_pending` quarantine to transcript-phase completion. The broker advances only when the retained quarantine is root-only/empty or when the lower layer returns `artifacts_removed`, so artifact bytes cannot vanish behind a success receipt (#3489).
- `gjc --worktree` / `gjc -w` launch no longer crashes with a raw uncaught `EEXIST` when the worktree bucket directory (`<repo>.gajae-code-worktrees`) is a broken symbolic link to unmounted or offloaded cold storage. The launch distinguishes dangling links and non-directory entries from valid directory symlinks or Windows junctions, reclassifies mkdir races, avoids disclosing raw link targets or unsafe shell commands, and never deletes or replaces an obstructing entry.
- POSIX parent identity reproof/fsync is now centralized before every promotable artifact-phase result, preventing a crash-window where a rename is lost after durable retirement is recorded (#3489).
- Artifact retirement, planned paths, retained authority, and transcript retry in both managed reconciliation and deletion now bind to the newest published `pendingEvidence` attempt, preventing stranded detached transcripts at paths absent from the newest receipt after a crash (#3489).
- Provider retry classification prefers the typed `stream_first_event_timeout` transport fact when present, falling back to error-message regex for message-only callers (#3496).
- Detached task receipts for in-memory parent sessions no longer advertise dead `agent://` output URIs. TaskTool allocates a session-lifetime durable artifact root under the process temp directory, persists child outputs there, authorizes parent and same-session descendants for scoped resolution, and omits the URI entirely when durable allocation fails (#3471).
- Managed-session replacement and cleanup now bind Windows destination mutation to exact native identity, keep lock acquisition/release retryable without reviving lost ownership, and report retained artifact payloads as `cleanup_pending` until only the verified root remains.
- Resuming a session no longer crashes with an unhandled rejection when another session transition is already running. The session picker dispatches resume through a void-returning callback, and `handleResumeSession` had no re-entrancy guard, so a second selection (or a resume issued while compaction, handoff, or a fork was in flight) reached `switchSession` and the `{ code: "busy" }` transition error rejected a promise nobody awaited. Resume now ignores an overlapping request with a status message, reports a busy transition as status, and still propagates every other failure. The progress lease is released on all paths.
- The interactive `Working…` indicator now remains visible and explicitly labels owner-scoped detached background work across foreground completion, provider errors, pending-submission aborts, and job completion, without resurrecting after TUI disposal (#3479).
- Activity-indicator suspension now detaches and restores the exact owned loader instead of stopping foreign transition UI; optimistic pre-init prompts still show and clear their spinner, context clear retains its eager teardown contract, and resume cancellation preserves transient state until session mutation actually begins.
- Activity-indicator stop and suspension helpers now fail safely for lightweight controller contexts with absent or partial status rails, while full interactive contexts retain exact loader detach/restore ownership.

## [0.12.1] - 2026-07-29

### Fixed

- Provider retry classification prefers the typed `stream_first_event_timeout` transport fact when present, falling back to error-message regex for message-only callers (#3496).
- Team Linux worker memory-guard replacement no longer holds the team task-mutation fence across the successor startup-ack wait, so concurrent `worker-startup-ack` can publish and selector-replacement no longer hangs under CI contention.
- Kitty/Ghostty inline images no longer remain visually pinned when transcript, pinned, or overlay rows are replaced, removed, scrolled, resized, or fully repainted. The TUI now parses only bounded named placements, soft-deletes overwritten placements from the previously committed physical frame, retains transmitted pixels, and restores placements from application scrollback without retransmitting image data.
- Reviewer `report_finding` evidence is no longer injected into caller-owned strict JTD completion data; full findings are published separately through a bounded artifact reference, and failed evidence publication now fails the task closed (#2893).
- Bash output-tail initialization now tolerates constrained `ToolSession` settings adapters that expose `get()` without `has()`, preserving the 1 KiB default and explicit head/tail overrides instead of crashing restricted and interceptor Bash execution.
- Managed-session startup failures now include their bounded preparation classification (and path-free native durability diagnostic when available), so Windows launch crashes no longer collapse to an unactionable generic error while filesystem paths and raw OS messages remain redacted (#3383).
- Single-model sessions now rotate immediately to another stored provider credential after a content-free quota or rate-limit failure, without requiring a synthetic model fallback chain. Credential rotation is replay-safe for content-free failures regardless of extension lifecycle participation, and traverses the full credential pool independent of `retry.maxRetries` (#3491).
- External credential discovery now follows `CLAUDE_CONFIG_DIR` and `CODEX_HOME` instead of always reading `~/.claude` and `~/.codex`, so importing from an account switcher (or any relocated Claude Code / Codex CLI config root) picks up the account the launching shell selected. Both variables resolve through the credential env trust boundary and must be absolute; redacted summaries name the variable, never the resolved path.
- The `acp_conformance` CI job runs again. The pinned upstream `acpx` checkout resolves its own imports (`@agentclientprotocol/sdk`, `zod`) from its own tree, but its dependencies were never installed, so the corpus runner aborted with `Cannot find module 'zod/v4'` before executing a single case. The checkout is now installed after provenance verification, and the reused warm cache still skips the reinstall.
- ACP prompt terminalization now binds each accepted execution handle to one immutable cancellation domain, reserves producer ownership before terminal publication, and quarantines only the exact run when settlement cannot be proven. The fixed 10-second fail-closed external error remains unchanged while internal diagnostics report only bounded resource kinds, hashed labels, clamped ages, and omitted counts.

### Added

- User-created Telegram forum topics can now start a GJC session by selecting the home folder, choosing a verified recent work folder, or entering an explicit folder path. The selected topic is adopted by the new session without creating or deleting a separate Telegram topic.
- The interactive terminal’s responsive IRC/todo work-lane contract now covers exact narrow/wide geometry, requested versus effective IRC visibility, direct-root pin ordering, todo lane bounds, remapped IRC toggles, and live composer shortcut hints.
- Managed-session startup now preserves bounded Windows ACL and identity failure classifications in path-redacted recovery guidance without broadening permissions, elevation, or unsafe fallback.
- Telegram topic synchronization now uses generation-CAS shared authority, durable pre-create claims, lease-fenced effects, bounded single-flight archive retries, and an isolated owner-backed validation-supergroup mode without deleting topics.

### Fixed

- Detached subagents spawned by the `task` tool are resumable again. The resume gate treated a missing record-level `sessionFile` as missing context even though task and managed-persistence sessions retain the descriptor consumed by the resume runner, so persisted role agents always fell back with `context_unavailable`. Resume eligibility now accepts an owner-compatible retained descriptor while preserving `not_found`, explicit `context_unavailable`, missing-runner `no_runner`, and `resume_failed` outcomes.
- Ralplan supports opt-in automatic handoff to ultragoal or team through a durable runtime-owned final receipt, with read-only team preflight and PLANNING-STUCK dominance.
- Subagent setup failures now retain a bounded, redacted cause through live progress, async snapshots, inspect/await, and terminal receipts instead of reporting an empty generic failure.
- Telegram notification sound can be set to all, important, or none; the reference CLI exposes this with `--sound <all|important|none>`, defaulting to all. Important (ask/idle only) and none are explicit opt-ins for quieter notifications.
- First-event provider timeouts are configurable and replayed only by AgentSession with a bounded attempt budget, progress-aware safety checks, and measured exhaustion details.
### Fixed
- Telegram image delivery now converts WebP and other decodable image formats to Telegram-compatible JPEG or PNG photos, preserves MIME types for files sent with `telegram_send`, and falls back to named document uploads when conversion is unsupported or invalid.
- `bun run install:dev` now removes only Bun launchers that resolve to the current checkout's CLI wrapper before validating the managed source link, preventing `bun link` from leaving `~/.bun/bin/gjc` ahead of the new `~/.local/bin/gjc` link on `PATH`.
- A same-tree detached/resumed subagent could not read a verified `agent://`/`artifact://` reference its parent could read (`No session - agent outputs unavailable`), even though parent/child/sibling tree reads are an explicit acceptance criterion of #326: the runtime never supplied `ToolSession.getAuthorizedArtifactsDirs`, so an adopted subagent (whose own `getArtifactsDir()` intentionally collapses to `null`) reached the scoped resolver with zero authorized directories. `ToolSession` now exposes `getAuthorizedArtifactsDirs`, derived only from the session's own explicitly adopted/shared `ArtifactManager` directory, and it is threaded through `read`, `find`, `search`, `ast_grep`, and `ast_edit`'s internal-URL resolution. No registry-wide session enumeration was added; unrelated sessions, missing metadata, and integrity failures remain denied and fail-closed exactly as before (#3302).
- The auth-broker connection (`GJC_AUTH_BROKER_URL` / `GJC_AUTH_BROKER_TOKEN`) is now resolved from trusted environment sources only. `discoverAuthStorage()` turns that configuration into the `AuthStorage` used for every provider, so reading it through the merged view that includes the caller's `cwd/.env` let a repository replace the agent's credential store wholesale — serving the credentials it authenticates with and receiving the ones it writes back. Resolution now uses the non-project resolver; shell, config-file and token-file configuration is unchanged.
- Image generation now resolves its OpenAI base URL and its `GOOGLE_API_KEY` fallback from trusted environment sources only. Both were read through the merged view that includes the caller's `cwd/.env`, so a repository could plant a `.env` choosing where authenticated image requests go, or supplying the credential they authenticate with. They now use the non-project resolver; shell and user-level configuration is unchanged, and the trusted `getEnvApiKey("google")` lookup still takes precedence over the fallback.
- GJC-managed tmux sessions work on macOS again. `gjc session create` failed with `gjc_tmux_profile_tag_failed_cleanup_failed` and leaked the session it had just created, and every close path failed with `gjc_tmux_owner_unverifiable` or `managed_owner_supervisor_signal_failed`. Three non-Linux gaps fed each other: guarded mutations pinned `#{pid}` to the placeholder PID that non-Linux server probes report (which no live tmux server can match), the owner start-time proof only read `/proc`, and SIGTERM dispatch only used the pidfd/handle-backed native signal that deliberately fails closed on macOS. Guards now emit the `#{pid}` clause only for a proven PID, the start-time proof falls back to the natives process incarnation off Linux, and macOS delivers SIGTERM to the already-proved owner PID. Session identity is still pinned by session id, session name and owner generation, and Linux behaviour is unchanged.

### Changed

- Session Observer now incrementally projects append-only session messages and narrowly patches late tool results, avoiding repeated full-history transcript projection while preserving eager output parity and safe full-projection fallback for ambiguous source changes.
- Compaction now publishes complete pruned tool outputs as session artifacts transactionally, carries active goal/workflow/todo state into summaries, and skips synthetic auto-continue when no unfinished work remains.

### Fixed

- Explicit `--mcp-config` sessions now honor each server's configured connection timeout during startup instead of aborting otherwise healthy tools-only servers at the ordinary 1.75-second startup ceiling; sessions without an explicit config retain the existing bounded startup policy.

### Resume fixes

- Eager todo initialization now gives the model the actual phased `todo_write` payload shape (`ops` → `init` → `list` → `phase`/`items`) instead of instructing it to send unsupported `content`, `details`, and status fields, preventing the first forced todo call from failing validation (#3403).

### Fixed
- Fast CLI help now advertises the active `search` built-in tool instead of the retired `grep` name.
- `bun run restart:sdk-broker` no longer crashes with an uncaught `unknown broker operation` error when the live broker predates the `broker.shutdown` operation; the restart now falls back to an identity-fenced `SIGTERM` on the published broker pid, which stops that process through the same rollback path before the replacement is started.
- `bun run restart:sdk-broker --close-session-hosts` closes the broker-hosted sessions before replacing the broker, so ACP clients no longer reattach to session hosts that keep serving the source they were spawned with. Only sessions served by a `sdk session-host-internal` process are selected, and each one is closed through the live broker's verified-identity teardown.

- Coordinator MCP now reconciles canonical structured questions from every workflow stage without misclassifying row-level gate diagnostics as malformed pagination, and unwraps accepted SDK gate-answer envelopes before reporting the terminal resolution.
- Queued named tool choices are revalidated against the live model and active tool set before each request, preventing first-turn eager todo, resolve, or yield flows from sending a stale forced choice after preflight tool changes.
- Ralplan role-agent writes now resolve an existing run's immutable owner session instead of creating workflow state and artifacts under each Planner/Architect/Critic transcript session; conflicting explicit session ids fail closed, and receipts expose the owner `session_id`.
- Subagent task panels now show the fast-mode glyph for the resolved provider in both live and completed states (#3402).
- Auto-retry now strips the whole trailing run of failed assistant attempts before continuing. A turn wedged by an `invalid_prompt` repair leaves two error assistant messages behind, and dropping only the last one left an assistant tail that `agent.continue()` refuses, so the retry died with "Retry continuation failed to start" and the turn was lost.

- Session Observer now receives persisted subagent session paths on lifecycle and progress events, so active ralplan reviewer transcripts render instead of remaining at `No transcript entries yet`.
### Changed

- Bash tool output now keeps only the last 1 KiB when it exceeds the inline capture budget, reducing noisy model input and nudging callers toward focused commands and dedicated search tools. Users who explicitly configure `tools.artifactTailBytes` or `tools.artifactHeadBytes` can set the tail budget or opt into head+tail middle elision. Complete streams received by the Bash tool remain artifact-backed when storage is available; client-truncated ACP tails stay explicitly marked incomplete and cannot be reconstructed locally. Direct user `!` commands retain the existing shared executor window.

## [0.12.0] - 2026-07-28
### Resume fixes

- Status-line pull-request discovery no longer lets the background `gh pr view` process inherit the interactive TUI's stdin, preventing a misconfigured or prompting `gh` executable from stealing keystrokes; the lookup now also fails closed when `gh` is unavailable and terminates after a bounded timeout (#3354).
- Completed `!` shell commands issued during an active agent turn now leave the bottom-pinned pending surface immediately instead of obscuring the live status area until the next prompt; the completed command is still retained for normal transcript insertion.
- The typed deep-interview repair CLI (#3040 and its follow-ups) was reverted and replaced with a minimal staged-transition surface: `gjc deep-interview stage --for <transition> --input '<json>'` (or `@file`), `check`, `apply`, and `discard`. The payload is one JSON document merged losslessly into current state — no per-field flag grammar. The session resolves from `GJC_SESSION_ID`, exactly one pending draft exists per session (no `--draft-id`), and the draft records the state revision it was staged against so `apply` CAS-checks it runtime-side; a stale draft is auto-invalidated with typed recovery guidance. `check` dry-runs the identical merge `apply` performs. Validation is core-schema only (envelope shape, bounded input sizes, locked intent-contract immutability); free-form interview fields pass through untouched.
- `gjc team` workers now publish their own heartbeat while a turn or owned background job is active, so a worker inside a single long tool call is no longer reported stale and stripped of its task claim. Liveness was published only when the model remembered to call `gjc team api update-worker-heartbeat` between turns, so any tool call longer than `GJC_TEAM_HEARTBEAT_STALE_MS` (default 120s) — a build, a test suite, a large read — caused the claim file to be deleted, the `in_progress` task to be reset to `pending`, and the worker to be refused re-claim with `worker_not_live:<worker>:stale_heartbeat` while it was still running, all against a 30-minute claim lease. The worker session now publishes at a third of the stale window (minimum 1ms, capped at 30s) from the top-level session only, and `gjc team` exports the configured window into worker panes so a tightened window applies to the workers policing it too. Recovery semantics are unchanged: a worker that publishes nothing is still recovered exactly as before.
- With all-tool discovery enabled, `task.eager` now keeps the `task` tool active so its delegation instruction can actually be followed; discovery guidance now distinguishes activating a tool from executing it and directs explicit parallel/delegation requests to discover the subagent capability before claiming workers started.
- Unknown `gjc team api` operations now fail as normal CLI usage errors instead of invoking the global uncaught-exception crash reporter. JSON mode returns a compact typed receipt with the invalid operation and suggestions; text mode prints one actionable line. Common mistakes such as `heartbeat` now point to `read-worker-heartbeat` or `update-worker-heartbeat`, and operation validation runs before team-state lookup so a missing `team_name` cannot hide the actual command error.
- Memory consolidation redacts GitHub tokens. The scrubber covered AWS ids, JWTs and keyword-prefixed keys, but GitHub tokens carry none of those keywords, so they reached `MEMORY.md` and `memory_summary.md` verbatim — and the summary is injected into every later session. Now covers the same three prefixes the contribution-prep scrubber already handled.
- The native skill hook resolves its config paths through the trusted directory helpers. It read `GJC_CODING_AGENT_DIR` / `GJC_CONFIG_DIR` straight from `process.env`, which Bun populates from `cwd/.env` before any module runs, so a repository could point the hook at a directory it ships and inject its own `skills.customDirectories` — bypassing the escalation guards that already exist for exactly this.
- Workflow settings are read from the config root under home. `GJC_CONFIG_DIR` is documented as a dirname under home and `dirs.ts` implements it that way, but the ralplan, ultragoal and deep-interview settings readers used the value as a full path, so setting it to the documented form made them look under the current working directory and silently fall back to built-in defaults.
- Session resident-cache directories are now swept when the process exits abnormally. `EphemeralBlobStore` removed its directory only in `dispose()`, and the directory name embeds the pid, so a terminated run's cache could never be collected by a later run. A developer machine held seven of them from dead pids, up to 26 days old, totalling 13.4 MB of externalized session text.
- Align managed fallback abort-after-exhaustion expectations with #3257 ownership release: a subscriber abort at terminal `message_end` no longer expects a second `requestRunTerminal(cancelled)` because the logical-run owner is already cleared.
- Python eval timeout annotations now prefer the caller-configured `timeoutMs` over remaining wall-clock budget so async setup cannot flake second formatting in CI.
- Overflow maintenance now stops cleanly when no-op compaction would replay the same oversized request; the runtime status explains that `/clear` preserves the current session ID before retrying.
- The Smithery origin, API base and API key (`SMITHERY_URL`, `SMITHERY_API_URL`, `SMITHERY_API_KEY`) are now resolved from trusted environment sources only. The origin serves the CLI auth session the user is sent to, the API base receives `Authorization: Bearer <apiKey>` on every call and returns the connection records the agent consumes, and the key is that credential — all three were read through the merged view that includes the caller's `cwd/.env`. Shell, config and stored-credential paths are unchanged.
- `gjc gc` now reports and prunes stale session local roots. Every session gets its own `<tmp>/gjc-local/<session-id>` directory seeded with a migration marker, and nothing ever removed them, so machines accumulated one per session indefinitely. Only marker-only directories past a 24h grace window are eligible, so a root holding real content or belonging to a session that just started is never touched, and prune re-validates immediately before removing.
- `docs/environment-variables.md` now describes the `$env` loading order as implemented. The login shell rc files (`~/.zshenv`, `~/.zprofile`, `~/.zshrc`, `~/.bash_profile`, `~/.bashrc`) are a real sixth source and were missing from the list, and the page claimed `.env` files mirror `GJC_*` keys to `GJC_*` keys — a rule that is self-referential and that no code implements in either direction.
- The package no longer advertises `./extensibility/custom-commands/bundled/review`. That module was deleted when the bundled agents were trimmed to four canonical role agents (#922), but its `exports` entry stayed, so the published surface declared a subpath that fails to resolve. A test now checks every declared `exports` / `main` / `module` / `types` / `bin` target resolves to a file that exists.
- `docs/fs-scan-cache-architecture.md` now points at files that exist. It referenced a `packages/natives/src/` tree that the package does not have (its JS surface is the generated `native/index.js` / `native/index.d.ts`), the grep tool as `tools/grep.ts` when it is `tools/search.ts`, and a `src/patch/index.ts` for the hashline flows that live in `tools/ast-edit.ts`.
- Provider base URLs resolved from the environment by the model registry are now read from trusted sources only. `resolveProviderBaseUrlFromEnv()` used the merged view that includes the caller's `cwd/.env`, and it is generic — `<PROVIDER>_BASE_URL` is derived for any provider on top of the explicit `ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` / `GOOGLE_BASE_URL` / `GEMINI_BASE_URL` aliases — so a repository could plant a `.env` that redirected authenticated traffic for every provider, including re-admitting a redirect the provider-level resolvers reject. It now uses the non-project resolver; shell and user-level configuration is unchanged.
- Web-search provider endpoints and keys are now resolved from trusted environment sources only: `KIMI_SEARCH_BASE_URL` / `MOONSHOT_SEARCH_BASE_URL`, `KIMI_SEARCH_API_KEY` / `MOONSHOT_SEARCH_API_KEY`, `XAI_SEARCH_BASE_URL`, and `ANTHROPIC_SEARCH_BASE_URL` / `ANTHROPIC_SEARCH_API_KEY`. Each provider sends its credential to the endpoint it resolves, so reading them through the merged view that includes the caller's `cwd/.env` let a repository redirect search traffic and collect the user's search credentials. Shell and user-level configuration is unchanged.
- The Exa MCP API key (`EXA_API_KEY`) is now resolved from trusted environment sources only. The key authenticates every Exa MCP call and travels in the request URL, so reading it through the merged view that includes the caller's `cwd/.env` let a repository decide which account the agent's searches run through — and therefore who could see those queries. `findApiKey()` also now returns `null` rather than `undefined` when nothing supplies a key, matching its declared type.
- The notifications channel switch and the Telegram reference client's bot credentials are now resolved from trusted environment sources only. `notificationsEnabled()` read `GJC_NOTIFICATIONS` / `GJC_NOTIFICATIONS_TOKEN` straight from the merged view that includes the caller's `cwd/.env` — unlike its siblings in `config.ts` and `session-control.ts`, which already read an injected env record — so a repository could open the session control/answer channel. The Telegram client's `GJC_TG_BOT_TOKEN` / `GJC_TG_CHAT_ID` fallbacks had the same exposure, and that client is normally run from inside a repository, so a planted bot token would have routed the session's notifications and the operator's replies to a bot the repository chose.
- SearXNG search configuration (`SEARXNG_ENDPOINT`, `SEARXNG_TOKEN`, `SEARXNG_BASIC_USERNAME`, `SEARXNG_BASIC_PASSWORD`) is now resolved from trusted environment sources only, completing the web-search provider family. The endpoint receives the `Authorization: Basic`/`Bearer` header, so reading these through the merged view that includes the caller's `cwd/.env` let a repository redirect the search and hand over the credential. An intentionally empty username or password remains meaningful for basic auth and is still honoured when it comes from a trusted source, while an empty value planted by the project `.env` is rejected.
- The `natives-*` architecture docs no longer describe native APIs that were removed. `countTokens`, `PhotonImage.parse/resize/encode` and `projfsOverlayProbe/Start/Stop` are absent from the addon's JS surface, and `tokens.rs`, `image.rs` and `projfs_overlay.rs` are not in the crate. ProjFS now reaches users through the iso backend (`isoProbe`/`isoStart`/`isoStop` with `IsoBackendKind.Projfs`, implemented in the `pi-iso` crate), and image decode/transform/encode moved to `Bun.Image` in TypeScript.
- `docs/ooo-bridge-extension-contract.md` no longer inverts the scope of the two directory overrides. `GJC_CONFIG_DIR` is a home-relative name (`<home>/<name>`, default `~/.gjc`), not a project directory name, and `GJC_CODING_AGENT_DIR` overrides the agent directory path through `path.resolve` rather than naming a directory under `$HOME`.
- Telegram deep-interview multi-select prompts now mark each option as checked or unchecked after every selection while preserving canonical option values and numeric callback routing.
- SDK revision-store spill directories are now removed when the process exits abnormally. `close()` already removed them, but a terminated session never reaches `close()`, so the spilled snapshot payload stayed in the system temp directory — unlike the sibling `shell-snapshot` and `python-runner-artifact` caches, which already registered a postmortem sweep. Only `mkdtemp` spill directories are tracked; a caller-supplied `storageDir` is durable session storage and is never swept.
- The `ask` tool no longer rejects a JSON-string-encoded single-sided Round 0 payload before coercion; only the retired contract+review pair stays terminal, so a provider that serializes `questions` as a string no longer drives the model into an unbounded retry loop.
- Browser launch overrides (`PUPPETEER_EXECUTABLE_PATH`, `PUPPETEER_PROXY`, `PUPPETEER_PROXY_BYPASS_LOOPBACK`, `PUPPETEER_PROXY_IGNORE_CERT_ERRORS`) are now resolved from trusted environment sources only. `Bun.env` is `process.env` and the env module merges the caller's `cwd/.env` into it, so a repository could previously plant a `.env` that chose the browser binary, routed every request through its own proxy, and disabled certificate validation. Resolution now goes through the non-project resolver (launching shell plus GJC/user-owned `.env` files); shell-level configuration is unchanged.
- The tab-worker native-free import contract no longer invents import edges: its re-export scanner matched a bare `export` declaration followed anywhere later in the file by ` from "…"`, so a `from` inside a comment or string produced a phantom dependency. It now matches real re-export syntax (`export * from`, `export * as ns from`, `export { … } from`) only, and still fails on genuine barrel imports and re-exports.
- The spawned command overrides `GJC_SDK_SESSION_COMMAND` (broker session host) and `GJC_HARNESS_PROCESS_START_COMMAND` (harness process-start probe) are now resolved from trusted environment sources only. `Bun.env` is `process.env` and the env module merges the caller's `cwd/.env` into it, so a repository could previously plant a `.env` choosing which binary those paths execute. Resolution now goes through the non-project resolver (launching shell plus GJC/user-owned `.env` files); operator and test usage is unchanged, and a malformed harness override stays fatal rather than falling back to `ps`.
- macOS screenshot paths now recover the narrow no-break space before `AM`/`PM` for any following separator, so IDE-attached files such as `Screenshot … 11.23.30 PM-1785075812409.png` resolve instead of failing with `ENOENT`; word continuations like ` PMX` are left untouched.
- ACP failures now reach the client with a real JSON-RPC code instead of collapsing to an opaque `-32603 Internal error`: internal string codes with an ACP counterpart map onto auth-required, resource-not-found, and invalid-params, the rest keep their discriminator in `data`, and translation happens once at the connection boundary. An unrecognized `extMethod` now returns `-32601` rather than a resolved `{ok:false}` payload.
- ACP `Diff.path` is emitted absolute as the schema specifies, `ResourceLink.size` is forwarded only when it is a safe non-negative integer, and a failed tool update no longer overwrites `kind` with `other`, so clients keep the category they use for icons while failure stays carried by `status`.
- Interactive `/resume` no longer awaits ordinary notification-endpoint rotation after predecessor fencing when the transition is stamped `interactive_selector_resume`; lifecycle/SDK identity-control paths still await readiness and use a fail-closed control-drain orchestration that sends terminal control outcomes only after successor readiness, while uncertain predecessor stop no longer starts the successor (#2914).
- Interactive TUI `/resume` commits a status-container progress lease before inspect/migration/switch work and clears it on every exit path, with generation-scoped render-commit wait that fails open when the terminal is stopped or unavailable (#2914).
- Interactive `/resume` progress lease fails open when `statusContainer` or UI lacks child-mutation/render-commit surface, preserving headless/minimal controller contexts without weakening full TUI progress-before-switch (#3234 post-merge).
- The documented `GJC_OPENAI_CODE_WEB_SEARCH_MODEL` environment variable now overrides the OpenAI-code web-search model; it is resolved GJC-first ahead of the legacy `PI_CODEX_WEB_SEARCH_MODEL` name (previously only the legacy name was read, so the documented name was a silent no-op).
- ACP prompt completion no longer hangs clients: terminal prompt outcomes are now projected through ACP after the SDK finalizes them.
- Prompt terminalization now exposes exactly one normalized outcome per accepted prompt, preserving the terminal reason or controlled failure code across SDK and ACP clients.
- ACP clients can reconnect to a live session while replaying their MCP server declarations. The live session keeps its immutable MCP configuration instead of rejecting the reconnect as a configuration mutation.
- ACP `AskUserQuestion` now routes through the registered SDK UI provider as a schema-valid form elicitation, including selector, free-text, and navigation-control responses for Air.
- Telegram `/session_create` and cold resume no longer route macOS or other non-Linux hosts through the Linux-only managed-owner supervisor. Non-Linux launches now bind success and cleanup to the exact tmux server, native session, and live pane process identities, scrub inherited managed-owner authority, and reject children that die during launch stabilization; Linux keeps its existing owner-isolation transaction.

### Changed

- Session Observer and the main transcript viewer no longer re-project and re-layout their full transcript on every paint. Source changes still refresh through registry/session-event notifications, while navigation rebuilds only affected layout variants; deterministic counters and replacement-safety coverage preserve selection, follow-tail, raw, expanded, fullscreen, mouse, theme, width, and source-replacement behavior.

### Added

- Deterministic tests for session_switch await policy (selector defer vs default/branch await), control-drain ordering, host pre-response readiness gating, and resume progress lease-before-switch behavior (#2914).
- Added `bun run restart:sdk-broker` for authenticated, identity-checked SDK broker replacement during local Air/ACP testing.
- Added an isolated Bun memory-baseline corpus with short/soak profiles across CLI, AgentSession, blob buffers, workers, Telegram, TUI, and shared/native boundaries; reports keep RSS, heap, external buffers, process-tree endpoints, active resources, throughput, and teardown evidence separate and advisory until variance is characterized.
- Telegram per-tool activity is now opt-in and remains durably controllable with `/toolactivity on|off` or the Notifications preferences UI; disabling it suppresses tool start/completion success and error bubbles without hiding assistant, ask, or session notifications.
- `/model`, `/login`, and `/provider` now order providers through one shared ranking: providers you already have (valid auth, in-flight validation, or a configured non-OAuth provider) come first, then providers whose stored credentials failed validation, then a curated list of well-known providers with regional and device variants grouped behind their primary, then everything else by display name. In `/model` rows, role/default rank and recent usage still take precedence over provider order (#3243).
- Ralplan now bounds architect and critic re-review lanes independently through `gjc.ralplan.maxReviewPassesPerLane` (integer 1–10; default 1). Per-lane budget exhaustion emits a lane-specific `PLANNING-STUCK` exit 3, fails closed against on-disk artifact floors, and repairs crash-gap retries. Architect and critic `gjc ralplan --write` calls can optionally pass `--lane-verdict`, which drives HUD lane pass counts and the latest verdict; critic/architect prompts and the ralplan SKILL workflow now ratchet re-reviews through persisted receipts.
- Ralplan consensus review lanes now persist same-session Architect and Critic subagent metadata (`--architect-id` / `--architect-resumable`, `--critic-id` / `--critic-resumable`) and resume those reviewers by default on pass 2+ with the mandatory re-review context bundle. Unavailable reviewer context falls back to a fresh lane spawn with role-scoped fallback metadata, preserving the existing sequential re-review cadence and receipt-only contract.

## [0.11.11] - 2026-07-26

### Added

- Added cross-platform memory-pressure observability with effective host/cgroup limits, configurable GC and restart advisory thresholds, typed Linux process probes, and a Windows Job Object native probe; unsupported lifecycle actions remain advisory-only.
- Added versioned memory-guard checkpoints with strict transcript/blob validation and fail-closed cross-process writer/TTY ownership claims for future graceful restart activation.
- Ralplan consensus planning now enforces a finite planner/revision iteration budget at the native write path (default 5, configurable via `gjc.ralplan.maxIterations`). Opening another planner/revision pass past the cap fails closed with exit code 3 and an operator-visible `PLANNING-STUCK` marker instead of silent unbounded re-review; `final`/post-interview escalation remains allowed without auto-implementation. The cap also floors against on-disk `stage-*-{planner,revision}.md` artifacts so a wiped, truncated, or malformed `index.jsonl` cannot fail open after prior openers (#3165).
- Added `grok-45-eco`, `grok-45-medium`, and `grok-45-pro` built-in xAI presets for `grok-4.5`; every role stays within the model's `high` reasoning cap, while the `xai` provider recommendation remains `grok-medium` to preserve existing defaults (#3177).
- JetBrains Air ACP sessions now preserve final answers across fast prompt completion, expose tool/retry/goal/notices and session title updates, apply Air's legacy `session/set_model` preset changes through the canonical session configuration path, accept client-supplied stdio/HTTP/SSE MCP servers, reject unsupported additional directories, and reject unavailable model presets before provider dispatch.
- Mouse support can now be enabled inside tmux and screen with `mouse.enabled: true`, so the wheel scrolls GJC's virtual session viewport before multiplexer scrollback. Dragging highlights rendered terminal text and copies it to the system clipboard on release while GJC owns mouse input. Mouse support remains disabled by default to preserve native terminal or tmux scrollback and selection behavior.
- macOS queue controls are now discoverable and platform-native throughout the composer, status/help surfaces, and queue editor: Option+Q queues while busy, Option+Up/Down selects queued messages, and the queue pane documents edit/remove/reorder controls. Added Windows-to-macOS default-shortcut parity coverage and terminal guidance for Option-as-Meta, enhanced protocols, and Control-key remaps.

### Changed

- When GJC owns mouse input (`mouse.enabled: true`), mouse-wheel scrolling moves the session viewport by three rows per notch instead of a full page. PageUp/PageDown keep page-sized transcript-lane steps.
- While reviewing transcript history, the status line and composer stay fixed at the bottom. Semantic assistant/tool output and visible capped-sidebar changes show `New output — type to follow`; duplicate, elided, hidden, geometry-only, and theme-only changes do not. Ordinary typing or paste returns to live output before editing without changing editor focus.
- Telegram per-tool activity is now opt-in and remains durably controllable with `/toolactivity on|off` or the Notifications preferences UI; disabling it suppresses tool start/completion success and error bubbles without hiding assistant, ask, or session notifications.
- Model preset landing now shows explicit `Enter: apply` and `d: set as default` hints; pressing `d` applies the highlighted profile as the default while Enter keeps the session-only apply path (#3161).

### Fixed

- Session Observer now reads stable source snapshots, publishes only complete JSONL appends, validates replacement candidates, and clears stale transcript/model/tool content on source replacement, truncation, deletion, unreadability, or malformed candidates. Its transcript projection remains eager full-history work; this does not add virtualization or bounded full-history memory.
- Session-manager fork/moveTo failure-injection tests now use a platform-aware hermetic seam: retained `RecoveryFsRoot` prototype spies on Linux and the direct native/fs fallbacks off Linux, with a required hit counter so a dead injection fails closed (#3209).
- The #3216 win32 cleanup-producer regression no longer hardcodes divergent directory size `4096`; it injects `nativeRoot.size + 1` so the test stays hermetic when Linux directory size is already `4096` (post-merge Dev CI red on `79f0de870`).

- The synchronous `local://` resolver now accepts a `cleanup_pending` legacy-migration marker instead of rejecting it as unsafe. The async gate already treats that state as settled — entries are installed and content-verified, and only retirement of the legacy source is outstanding — so a managed session whose migration ended in `cleanup_pending` previously failed closed with "Unsafe local:// migration marker" on every `local://` read even though `initializeLocalRoot()` had succeeded. Both marker checks now share one settled-state definition; unrecognized marker values are still rejected. Follow-up to #3080; the asymmetry has been reachable since #2797.
- `/new`, `fork()`, handoff, `/resume`, and branch/tree-jump transitions privately prepare successor identity/transcript/artifacts and immutable managed migration authority, run verified managed `local://` readiness as the last fallible action, then synchronously adopt and publish — so no public manager/agent getter sees the successor before readiness. Pre-commit failure exact-discards staged state; cleanup authority survives dispose/shutdown; handoff/post-commit faults use committed-degraded contracts. Managed staging renames are sorted for deterministic partial-install rollback. Closes the residual #3080 manager-identity window (#3138; builds on #2797 / #2925).
- Workflow-state readers and handoff paths no longer write corrupt-state warnings straight to `process.stderr`, which painted raw bytes over the live TUI composer during interactive sessions. Warnings now route through the TUI-safe file logger while `gjc state read`/`status`/`handoff` still surface them on the structured command-result `stderr`, so corrupt state stays distinguishable from absent state for CLI/automation (#3002).
- Managed model fallback now gives each exhausted entry at most one retry with a rotated credential before advancing, so repeated quota failures cannot consume the attempts reserved for downstream models.
- Windows managed-session artifact migration now uses the native directory-tree root for retained cleanup identity both when producing `cleanup_pending` records and when validating them, avoiding Bun's zero-valued directory `lstat` metadata and false `durability_failed` results while preserving fail-closed authority checks (#2913).
- Legacy-session artifact migration now retries transient EINTR interruptions during no-replace artifact publishes and classifies exhausted interruptions as pre-mutation failures instead of surfacing `durability_failed` (#3077).
- Alibaba Token Plan first-event timeouts also match the exported lazy-stream watchdog text, preserve sticky fallback selection across later turns, avoid same-candidate auto-compaction replay, and reset attempt/overflow budgets only when an accepted queued steering/follow-up successor starts (#3026).
- Legacy auto-compaction now caps provider `Retry-After` delays at `retry.maxDelayMs` instead of sleeping for an unbounded server hint (#3156).
- Queued steering and follow-up successors now reset predecessor fallback attempt budgets and overflow-maintenance counters only after `continue()` accepts the queued turn, without clearing the sticky fallback cursor.
- Questions about `ultragoal` behavior now stay on the direct-answer path instead of being misclassified as requests to start the durable workflow.
- Workflow intent routing now requires a leading `/skill:ultragoal` for slash-command escalation and recognizes Korean object-particle requests such as `ultragoal을 사용해줘` without routing questions that merely mention the command.
- Aligned the startup GJC Forge splash border with the composer trailing gutter, including the one-row constrained fallback.
- `gjc resume` and delete no longer pay a durable (fsync-backed) lock acquisition for managed session tombstones that have nothing left to reconcile; a scope with many accumulated already-completed tombstones opens noticeably faster (#3067).
- Task output-limit environment overrides now accept only complete positive decimal safe integers; malformed, fractional, exponent-form, whitespace-padded, and precision-losing values fall back to the documented defaults instead of being partially parsed (#3175).
- Task output-limit environment overrides now honor values loaded from agent, config-root, and home dotenv files through the shared utils env loader while retaining strict positive safe-integer validation and canonical GJC-first alias precedence.
- `--thinking` now advertises the supported Effort levels and fails closed with a usage error for invalid, missing, empty, or flag-adjacent values, rather than silently ignoring a token or consuming another flag.
- MCP servers configured with a large `timeout` no longer widen the startup hang window for every consumer. The long startup ceiling now applies only to ACP lifecycle launches that supply their own MCP servers, derived from the session readiness deadline with reserved headroom; ordinary CLI/SDK `mcpConfigPath`, project, user, and plugin-bundle consumers keep the short default. An ACP launch that reaches the readiness cutoff before MCP startup now fails fast as a pending startup instead of silently falling back to the ordinary ceiling.

- SDK MCP stdio (`gjc mcp-serve sdk`) now awaits in-flight JSON-RPC handlers after stdin EOF so tools/call responses finish and WebSocket clients close before process exit; the entrypoint e2e fixture bounds child/server/temp cleanup on success and failure so the suite cannot hang for the full 60s on a stuck server.
- Shared kind-aware durable invocation reconciliation substrate for `turn.prompt` and `skill.invoke` (#3031/#3032/#3035): private `.sdk-reconciliation` store, awaitable preflight accept fence, non-hanging skill early-accept with optional `clientRef`.
- AD-L-G02 daemon session CLI e2e is less flaky under CI load: mock WebSocket servers defer `server_hello` one tick, and query failures report stdout/stderr so a non-zero exit surfaces the real SDK error instead of only `exitCode`.
## [0.11.10] - 2026-07-25
### Changed

- The built-in `claude-opus`, `opus-codex`, and `fable-opus-codex` presets now use `anthropic/claude-opus-5` instead of `anthropic/claude-opus-4-8`, with effort suffixes preserved; `packages/ai/src/models.json` was regenerated so `anthropic/claude-opus-5` resolves; non-opus roles (`anthropic/claude-sonnet-5` executor/planner overrides, codex and fable roles) are unchanged.

## [0.11.9] - 2026-07-24

### Fixed

- Restricted role-agent `bash` now accepts literal mid-word tildes, so git revision syntax such as `git diff HEAD~1` no longer has to be quoted. Bash performs tilde expansion only at the start of a word, so word-initial forms (`~`, `~/path`, `~user`) remain blocked.
- Restricted role-agent `bash` now rejects unquoted tildes at every bash expansion position inside assignment words, including the compound `name+=value` form, so `A=~`, `A+=~`, `foo=~root/bar`, `A=x:~`, `A+=x:~`, and repeated colon segments such as `a=x:~:y:~` fail closed. Tildes bash does not expand — mid-word git revisions (`HEAD~1`), non-assignment words (`--opt=~`, `1abc=~`, `a++=~`, `a+b=~`), and quoted forms — remain allowed (#3117).
- Read-only role agents (`architect`, `planner`, `critic`) now receive the `irc` coordination tool and a read-only git prefix set (`status`, `log`, `show`, `diff`, `blame`, `rev-parse`, `ls-files`) in restricted bash; mutating git and arbitrary shell stay blocked. `irc` also stays in the initial active tool set for subagents whenever the parent runtime reports IRC availability, instead of costing a discovery round-trip (#3109).
- The restricted-bash workflow guard now allows `/dev/null` redirects (so `cmd 2>/dev/null` is no longer treated as a repository write during planning phases) while keeping `/dev/stdout`, `/dev/stderr`, and `/dev/fd/<n>` blocked, failing closed on `exec` redirections, and recognizing `>|`, `>&path`, `<>`, path-qualified writers, and every `dd of=` operand (#3127).
- The vendored `insane-search` engine no longer treats a `429` as terminal: rate-limited probe and grid candidates back off (linear escalation honoring `Retry-After`, hard-capped at 30s) and continue through grid diversity and browser fallback. The backoff base from `INSANE_RATE_LIMIT_BACKOFF_S` is validated and clamped, so non-numeric, `NaN`, infinite, negative, or huge values can no longer raise, hang, or defeat a per-attempt deadline, and sleeps stay short enough to honor cancellation (#3131).
- ACP sessions now apply execution permission decisions to eval calls and to tools invoked from JavaScript or Python eval contexts, while non-ACP session behavior remains unchanged.
- Interactive prompt cancellation now reaches API-key preflight through `ModelRegistry`, allowing aborted submissions to clear immediately even while a shared credential-usage request continues in the background.
- Alibaba Token Plan canonical first-event timeouts now surface without session retry/fallback replay and are not internally retried by auto-compaction, preventing repeated provider usage (#3026).
- Delegated-task and subagent status surfaces now distinguish provider recovery from normal running, identify first-event versus idle-stream stalls, show retry budget and provider-progress age, and aggregate concurrent degradation by provider (#3071).
- Telegram notification daemon ownership hardening (#3048): Bot API outcomes now share one honest classifier so both the initiating `429` response and cooldown-suppressed calls settle retryably instead of being lost or falsely rejected, including selected acknowledgements; exclusive operator work is registered before its callback can throw; notification health degrades corrupt daemon-state JSON to a warning; root-registration ownership tokens propagate through injected and built-in ensure, rollback, reconciliation, teardown, and abandoned-startup cleanup seams, with token-bearing rows refusing tokenless cleanup while genuinely legacy rows retain root-match behavior; and initial daemon readiness is published only after the matching heartbeat sidecar rename is durable, so no waiter can attach during the proof window.
- `/new`, `fork()`, handoff, `/resume`, and branch/tree-jump transitions now complete verified managed `local://` legacy-root migration for the successor session identity *before* that identity is published to the agent, the workflow-gate emitter, or extension hooks, so no observer can resolve `local://` against an ungated root across the gate's `await` boundary. Matches cold-start `createAgentSession()` (#2797) and extends `/resume` (#2925). Sending a prompt right after `/new` no longer fails with "local:// legacy migration must complete before path resolution".

- Telegram notification topics now fence malformed successful `createForumTopic` responses per session endpoint, preventing repeated ambiguous topic creation while keeping explicit Bot API failures retryable.
- Windows managed-session resume no longer reports `durability_failed` when Bun rejects `fsync` on the read-only descriptor used to revalidate an existing canonical binding; Windows now uses an owner-writable descriptor for that durability fence while retaining no-follow and pre/post identity/content checks.
- SDK daemon CLI end-to-end tests now capture spawned child stdout and stderr through temporary files instead of pipes, removing the CI pipe teardown race that replaced the product exit contract with SIGPIPE status 141 (#3024).
- Interactive launch bootstrap is now suppressed for parser-accepted `--print=`, `--help=`, and `--version=` equals forms, keeping non-interactive output free of the warming-workspace preamble on TTYs.
- Managed legacy-session artifact migration now accepts up to 50,000 files, processes copy work in bounded batches, and reports capacity exhaustion separately from unsafe artifact topology (#2935).
- Kimi Code first-event timeouts now surface after the provider's continuous first-event wait instead of replaying the full request from zero.

- Rejected subagent schema payloads now retain their complete structured data in canonical output artifacts; inline results remain bounded while `agent://` output stays lossless (#2894).
- Managed legacy-session artifact migration now validates Windows directory roots from the native tree snapshot, tolerates only lazy metadata on plain Windows directories, and replays both clean and cleanup-pending detaches while retaining fail-closed receipt validation. Canonical binding durability sync uses a writable no-follow handle on Windows NTFS stacks that reject `FlushFileBuffers` on read-only handles (#3015, #2913).
- A single managed session tombstone that fails to reconcile (e.g. an artifact directory identity mismatch) no longer blocks resume or delete of every other session in the same managed scope; the failure is isolated to that tombstone and logged, unless it belongs to the session currently being opened, which still fails closed.
- Added Linux-only team worker memory-pressure replacement with checkpoint classification, bounded retries, deterministic target selection, and fail-closed blocked tasks; Windows and macOS remain advisory-only.

## [0.11.8] - 2026-07-23
### Added

- Keybinding configuration now keeps portable canonical text while runtime shortcut labels render platform-native, including concise MacBook glyphs in inline surfaces and glyph-plus-text accessibility labels in `/hotkeys` and `/help`; `/hotkeys` remains authoritative for effective remapped bindings.

### Added

- Plans and delegated tasks carry an authoritative repository binding (`gjc.repository_binding.v1`). Ultragoal/ralplan stamp identity at creation; task lanes stamp omitted bindings from session cwd **before** agent discovery; ralplan stage writes and handoff re-entry enforce the seed binding; declared paths must stay under the bound root; task receipts include the resolved identity; linked isolation worktrees must match the source repository (#2901).

### Fixed

- Runtime MCP OAuth credentials are now bound to their authorized server origin and token endpoint, reject redirecting refresh responses, and fail closed when legacy or changed configuration lacks an exact match.
- `/share` now keeps full-session HTML in owner-private unpredictable staging until the share handler or `gh gist create` process has fully stopped; cancelling a blocked gist upload terminates and awaits that process before reporting cancellation and removing the export.
- MCP diagnostics now redact opaque endpoint paths, user information, query values, and fragments without changing outbound requests, and parse-failure logs omit response bodies that could echo request secrets.
- Telegram notification daemon self-heals degraded on-disk state: permanently missing scan roots are pruned (so one deleted worktree no longer disables orphan-topic cleanup), and retained exact-unlink transition/placeholder artifacts are reaped on ownership acquire and each scan. `gjc daemon reload` can recover without manual filesystem surgery (#2956).
- On macOS, resuming a managed session no longer fails with `identity_mismatch` when the first write-append open changes only file `ctime` (e.g. APFS write-provenance / `com.apple.provenance`). `appendSync` allows a single bounded re-capture + retry when `dev`/`ino`/`size`/`mtime`/SHA-256 remain unchanged, and still rejects real content races and repeated ctime transitions (#2944).
- Interactive `/resume` / `AgentSession.switchSession()` now awaits verified managed `local://` legacy-root migration for the newly selected session before post-commit lifecycle proceeds, matching cold-start `createAgentSession()` readiness from #2797 so synchronous `local://` resolution no longer fails with "legacy migration must complete before path resolution" after a mid-session switch (#2925).
- Concurrent edits to the same file path are serialized through a path-scoped mutation lock (in-process always; durable cross-process lock on the real filesystem). Disjoint concurrent `applyPatch` / replace mutations no longer silently overwrite each other, and a commit-time content check rejects writers that observe a mid-flight change (#2900).
- Concurrent edits to the same file path are serialized through a path-scoped mutation lock (in-process always; durable cross-process lock on the real filesystem). Disjoint concurrent `applyPatch` / replace mutations no longer silently overwrite each other, and a commit-time content check rejects writers that observe a mid-flight change. The production `executePatchSingle` / `LspFileSystem` path explicitly enables the durable lock rather than inferring it from FileSystem object identity (#2900).
- Lean notification verbosity no longer floods remote clients with intermediate tool-turn `turn_stream` frames. Under `/lean`, the latest assistant answer is deferred until `agent_end` (idle); ask lead-ins still flush immediately before inline buttons, and `/verbose` keeps per-turn streaming (including opt-in live frames) (#2863).
- Ultragoal `complete-goals` no longer reports contradictory next actions when every incomplete story is `blocked` or `review_blocked`. Text and JSON now agree on `next-action=resolve-blockers` with blocked goal IDs/status; failed-only schedules surface `retry-failed`; `execute-goal` always includes a `goal_id` (#2903).
- Bound each Python tool bridge bearer capability to one active session registration, reject non-canonical or empty bearer credentials before lookup, and rotate authority whenever a retained session replaces its kernel.
- Deep Interview now scopes provider-facing `ask` metadata to the persisted workflow stage, including after durable session resume: Round 0 advertises only the locked `intent_contract` branch, later rounds advertise ordinary and `intent_review` branches, foreign workflow gates cannot seed recorder state, and wire-valid empty positive-round reviews reach canonical Zod diagnostics while malformed authority remains fail-closed.
- Bounded docs.rs rustdoc downloads, legacy cache reads, and gzip expansion before parsing or caching; transport-level content encoding is disabled and rejected so Bun cannot decompress outside the explicit output guard.

### Added

- Added SDK v3 prompt reconciliation through `turn.prompt_status` with caller-supplied `clientRef` correlation, bounded live-session lifecycle retention, reconnect-safe lookup, and explicit ordered non-replay semantics for `turn.prompt` (#2930).
- Added `models.profiles.list` discovery of the effective built-in plus `models.yml` profile catalog, exact-ID pre-spawn validation that reloads host configuration for each lifecycle request, and structured `unknown_model_profile` / `model_profile_registry_error` details across lifecycle startup failures (#2931).

## [0.11.7] - 2026-07-22
### Added

- `/btw` now opens an ephemeral multi-turn side chat: plain text continues the side thread until Esc returns to the main chat, while visible text-only context stays outside the main transcript and session observability/debug hooks and is scrubbed synchronously on close or abort.
- Added `statusLine.showActionHints` (default: `true`) to hide contextual action hints while retaining configured status-line segments.
- `skill_discovery` empty results now carry a `notice` when discovery config caused the emptiness — naming the exact disabled setting (`skills.enabled`, `skills.enablePiProject`, or `skills.enablePiUser`) and the `gjc config set` command to enable it. Previously a disabled config was indistinguishable from "no skills exist", silently hiding freshly written user/project skills.
- `generate_image` now supports Alibaba Bailian (Token Plan) `wan2.7-image` as an image provider: set `providers.image` to `alibaba` (or let auto-detect find `ALIBABA_TOKEN_PLAN_API_KEY` / a registered `alibaba-token-plan` key), override the model with `providers.imageModel` (e.g. `wan2.7-image-pro`). Short-lived OSS result URLs are downloaded immediately, and image editing works via input images.

### Fixed
- Telegram `/session_recent` now retries one concurrently appended managed transcript and omits only candidates that remain unstable, preserving independently verified recent-session rows.
- Repeated byte-identical stale SDK broker locks no longer cause startup to loop when a prior tombstone exists.
- ACP session close now rotates idempotency keys for resumed attachment generations while retaining the same key across terminally uncertain close retries.
- ConversationStore now tolerates only unsupported Windows parent-directory durability errors after preserving temporary-file fsync and atomic rename.
- Ralplan no longer re-asks for execution approval when the user already explicitly named `ultragoal` or `team` in the current turn; that naming is the consent.
- Interactive Windows startup now stays keyboard-ready with large session histories and native multiplexers by showing an interactive-only bootstrap before the first TUI start, deferring bounded recent-session discovery until afterward, and reducing psmux frame pressure while preserving the three-second animation and update checks.

- Cron guidance now routes silent recurring polling and event-driven PR/CI watchers to `monitor`, because every cron firing starts a normal assistant turn and prompt wording cannot reliably suppress its response.
- Ordinary `ask` calls now normalize a provider-emitted `deepInterview: null` placeholder instead of misclassifying it as malformed Round-0 intent recovery data and rejecting it before coercion.
- SDK event replay authorization now refreshes the negotiated capability cache synchronously from the native-sanitized replay snapshot before host filtering, preserving initial and repeated-hello capability updates without trusting client frame claims.

- Plugin-bundle HTTP and SSE MCP requests now bind every connection to a validated public address and revalidate bounded redirects before following them.
- Deep Interview now exposes stage-specific provider-facing `ask` metadata: Round 0 advertises only the locked `intent_contract` branch, while later rounds advertise ordinary and `intent_review` branches, preventing strict-schema constraint stripping from making an invalid empty Round 0 review selectable.
- Deep Interview now exposes stage-specific provider-facing `ask` metadata, including after durable session resume: Round 0 advertises only the locked `intent_contract` branch, while later rounds advertise ordinary and `intent_review` branches. Remaining strict-schema constraints that providers cannot express fail closed with bounded corrective guidance instead of an opaque retry loop.
- Deep Interview now exposes stage-specific provider-facing `ask` metadata, including after durable session resume: Round 0 advertises only the locked `intent_contract` branch, while later rounds advertise ordinary and `intent_review` branches. Foreign workflow gates can no longer seed Deep Interview recorder state, and remaining strict-schema constraints that providers cannot express fail closed with bounded corrective guidance instead of an opaque retry loop.
- Documented that custom OpenAI-compatible models omit vision by default: when `input` is unset, GJC treats the model as text-only and strips images with `[image omitted: model does not support vision]`. Vision backends must set `input: [text, image]` in `models.yml`.
- Restored `/models` preset landing navigation after the Image Generation row and made compaction/pruning regression fixtures use an explicit 200K context boundary instead of a mutable provider descriptor default.
- Fixed Windows legacy session artifact migration by using native directory identity size, a traversable detached-path alias, and writable file handles for final durability sync.
- `gjc setup credentials` now auto-imports only OAuth credentials with a finite expiry strictly in the future. Expired or malformed-expiry discoveries remain visible as non-importable records, and existing imported credentials remain recoverable through `/login`.
- Resumed managed sessions now complete the verified legacy `local://` artifact migration before synchronous path resolution, preserving legacy scratch files instead of failing startup with a migration-order error.
- Corrected Telegram's uncertain lifecycle guidance so create, close, and resume commands describe their own possible outcome; close and resume no longer display the create-only duplicate-start warning.
- Telegram ask notifications now preserve the authoritative recommended choice from native asks and workflow gates, marking that option as `(Recommended)` in the message body without changing button indices or submitted answers.
- Telegram `/session_close` now fails closed when tmux disappearance cannot be confirmed, and publishes the managed owner verdict before locked terminal-state preservation so normal close finalization is not delayed behind that state path.
- Managed publication now fails closed on malformed, committed, or mutation-unknown native outcomes: it never retries or cleans a destination, and preserves bounded atomic-unavailable/durability diagnostics through managed startup (#2804).

## [0.11.5] - 2026-07-20
### Fixed

- Internal transcript PageUp/PageDown now keeps moving through tool-output and other non-semantic rows instead of intermittently becoming a no-op after scrolling through anchored conversation content.

## [0.11.5] - 2026-07-20
### Changed
- Telegram live-message streaming now defaults on for configured Telegram notifications through one durable global preference, supports live in-session preference refresh without weakening redaction, and keeps Discord and Slack finalized-only; process environment overrides remain available.
- Telegram tool-activity updates can now be disabled persistently with `/toolactivity off` (and restored with `/toolactivity on`) or from the Notifications preferences UI, without disabling final answers, asks, or other notification delivery.
- Reduced the default session's initial context and corrected tool-schema token accounting: `estimateToolSchemaTokens` now measures the provider-visible wire schema instead of raw zod schema objects, so `/context`, the status line, and compaction report real cost; the `ask` tool is now registered-not-attached (attached only for pending workflow gates, canonical GJC workflow skill activation, and plan-mode enforcement) and its wire schema was slimmed. A fresh default session drops from 12 to 11 resident tools with markedly lower reported non-message context (#2729).
- Session files now use v5 authority records. Do not roll back to a v4 writer after v5 session data has been created: v4 writers cannot preserve independent MCP and discovered-built-in selections.

### Fixed
- SQLite `read` raw queries now accept exactly one explicit `SELECT` statement, reject comments and statement tails before opening the database, recheck the invariant at execution, and enable SQLite query-only mode as defense in depth.
- Direct HTTP(S) marketplace catalogs now use connection-bound public-address validation, bounded redirects, and a 2 MiB response limit before parsing or caching.
- Bounded MCP list pagination by cursor-cycle, page-count, and item-count limits while preserving abort and cache behavior.
- Shell environment snapshots now use one process-private temporary root with exclusive private files, trusted cache validation, and whole-root shutdown cleanup instead of a predictable shared directory.
- Python kernel startup now materializes its bundled runner in one process-private temporary directory with exclusive file creation instead of consulting a predictable shared cache path.
- SSH command construction and discovery now reject malformed destinations with unsafe prefixes or control characters while preserving normal host, address, username, and alias forms.
- Bounded MCP resource URI and template matching now skips oversized templates and uses deterministic literal-segment matching instead of dynamically constructed regular expressions.
- Fixed the `subagent` tool's `resume` action silently swallowing manager failures. Resume outcomes other than `context_unavailable`/`not_found` (`no_runner`, `resume_failed`, `owner_shutdown_in_progress`, …) were dropped and the stale terminal subagent snapshot was returned as if the resume had succeeded, so ralplan's re-review loop believed the persisted Planner had resumed when it had not and never fell back correctly. The resume action now surfaces every non-ok reason (matching the `steer` branch), and the task resume runner marks a resumed subprocess that aborted or exited non-zero as a `failed` job (carrying its rendered failure summary) instead of reporting it `completed`.
- Daemon timeout flags now reject missing, malformed, non-positive, fractional, whitespace-containing, and unsafe integer values before daemon command side effects instead of partially parsing them.
- Hardened standalone HTML session exports so session identifiers, provider/model labels, and embedded raster images remain confined to their intended HTML contexts; malformed image payloads are omitted.
- Restored legacy `gjc coordinator-mcp` and root `gjc --team --team-size <n>` routing to their native MCP and team commands, with strict team-size validation that prevents malformed legacy flags from selecting team lifecycle actions.
- Fixed the `subagent` tool's `resume` action silently swallowing manager failures. Resume outcomes other than `context_unavailable`/`not_found` (`no_runner`, `resume_failed`, `owner_shutdown_in_progress`, …) were dropped and the stale terminal subagent snapshot was returned as if the resume had succeeded, so ralplan's re-review loop believed the persisted Planner had resumed when it had not and never fell back correctly. The resume action now surfaces every non-ok reason (matching the `steer` branch), and the task resume runner marks a resumed subprocess that aborted or exited non-zero as a `failed` job (preserving its error text) instead of reporting it `completed`.
- OpenRouter image generation now retrieves provider-returned HTTP(S) images only through connection-bound public-address validation, revalidates bounded redirects, and enforces image content-type and byte limits before buffering.
- Fixed session resume crashing with `TypeError: undefined is not an object (evaluating 'usage.input')`, and hardened both usage-aggregation paths against silent total corruption, when a persisted transcript contained a parseable-but-malformed assistant or `task` tool-result entry — as produced by torn concurrent multi-writer / NFS appends. `parseSessionEntries` accepts any parseable JSON, so a corrupt `usage` could be absent, `{}` (NaN totals), numeric strings (`"0" + "10"` → `"010"`), negative (silently reducing totals), a present-but-null/incomplete `premiumRequests`/`cost`, a non-record `cost` (e.g. an array), or cumulatively overflow to `Infinity`. Both the resume (`#buildIndex`) and runtime append (`#appendEntry`) paths now route through one shared validator that requires every usage bucket and `cost.total` to be finite non-negative numbers (defaulting only truly-absent `premiumRequests`/`cost`, and rejecting present-but-null/incomplete fields rather than zeroing them) and rejects any record that would overflow cumulative totals, skipping and reporting the malformed record instead of poisoning every `getUsageStatistics()` consumer.
- Fixed the `subagent` tool's `resume` failing immediately for a persisted ralplan Planner. A subagent that finishes by calling `yield` (or is torn down right after a tool executes) left the saved session ending on an assistant `toolCall` with no matching `toolResult`; replaying that history on resume produced an invalid provider request (a `tool_use` not followed by a `tool_result`) that failed the resumed turn at once. Resumed transcripts now reconcile any trailing unpaired tool call with a synthesized placeholder result before the first resumed prompt. Additionally, a failed/no-op resume leg no longer overwrites the prior run's success output artifact with an empty file.
- Fixed persisted subagent resumes being rejected before session reconstruction with `Session is inside managed storage but is not an authorized managed candidate`. Child session files intentionally live inside their parent session's artifact directory and are not top-level resume-picker candidates; the task resume path now explicitly opens the exact internally registered child session directory while retaining strict candidate validation for user-selected managed sessions.
- Managed session resume scans now read only a bounded no-follow header prefix from foreign workspace transcripts, while fully recapturing and revalidating owned candidates before granting migration, receipt, or deletion authority.
- Secret obfuscation now uses authenticated process-local placeholders that remain stable within a running process and opaque after restart.
- Workspace-wide LSP diagnostics now fail closed instead of launching build or typecheck subprocesses outside execution-tool authorization; concrete-file and glob diagnostics remain available.
- Clean, side-effect-free canonical provider stream first-event and next-event watchdog failures now retry on bare single-model legacy sessions; explicit legacy disable, managed fallback, and fail-closed structured, non-watchdog, or unsafe attempts are unchanged.
- Remote MCP HTTP and SSE responses now enforce finite content and message budgets before parsing or dispatch.
- Added fail-closed managed tmux owner SIGABRT recovery: exact-child supervisor receipts and pre-CLI admission now bind replacement ownership, strict durable Ultragoal/transcript evidence reconciles terminal child yields over stale nonterminal runtime state, recovery hydration remains write-free until an ownership fence, and hostile identity, corruption, concurrency, and path boundaries preserve dirty product files (#2681).
- Restored non-root startup on Synology and other Linux container filesystems that definitively report POSIX ACL storage unsupported for managed session paths; explicit `--session-dir` semantics and all owner, mode, type, symlink, identity, and scope-binding checks remain unchanged and fail closed (#2687).
- Preserved access to legal SQLite table names beginning with `sqlite` but not reserved `sqlite_`.
- Decoupled the Telegram daemon's operational generation from its notification protocol version and advanced it through generation 9, so upgrades from v0.10.2 replace generation-3 owners through durable provisional/ready/retired handoff, canonical process-incarnation fencing, stable process-reference signaling, tri-state foreign-owner provenance, owner-lock leases, identity-atomic transition markers, and retained managed-filesystem authority changes instead of silently attaching. Discord and Slack now use generation 6 with stable process-reference signaling, exact owner/reclaim leases, ownership-heartbeat loss shutdown, replacement of physically live but incompatible owners, and the same retained native-authority boundary; focused rolling-upgrade regressions and a per-family lifecycle authority guard enforce the contract (#2278, #2687).
- Activated discoverable built-in tools now persist with independent MCP and discovered-built-in authority, preserving explicit empty selections and restoring only eligible built-ins across resumed lifecycle transitions.

## [0.11.4] - 2026-07-20
### Added
- Bracketed pastes containing complete lists of saved static-image paths can now attach up to 16 images in source order after explicit confirmation. Paste transactions are cancellation-safe, disabled in command modes, enforce source, encoded-output, dimension, and decoded-memory budgets before commit, reject animated, remote, linked, or path-swapped sources, and restore the literal paste on cancellation or failure.
- Rich tool-call rendering in the transcript viewer: both the session-observer and main-session transcript viewers now share one formatter so tool calls render identically (args summary + intent, then a result block with ✓ done / ✗ error / ⏳ pending states). Fixes a latent bug where a tool call with no result rendered as a false "✓ done"; expanded results cap at 100 source lines with raw/copy exposing the full text (#2656).

### Changed
- Updated the Kimi Coding Plan Eco, Medium, and Pro presets to Kimi K3 with its supported `low`, `high`, and `max` reasoning efforts.

### Removed
- Removed the legacy worktree cleanup implementation behind the `@gajae-code/coding-agent/cli/worktree-cli` and `@gajae-code/coding-agent/commands/worktree` package subpaths (base and `.js` forms). The `gjc worktree`/`wt` CLI command has been unregistered since the workflow-surface narrowing; the modules behind it (including the recursive-deletion `clear` path) were only reachable as package imports. The subpaths now resolve to throwing tombstone modules whose error explains the deliberate removal and the supported replacement: inspect leftover managed worktrees under `~/.gjc/wt` manually and use `git worktree remove` / `git worktree prune`.

### Fixed
- MCP OAuth discovery now treats transport error hints as classification-only and uses one public-network-validated, redirect-aware, issuer/resource-bound traversal budget across metadata aliases and cycles.
- Telegram `/btw` rich-delivery E2E coverage now awaits native and daemon teardown ownership, records exact per-iteration lifecycle phases, and uses an internal exact-tuple terminal-delivery receipt to keep fallback stress deterministic under shard load without extending the original test timeout.
- Malformed spurious Round-0 review metadata no longer blocks an otherwise valid locked-intent question/gate, while durable intent safety remains fail-closed (#2643).
- Restricted role-agent `gjc state` command authorization now fails closed on argv-classification disagreement: one shared manifest-aware native state argv grammar (action names, flag arity, positionals, effective modifiers, selector candidates) is consumed by both runtime dispatch and the policy boundary, which rejects ambiguous selectors, malformed flags, destructive actions, and file-backed input (#2665).
- Browser tab workers now bootstrap through their actual isolated entry without accidentally loading native bindings through broad imports, and startup fails closed instead of falling back to unguarded inline execution; compiled/package smoke now exercises the tab worker, with a PR-head darwin-arm64 CI gate (#2598).
- Added evidence-preserving recovery for legacy multi-writer SDK session-index corruption: `gjc gc` now diagnoses corrupt prefixes, `--repair-session-index` quarantines the original snapshot/log under the session-index lock before atomically restoring only the checksum-valid monotonic prefix, and append failures point operators to the explicit repair path (#2654).
- Malformed selectors on internal read URLs now fail explicitly instead of silently falling back to an unbounded resource read.
- Newly registered earlier resource-GC policies advance the pending sweep without postponing an already earlier sweep.
- Provider onboarding wizard completion is now deterministic under CI load: duplicate in-flight confirmation is suppressed, success tests await the real refresh/notification/status boundary instead of fixed sleeps, and the newly configured model is verified through the subsequent model selector.
- OpenAI-compatible web search now turns malformed successful response bodies into bounded provider errors while preserving normal provider fallback (#2593).
- Windows session storage now keeps a symlink-resolved drive-letter path for Bun filesystem I/O instead of a native Volume GUID identity path, preventing `ENOENT` failures during resident-cache writes that could drop the final assistant message at turn completion.
- On platforms with verified retained publication identity support, detached SDK broker processes now stop after durable loss or replacement of their owned publication while preserving warm reuse and protocol/state formats (#2583). Bounded-practical limitation: requests admitted before the first definitive loss observation—or after authoritative same-object recovery and before a later loss observation—may still perform pathname-based index, ledger, lifecycle, cleanup, or child effects. A detached session-host child spawned by an admitted request may outlive broker exit and may continue pathname effects for the session lifetime. Broker self-reap does not cancel or signal that child and does not provide absolute hostile replacement isolation.

## [0.11.3] - 2026-07-19

### Changed
- The `read` tool is now receipt-by-default: bare and unparseable reads return a bounded receipt (≤50 lines / 10 KiB) with a re-read-with-selector footer only when truncated, `:raw` stays pure verbatim up to a max(2 MiB, spill threshold) ceiling, structural summaries cap unit-granularly at 20 KiB while preserving elision and source-recovery footers, and directories are byte/line capped and never spill. Only an explicit full-content selector (`:raw` or an explicit range) with real content is spill-eligible. Subagent previews now enforce a real byte/code-point cap via per-shape render budgets plus a shape-aware artifact-eligibility tag enforced centrally in output-meta.

### Fixed
- Ultragoal objective ownership no longer treats arbitrary strings that merely mention `goals.json`/`ledger.jsonl` as Ultragoal-owned; only the exact default aggregate objective qualifies for the known-objective path.

### Fixed
- Workflow-state handoff no longer self-locks the active-state cache, so a same-turn skill handoff (e.g. ultragoal → ralplan) completes instead of stalling behind a lock the handoff itself still holds (#2638).
- SDK host shutdown now retries a failed broker unregister instead of short-circuiting with a stale broker-index entry, while retained startup-cleanup owner-release failures remain isolated from the red extension-error path (#2625).
- Non-TTY launches now fail fast when stdin is empty and automatically use print mode for positional prompts and `@file` inputs, preventing orphaned interactive TUI processes (#2507).

### Fixed

- The command palette now labels `app.session.fork` as “Branch from message,” matching its user-message selector and `AgentSession.branch()` behavior while preserving the existing action ID for keybinding compatibility.

## [0.11.2] - 2026-07-19

### Changed
- Simplified the release CI to a minimal, industry-standard workflow: a version tag builds the native addons and binaries, then publishes to npm and cuts the GitHub Release in one self-contained job graph. Removed the source-SHA re-verification gate, evidence-provenance handshake, and draft/finalize multi-job dance that made every version-bump commit fail CI. The lint/type-check job is now native-free (biome + tsc); runtime doc and SDK-canonicalization checks moved to the test job so they run with a built addon.

### Fixed
- The Python eval runtime now honors the documented `GJC_*` environment variables instead of silently reading only legacy `PI_*` names. `GJC_PY` (tokens `0`/`bash`, `1`/`py`, `js`, `mix`/`both`) overrides the eval backend allowance with precedence over legacy `PI_PY`/`PI_JS`; `GJC_PYTHON_SKIP_CHECK`, `GJC_PYTHON_IPC_TRACE`, and `GJC_PYTHON_INTEGRATION` are read first with `PI_*` fallback (OR semantics, so either truthy name wins). Operators following `docs/environment-variables.md` and `docs/python-repl.md` who set `GJC_PY=py` previously saw no effect — a silent docs/runtime contract break. Legacy `PI_*` names remain supported for backward compatibility.

### Fixed
- Team worker launches now receive the validated owning `GJC_SESSION_ID` for sanctioned session-scoped writes while preserving absent identity, fail-closed resolution, and separate spawn provenance (#2597).
- Managed and explicit session directories now canonicalize benign ancestor symlinks (e.g. macOS `/var -> /private/var`, a symlinked `$HOME` or project directory) to a symlink-free trusted root before the strict owner-only and reparse guards run, so session creation, moves, resume, and writes no longer fail with `reparse_point` / `Unsafe reparse storage path` under a symlinked temp root or home. The native primitive stays strict and continues to reject symlinked components at or below the trusted root.
- Skill invocation failures now list available skill names so agents can recover from typos without a blind retry loop.
- Workflow state receipts now use canonical session-layout paths, require resolved session identity, and report a `state_path` that matches native write/clear output (#2393).
- Coordinator MCP operational calls now canonically bootstrap or reuse the agent-global SDK broker when discovery is absent or stale, while coordinator/hermes JSON checks report catalog and broker-discovery readiness separately without mutating broker state (#2552).
- Coordinator MCP question polling now requires a session, reconciles pending workflow gates into bounded public questions, diagnostics, and reconciliation state, and submits bound idempotent answers through `workflow.gate_answer` without exposing private gate payloads (#2550).
- Runtime skill discovery now follows native user config-root precedence: nearest project, canonical `GJC_CONFIG_DIR`/`PI_CONFIG_DIR`/`.gjc` `agent/skills`, configured legacy `<config>/skills`, then historical legacy `~/.gjc/skills`, preserving exact fallback precedence (#2572).
- Opt-in stalled team-worker continuation now remains default-off (`GJC_TEAM_AUTO_CONTINUE_STALLED_WORKERS=1`) and sends at most two fenced, journaled fixed prompts only to a verified stale worker pane with a matching live claim and sufficient lease; unknown restart state fails closed, with no provider replay, pane lifecycle action, or claim mutation (#2580).

### Fixed
- SDK host response delivery to a disconnected client no longer escalates a second structured-error send failure into a process-level unhandled rejection; failures stay local to that connection.

### Fixed
- Repository LSP configuration can no longer define process-affecting server behavior: project files may control declarative matching, activation, and capabilities, but cannot set launch fields, initialization options, or opaque server settings. Trusted canonical user configuration outside the project retains those overrides; project-controlled plugin roots and the quarantined `--plugin-dir` surface cannot inject them. Automatic discovery uses trusted external executables and rejects repository-owned lexical paths as well as symlink-resolved project binaries; status uses the session cwd as its lspmux trust root. `GJC_DISABLE_LSPMUX=1` is the canonical opt-out and `PI_DISABLE_LSPMUX=1` is a supported compatibility alias; either truthy value disables lspmux probing and wrapping.
- Palette slash commands now run only from an empty composer; drafts are never touched.
- Individual default and named-role model assignments now keep the model selector open for consecutive choices, while batch assignments retain their existing close-on-success behavior.
- Aborting a session without an enabled active goal no longer suppresses the first reminder when a goal is activated later; active-goal abort suppression is one-shot, goal-owned, and clears across inactive or replacement-goal transitions (#2436).
- Palette slash submissions no longer clear or rewrite composer text, cursor state, history, or pending images created while an asynchronous input hook is awaiting; canonical keyboard submission cleanup remains unchanged (#2441).
- Dead browser-tab recovery now expires descriptors without releasing replacement, revived, or differently owned tabs, while exactly-once teardown closes stale targets and releases browser holds without refcount underflow (#2437).
- Local-memory Phase 1 no longer processes history from another working directory.

### Added
- Double-Esc now clears an idle draft after a confirmation hint, saving it to prompt history; from an empty editor it follows the configured tree, branch, or disabled action.
- Added a searchable command palette with direct action dispatch; slash commands run only from an empty composer, and drafts are never touched.
- Added deep-interview intent manifests that preserve user-locked artifacts, surfaces, and integrations through Round 0.
- Added Telegram `/btw <question>` support through the `ephemeral_turn_v1` SDK capability in authorized known private-session topics. It uses current-session context in an isolated side turn without injecting or persisting user or assistant messages in main history, remains available while the main turn is busy, permits two concurrent questions per logical session, and cancels provider work at the 120-second host deadline; `notifications.telegram.btw.enabled` defaults to `true` as the local kill switch.
- Telegram `/btw` replies now use eligible complete Bot API 10.1 structured Markdown once as `{rich_message:{markdown,skip_entity_detection:true}}`, correlated in the source topic. Tables and math use Telegram Markdown support rather than outgoing native blocks or media; ineligible content and definite rich rejection use correlated HTML, while ambiguous outcomes never retry or fall back and `/rich off` remains HTML-only.

### Fixed

- Sessions running a managed model fallback chain no longer wedge with repeated `Retry failed after N attempts: The object can not be cloned.` after a provider HTTP error whose response carried headers. The live `Headers` instance attached to the provider error was not structured-cloneable, so the managed attempt snapshot replaced the real provider failure with a local `DataCloneError` on every model in the chain — misreporting `Model fallback chain exhausted` and permanently failing every subsequent prompt while the provider kept erroring. Transport facts now retain only plain-record retry signals, and the attempt snapshot degrades gracefully instead of failing the attempt.

### Fixed
- Active deep-interview sessions now resume automatically after a normal assistant stop while ordinary active interviewing remains eligible, using bounded workflow-state continuation; recovery, leak, stale-state, handoff, and crystallization blocks remain Stop-gate handled.

### Changed
- Removed deprecated `DiscoverableMCPTool`, `DiscoverableMCPSearchIndex`, and related MCP-only discovery helper exports. Use the unified `DiscoverableTool` discovery APIs; the `mcp.discoveryMode` settings alias remains supported.

### Fixed
- Connected MCP server instructions now remain untrusted user-role data instead of entering the cached system prompt; hostile file paths, working directories, and workspace-tree metadata are structurally encoded, and volatile project context is removed from durable session history between requests.
- Restored the strict G002 public-surface quarantine by removing the default README advertisement for the private coordinator MCP runtime.
### Added

- Added opt-in prompt suggestions (Claude Code-style ghost-text autocomplete): with the `promptSuggestions` setting enabled, a smol-model prediction of your likely next prompt renders as dim ghost text in the empty composer after each agent turn; Tab accepts it, typing dismisses it, and a new turn clears it. Predictions are heuristically gated (silence on evaluative/meta/agent-voice/overlong output) and never generated while the composer has text or a turn is streaming.

## [0.11.1] - 2026-07-16

### Fixed
- Published the Windows Bun workflow-gate durability fix that tolerates unsupported `EPERM` directory `fsync` failures after an already-committed atomic rename; the 0.11.0 package changelog described the fix, but the released artifact did not contain it (#2316).

## [0.11.0] - 2026-07-15

### Fixed
- Discord inbound lease recovery now exposes a deterministic scheduler seam, preserves exponential retry wakeups after transient endpoint lookup failures, and cancels pending recovery exactly on daemon stop instead of relying on wall-clock sleeps in regression coverage.
- Input-free interactive TTY startup now keeps the TUI reachable when configured model profiles are missing required provider credentials, skips only the blocked profiles, and preserves later `--mpreset` and explicit model/thinking precedence; redirected terminals, input-bearing, resume-continuation, image-only, print/text, and unrelated activation failures remain fail-closed (#2277).
- Windows Bun runtimes no longer crash while committing the durable workflow-gate store when directory `fsync` reports the unsupported-operation code `EPERM`; unexpected directory-sync failures remain fail-closed.
- Browser geo settings now propagate coherently across request `Accept-Language`, navigator languages, and `Intl` locale/timezone surfaces; configured managed browsers are isolated by geo/profile posture, concurrent acquisition is serialized, and unset geo preserves Chromium's native locale/timezone instead of injecting a fixed New York profile.
- Windows startup no longer fails when the platform rejects the workflow-gate store's parent-directory durability sync with `EPERM`; file fsync and atomic replacement remain enforced.

- Cooperative mid-run context maintenance now waits at a cancellation-aware FIFO consumer-drain checkpoint before flushing or rewriting session history. Materialized tool results and steering messages are synchronously canonicalized first; aborted barriers and hook/signal-cancelled compactions settle without rewriting or scheduling a continuation. Promotion, pruning, and compaction each start a clean provider/prompt-cache epoch. Script-aware #2067 unsent-delta accounting remains cache-free and distinct from the lifecycle checkpoint.
- Classified the cooperative mid-run maintenance driver and token estimator test seams as locked non-public SDK exclusions, restoring deterministic operation-inventory generation and post-merge dev CI coverage.
- Accepted SDK prompts now deliver correlated `agent_start` and exactly one terminal lifecycle frame directly to the requesting authenticated WebSocket connection while retaining replayable host events. Harness owner observation also waits for every previously accepted frame to finish serial persistence, so message-update storms and polling gaps cannot hide sticky completion evidence (#2169).
- `/new` now fails closed while owned subagent shutdown is unproven: it preserves the current session identity and shows an actionable cleanup notice. Successful replacement waits for cooperative child cancellation, cancels owner jobs before switching identity, and `/drop` creates the replacement before attempting old-session deletion (#2261).

### Added

- Added the additive SDK Q10 model-catalog DTO: `Q10`, `models.list/current`, `models.list`, and `models.current` now return the same paged registry rows with reasoning/thinking capability metadata and current-model readback. `thinking.validLevels` is an `off`-first canonical menu; sparse raw reasoning descriptors remain available for inspection. The public DTO types are exported from `@gajae-code/coding-agent/sdk`, while undocumented `/sdk/models` deep imports remain unavailable. `inherit` is readback-only and malformed descriptors fail with a safe internal SDK error (#2163).
- Gajae Pet selection is now terminal-capability aware: unsupported terminals show an actionable warning (with multiplexer-specific guidance for tmux/screen/zellij, including the `PI_FORCE_IMAGE_PROTOCOL=sixel` expert opt-in), `/pet` and Settings disable the unavailable `RedGajae`/`BlueGajae` choices while `off` stays selectable, a saved-but-unavailable choice is identified as `(saved)`, and the public command names are consistent across execution, completion, and inline hints (`/pet RedGajae`, `/pet BlueGajae`, `/pet off`, case-insensitive).
- Added the standalone `@gajae-code/bridge-client` transport-only v3 SDK package. It exports `SdkClient` and its associated types; `@gajae-code/coding-agent/sdk` remains a compatibility re-export with the exact same class identity. Historical BridgeClient backend protocol, handshake, commands, SSE, and host-control bypass surfaces remain unavailable.
- Added explicit `--mcp-config <absolute-path>` support for one trusted, tools-only MCP config in top-level standalone sessions without enabling automatic user or project MCP discovery; exact reads reject links and identity changes, and MCP tool-name collisions fail closed.
- Added additive v3 workflow-gate correlation compatibility surfaces (#2171): explicit Rust workflow-frame readers/registration preserve `workflowGateId` without changing legacy `ActionNeeded`, `ServerMessage`, or `register_ask`; N-API retains `registerAsk` and adds correlated/arbitrated registration and exact unclaimed-retirement APIs. Private presentation leases, routes, claims, receipts, epochs, and endpoint generations remain non-public.
- Added the versioned, readonly managed session-directory SDK: `SESSION_DIRECTORY_API_VERSION`, `resolveManagedSessionScope`, and `listManagedSessionCandidates` are exported from `@gajae-code/coding-agent/sdk`. The package boundary continues to reject private `session/internal/*` imports. Managed writes use v2 workspace scopes with validated opt-in legacy copy-retain migration (#2177).

### Changed
- Explicit fold choices from the user shortcut or extension `setToolsExpanded` now pin a block for its component lifetime, so automatic stamping no longer overwrites them; sessions that never toggle are unchanged.

- Renamed the notifications SDK to the Gajae-Code SDK: `docs/notifications-sdk.md` is now `docs/sdk.md`, `src/notifications/` is now `src/sdk/bus/`, and `src/sdk.ts` is now the `src/sdk/` module directory. Old deep-import specifiers no longer resolve.
- Moved SDK discovery from `.gjc/state/notifications/` to `.gjc/state/sdk/`. Restart sessions and daemons together when upgrading; the runtime does not dual-scan the old and new directories.
- Removed the `--mode rpc`, `--mode rpc-ui`, and `--mode bridge` external ingress modes. Machine clients must use the SDK WebSocket interfaces documented in `docs/sdk.md`; no RPC or Bridge compatibility path remains.
- Documented the current GPT-5.6 Codex and combo profile mappings as product judgments, including the durable `opus-codex` `anthropic/claude-sonnet-5` planner override and `fable-opus-codex` `anthropic/claude-opus-4-8:medium` planner.
- Resolved the SDK v3 workflow-gate shipping classification (#2171): `workflowGateId` and Q12 diagnostics are additive SDK v3 surfaces, while `action_needed.id` remains the transient, generic `reply.id` authority. `expectedSessionId` omission remains accepted and audited for the entire SDK v3 line; new clients must send it, and mandatory enforcement or removal can occur no earlier than SDK v4 only after at least one full published deprecation release/window with deployed-client notice. Explicit session mismatches fail closed before resolution; mismatched sessions, stale/reissued actions, and unsafe ambiguity never regain authority.
- Documented release pairing: the `@gajae-code/coding-agent` runtime and `@gajae-code/natives` native addon ship from the same source release at exact matching package versions, with the native loader version sentinel enforcing the pair. Mixed native/runtime versions are unsupported and cannot claim SDK compatibility.
### Fixed
- Startup continuation now participates in the existing managed fallback and in-flight recovery envelope, preventing a retryable resumed turn from publishing `agent_end`/idle before retry success, exhaustion, cancellation, or startup failure has settled (#2092).

- Fenced SDK WebSocket lifecycle callbacks and request settlement to the owning retry cycle/socket incarnation, so stale open, close, error, message, and timeout delivery cannot reject or corrupt work on a replacement connection; sent mutations remain non-replayed and deterministic race regressions cover the reconnect boundary (#2164).
- Owned LSP stdin `EPIPE` and `ERR_STREAM_DESTROYED` failures now terminalize and evict only the affected client, reject pending and stale requests with a stable transport-closed error, and permit clean client recreation without suppressing serialization or unrelated sink failures (#2138).
- Serialized fresh prompt preflight and durable default-model selection through deterministic per-session admission, preventing a later `model.set` from overtaking an earlier prompt while preserving provider-stream and continuation behavior (#2199).
- Direct SDK broker lifecycle hosts now wait for their session-owned startup capability before publishing lifecycle readiness. Only a `started` capability permits a ready marker; failed startup requires proven process, endpoint, and host cleanup before the broker reports `spawn_failed` with no endpoint available, otherwise it preserves terminal uncertainty (#2168).
- Isolated default source-mode SDK broker and session-host respawns from caller Bun startup policy. Internal children now use fixed `--no-env-file` plus product-owned config/entrypoint paths, scrub `BUN_OPTIONS` and mutable compiled markers, preserve compiled/custom launch compatibility, and clean up owned broker children on startup failure without leaking launch secrets (#2178).
## [0.10.2] - 2026-07-14

### Added

- Gajae Pet selection is now terminal-capability aware: unsupported terminals show an actionable warning (with multiplexer-specific guidance for tmux/screen/zellij, including the `PI_FORCE_IMAGE_PROTOCOL=sixel` expert opt-in), `/pet` and Settings disable the unavailable `RedGajae`/`BlueGajae` choices while `off` stays selectable, a saved-but-unavailable choice is identified as `(saved)`, and the public command names are consistent across execution, completion, and inline hints (`/pet RedGajae`, `/pet BlueGajae`, `/pet off`, case-insensitive).

### Changed

- Documented the current GPT-5.6 Codex and combo profile mappings as product judgments, including the durable `opus-codex` `anthropic/claude-sonnet-5` planner override and `fable-opus-codex` `anthropic/claude-opus-4-8:medium` planner.
- RPC clients can now durably select the machine-global default model and effective thinking level for subsequent messages, while project policy and resumed session history retain precedence; a late live-apply failure now restores the prior runtime model and thinking level without masking the original error.

### Fixed

- Gajae Pet overlays no longer leak images or stale pixels across lifecycle changes: each widget owns a randomized Kitty image ID (deleted on disable, replace, switch, and dispose), the previous Sixel footprint is tracked and erased on movement, resize, and narrow-terminal fallback, replaced pet widgets are disposed before their successors install, and a saved pet preference survives editor replacement while graphics are still unavailable (so a delayed Sixel capability probe can still activate it). Teardown is exception-safe and idempotent: a failed or unavailable terminal write never aborts logical disposal or steals a successor widget's overlay slot, and Sixel/Kitty cleanup authority is retained until the erase is actually delivered so a later mode switch or dispose retries it.
- Gajae Pet cleanup that fails during final widget disposal is now retained by the TUI for retry, and Kitty image IDs remain reserved until their exact-ID delete is delivered.
- Fixed `gjc --tmux` startup from GNOME and other VTE terminals by recognizing `vte-spawn-*.scope` only when cgroup metadata proves matching user-manager ancestry (#2159).
- Fixed native Windows `GJC_TMUX_COMMAND=tmux` resolution when WinGet's `tmux.exe` is a psmux alias with a generic `tmux` banner: GJC now compares executable identity with the installed `psmux.exe`/`pmux.exe` companions and fails closed when identity cannot be established instead of authorizing native-tmux semantics (#2086).

- Accepted or declined initial external credential-import decisions now persist across normal restarts and upgrades, suppressing automatic startup and bare `/login` discovery; same-version legacy markers remain compatible and explicit `/provider` import remains available ([#2117](https://github.com/Yeachan-Heo/gajae-code/issues/2117)).

## [0.10.1] - 2026-07-13

### Added

- Added an owner-proof idle session reaper and `gjc_coordinator_stop_session` for ephemeral (delegate-created) coordinator sessions. Termination goes exclusively through the canonical SDK broker `session.close` lifecycle (durable process identity verified before close) — never a raw `process.kill` or tmux control. The reaper re-validates ephemeral and no-active-turn state at close time under the same per-session mutation lock as delegate reuse, and purges coordinator metadata only after SDK closure is verified, retaining it when closure cannot be confirmed (#2080).

- Added an owner-proof idle session reaper and `gjc_coordinator_stop_session` for ephemeral (delegate-created) coordinator sessions. Termination goes exclusively through the owner-proof `forceCloseGjcTmuxSession` path (pid, native session id, owner generation, server key, and start time all verified before SIGTERM) — never a raw `process.kill`. The reaper binds each close to the persisted runtime-state file, re-validates ephemeral and no-active-turn at kill time under the same per-session mutation lock as delegate reuse, and purges state only on verified termination (#2080).
- SDK clients can now pass an explicit thinking level to `model.set`, atomically applying the effective model and thinking level to the active session and the machine-global default for future sessions, while project policy and resumed session history retain precedence.
- Context-usage tokens/% now use provider-reported usage as the single source of truth on every surface. `AgentSession.getContextUsage()` returns a source-tagged snapshot (`source: "provider_anchor" | "heuristic" | "unknown"`), and the status-line `context_pct` segment, inline model percentage, and `/context` totals consume that snapshot instead of recomputing an independent heuristic sum — so footer, status line, `/context`, ACP, and RPC can no longer disagree about the same session state. Heuristic estimation now applies only when no provider anchor exists (session start, aborted/error-only turns), and that fallback now includes the fixed system-prompt/tool/skill context it previously omitted. The pre-prompt compaction estimate honors the latest-compaction boundary (never anchors on stale pre-compaction usage totals), unknown post-compaction usage stays unknown (status line renders `?`; ACP omits the `usage_update` instead of reporting `used: 0`), and `/context` labels its total by provenance with a reconciliation line when the estimated category composition diverges from the provider-reported total.
- Post-durable default-model-selection failures now expose a stable SDK error with bounded `restored`, `partial`, or `unknown` rollback state; preflight and validation errors remain unchanged.
### Fixed
- Made SDK host startup publish its runtime and initial identity frame before exposing direct-v3 transport endpoints, preventing early replay/control frames from being dropped or dereferencing uninitialized session state under concurrent test and process load.

- IRC deliveries now accept their exchange batch in the recipient's volatile current-session queue before recipient/main UI observations or sender success. Awaited deliveries generate the reply first, then accept the ordered incoming + auto-reply pair and commit the IRC roster claim before observation; provider failures and sender aborts before acceptance leave no ghost exchange, while observer failures after acceptance are isolated. This is not a durability guarantee: durable history injection remains a later flush and no fsync, recovery, persistent IDs, or deduplication was added.
- `/model` role-agent assignments now replace active model-profile overrides immediately, so changing architect, critic, planner, or executor models and reasoning levels takes effect in the current session and remains persisted afterward.
- Print mode now records terminal text-mode errors as exit status 1 (or 78 for context overflow) without bypassing output quiescence or session disposal. It retains JSON event delivery through disposal and suppresses `EPIPE` from its owned stdout; `ERR_STREAM_DESTROYED` is suppressed only after that `EPIPE` has latched, while other output failures remain errors.
- Preserved clipboard image attachments when the interactive editor clears the composer before dispatching the submit callback, so Alt+V image placeholders still send their image blocks instead of placeholder-only text (#2126).
- Extension contexts now receive a defensive copy from `getSystemPrompt()` instead of the live mutable system-prompt array, so an in-place mutation by an in-process extension can no longer bypass context-revision tracking and serve stale display-only context-usage estimates.
- Completed bracketed-paste input now returns a manually paged transcript to live output before the paste is dispatched, including asynchronous consumed and unconsumed paste paths.
- Prevented orphaned background processes by reaping failed detached harness owners and their exact SDK session children with verified TERM/KILL cleanup, giving only the invocation-scoped transport-close capability permission to break a direct owner-stop cycle while every public stop caller awaits truthful teardown, keeping runtime-owner lease/heartbeat authority live while failed teardown retries, binding broker discovery to OS process incarnations before accepting retained ownership, making isolated ACP and broker subprocess tests stop their exact broker before deleting temporary state, and adding a cooperative Telegram daemon watchdog that stops superseded or non-progressing owners.
- Preserved clipboard image attachments when the interactive editor clears the composer before dispatching the submit callback, so Alt+V image placeholders still send their image blocks instead of placeholder-only text (#2126).
- Completed bracketed-paste input now returns a manually paged transcript to live output before the paste is dispatched, including asynchronous consumed and unconsumed paste paths.
- Telegram `/usage` now includes secret-safe 5-hour and weekly limit summaries when normalized provider data is available, `/reasoning` reports effort/scope/display with compatible controls and accurate help, and `/model` offers owner-authorized one-shot inline buttons with stale/invalid callback safeguards (#2095).

## [0.10.0] - 2026-07-12

### Added

- Added an opt-in `/pet on|off` composer companion with idle gaze, working claw motion, and occasional automatic flex animation on Sixel- and Kitty-graphics terminals.
- Added Grok 4.5 to the bundled Grok Build catalog with its published 500K context window, multimodal input and token pricing, normalized its official `grok-4.5-latest` and `grok-build-latest` aliases, and limited reasoning effort to the documented `low`, `medium`, and `high` levels.

- Added `notifications.sessionScope` (`all` default | `primary`). Under `primary`, the separate-process child sessions GJC spawns (team workers, harness RPC owners) no longer register their own Telegram forum topic / notification endpoint unless they explicitly opt in (`GJC_NOTIFICATIONS=1`, the `/session_create` path). The default `all` fully preserves current behavior, and user-opened CLI/tmux/headless sessions are never affected. The provenance marker is per-spawn and non-dynastic (consumed once at session startup, never inherited by grandchildren) (#1908).
- Coordinator MCP `gjc_coordinator_start_session` and the `gjc_delegate_plan`/`gjc_delegate_execute`/`gjc_delegate_team` tools now accept an optional `mpreset` argument that authoritatively activates a GJC model profile for a fresh session, with the same semantics as `gjc --mpreset <profile>` (#2003). The name is resolved through the merged built-in/custom profile registry before launch (so custom profiles keep working), legacy profile aliases are canonicalized exactly like the CLI (e.g. `codex-standard` → `codex-medium`), and the canonical profile is injected into the child startup command so the profile's default and per-role models apply from the first turn; unknown names are rejected with the available-profile listing and never reach a spawned command, and an unreadable/invalid `models.yml` fails closed with a distinct `model_profile_registry_error` rather than silently degrading to built-ins only. An explicit empty/whitespace `mpreset` is rejected rather than treated as omission. The effective profile is recorded on the session and surfaced in coordinator status/readback, reusing a session with a conflicting `mpreset` fails with a stable `mpreset_conflict` reason, and calls that omit `mpreset` keep their current behavior. The existing advisory `model` prompt hint is unchanged, and no machine-wide durable default is introduced (#2066 remains out of scope).

- `notifications.telegram.topics.nameTemplate` configures the Telegram forum-topic title, with `{repo}`, `{branch}`, and `{title}` placeholders, so operators can put the session title first (e.g. `"{title} · {repo}/{branch}"`) and keep concurrent sessions on the same checkout distinguishable in the topic sidebar. The template applies only when every placeholder it references resolves for the session; otherwise the daemon falls back to the built-in `{repo}/{branch} - {title}` composition (and its title/repo/branch fallbacks). Unset preserves the current naming exactly (#1909).
- Added the IRC chat-room surface: IRC messages persist in the main chat scrollback by default (no 10s TTL) with stable observation ids and a rebuild-aware ledger, and the agent is IRC-aware. Enabling `irc.sidebar.enabled` moves the persistent record into the opt-in `alt+i` sidebar split while inline display stays ephemeral. Message persistence and agent awareness are default-on; the sidebar panel itself is opt-in (#2018).
- Added a transport-agnostic, secret-safe shared notification service (status, health, test delivery, ownership-protected recovery) consumed by both the `gjc notify` CLI and the cross-mode `/notify` slash command (TUI + ACP), so onboarding and daemon recovery no longer duplicate daemon/config logic per surface. `/notify` now exposes `status|health|test|recovery|setup` and `gjc notify` gains `health`/`test`/`recovery` with `--probe`/`--message` (#2050).
- Added beginner-safe `gjc daemon` operational shortcuts sharing one operator contract so the guided human surface and machine-readable `--json` never drift: a `restart` alias that resolves to reload-if-running-else-spawn, concise per-daemon output by default with `--verbose`/`-v` for runtime detail and the full roots list, and an actionable structured recovery path when token/chat ownership mismatches instead of a large payload ending in `blocked`. Exit codes stay 0 on success / 1 on failure (#2057).
- Added fail-closed ACP session deletion: the delete path refuses rather than proceeding when the target session cannot be safely resolved, and retains the inode in the replacement case (#2074).
- Added the interactive **Notifications** settings tab with masked Telegram setup, global and session controls, health/test/recovery/reconnect actions, atomic preference saves, and safe blocked-owner recovery guidance (#2050).

### Changed

- Migrated the repository type-check and release declaration pipeline to stable TypeScript 7.0.2 with a non-mutating publish-type gate.
- Rebalanced GPT-5.6 Codex and combo presets around published family tiers and reasoning-effort curves. The executor assignments are informed by descriptive repeated local exact-edit evidence from selected TypeScript tasks; default, planner, architect, and critic assignments remain product judgments rather than benchmark claims.
- Cache-miss diagnostics now separate actionable, diagnostic-only, and provider-side-suspected causes instead of asserting a user-side fix for every miss (#2020). A large, costly miss with no cache reads or writes is reported as a neutral `Cache notice` marked "provider-side suspected / not user-actionable" (with what GJC cannot determine) rather than telling the user to keep a stable prefix; a miss with reads but no writes is reported as diagnostic-only without asserting a single cause; and the "cache write without enough matching reads" warning now only fires when reads actually fail to cover the writes. The existing miss cost summary and the #1929/#1936 pricing/provenance safeguards are unchanged.

### Fixed
- `/btw` side questions now start independently while the main answer is still streaming, read only committed conversation state, avoid main-session callbacks/history/IRC-roster mutation, disable silent provider retries, and fail the side request visibly if no provider event arrives within 15 seconds.

- Fixed native-Windows coordinator/runtime compatibility by treating psmux's successful empty `list-sessions` response as an absent server while keeping malformed rows fail-closed, reading process incarnations through a validated PowerShell start-time query, sharing the existing BOM-free encoded PowerShell pane command, preserving multiline SDK prompts behind semantic readiness, and retaining runtime command/turn acknowledgement identity across Windows-equivalent workspace paths (#2145).
- The coordinator MCP owner-server probe now recognizes tmux ≥3.7's missing-server diagnostic (`error connecting to <socket> (No such file or directory)`) as an absent server. tmux 3.7 changed the wording from the older `no server running on <socket>`, which the coordinator probe did not match — so a brand-new coordinator socket (which never has a server yet) was misclassified `unverifiable` instead of `absent`, and **every** `gjc_delegate_*` / session create failed closed with `coordinator_tmux_owner_server_unverifiable` on tmux ≥3.7. The coordinator and `gjc` harness probes now match the same no-server wordings the other owner-isolation probes already did.
- Preserved explicit Telegram forum-topic renames as durable user-owned names, immediately re-asserting delayed edits while retaining restart and rename-race recovery (#1910).
- Prevented typed provider safety stops from entering automatic retry loops and aligned ACP refusal reporting with the provider-native classification.
- `gjc resume` now aliases value-less `--resume`, requests confirmation before opening and continues a resumable tail once only; terminal tails open idle, and headless bare resume exits with explicit `--resume <id>` guidance (#1973).
- Telegram answers to interactive and unattended `ask` prompts now receive a semantic, origin-bound `Selected!` acknowledgement before workflow continuation, with typed multi-select controls, no acknowledgement for toggles/clarifications/skips, at-most-once delivery attempts, and truthful failure/unknown outcomes (#1974).
- Isolated unsafe Linux/systemd tmux owners into independently proven user scopes before server creation, added generation-bound exact-SIGTERM shutdown verdicts and deduplicated incident/recovery provenance, and removed pane payloads from raw session lifecycle evidence (#1938).
- Coordinator MCP stdio server now dispatches JSON-RPC requests with bounded concurrency and answers the standard `ping` utility, so a long-running tool call (e.g. `gjc_coordinator_await_turn`, which polls for minutes) no longer starves keepalive on the same channel — without the state/lifecycle races unbounded concurrency would introduce. Same-session mutations (notably `send_prompt`) serialize through a per-session lock so concurrent calls can no longer persist two "active" turns; `ping` is a reserved control frame that bypasses the data-concurrency cap; data-handler fanout is bounded (excess rejected as `server_busy`); `writeLine` failures are terminal instead of poisoning the write chain; and EOF drains in-flight handlers under a bounded timeout before returning.
- Prevented best-effort coordinator runtime-state persistence from surfacing unhandled rejections when a session root disappears during teardown, preserved public-safe coordinator errors including bounded-concurrent handler failures, bound tmux GC pruning to the collected native session identity and owner generation so same-name replacements are never removed, and preserved subsecond ordering between canonical verdict artifacts and their evidence receipts (#1938).
- The `browser` `close`/`close all` tool now enforces one end-to-end deadline (the existing `timeout` option, default 30s) across the whole teardown chain — `releaseAllTabs`, `waitForClosed`, `worker.terminate()`, and `releaseBrowser()`/`disposeBrowserHandle()` — so an externally CDP-connected target dying mid-close can no longer hang or terminate the calling agent session. Steps that exceed the shared budget are detached best-effort with their rejections swallowed (no unhandled rejections, no worker/browser/tab registry leaks, timers cleaned up); the tool always settles with a close count or a structured error, and normal live-target close semantics are unchanged (#2027).
- Pre-prompt context estimates now anchor on the last successful assistant's total usage (input + cache + output) instead of prompt-only tokens, and skip error/aborted turns when picking the anchor, so a large-reasoning turn (e.g. GPT-5.6 Sol at high efforts) no longer vanishes from the estimate right before the next request. The compaction token-correction ratio likewise no longer counts the anchor assistant's own output in its heuristic denominator, since that output was the anchored request's response rather than part of its prompt.
- The coordinator MCP tmux owner-server probe (`probeServer`: `tmux list-sessions` plus a portable `ps` incarnation read) now tolerates transient spawn failures. Under load a momentary `Bun.spawn` failure (e.g. `EAGAIN`, or a brief tmux/`ps` hiccup) threw straight out of the probe, and because `proveCoordinatorOwnerServer` only retries on non-safe *state* — not on a thrown exception — a single blip aborted the whole delegate immediately with `coordinator_tmux_owner_server_unverifiable` (a ~0.03s hard failure). The probe now surfaces a transient failure as an `unverifiable` state so the existing bounded retry loop can re-probe once it clears; owner isolation is unchanged (`unverifiable` still fails closed, creating no session).
- Coordinator-created tmux sessions now require an immutable, launch-bound runtime readiness marker before any prompt keys are injected, preventing early input races and preserving recoverable readiness-timeout evidence (#2009).
- Unsent-context token estimates are now script-aware (CJK characters charged near 1 token/char instead of chars/4), and the per-message delta estimate is no longer cached: the compaction-threshold decision always recomputes from current content, so an in-place mutation (e.g. a same-length ASCII→CJK edit) can never gate the threshold with a stale 4x-undercounted estimate.
- Capability-gated controlled ask delivery per connection: clients default-deny until they negotiate `ask_controls_v1`, renegotiate after reconnect, and receive the additive non-actionable `action_unavailable` frame instead of stripped option buttons when non-capable (#2029).
- `NotificationServer#pushFrame` now rejects `ActionNeeded` frames; emit asks and idle notifications through `registerAsk` / `noteIdle` so controlled asks remain capability-gated per connection (#2029).
- A rolling/in-place upgrade that left a still-live pre-upgrade Telegram daemon owning the lock is now reloaded instead of silently attached. The old daemon speaks the pre-#1999 wire protocol without ask-ack/controls, so its `Selected!` acknowledgements were dropped; an operational `DAEMON_GENERATION` (tied to `NOTIFICATION_PROTOCOL_VERSION`) now flags a fresh live owner running an older generation as reload-required, and the new host registers its session root then hands off through the cooperative SIGTERM/control path (#2028).
- A numeric gate reply is an option index: an out-of-range index is no longer accepted as free-text `Other`. `mapAnswerToGate` returns a discriminated result and the unattended handler closes the exact claim/receipt and reissues instead of durably accepting or emitting a `Selected!` ack; the JSON-string free-text path is preserved (#2030).

## [0.9.6] - 2026-07-10
### Changed

- Moved the `codex-eco`/`codex-medium`/`codex-pro` presets and the `opus-codex`/`codex-opencodego`/`fable-opus-codex` combo presets from `gpt-5.5` onto the GPT-5.6 tier family: Sol drives `default` and `architect` on every codex preset (eco `sol:medium`, medium `sol:high`, pro `sol:xhigh`/`sol:max`), with Luna/Terra covering the lighter executor/planner/critic roles by tier.
### Fixed

- Corrected launch-update guidance: only recognized Bun global installs, recognized Windows npm installs, and supported bundled-installer binaries may use `gjc update`; source checkouts and dev links must use their checkout workflow, while unrecognized package-manager or PATH installs must use their original update method. Successful updates require authoritative post-update version and smoke verification.

### Fixed

- Fixed v0.9.3–v0.9.6 compiled release binaries crashing at first real launch with `Cannot find module '/$bunfs/root/node_modules/handlebars/lib/index.js'` while `--version`/`--help` still worked (#1939). `--minify` silently dropped the handlebars bunfs extra entrypoint; handlebars is now bundled through a statically-traceable `require("handlebars")` in `@gajae-code/utils` prompt rendering (still lazy at runtime), and the fragile extra entrypoint is gone from both release and dev compile args. `--minify` and its startup-RSS win are retained.

## [0.9.5] - 2026-07-09
### Fixed

- ACP permission prompts now honor `clientCapabilities._meta.gjc.permissionHandling` and the `GJC_ACP_PERMISSION_MODE` fallback, so `auto` and `always-allow` no longer emit `session/request_permission` calls while invalid values fail safely to `prompt`.
- Model selector batch assignments ("Set for all role agents" / "Set for all targets") now open the reasoning-effort menu whenever any batch target requires an explicit choice (e.g. Anthropic reasoning models like `claude-fable-5`), and the chosen effort plus the full batch survive the menu. Previously the menu never appeared for Anthropic models (silently persisting `:off` selectors for every role agent), and for OpenAI/Codex models picking an effort collapsed the batch to a DEFAULT-only assignment.
### Added

- ACP clients now receive GJC automatic-compaction start/end state through additive `session_info_update` metadata, including the compaction action, trigger, retry/abort/error outcome, and busy-to-idle phase transitions.

## [0.9.4] - 2026-07-09
### Fixed

- `RpcClient.onSessionEvent()` now exposes the full renderer-facing agent-wire event stream instead of dropping non-core session events such as notices, todo reminders, retry events, subagent steering, thinking-level changes, and goal updates, while `onEvent()` remains the filtered legacy `AgentEvent` subscription path.
- Clarified `/session delete` help, confirmation, and completion wording to specify current vs. selected session transcript/artifact deletion and that other sessions plus topic/history metadata are not removed (#1913).
- Home screen build labels now come from install/build metadata, so release binaries and package installs no longer show a misleading dev build label (#1911).

## [0.9.3] - 2026-07-09

### Fixed

- Stabilized hotfix release checks by making generated docs lazy-load verification independent of Bun's module cache, preserving the docs index sync gate, and polling coordinator MCP watch reconciliation for runtime prompt acknowledgements.
- Updated macOS-specific queue shortcut and clipboard-temp image paste tests to match the shipped runtime behavior.

## [0.9.2] - 2026-07-09
### Added

- Added `gjc --credential <selector>` for pinning a stored provider credential by `email:`, `id:`, `account:`, `project:`, or `provider/email:` during a session.
- Added `--mpreset <profile>` support to Telegram `/session_create`, forwarding both `--mpreset <name>` and `--mpreset=<name>` as split argv to the spawned GJC child.
- Added the built-in `skill_discovery` tool for runtime discovery of custom project/user skills without injecting the full skill catalog into the core prompt.
- Pasting or drag-dropping a path to an existing image file now attaches the image and inserts an `[image N]` placeholder, including quoted paths, `file://` URIs, `~/` expansion, spaces, and macOS screenshot narrow no-break spaces.
- Pasted clipboard-temp image paths now attach as `[image N] source="/path"`, so the model receives both the image payload and the retrievable raw temp file path; ordinary saved image paths remain literal prompt text instead of being consumed into opaque placeholders.

### Changed

- The status line (information bar) token-percentage now renders inline within the model segment, right after the reasoning-effort indicator, instead of as a trailing segment at the far end of the line, so the context usage percentage stays grouped with the model it describes. The standalone `context_pct` segment was removed from the `default`, `default-usage`, `compact`, `full`, `nerd`, `ascii`, and `custom` presets (it remains available for `minimal` and custom configs); the inline percentage is color-coded by context-usage level, can be disabled per-preset with `segmentOptions.model.showContextPercent: false`, and is auto-suppressed when a standalone `context_pct` segment is also active so the value is never shown twice.

### Fixed

- Finalized notification turn mirrors now default to the bounded full-turn cap so Telegram's existing chunked delivery can send long assistant answers instead of receiving an already-truncated 3500-character summary; `GJC_NOTIFICATIONS_TURN_MAX` remains available to lower the cap for summary-style mirrors, and live previews stay capped as one editable message.
- `gjc --tmux` now wraps the inner GJC command with a durable `tmux-exit.json` marker next to `runtime-state.json`, so a tmux-resident session that exits before normal runtime-state finalization leaves a public-safe exit timestamp/code for silent-vanish diagnosis (#1746).
- `gjc --tmux` terminal titles now track live tmux session renames while preserving the friendly project/branch title for untouched generated session ids.
- Telegram session forum-topic renames now remain retryable after a transient `editForumTopic` failure, so topics do not get stuck at the provisional `GJC <session>` name while the daemon incorrectly records the final title locally.
- `/effort` selector choices now show the current reasoning effort and mirror `/model` by asking whether to apply the selected effort for the current session or save it as the default, including support for persisting `off`. Default model presets also sync their encoded effort into the persisted effort default so later `/effort` defaults are not overwritten on restart.
- Composer queue submissions (`Alt+Q` / `app.message.queue`) force one-at-a-time follow-up delivery, including replay after compaction, without disabling broader batch mode for other follow-up callers.
- `--credential` now rejects a missing selector immediately instead of falling through into session launch with no output.
- `gjc-session` prompt/monitor postmortem helpers now work on macOS's system Bash/Python, so missing tmux sessions write the public-safe `vanished.json` marker and prompt injection exits through the guarded refusal path.

## [0.9.1] - 2026-07-08

### Added

- Added `/effort` to show or set the current model reasoning effort, including a selector UI when run without arguments and validation for invalid effort names.
- Added `--mpreset <profile>` option to the Telegram `/session_create` command, allowing users to specify a model profile preset when creating a session remotely (e.g. `/session_create path /repo --mpreset codex-eco`). Both `--mpreset <name>` and `--mpreset=<name>` forms are supported.
- Pasting or drag-dropping a path to any existing image file into the interactive editor now attaches the image and inserts an `[image N]` placeholder instead of leaving the raw path in the prompt. Quoted paths, `file://` URIs, `~/` expansion, spaces, and macOS screenshot narrow no-break spaces are handled when the whole paste is a single supported image file.
- Added `GJC_NOTIFY=off` as a per-run completion-notification opt-out.
- Telegram mirror messages now render as native Bot API 10.1 rich messages on by default with a global `/rich on|off` toggle, while failing closed to the previous HTML path.

### Changed

- Upgraded the Extragoal review template with a stronger reviewer lane and optional maximalist N-of-N review recipe.
- Added `--mpreset <profile>` option to the Telegram `/session_create` command, allowing users to specify a model profile preset when creating a session remotely (e.g. `/session_create path /repo --mpreset codex-eco`). Both `--mpreset <name>` and `--mpreset=<name>` forms are supported. The preset is passed as a regular `--mpreset` CLI flag to the spawned `gjc` child, where the existing `applyStartupModelProfiles` flow activates it.
- Added the built-in `skill_discovery` tool for runtime discovery of custom project `.gjc/skills` and user `~/.gjc/skills` without injecting the full skill catalog into the core prompt; selected discovered skills are loaded narrowly through the existing `skill` invocation path (#1815).
- Pasting or drag-dropping a path to any existing image file into the interactive editor now attaches the image and inserts an `[image N]` placeholder instead of leaving the raw path in the prompt. Previously this only worked for clipboard temp files (`/tmp/clipboard-*` or `/var/folders/xx/yy/T/clipboard-*`); terminal drag-drop paths — including shell-escaped spaces and the U+202F narrow no-break space in macOS screenshot names — pasted as long raw path text. Quoted paths, `file://` URIs (decoded via Node's `fileURLToPath` semantics, including Windows drive-letter, `file://localhost`, and UNC forms), and `~/` expansion are handled; the whole paste must be a single path to an existing image file whose content carries a supported image signature (PNG/JPEG/GIF/WEBP), so prose containing paths and non-image files with image-looking extensions are inserted unchanged. When attachment still fails (unsupported content, oversized image, load error), the original pasted text is replayed into the editor instead of being consumed.

### Fixed
- Composer queue submissions (`Alt+Q` / `app.message.queue`) now force one-at-a-time follow-up delivery for those queued prompts, including replay after compaction, so they do not collapse into one batched model call even when the broader follow-up mode is set to `all`.

- `gjc team` on Windows/psmux now targets the GJC-managed leader session by name instead of trusting the inherited `TMUX_PANE`.
- Kitty inline images no longer duplicate/stack or paint over transcript text when the diff renderer repaints the image line.
- Session creation now forwards `--mpreset` as split argv instead of one combined argument.
- The RPC server restores the duplicate-listen boundary error instead of masking it.
- Mac Option+Enter no longer collides with the queue-message binding.
- Telegram completion notifications suppress dot-only messages, and Telegram-originated input is shown immediately and recorded in prompt history.
- The computer red-team CI gate avoids false positives from non-computer-control changes.

## [0.9.0] - 2026-07-07
### Added

- Added `/quit` as an alias of the `/exit` slash command, so the near-universal REPL/CLI quit verb shuts the session down instead of silently falling through to the model as a chat message. Like `/exit` it is a TUI-only shutdown: it is not advertised to ACP clients and is not listed as its own autocomplete/help entry.
- Added a `/clear` slash command that clears the active conversation context while preserving the current session id and durable session history (#1677).
- Added an Extragoal local skill template (`docs/extragoal-skill-template.md`) documenting an external final review gate on top of `ultragoal` — a fresh-context, cross-family, tool-restricted read-only reviewer with a machine-parsable verdict contract, mandatory bundle secret scan, prompt-injection stance, explicit findings triage, and a bounded re-sign loop — plus a `reviewer` stance profile and cross-session review-gate recipe in `docs/multi-vendor-profiles.md`, pinned by `test/extragoal-template.test.ts`.
- Added opt-in live turn streaming to the notifications surface. With `GJC_NOTIFICATIONS_STREAM=1` the session WebSocket now emits throttled non-finalized `turn_stream` frames (each carrying a per-turn `messageRef`) as the assistant message streams, and the Telegram threaded daemon edits ONE message in place — via `editMessageText` keyed by `(session, coalesceKey)` — so the finalized text lands on the same message instead of posting a new one. Off by default; without a `messageRef` finalized turns keep their legacy one-message-per-turn behaviour. Throttle interval is `GJC_NOTIFICATIONS_STREAM_INTERVAL_MS` (default 500ms); streamed frames remain suppressed under redaction.
- Added `GJC_NOTIFICATIONS_TURN_MAX` to raise the finalized `turn_stream` text cap (default 3500 chars, unchanged) so full long turns reach split-capable clients — the Telegram daemon already fans a long finalized turn across multiple messages via `splitTelegramHtml` — instead of being truncated with an ellipsis at the notification layer. The value is clamped to a finite `[280, 40000]` range: a non-finite or non-positive value (unset, `NaN`, `Infinity`, `<= 0`) falls back to the default, so the cap can never be unbounded. Opt-in and off by default (mirror stays a glanceable per-turn summary); live in-progress frames are intentionally NOT raised so a streaming turn stays one editable preview message rather than fanning across sends.

### Fixed
- Composer-pasted images now follow the visible `[image N]` placeholders at submit time, so deleting a pasted image placeholder with Backspace removes that attachment instead of still sending it to the model or Telegram session mirrors.

- Stopped the Hindsight memory backend from injecting stale public-memory-tool guidance into the system prompt. `hindsight/backend.ts`'s `STATIC_INSTRUCTIONS` still instructed the model to "Use `recall`/`retain`/`reflect`", but those tools were removed from the public surface in #341 — they are unregistered in `BUILTIN_TOOLS`/`HIDDEN_TOOLS` and not discoverable — so every Hindsight-backed session advertised three uncallable tools in its injected `# Memory` block. #341 cleaned the local-memory prompts, public docs, and tool registry but never touched the Hindsight backend's own injected block, and its `public-memory-tool-surface.test.ts` guard list omitted `backend.ts`, so the drift went unnoticed. The block now describes the automatic recall/retain lifecycle instead of naming tools, and the regression test now guards `hindsight/backend.ts`.
- Task subagent output-ID allocation now reserves numeric prefixes from `.jsonl` session artifacts and `.patch` sidecars as well as final `.md` outputs, preventing resumed or partial subagent runs from reusing an existing artifact stem (#1733).
- Restored `/changelog` as a built-in slash command, including autocomplete and `/changelog --full`/`/changelog full`, so the What's New prompt no longer points at a missing command.
- Corrected the Extragoal template's read-only enforcement claim: the `--tools` allowlist governs the built-in tool surface, while the runtime injects the session `goal` tool (when `goal.enabled` is on) and `generate_image` (when an image credential exists) beyond it. Because goal's mutating ops persist session mode state, disabling it is now a mandatory gate precondition (`goal.enabled: false` in the review working directory's `.gjc/config.yml`), and reviewer calls outside the allowlist are gate-failing contract violations.
- Corrected the GJC dogfood template's user-level install instructions (`docs/gjc-dogfood-skill-template.md`): the documented verbatim `cp` wrote to `~/.gjc/skills/`, which is not the user-level scan location (`~/.gjc/agent/skills/`), and left the YAML frontmatter mid-file, so the scan's required `description` never parsed and the installed skill was silently skipped. The template now documents the same frontmatter-first `sed` extraction into the scanned location that the Extragoal template ships with, pinned by `test/gjc-dogfood-template.test.ts`.
- Reconciled the skill-template docs with review fixes that raced their merges: the Extragoal reviewer now disables the injected `goal` tool via a dedicated gate directory outside the repository (keeping the reviewed checkout clean, per the #1685 review) instead of an untracked in-repo `.gjc/config.yml`; both the Extragoal and dogfood templates document the one-time skill-discovery enablement (`skills.enabled` plus the install-matched `enablePiUser` **or** `enablePiProject` scan — not both, since the project scan opts every future session into repo-local `.gjc/skills` discovery, per the #1704 review); and the Extragoal template test pins the corrected contract strings.
- Team worker auto-checkpoints now exclude the entire `.gjc/_session-*/` session subtree instead of nine enumerated subdirectories, so extragoal gate receipts and the session activity marker are no longer committed with `--no-verify` and merged into the leader branch on repos that do not gitignore `.gjc/_session-*/`.
- The Telegram threaded daemon now schedules each split chunk of an oversized message through the shared rate-limit pool: a granted send slot maps to exactly one Bot API send, and continuation chunks are re-queued (one token each) rather than fanned out against a single token. This keeps a long finalized turn (e.g. raised via `GJC_NOTIFICATIONS_TURN_MAX`) within the documented per-chat rate-limit / round-robin fairness invariant instead of bursting many `sendMessage` calls on one slot.
- `gjc config list`, `gjc config get`, and `gjc config set` now redact secret-like setting paths even when malformed config files store object, array, boolean, or numeric values there; `--show-secrets` remains the explicit unsafe opt-in (#1738).
- `gjc ultragoal` now fails with a clean CLI error (exit 1, actionable stderr) instead of crashing with an uncaught `SessionResolutionError` stack dump when no session id is resolvable (missing `GJC_SESSION_ID` / `--session-id`), and `gjc ultragoal --help` no longer requires a resolvable session.
- Local memory consolidation no longer falls back to the registry's first model (which can be a retired model the provider rejects with 404, permanently failing every phase1/phase2 job). When no model role is configured and the session model is not yet resolved, it now prefers the most recently used model before falling back to registry order.
- `search` no longer fails schema validation when `paths` is omitted; it defaults to searching the working directory, matching the common repo-wide-search intent.
- Ultragoal validation-batch tests are now hermetic: they previously ran `computeCheckpointChangeSet` against the enclosing repository, so their hardcoded declared change set only covered the branch that introduced them — failing for any contributor branch or dirty checkout that touches other files. They now use a root outside the git work tree with `CI_DEV_CHANGED_PATHS` pinned to the declared paths.
- Added a discoverable `bisect` tool that hunts down the commit responsible for a regression by driving `git bisect` with a shell predicate (exit `0` = good, `125` = skip, any other non-zero = bad). It validates preconditions (git repo, clean worktree, `good` is an ancestor of `bad`), resolves the worktree top level once and runs every git command, the predicate, and teardown from that repository root (so it is safe even when invoked from a subdirectory that a candidate commit deletes), and always restores the working tree on completion, error, or abort — running `git bisect reset` and then discarding any tracked-file edits the predicate made (`git reset --hard`) so the promised clean teardown holds even when the predicate mutates tracked files (untracked files the predicate creates are left in place and reported, never deleted). It supports an `invert` mode to find the commit that *fixed* a behavior, bounds the search with `maxSteps` and a per-step `stepTimeoutMs` (a timed-out step counts as a skip), and reports the culprit commit with its author, date, subject, changed files, and every revision it tested.

## [0.8.2] - 2026-07-06
### Added

- Queued message selector entries can now be reordered with `Ctrl+Up` / `Ctrl+Down`, with `Ctrl+Shift+Up` / `Ctrl+Shift+Down` still accepted when the terminal forwards them, while keeping the current draft intact.

- Added `gjc completion inshellisense`, which generates or installs a Fig/withfig-compatible `gjc` completion spec for Microsoft inshellisense without adding inshellisense as a runtime dependency.

### Fixed
- `gjc team` packaged binaries now avoid persisting Bun virtual `/$bunfs/...` entrypoints as worker commands, so tmux worker panes launch through a real executable or the `gjc` fallback instead of immediately disappearing (#1661).
- `computer` now honors `include_screenshot` and `computer.autoScreenshot` by returning bounded post-action screenshots, and batch steps now respect nested per-step `timeout` values.
- Custom-provider `gpt-5.5` entries without an explicit `contextWindow` now default to the 272K Codex prompt budget unless the provider uses first-party `openai-responses`, so Codex passthrough proxies (e.g. CLIProxyAPI) compact in time instead of dying with `context_length_exceeded` at ~272K while the registry advertises 1M.
- `telegram_send` now rejects files over Telegram's document upload limit before reading them into memory or handing them to the notification sink.
- `telegram_send` now rejects file attachments while Telegram notification redaction is enabled, preventing explicit file sends from bypassing the redaction boundary.
- Telegram notification daemons now persist consumed update ids so threaded replies are not reinjected after a daemon restart replays old `getUpdates` entries.
- Telegram notify setup now hides the interactive BotFather token prompt input, preventing the raw bot token from being echoed into terminal scrollback while pairing notifications.
- Telegram unattended workflow-gate listeners are now disposed when a notification session stops, preventing stale stopped servers from retaining future gate emissions after shutdown or notification restart.
- Telegram notification setup and daemon delivery now reject non-private Telegram chat ids before saving configuration, creating forum topics, or sending session content, preserving the private-chat-only routing boundary.
- Telegram daemon autostart now refuses to attach a new session to a live daemon whose persisted bot-token fingerprint or chat id differs from the current settings, and it avoids registering the session root until ownership is trusted so rotated Telegram credentials cannot keep leaking through the old daemon.
- Skill autocomplete now supports direct skill-name prefixes after prompt text (for example, `please /ra` → `/skill:ralplan`) while keeping bare `/` menus free of skill entries.
- `gjc --tmux` on native Windows/psmux now keeps the status line and composer pinned to the bottom after viewport redraws by honoring the GJC tmux launch marker as a multiplexer signal even when `$TMUX` is absent.
- Provider safety refusals (e.g. Anthropic `stop_reason: "refusal"` → `Refusal (<category>): …`, and `sensitive` → `Content flagged by safety filters`) are now classified as terminal retry errors and surface immediately. They previously fell through to the unbounded `"unknown"` retry class, and because a refusal is deterministic for the submitted context, the session looped refusal → retry → refusal forever — resubmitting the full context every `retry.maxDelayMs` and re-billing it as a prompt-cache write whenever the backoff outlived the cache TTL (#1655).
- Coordinator event watches now wake on runtime sidecar state changes and emit a bounded `turn.acknowledged` event, so a tmux-resident session that accepts a prompt and then vanishes is durably classified as recoverable `tmux_session_missing_after_prompt_acknowledgement` instead of leaving only a watch registration with no terminal verdict (#1496).
- Raw tmux-resident sessions now persist public-safe runtime state under their `.gjc/_session-*/runtime/runtime-state.json` even when no coordinator sidecar env is present, so post-ack owner exits leave recoverable `process_exit_before_terminal_state` evidence instead of looking like successful cleanup with no durable output (#1496).

- `gjc config list`, `gjc config get`, and `gjc config set` now redact secret-like string settings by default, with `--show-secrets` as an explicit unsafe opt-in.

- `/new` session-start notifications now render directly under the welcome panel instead of leaving an extra blank row above the confirmation line.
- Goal completion now preserves the terminal `goal({op: "complete"})` state even when a `goal_updated` extension hook throws, preventing hook-side write errors from trapping a verified ultragoal run in the continuation loop.
- Ultragoal completion no longer requires the computer-use red-team suite for non-computer changes that only touch the shared `tools/index.ts` registration file.
- Task subagent output-ID allocation (`AgentOutputManager`) is now concurrency-safe. `#ensureInitialized` previously set a boolean flag *before* the awaited `readdir`, so when two `task` calls are dispatched in the same turn (they run concurrently as shared-concurrency tools on one session-scoped manager) the second allocation short-circuited initialization and started from index `0` while the first scan was still in flight — colliding with existing `N-*.md` outputs and duplicating ids across batches, which overwrote prior subagent outputs on resume. The scan is now memoized as a promise so concurrent `allocate`/`allocateBatch`/`peekNextIndex` calls await the same `readdir` before `#nextId` is derived.
- Task fork-context seeds (`inheritContext`) now keep a *contiguous* run of the most recent messages. The selection loop in `buildForkContextSeed` walks newest→oldest but used `continue` when a message overflowed the token budget, so it skipped the oversized recent message and scavenged smaller *older* ones — yielding a non-contiguous seed that misrepresents the conversation and breaks the recency contract of the `receipt`/`last-turn`/`bounded` modes. It now `break`s at the first over-budget message, so a subagent receives the actual recent context (or none) instead of a jumbled mix of recent and stale messages.
- The `/settings` → Model → **Default Model Profile** submenu now lists the model profiles from the registry instead of rendering an empty `No matching commands` list. The setting declared `options: "runtime"`, but the selector layer only injected runtime choices for themes, thinking level, and the status-line preset — never for model profiles — so the submenu was always empty. Selecting a profile there now also applies it live (the same `activateModelProfile` path as the `/model` preset flow) and persists it as the startup default, instead of only persisting for the next startup.
- `gjc stats` now resolves to the usage-statistics command instead of being treated as a chat prompt. `src/commands/stats.ts` (backed by the `@gajae-code/stats` dependency via `src/cli/stats-cli.ts`) existed and was fully functional, but the entry was never added to the `commands` registry in `cli.ts`, so `isSubcommand()` returned false for `stats` and the invocation fell through to the default `launch` command — leaving the usage dashboard/summary unreachable and absent from `gjc --help`. Registered the command and listed it in the help surface.

## [0.8.1] - 2026-07-04

### Added

- Skills can now be invoked inline within a prompt (for example, mid-message `/skill:*` references) instead of only as a standalone prompt, with matching autocomplete, input-controller queueing, and ACP dispatch support.

### Fixed

- The Telegram notification daemon now tombstones a session endpoint generation after `session_closed`, preventing the scan loop from reconnecting to the still-live old endpoint and recreating an empty topic immediately after deleting the original topic.
- `/contribute-pr` in the interactive TUI now prepares the redacted manifest and worker prompt without spawning a second GJC process on the same terminal, avoiding competing TUI renderers that make the chat viewport jump around. Run the generated worker prompt from a separate terminal instead.
- The `skill` tool is now a default-registered essential tool instead of a discoverable one, so skill chaining is always available without first activating it through tool discovery.
- The coordinator MCP server now classifies vanished-but-acknowledged tmux turns correctly, avoiding spurious delivery failures when a delegated turn's pane disappears after its prompt was already acknowledged.
- Tool-call argument strings are now sanitized of invalid Unicode surrogate sequences across providers (Anthropic, Bedrock, Google, OpenAI chat/completions/responses, and Codex), including custom Responses tool input, preventing malformed tool calls from breaking requests.

## [0.8.0] - 2026-07-04

### Added

- Reduced default initial-context size by shipping only essential tools up front. `tools.discoveryMode` now defaults to `"all"` and the essential resident set grew to `read, bash, edit, write, search, find`; non-essential built-in tools are hidden behind `search_tool_bm25` and discovered on demand. The system prompt gained a `<tool-discovery>` block that documents the discovery tool and lists discoverable tools with one-line summaries, so the model knows what it can activate without carrying every tool schema. This drops a typical initial context from ~63K tokens (tool-schema dominated) to under 20K.

- The `/model` preset landing now shows the session's current preset, model, and per-role assignments in the header, marks the active preset with `(current)`, and Enter now expands/collapses provider groups (right/left arrows still work).
- MCP server definitions now accept an optional per-server `autoload` boolean that controls whether a configured server connects at session startup. The flag is threaded through the config schema, both parsers (the `mcp-json` discovery reader and the runtime-mcp config loader), an `autoloadOnly` load option on `discoverAndLoadMCPTools`, and a `setServerAutoload` config writer, establishing the config contract that the opt-in runtime and the autoload/connect management surfaces wire up in follow-up changes.
- GJC no longer inherits MCP servers live from other hosts. The MCP capability registrations were removed from the Cursor, Gemini, OpenCode, and Windsurf discovery providers and the MCP-only VS Code discovery provider was deleted, so GJC MCP servers now come only from GJC's own config, plugin bundles, or an explicit import rather than being read live out of another host's configuration; rules, settings, and skills discovery are unchanged.

- Added a `statusLine.maxRows` setting (Appearance → Status Line Rows). When it is greater than 1, status line segments that overflow a narrow terminal now wrap onto additional rows instead of being dropped; the default of 1 keeps the existing single-line, drop-on-overflow behavior. The Appearance preview reflects the wrapped layout.

- The startup welcome screen now renders as a viewport-sized `GJC Forge` panel that reserves the bottom composer/HUD rows, keeps the prompt fixed, and folds What's New highlights into the splash instead of appending a separate changelog block.

### Fixed

- Escape now reliably cancels active context maintenance, handoff generation, retry backoff, and workflow ask dialogs even when transient UI focus or typed drafts would previously consume the key.
- The session tree picker now keeps its selection index valid when it starts on an empty filter mode, receives navigation input, and then switches back to a populated filter.
- The main composer now uses `PageUp` / `PageDown` to page the visible transcript viewport instead of duplicating prompt-history navigation; `Up` / `Down` and `Ctrl+R` remain the prompt-history paths, and autocomplete lists keep their own page navigation.
- Shared the duplicated two-column dashboard renderer used by agent and extension dashboards, keeping narrow-width truncation behavior in one tested component.
- Avoided duplicate line splitting when formatting `ast_grep` matches, reducing allocation in large structural-search result rendering.

- Tab now queues prompt drafts immediately while the agent is streaming or compacting instead of opening/applying forced file autocomplete first.

## [0.7.11] - 2026-07-03
### Fixed

- Coordinator MCP tmux prompt delivery now dismisses any focused TUI autocomplete menu after paste-buffer insertion and before `Enter`, so multiline delegated `/skill:*` prompts submit to the runtime and emit the expected prompt ack instead of selecting the highlighted skill completion and timing out.
- The Python `gjc_rpc` client no longer tears down its reader loop on real server frames it had not modeled: OAuth `open_url` extension-UI requests emitted during `login`, `workflow_gate` frames carrying structured `{value, label, description}` options (the `next_workflow_gate()` queue path re-parsed them with a legacy strings-only parser), and `max`/`inherit` thinking levels returned by `get_state`/model info. `install_headless_ui` now answers interactive UI requests with `extension_ui_response` frames instead of misrouting them as `workflow_gate_response` commands, and `get_pending_workflow_gates()` is exposed as a typed method. Previously dropped payloads (`notice`, `thinking_level_changed`, `goal_updated`, `irc_message`, `subagent_steer_message` events; `agent_end.stopReason`/`telemetry`/`coverage`; `auto_retry_start.unbounded`; `auto_compaction_end.continuationSkipReason`; gate `required`) now parse into typed models, and the env-gated real-binary lane covers the new surface.
- The session picker now keeps its selection index valid after an empty search result is navigated and then cleared, so Enter still resumes the first restored session instead of leaving the list with no selected row.

## [0.7.11] - 2026-07-03

### Fixed

- Notification endpoints now emit the graceful `session_closed` frame on process teardown (native terminal-window close/SIGHUP, SIGTERM, fatal errors) via a postmortem cleanup, so the managed Telegram daemon deletes the session's forum topic instead of orphaning it when the session dies without a clean `/quit`.
- Native Windows terminals now default `app.message.queue` to `Alt+Q` instead of `Alt+Enter`, avoiding the Windows Terminal fullscreen shortcut conflict (#1422).
- Coordinator MCP tmux prompt delivery now submits with tmux `Enter` instead of `C-m`, while preserving runtime prompt-ack/`turn_start` as the delivery success gate (#1409).
- The session-close resume hint now prints the `gjc --resume <id>` command on its own line so it can be selected and copied without the surrounding prose.
- Coordinator MCP tmux prompt delivery now uses a paste buffer for prompt text before submitting with `Enter`, preserving multiline delegated `/skill:*` prompt separators that `send-keys -l` could flatten into an unstarted visible prompt (#1416).

- `/retry` now resumes sessions left with an interrupted user/custom/tool-result tail after a crash or power loss, and recovers unresolved assistant tool-use tails instead of reporting "Nothing to retry".
- Queued prompt shortcuts now keep working during auto context-full compaction: Tab/Alt+Enter queue text immediately, `/skill:*` entries replay through the skill invocation path after compaction, and Alt+Up restores only the newest queued prompt for editing instead of merging the full queue.
- Skill prompt cards now size their collapsed arguments preview to the current terminal width instead of wrapping at a fixed narrow column.
- `gjc update` now refreshes opted-in on-disk default workflow skill copies (written by `gjc setup defaults` under the agent dir) after a successful update, so they no longer stay stale relative to the embedded defaults; copies that were never installed are left absent.
- `gjc migrate` skill imports now tolerate project or agent directories reached through macOS system symlinks such as `/var` while still rejecting symlinked destination skill directories, so local dry-run/live parity tests no longer fail on Darwin temp paths.
- cmux workspace auto-renames now include a `GJC: ` prefix so renamed workspaces remain identifiable as GJC sessions.
- The cmux workspace auto-rename is now ownership-guarded: GJC reads the current workspace title via `cmux workspace list` and only renames a workspace that still has its default title, so it no longer overwrites a user-pinned workspace name or thrash a shared workspace title across multiple sessions running under the same `CMUX_WORKSPACE_ID`. Opt out with `GJC_NO_CMUX_RENAME`.
- `gjc --tmux --resume` now reaches the session picker/resume target instead of auto-attaching a same-branch managed tmux session before the inner resume resolver runs.
- `gjc --tmux` now preserves a newly created managed tmux session when `attach-session` exits after the parent SSH/PTY closes but the tmux server still reports the session live, so closing a Windows Terminal tab no longer kills the Mac host session before reattach.
- Managed `gjc --tmux` launches now size the inner tmux window to the caller terminal minus inherited tmux status lines, preventing the bottom of the GJC input from being clipped when the user's tmux status bar is visible.
- Managed `gjc --tmux` launches no longer pin the initial window to `manual` sizing on native tmux. The pre-attach reassert used `resize-window`, which flips the window's `window-size` option to `manual` and stops `attach-session` from resizing the window to the real terminal; when the attaching terminal was larger than the capture-time size, tmux left a smaller-than-client window and painted the uncovered area with `·` fill. The window now stays on `window-size latest` so it tracks the attaching client (psmux keeps the explicit `resize-window` reassert).

### Changed

- `web_search` latency overhaul: provider hard timeouts are now class-based (pure search APIs 15s, LLM-mediated providers 120s, Kimi 35s aligned to its upstream 30s budget, replacing the uniform 300s ceiling; an explicitly configured `web_search.timeout` still overrides — the schema default no longer reinstalls 300s), DuckDuckGo is fired as a background hedge 3s into a slower primary so a failing primary falls back to an already-settled result, the Gemini 429/5xx retry-delay budget dropped from 5 minutes to 30 seconds, resolved provider chains are cached per AuthStorage for 60s keyed on the credential generation (availability probes skipped on repeat searches; login/logout invalidates immediately), and `WebSearchTool` prewarms the chain at construction. Measured: hung-primary fallback 301s → 15s, slow-failing-primary fallback ~7s → 6s, repeat chain resolution ~26ms → ~0.01ms.
### Fixed

- Kept the default composer border one cell inside the terminal edge to avoid the right rounded input corner protruding in narrow/macOS terminal renderers.

## [0.7.10] - 2026-07-02
### Added

- GJC now best-effort renames the containing cmux workspace to the current GJC session name when running inside a cmux terminal.
- Added a status line default usage preset (#1305).
- Telegram threaded updates now include verbose session identity (session id and cwd) metadata.

### Fixed

- Deep Interview option-clarification prompts now stay out of the interview transcript and ambiguity recorder, so asking about displayed choices no longer persists as the round answer before the user selects an actual option.
- `gjc daemon reload telegram` now spawns the replacement daemon with a stable owner pid so the new daemon does not exit immediately after the short-lived reload CLI process ends.
- Ralplan role agents can now persist Planner/Architect/Critic artifacts through a sanctioned `GJC_RALPLAN_ARTIFACT` env handoff (`--artifact-env GJC_RALPLAN_ARTIFACT`), avoiding restricted-bash failures on markdown containing quotes, backslashes, shell-expansion characters, or command-substitution syntax.
- Managed `gjc --tmux` launches now size the initial window correctly (#1376).
- Coordinator tmux-delivered turns now submit correctly on Enter (#1372).
- Generic long `retry-after` rate limits are now kept retryable instead of being treated as fatal (#1370).
- Anthropic hard 429 responses are now surfaced before the stream watchdog fires (#1369).
- Internal sessions are now hidden from the Telegram recent-session list (#1367).
- Telegram messages exceeding the length limit are now chunked correctly.
- Web search now falls back correctly when the preferred provider is unavailable (#1348).
- Restored the Ultragoal team checkpoint snapshot wording (#1349).
- Deep Interview now exits early for simple tasks instead of running unnecessary rounds (#1311).
- Ultragoal goal identity and state now resolve solely from the canonical session-scoped `goals.json`, eliminating goal-identity drift from transcript-replayed snapshots (#1344).

## [0.7.9] - 2026-07-01
### Added

- Added an optional Tavily web search provider selectable through `providers.webSearch`, using `TAVILY_API_KEY` only when Tavily is selected (#1323).

### Fixed

- Deep Interview now treats English `implementation` and Korean `구현` wording as eventual-target language, not permission to edit code or launch implementation before post-interview approval (#1320).
- Restored default Enter = submit in the main chat composer. #1326 rerouted plain Enter in the prompt to insert a newline (making Ctrl+Enter the submit chord); this reverts that so Enter submits again and Shift+Enter inserts a newline.
- Registered the optional `gjc acp` subcommand so Zed/custom ACP clients can launch the ACP stdio server through the documented command entrypoint, and documented the Zed `agent_servers` custom-agent shape (#1327).
- Compiled binaries can now include the hidden Telegram daemon CLI entrypoint without hanging root startup, and release builds preserve that entry so `gjc notify daemon-internal --smoke` is available in standalone binaries (#1288).
- Documented Windows Terminal BEL limitations for terminal bell notifications and added a PowerShell `completion.notifyCommand` beep workaround example (#1318).

## [0.7.8] - 2026-06-30
### Added

- Terminal bell notifications can now be enabled for agent completion, approval, and ask/user-input prompts (#1277).
- Added a generic `providers.local.openaiCompat` models config path for OpenAI-compatible local endpoints plus `gjc local-provider smoke` for bounded streaming chat-completion checks (#1246).
- Added `gjc local-provider discover` / `models` to list model IDs from a configured local OpenAI-compatible provider via `GET /v1/models`, with clear network and response-shape errors and no chat-completion request (#1247).

### Fixed

- Fixed a class of TUI renderer crashes (`TypeError: undefined is not an object (evaluating 'x.trim'/'x.split')`) where render helpers typed `(x: string)` ran a string op on an optional/possibly-undefined tool-detail field. The deep-interview/ralplan `ask` renderer crashed on a result with a missing `question`; caught during streaming but fatal on render/teardown paths such as `/background` detach. Hardened `normalizeText`, `getPreviewLines`, `shortenPath`, and the eval git_log status-event renderer (#1290).

- `gjc update` now verifies the installed runtime after package-manager failures and treats a nonzero Bun/npm exit as recoverable when the requested version and smoke test actually landed, avoiding false failures from Bun tarball extraction errors (#1280).
- Deep Interview now prefers the native hook's pre-resolved ambiguity threshold state and avoids surfacing missing optional settings files as failed `Read` calls during Phase 0.
- Scoped Ultragoal ask-guard checks for `deep-interview` and `ralplan` asks to the current session, so stale or ambiguous Ultragoal state from other sessions no longer suppresses the choice UI while same-session active Ultragoal blockers still apply.
- Submitted user prompts now use the live terminal viewport width in wide Windows Terminal/PowerShell sessions, keeping Korean/CJK prompt wrapping responsive without changing narrow layouts (#1239).
- Coordinator MCP now fails tmux-delivered turns that never receive a runtime prompt acknowledgement/`turn_start`, surfacing an explicit unacknowledged delivery reason instead of leaving Hermes/Oren waiting on a normal active/running state (#1237).
- Telegram now advertises `/session_create`, `/session_recent`, `/session_close`, and `/session_resume` in the bot command menu so lifecycle control commands are discoverable from `/` autocomplete.
- `gjc --tmux` now prefixes the root terminal title (`GJC: tmp`) and managed tmux window names (`GJC-tmp`) with a GJC workspace label so terminal multiplexers and workspace switchers do not fall back to noisy launch paths.

## [0.7.7] - 2026-06-28
### Added

- Added a `#` prompt action that enters tmux copy-mode and searches backward to the previous rendered `user` input marker, providing a tmux-local previous-input scroll jump without relying on terminal-specific modified key chords.

### Fixed

- Fixed steering regression where a prompt submitted while the agent was busy (`busyPromptMode: "steer"`) could stall in the steering queue — shown as a `Steer:` chip but never delivered — until the user pressed Esc to interrupt. A steer queued while no live agent loop was running (the busy/unwind window between a finished turn and the session going idle) now schedules a continuation so it is delivered promptly, mirroring the follow-up queue. A live loop still consumes the steer at its next tool/turn boundary, so steers are never double-delivered.

## [0.7.6] - 2026-06-28

### Fixed

- Kept Codex `reasoning.encrypted_content` replay strings out of the resident large-text externalizer so resumed sessions preserve the provider's opaque encrypted reasoning payload instead of degrading it to a resident blob object.
## [0.7.5] - 2026-06-27

### Fixed

- Guarded `parentId` session-tree walks against cycles to stop resume from exhausting memory (OOM) on self-referential or cyclic parent chains (#1193).
- Guarded `getTree` against child cycles so cyclic child references can no longer drive unbounded traversal (#1195).
- Elided runaway thinking-token loops in the assistant message renderer so repeated thinking output no longer grows without bound (#1196).
- Made `gjc session` create/list work on psmux-backed multiplexers (#1192).
- Sanitized dot-prefixed cwd window titles so tmux window names render correctly (#1198).
## [0.7.4] - 2026-06-27

### Added

- Native Windows `gjc --tmux` is now backed by [psmux](https://github.com/psmux/psmux) when no real tmux is on PATH: a new `psmux-detect` module probes `psmux` / `pmux` / `tmux` on Windows and resolves the multiplexer to use, `tmux-common.ts` re-exports the resolver for downstream callers, and `buildDefaultTmuxLaunchPlan` builds a real PowerShell-encoded `--tmux` plan instead of falling through to the direct-launch diagnostic. The native Windows `gjc session` / `gjc team` ownership-tag and worker-spawn paths therefore work end-to-end on a Windows host with psmux installed (no WSL required).
- Three new environment knobs back the Windows psmux path: `GJC_PSMUX_COMMAND` (force the multiplexer to be treated as psmux), `GJC_PSMUX_DETECTION` (`off` / `false` to skip probing entirely), and `GJC_PSMUX_FORCE_DETECT` (`1` / `true` to re-probe on every call). `GJC_TMUX_COMMAND` and `GJC_TEAM_TMUX_COMMAND` continue to override the multiplexer selection on every platform.
- Implemented a GJC plugin bundle architecture: `gjc plugin install <path|package>` resolves and installs declarative GJC plugin bundles into user/project scope with a content-addressed registry (per-file SHA-256 manifest hashes), and `gjc plugin list|doctor|enable|disable|uninstall` manage them; local-path bundles install offline without npm (#1149).
- Added Telegram-driven session lifecycle control so sessions can be created, closed, and resumed from Telegram, with per-session topic management wired into connect and shutdown (#1148).
- Added a keyless `insane` web search provider that safely ports upstream [`fivetaku/insane-search`](https://github.com/fivetaku/insane-search) public-route fallbacks (MIT; vendored engine pinned in `packages/coding-agent/vendor/insane-search/`) without TLS impersonation, browser/cookie bypasses, credential storage, or auto-installed dependencies (#1011).
- Added durable cold-spill eviction for compacted session history: after a compaction, `SessionManager.evictCompactedContent()` moves pre-`firstKeptEntryId` payloads (user/assistant text, thinking, tool-call arguments) out of the hot JSONL and resident heap into durable content-addressed sidecar blobs via `BlobStore.putImmutableSync`, keeping hot retained bytes bounded regardless of pre-compaction history size while preserving graph integrity and the compaction summary (#1166).
- Added a non-materializing, path-only `buildSessionContext()` that no longer populates `#materializedEntriesCache` and performs zero cold-spill reads on covered compacted branches, plus fidelity read APIs (`getEntryForFidelity`/`getBranchForFidelity`/`getEntriesForExport`) that rehydrate cold-spilled content on demand for HTML export, branch & re-edit, and branched-session creation (#1166).
- Added `BlobStore.putImmutableSync`/`getCheckedSync` (plus `EphemeralBlobStore`/`MemoryBlobStore` overrides): immutable, crash-safe, hash-verified content-addressed install (exclusive copy fallback + fsync) and checked reads that throw `BlobCorruptError` on corrupt blobs and never return silent wrong data (#1166).
- Raised the `deep-interview` default maximum round count to 100.
- New unit tests under `packages/coding-agent/test/gjc-runtime/psmux-detect.test.ts` cover detection verdicts, override precedence, cache behavior, and the `resolveGjcTmuxBinary` Windows / POSIX resolution paths.

### Changed

- `gjc team` now adopts any real tmux session as its leader — including one you started yourself outside `gjc --tmux` — by writing and reading back GJC's `@gjc-profile` ownership tag, instead of only accepting `gjc --tmux`-launched sessions. Providers that cannot round-trip tmux user options (e.g. psmux) are still rejected as unmanaged (#1140).
- `gjc team` now fails with actionable guidance when there is no tmux leader to host workers: running it with no tmux installed reports `tmux_not_installed`, and running it outside any tmux session reports `not_inside_tmux` (with a hint to start one via `gjc --tmux` or your own `tmux`, or use `--dry-run`), instead of surfacing raw tmux stderr (#1143).
- Improved `ultragoal` artifact-gate guidance in the completion quality gate (#1163).

### Fixed

- First-time `gjc` startup now shows only the installed/current version changelog entry instead of dumping the full historical changelog before the actionable UI; full history remains available through `/changelog --full` (#1184).
- `gjc --tmux` on native Windows no longer silently falls through to a tmux-less launch: when psmux is installed the plan now boots gjc through a PowerShell-encoded inner command, when no tmux-class binary resolves on PATH the diagnostic points at the psmux install URL and `GJC_TMUX_COMMAND` override, and explicit `GJC_TMUX_COMMAND` overrides are honored on every platform.
- The `gjc team` worker-command string is now formatted for the host shell: on Windows + psmux each env assignment uses the `$env:VAR = 'value';` PowerShell form (with PowerShell-safe single-quote escaping) instead of the POSIX `VAR='value'` form, so worker panes spawned via psmux ConPTY panes inherit the right `GJC_TEAM_*` environment.
- `createGjcTmuxSession` now chooses the new-session bootstrap command for the host shell: PowerShell `$env:GJC_TMUX_LAUNCHED = '1'; gjc` on Windows, `exec env GJC_TMUX_LAUNCHED=1 gjc` on POSIX, so psmux-managed sessions tag the spawned gjc the same way tmux-managed ones do.
- `applyGjcTmuxProfile` no longer hard-fails the `gjc --tmux` boot on Windows when psmux drops the UX profile round-trip. When the resolved multiplexer is psmux, only the `mouse` / `set-clipboard` / `mode-style` UX keys are filtered out; the `@gjc-profile` / branch / project / session-identity ownership tags are still emitted because those are required for `gjc session` and `gjc team`.
- `renameExistingTmuxWindowIfNeeded` no longer short-circuits on `platform === "win32"`: on a Windows host running psmux inside `gjc --tmux`, the leader window now inherits the project:branch title the same way it does on POSIX.
- Fixed tmux startup fast paths (#1142).
- Deep Interview (and any scrollable `ask`/hook selector) no longer enables SGR mouse reporting, which was hijacking the mouse wheel and disabling the terminal's native scrollback while a question was on screen. The wheel now scrolls the terminal as usual; long questions still scroll inside the dialog via PgUp/PgDn (#1164).
- Scrollable Deep Interview question boxes now show explicit `▲ more` / `▼ more` affordances when hidden question text exists, and selector mode also supports Ctrl+u/Ctrl+d as question-scroll aliases for PgUp/PgDn (#1164).
- Fixed unbounded memory growth in long sessions: the full verbatim transcript was retained in `SessionManager.#fileEntries`/`#byId` forever across compactions (compaction only summarized the LLM-bound context), so long coding sessions could OOM. Compaction now reclaims hot resident content via cold-spill, the `AgentSession.compact()` post-append path no longer bulk-materializes the branch, and assistant tool-call arguments/text are no longer kept verbatim indefinitely (#1166).
- Lossless branch/export fidelity after compaction: HTML export and branch & re-edit now rehydrate cold-spilled pre-compaction content instead of showing tombstone notices, and branched-session creation preserves cold-spill refs without truncating >500k-char content (#1166).
- Materialize resident blobs before branch export so exported branches never reference unresolved resident blob refs.
- Inherit the live fast-mode (`serviceTier`) into task subagents so delegated work uses the parent session's service tier (#1171).
- Fixed model selection after model-profile preset activation so the activated preset's model is actually used (#1172).
- Preserve session-only model-profile overrides instead of dropping them on later resolution (#1175).
- Fixed the `gjc ultragoal checkpoint` goal-snapshot fallback so checkpoints reconcile correctly when a fresh snapshot is unavailable (#1177).
- Added durable `gjc-session` diagnostics for the routed-session harness scripts (#1189).
- Telegram: apply verbosity commands (#1139), fix clarify-choice rendering (#1147), create the session topic on connect, and delete session topics on shutdown.

### Documentation

- The native Windows psmux section in `docs/environment-variables.md` now reflects that `gjc --tmux` builds a real tmux-backed plan via psmux, lists the new `GJC_PSMUX_*` knobs, and explains the worker-spawn shell-quoting rule. The bundled `team` skill doc points readers at the same environment section instead of the legacy "psmux is not fully supported" warning.
- Clarified Windows tmux fallback guidance.
- Credited `fivetaku/insane-search` for the ported public-route search fallbacks.

## [0.7.3] - 2026-06-25

### Added

- Added the `gruvbox-dark` built-in theme: the canonical Gruvbox dark palette mapped across every GJC theme token, selectable via `/theme`.
- Added a standalone MCP registration command: `gjc mcp add|list|remove` writes explicit user-provided MCP server definitions (stdio/http/sse) into GJC config without importing or inheriting other tools' live MCP configs, with env/header/auth values redacted in output (#1095).

### Changed

- Refined the interactive composer chrome so the input box, status rail, and welcome banner share one visual language: the composer now uses a rounded border (matching the rounded welcome banner) instead of a sharp rectangle, and the status rail uses the subtle elevated `userMessageBg` surface tone instead of the heavy `statusLineBg` block, so it reads as a quiet layered zone rather than a solid bar. Both resolve through existing semantic theme slots, so every bundled theme tracks automatically.
- When a Composer harness model is active, the `bash` tool now hard-blocks repository file I/O — pipes, process/heredoc/command substitution, redirection, `tee`, file read/discovery (`cat`/`head`/`tail`/`grep`/`find`/`ls`), file mutation (`cp`/`mv`/`rm`/`touch`/`mkdir`/`chmod`/`ln`), `sed`/`awk`, git file-read subcommands, and script file I/O — unless the command is on a strict allowlist (`bun test`/`run check|test|build`, `cargo test|check|build`, `git status`/`rev-parse`, package version queries), forcing Composer models to use the dedicated find/search/read/edit tools for file discovery and mutation (#1027).
### Documentation

- Documented the docs-only Aside evaluation boundary as an opt-in search/context retrieval sidecar using explicit user-provided MCP configuration, with browser actions, login flows, payments, internal tools, secrets, and raw browser/session payload logging out of scope by default (#1097).
- Added a UI design and visual QA contract governing future TUI/dashboard/terminal visual work (#1101).
- Added a CodeGraph custom-tool integration guide (#1073).
- Documented the Windows psmux namespace boundary for `gjc --tmux`, `gjc session`, and `gjc team`: cwd/`-c` is now called out as a start directory rather than server isolation, `-L <namespace>` is identified as the psmux namespace primitive, and tmux command overrides are documented as executable names rather than shell command lines (#1118).
- Clarified the Telegram Threaded Mode fallback documentation (#1122).

### Fixed

- Expanded the initial GJC forge welcome box to the live terminal viewport width and pinned the status/composer area to the bottom when the startup layout is shorter than the screen (#1120).

- Deep Interview Restate/option gates now recover through the ask selector path instead of waiting on plaintext `Options:` output.
- Widened the forge splash on wide terminals so it no longer clips (#1110).
- Parse quoted SSH remote host names in the slash-command host parser (#1104).
- Tolerate an unreadable git HEAD in the status chrome instead of throwing (#1072).
- Registered the `plugin` command in the CLI command registry so `gjc plugin …` (install/uninstall/list/marketplace/enable/disable/doctor) resolves instead of silently falling through to the default launch/chat command — the command was implemented and tested but was never registered (#1071).
- Keybinding/Ctrl+Enter newline-handling sweep across the editor and input controller (#1111).
- Fixed model-profile default badge precedence in the `/model` selector so the correct default-profile badge wins (#1117).
- Prevented duplicate Telegram topics being created for transient sessions (#1125).

### Security

- User-supplied URL reads now share the public HTTP(S) network guard that was previously insane-fallback-only: the initial target, the redirect chain, and binary-conversion redirects are all revalidated against private-network blocking before any request is opened or followed, closing an SSRF path through the normal read-tool fetch pipeline (#1114).
- Bridge workflow-gate responses now require the claimed controller token before the unattended control plane may resolve a gate, and the `workflow_gate_response` RPC command was raised from prompt scope to control scope, so prompt-only clients can no longer answer lifecycle workflow gates (#1116).

## [0.7.2] - 2026-06-24
### Added

- Added a keyless `insane` web search provider that safely ports upstream insane-search public-route fallbacks without TLS impersonation, browser/cookie bypasses, credential storage, or auto-installed dependencies (#1011).
- `web_search` `auto` mode now drives native provider search over proxies/custom endpoints by reusing the active model's own credential + baseUrl when canonical native creds are absent: `activeContextNativeId()` matches the model's wire api (+ model-id family) to `anthropic` (anthropic-messages), `openai-compatible` (openai-responses/completions), or `gemini` (google-generative-ai Generative Language), each falling back to DuckDuckGo if the endpoint does not support web search.
- Added built-in C# LSP detection for `csharp-ls`, with `omnisharp` preserved as a fallback when `csharp-ls` is unavailable (#1054).
- Added Discord and Slack notification adapters alongside the existing Telegram surface, so action-needed signals and replies can be routed to those clients (#1043).
- Telegram daemon now supports inbound and outbound photo/file attachments, forwarding agent images and accepting user-sent media (#1053).
- `gjc` verifies Telegram Threaded Mode during notification setup and falls back to a flat private chat when topics are unavailable (#1029).

### Fixed

- Hardened context-overflow recovery so automatic maintenance clears the TUI loader, surfaces overflow completion/skip status, retries resumable tails safely, and falls back to the synthetic auto-continue prompt for non-resumable tails when enabled.
- `web_search` native providers no longer discard genuinely grounded answers that omit structured `url_citation` annotations: when a search demonstrably ran — Responses `web_search_call` / `tool_usage.web_search`, a Chat Completions search request, or Anthropic `web_search_tool_result` / `server_tool_use` / `server_tool_use.web_search_requests` — sources are recovered from inline markdown links and bare URLs. Inline recovery is gated on that real-search signal so a stray prose URL in a non-search answer is never promoted to a citation, and Anthropic now fails closed to DuckDuckGo when Claude answers from stable knowledge without searching. Inline-citation helpers are shared via `providers/text-citations.ts`.
- Preserve GJC-managed tmux sessions on attach/disconnect instead of tearing them down, and stop implicitly attaching on launch (#1063).
- Corrected the auto-compaction output reserve so post-compaction responses keep adequate headroom (#1021).
- Improved active-input shortcut hints and the busy-input queueing hint for clearer in-session guidance (#1022, #1024).
- Fixed the Ultragoal ask guard blocking the `ask` tool when no GJC session can be resolved. `ultragoalReadPaths` falls back to the legacy/global `.gjc/ultragoal` directory when neither `GJC_SESSION_ID` nor an auto-detectable active session is present, but the follow-up `readUltragoalPlan`/`readUltragoalLedger` reads ignored that resolution and re-ran session detection, throwing `no active GJC session found` and surfacing `durable_state_unreadable` — which blocked `ask` for every agent even with no active Ultragoal run. `ultragoalReadPaths` now returns the resolved session id (or `null`); the ask guard treats a null session as inactive and falls open, and threads the resolved id into the plan/ledger reads so they no longer re-resolve. An inconsistent state (state dir present but `goals.json` missing/empty) still fails closed so the pause guard keeps blocking give-ups.
### Added

- Added a user-level `completion.notifyCommand` hook that runs a shell command with `GJC_NOTIFICATION_*` payload environment variables when an agent turn completes, enabling cmux/desktop/webhook completion alerts without project-config command execution.

## [0.7.1] - 2026-06-23
### Fixed

- Fixed packaged source installs (`gajae-code` wrapper) failing `gjc --smoke-test` because native smoke/fallback imports used monorepo-relative paths instead of the `@gajae-code/natives` package export.
- Fixed Telegram/notification turn ordering around pending asks: the assistant's lead-in text is now emitted before the ask prompt, and only the assistant `message_end` is captured as the pre-ask turn text, so remote prompts show the correct context instead of stale or duplicated output (#1006, #1007).

## [0.7.0] - 2026-06-22

### Added

- Notifications SDK with configure-once Telegram UX: each session exposes a loopback WebSocket endpoint with a discovery file and a generic `action_needed`/`reply` JSON protocol, so any client (Telegram, Discord, Slack, mobile) can see action-needed signals and answer pending asks without RPC or terminal scraping (#874).
- Managed Telegram reference daemon with a threaded per-session surface: one-time identity header, streamed `context_update` (last message, task, goal, token/model usage, diff), live/finalized turn output, and agent image streaming (`image_attachment` + multipart `sendPhoto`).
- Typing indicator and native double-check acknowledgements for Telegram inbound messages (#989).
- Scalable `gjc daemon` control plane with safe reload, enforcing one `getUpdates` poller per bot token so new sessions attach to the existing daemon instead of causing Telegram 409 conflicts (#993).

### Changed

- Asks are exempt from redaction so remote prompts stay readable and answerable; idle summaries are stripped and streamed content frames (`turn_stream`, `context_update`, `image_attachment`) are suppressed when redaction is enabled (#998, #1001).
- Telegram replies are routed by their thread/topic; removed the legacy `/answer <session-tag>` command in favour of thread-native replies.
- Pretty HTML formatting and markdown-table rendering for Telegram daemon output (#986, #997).

### Fixed

- Strip embedded option indexes from Telegram button labels and stop double-numbering inline buttons (#994, #996).
- Free-text answers resolve pending asks and ask choices remain unredacted (#998, #1001).
- Recover in-flight sessions after a connection drop and connect new sessions during the `getUpdates` long-poll (#988, #990).
- Daemon hardening: deliver ask buttons at invocation, fix the topic-reuse race, write daemon logs to file with resilient frame handling, and de-duplicate idle output (#985, #991, and related).
### Fixed

- Avoided automatically reusing stale GJC-managed tmux sessions from older GJC versions after an upgrade; scoped `gjc --tmux` reuse now only auto-attaches sessions tagged with the current version.

## [0.6.5] - 2026-06-21

### Changed

- Scoped all GJC workflow state by session ID: skill state, plans, specs, and ledgers now live under per-session `.gjc/_session-{id}/` directories, so concurrent or resumed sessions no longer collide on shared workflow state.
- Hardened skill-state hooks with a writer revision policy, a force-ask on Stop, HUD reconciliation, and an fd-duplication guard; forced/authoritative state writes now survive corrupt prior state (#950).
- Migrated the global debug shortcut into the keybinding registry and added `pasteImage` as a single source of truth with default-collision diagnostics (#925, #939).
- Added safe import of custom skills and MCP servers via `migrate` (#944).
- Trimmed the bundled agent roster to the four canonical role agents: planner, architect, critic, and executor (#922).
- Added a research-plan ledger spike (#933).

### Fixed

- Stopped including `bash` in the planning-phase mutation guard. The phase-boundary block (and the always-on `.gjc/**` runtime-owned block) now apply only to the fully-pathed `write`/`edit`/`ast_edit` tools; `bash` is never blocked by any workflow skill (`deep-interview`, `ralplan`, `ultragoal` goal-planning), so read-only shell commands run freely during planning. This reverts the `bash` parity added in 0.6.2; product-code and `.gjc/**` mutation are still gated through the dedicated edit tools (#951).
- Rejected counterexample-only claims in research-plan regardless of `dropCondition` wording (#942).
- Bounded computer-use screenshot inline images to avoid oversized payloads.
- Tolerated missing session env in guard reads (#930).
- Fixed a session-scoped active-state staleness bug where a freshly written mode-state revision was not reflected in the in-memory envelope, so the active-state/HUD writer skipped the newer update as stale — dropping the deep-interview ambiguity HUD chip after scoring and leaving the active-skills phase mirror behind a `handoff` transition.

### Documentation

- Documented standalone MCP boundaries (#923).

## [0.6.4] - 2026-06-20

### Changed

- Added `startup.welcomeBannerMode = "square"` for a square-corner Unicode welcome-logo fallback, and stopped treating Windows Terminal (`WT_SESSION`) as an automatic ASCII downgrade; `auto` now preserves the rounded Unicode logo while `unicode`, `square`, and `ascii` remain explicit overrides.

- Improved image input discoverability by adding an interactive `#paste-image` prompt action and clearer clipboard fallback guidance when no image is available.

- Improved skill migration guidance for users moving custom skills onto the current skill system (#899).

### Fixed

- Fixed native Windows tmux launch and hardened Windows tmux root launch, and resolved follow-up Windows tmux launch and input regressions (#884, #895, #906).
- Fixed `EXDEV` failures when moving session artifacts across filesystems (cross-device session artifact moves) (#886).
- Excluded user context files from the project prompt so file-level context filtering no longer leaks user-scoped files into project context (#885).
- Fixed a bash cancellation descendant-cleanup race so cancellation now waits for child-process cleanup within a bounded stall prompt (#893).
- Fixed the TUI dropping the first `/goal set <objective>` command from input history: the typed command is now recorded whenever args are supplied, regardless of prior goal-mode state (#910).
- Fixed Ctrl+Enter/Ctrl+Shift+Enter newline handling in the editor: idle Ctrl+Enter now falls through to newline insertion while keeping Ctrl+Enter as the busy-session follow-up shortcut, and Ctrl+Shift+Enter inserts a newline (#911).
- Fixed parsing of psmux modified-enter key sequences in the TUI (#918).

### Documentation

- Documented Windows Terminal welcome-logo troubleshooting with Cascadia Mono / Cascadia Mono Nerd Font and the profile `fontFace` setting.
- Documented CLI `@image` attachments and interactive TUI clipboard image paste fallbacks in the root README.

- Documented lifecycle notification hooks (#903).
- Added a routed GJC session guide for Clawhip/Hermes/OpenClaw visible routed sessions and linked it from the Hermes docs and operator instructions.

### Fixed

- Fixed combo/cross-provider model presets flipping the main provider on resume. A profile's main model was applied through `setModelTemporary`, which records the session `model_change` with `role: "temporary"`; on resume the session restored `models.default` (the stale pre-profile base model), so an "Apply for this session" combo like `opus-codex` came back on the base default (e.g. `openai-codex/gpt-5.5`) instead of the profile's main model (`anthropic/claude-opus-4-8`). Profile activation now records its main model as the session default (without writing global settings), while transient retry/fallback/context-promotion/plan-mode switches keep `role: "temporary"` so the issue #849 protection is preserved.
- Hardened the model-profile activation rollback so a failed activation no longer poisons the resume default. The rollback previously restored the pre-activation *live* model as the session default (`role: "default"`); if the user was on a transient retry/fallback/context-promotion/plan switch when activation failed, that transient model was promoted to the resume default and weakened the issue #849 protection. Activation now snapshots the pre-activation resume default separately from the live model and re-asserts it on rollback, while the runtime live model still rolls back as a transient switch (`role: "temporary"`).

## [0.6.3] - 2026-06-19

### Fixed

- Reverted the experimental minified npm-bundle distribution introduced in 0.6.2. The published `@gajae-code/coding-agent` shipped both `src/` and ~30MB of `dist/` bundles (`cli.js` plus stats/browser/eval worker bundles), which pushed the package past npm's registry payload limit (`E413 Payload Too Large`) and blocked publishing of `@gajae-code/coding-agent` and the `gajae-code` wrapper (so 0.6.2 only partially published the sibling libraries). The CLI `bin`/`./cli` export ships from `src/` again, matching the layout that published cleanly through 0.6.1; the embedded tiktoken/o200k tokenizer removal is unaffected. Local measurement showed the bundle gave no idle-RAM benefit over running from source.
- Fixed `edit-mode.ts` importing the full `@gajae-code/utils` barrel (which re-exports native-addon-backed `ptree`/`procmgr`); it now imports `$env` from the `@gajae-code/utils/env` subpath, so schema generation and other lightweight paths no longer eagerly load the native addon.

## [0.6.2] - 2026-06-19

### Changed

- Reconciled the planning-phase mutation guard into one uniform policy across skill states (`deep-interview-mutation-guard.ts`). Previously only `deep-interview` blocked product-code mutation (and it blocked *all* `write`/`edit`/`ast_edit` targets, including neutral `/tmp` scratch), while `ralplan`/`ultragoal` planning enforced nothing beyond the always-on `.gjc/**` runtime-owned block, and `bash` got a free pass to mutate product code during the interview. Now: (1) the phase-boundary block is shared by every pre-approval planning phase — `deep-interview`, `ralplan`, and `ultragoal`'s `goal-planning` phase (`team` and executing `ultragoal` are unaffected); (2) `bash` reaches parity with `write`/`edit`/`ast_edit` so product-mutating shell commands are blocked too; and (3) neutral scratch writes to a system temp directory (`os.tmpdir()`/`$TMPDIR`, `/tmp`, `/var/tmp`) outside the project tree are always allowed, so an agent can stage a draft and persist it through the sanctioned CLI (`gjc deep-interview --write --spec <temp-path>`, `gjc ralplan --write --artifact <temp-path>`). The `.gjc/**` block is unchanged. Each planning skill now emits its own block message.
- Made the reconciled mutation guard skill-transition/return safe by keying the block off the single canonical *current* workflow skill (the resolved top-level `skill` the HUD and skill-tool chain guard already use) instead of independently scanning every skill. Phase semantics now match the manifest and the Stop hook's `STOP_RELEASING_PHASES`: `handoff` and ralplan's pre-approval `final` keep blocking for `deep-interview`/`ralplan` (until the skill is demoted or cleared), executor phases (`ultragoal` `pending`/`active`/`blocked`) release, and a missing/corrupt mode-state still fails open. As a result a handoff (e.g. ralplan → ultragoal) never lets a stale planning entry block the executor, and a return (e.g. re-entering ralplan/deep-interview after a goal completes) reliably re-blocks.
- Hardened the reconciled guard after architect + red-team review: the `gjc …` bash fast-path no longer skips scanning for compound/redirected/multiline commands (`gjc …; tee src/x`, `gjc … && echo x > .gjc/state/foo`, and newline-separated `gjc …\ntouch src/x` are now caught); the current-skill resolver prefers the most-recently-updated active entry so a stale planning row can never block a newer executor; neutral-temp classification canonicalizes paths (realpath of the nearest existing ancestor) so a `/tmp` symlink or macOS `/tmp`→`/private/tmp` alias pointing back into the project/`.gjc` is blocked; the deferred `ast_edit` apply path now mirrors the always-on `.gjc/**` block; and a heredoc delimiter (`<<EOF`) is no longer mis-read as a write target. Bash mutation detection remains best-effort defense-in-depth (the authoritative guard is the fully-pathed `write`/`edit`/`ast_edit` tools). Added generic guard exports (`getWorkflowMutationDecision`/`assertWorkflowMutationAllowed`/`assertWorkflowMutationRawPathsAllowed`) used by the session and `ast_edit` callers, with the `*DeepInterview*` names retained as compatibility aliases.
- The published `@gajae-code/coding-agent` npm package now ships a prebuilt **minified** `dist/cli.js` (built with `bun build --minify`, not `--compile`) as the CLI entrypoint; the native addon and the stats/browser/eval worker entrypoints are emitted as externals so the bundle loads them from `node_modules` at runtime, and release compiled binaries also gain `--minify`. Measured `gjc --help` RSS dropped from ~302MB (running from source) to ~120MB (#879, #881).
- Lazy-loaded the `eval` tool and its Python-kernel backend via dynamic import, so the kernel and its dependencies are no longer eagerly imported at startup and load only when the `eval` tool actually runs (#879).
- `rust-analyzer` is now treated as an optional LSP server: its startup failure no longer raises a startup warning (it is auto-installed lazily on demand), while non-optional LSP server startup failures still warn (#872).

### Fixed

- Fixed planning-pipeline stage precedence so activating a downstream stage (`deep-interview → ralplan → ultragoal`) supersedes upstream stages by pipeline rank, preventing a stale upstream row from continuing to own the HUD, mutation gate, or primary active-state snapshot (#878).
- Made `gjc state doctor` resolve the session id like every other state command (explicit `--session-id`, then payload `session_id`, then the `GJC_SESSION_ID` env var set for agent-initiated invocations), so it inspects the caller's session-scoped state files instead of a default location (#880).
- Fixed a second workspace-relative import that the 0.6.0 #867 fix missed: `edit-mode.ts` now imports `$env` through the `@gajae-code/utils` package boundary instead of `../../../utils/src/env`, so global Bun installs no longer crash resolving edit mode, with package-boundary regression coverage (#868).

## [0.6.1] - 2026-06-18

### Fixed

- Fixed the `computer` tool (and any other `z.union`/discriminated-union tool) shipping a bare top-level `anyOf`/`oneOf`/`allOf` `input_schema` root that strict providers (Amazon Bedrock Converse incl. Kiro/CodeWhisperer relays, OpenAI strict mode, Gemini) reject. Tool schema roots are now flattened to a single `type: "object"` across all providers via the shared `flattenToolRootCombinators`. See `@gajae-code/ai` 0.6.1.
- `gjc update` now runs the freshly installed `gjc --smoke-test` after version verification and tells users to restart running sessions, surfacing stale or partial runtime updates such as native-addon release mismatches immediately.

## [0.6.0] - 2026-06-18
### Added

- Exposed the existing goal-pause capability through the `goal` tool as `goal({op:"pause"})`. The runtime `pauseGoal()` method and `paused` status already existed and were reachable via the `/goal pause` slash command and the goal menu, but the agent-facing `goal` tool only enumerated `create | get | complete | resume | drop` — so an agent could not park a goal whose remaining work was blocked on human input. It was forced to either `drop` (clearing the goal) or leave the goal `active`, which re-fired the hidden autonomous-continuation steer every turn with no exit condition. `pause` reuses the existing `paused` status and continuation gate (`buildContinuationPrompt` already returns `undefined` when `enabled=false`), parks the goal without dropping it, persists as `goal_paused`, and is resumable via the existing `resume` op. The active-goal and continuation prompts now instruct the agent to pause when every outstanding deliverable is genuinely human-blocked. `pauseGoal()` now rejects any goal whose status is not `active`, so a completed or dropped goal cannot be driven into a paused-mode lifecycle when paused through the tool.

### Fixed
- Fixed global Bun installs crashing during interactive startup when edit-mode resolution followed a workspace-relative `packages/utils/src` import that is absent from the published package layout; coding-agent now imports `$env` through the `@gajae-code/utils` package boundary and has regression coverage for sibling workspace source imports (#867).

- Restored steer-by-default while the agent is busy: `busyPromptMode` now defaults to `steer`, so Enter on a normal prompt interrupts the active turn. Queueing for the next turn is reserved for the explicit Ctrl+Enter follow-up keystroke (or `busyPromptMode: "queue"`); existing steer/cancel plus explicit queue/dequeue controls remain separate (#829).
- Fixed `gjc rlm "<question>"` consuming the seeded question as a one-shot autonomous run that exited immediately; a seeded prompt now lands in the interactive composer so the research session stays interactive.

### Added

- Added an opt-in `gjc rlm` research mode (v1, interactive): a Jupyter-notebook-style research session over the existing agent loop, backed by the shared persistent Python kernel. It loads a distinct research system prompt, restricts the toolset to a hard-gated allowlist (`python` + `read` + `web_search`, asserted after tool-registry assembly — no `bash`/edit/arbitrary mutation), optionally loads a project-root `DATA.md` (overridable via `--data <path>`), aggregates every executed cell live into `.gjc/rlm/<session>/notebook.ipynb` (single-queue atomic temp-rename writes with post-write validation), and synthesizes `.gjc/rlm/<session>/report.md` on session exit. Autonomous goal-arg runs, `--resume`, managed per-workspace venv provisioning, and the optional `>=N` completion gate are deferred follow-ups.
- Added an experimental opt-in `computer` desktop-control tool surface for local macOS screenshot/input coordination, backed by native `ComputerController`/`computerScreenshot` bindings and gated through settings/tool registration so it can continue stabilizing on `dev` outside the 0.5.4 patch release.
- Dropped deprecated GitHub Actions Intel macOS (`macos-13` / `darwin-x64`) release-binary coverage after the runner pool repeatedly blocked v0.6.0 publish; Intel macOS users should install through npm/Bun or build from source.
- Re-enabled GitHub Actions Intel macOS (`darwin-x64`) release-binary coverage using the `macos-15-intel` runner, so standalone `gjc-darwin-x64` binaries ship again alongside Apple Silicon.

## [0.5.4] - 2026-06-17

### Fixed

- Fixed subagent resume returning `not_found` after terminal job eviction removed the in-memory subagent record. Resume descriptors are now retained as durable same-session metadata and rehydrate a resumable record from the saved subagent session file, so ralplan Planner revision passes can resume with fallback metadata instead of forcing a fresh Planner spawn after 0.5.3.
- `AgentSession` now forwards the live provider session state (`providerSessionState`), session affinity id (`providerSessionId ?? sessionId`), and configured WebSocket transport preference (`preferWebsockets`) into local maintenance one-shot calls — manual/automatic compaction summaries, handoff generation, and tree branch summaries — via a shared `#maintenanceProviderTransport()` helper. Previously these Codex/OpenAI-compatible maintenance calls could fall back to HTTP/SSE and lose `session_id` affinity even when `providers.openaiWebsockets: "on"` routed live turns over WebSocket (#736).
- Fixed `ollama-cloud` first-event timeouts driving an unbounded, usage-spiking retry loop. The ollama-chat backend (exclusively `ollama-cloud`; local Ollama uses the `openai-responses` API) can stall before its first token even for tiny prompts, surfacing `Provider stream timed out while waiting for the first event`. That message matched the generic transient classifier, which retries forever (capped only on delay), so every continuation re-issued the full request to a billable backend and silently spiked usage — disabling retries was the only workaround. First-event timeouts on the ollama-chat API are now a distinct fail-closed class bounded by `retry.maxRetries`: they still retry transient cold starts a few times, then surface instead of looping. First-party providers keep their existing unbounded first-event-timeout retry behavior (#713).
- Interactive sessions no longer orphan the `browser` tool's headless/spawned Chrome (and the Python eval kernel) to PID 1 when killed by a signal. The interactive entry now registers a bounded, idempotent `postmortem` cleanup (`session-subprocess-teardown`) that runs `AgentSession.disposeChildSubprocesses()` on `SIGINT`/`SIGTERM`/`SIGHUP`, force-releasing the session's browser tabs (`kill:true`) and disposing its Python/JS kernels — the teardown the graceful `/quit` (`dispose()`) path already performs but that an external `kill`/terminal-close used to bypass. Headless `disposeBrowserHandle` now also SIGTERM/SIGKILLs the captured Chrome process tree as a fallback when forced, so a wedged renderer can't survive a bounded CDP `close()`; graceful release behavior is unchanged. The teardown is time-boxed (5s) so a stuck subprocess can't hang process exit (#698).
- Added first-class xAI search provider support for the `web_search` tool and `gjc q`, including OAuth/API-key auth, web/X/combined search modes, xAI web/X filters, image/video options, citation controls, usage reporting, Settings provider selection, CLI flags, config schema wiring, and edge-case coverage.

## [0.5.3] - 2026-06-16

### Added

- Added `GJC_CREDENTIAL_RANKING_MODE` env var (`balanced` (default) | `earliest-reset`), wired through `discoverAuthStorage` into `AuthStorage.credentialRankingMode`. `earliest-reset` selects multi-account OAuth credentials earliest-expiry-first so soon-to-reset tumbling-window quota (e.g. Claude 5h/7d) is drained before it is lost at reset; unset/unknown leaves the default `balanced` behavior unchanged.
- The `/model` selector and `/fast` status now show a per-model fast-mode indicator (`⚡`) resolved with the provider-aware predicate, including subagent (role) models evaluated against the effective `task.serviceTier` (falling back to the session tier when `inherit`), so it is visible at a glance which models will run with Anthropic `speed:"fast"` / OpenAI `service_tier=priority`. Display-only: no `serviceTier`/`modelRoles`/`agentModelOverrides` writes (#691).
- Added an opt-in `GJC_BRIDGE_ENDPOINTS` env var (`all`, or a comma list of matrix keys: `events`, `commands`, `control`, `uiResponses`, `hostToolResults`, `hostUriResults`) to enable bridge-mode session-control endpoints, which were previously permanently fail-closed (`403 endpoint_disabled`) with no supported way to turn them on. Unset stays fail-closed and backward compatible (#663).

### Fixed

- Auto-compaction no longer silently requires OpenAI when the active route is a custom Anthropic-capable provider. The compaction model-candidate selection already prefers the active session model, but its last-resort "largest-context model" fallback scanned the entire bundled catalog across all providers, so a stray OpenAI credential (e.g. an out-of-credit key left in the environment) could be picked when the active provider's compaction credential was unusable — turning OpenAI into an implicit hard dependency. The implicit fallback is now scoped to the active model's provider; cross-provider compaction still works but only when explicitly configured via `modelRoles`. When the active provider cannot compact and no role is configured, compaction now fails with the existing clear, provider-specific credential error instead of reaching for OpenAI (#697).
- Long-running-session freeze/leak remediation across the TUI, agent, and tool runtime: the TUI render loop, component-dispose lifecycle, and markdown highlighting are hardened against huge frames and reuse leaks (#716); agent context append, an emergency-compaction floor that cannot be disabled, token accounting, and session resource teardown (own-session browser tabs, LSP clients, Cursor conversation cache) are bounded (#717); oversized tool inputs/outputs are capped (8 MiB edit/read guard ahead of the notebook fast-path, 1000-row SQLite raw-query cap, 16 MiB artifact / 256 MiB archive read caps, budget-bounded browser return serialization) (#721); native synchronous entrypoints add defense-in-depth caps for tokenization, highlighting, and fuzzy edit matching (#744); and the session blob store is LRU-bounded (64 MiB / 4096 entries) with bounded-concurrency blob resume (limit 8) (#719).
- Process & resource lifecycle hardening so child processes and external resources are reliably reaped on disconnect, abort, and shutdown, built on a new owned-process foundation — process-group ownership with escalating SIGTERM→SIGKILL tree termination, idempotent dispose, and a postmortem reap hook (F1). Owned-process handles are terminalized on clean drain so a retained handle can never signal a recycled PID/process group (B1); the native blocking-task boundary, PTY lifecycle, and pi-shell timeout/abort reaping are hardened (U1–U3); the Python eval kernel (U4) and JS eval worker/VM (U5) coalesce concurrent first cells, settle queued/pending runs on teardown, and return worker/kernel counts to baseline; bash shell sessions are owner-scoped with one-shot async/monitor jobs and a hard artifact byte cap (U6); DAP adapters and LSP servers are spawned as owned processes and killed on terminate/timeout/reload (U7); MCP stdio/HTTP/SSE transports and the manager close idempotently with stale-publication identity guards (U8); the async job-manager bounds dispose, the delivery queue/retry (with dead-lettering), and terminal purge (U9); and tmux GC never prunes live/attached sessions and reaps only durably-owned orphans (U10).

## [0.5.2] - 2026-06-15

### Fixed

- Rendered `/dump` tool-call parameters with readable structured bodies, XML-safe text, and decoded Unicode escape sequences so `ask`/`proxy_ask` deep-interview payloads no longer show dense `questions` JSON or literal Korean `\u...` escapes.
- Prevented `gjc --tmux` partial-launch diagnostics from throwing when stderr is already closed during shutdown.
- Fixed v0.5.1-style macOS/Linux standalone binaries crashing before the first model request with `Cannot find module '@gajae-code/natives' from '/$bunfs/root/gjc-*'` when pre-prompt context maintenance invokes the native tokenizer.
- Mapped the retired `codex-standard` model profile name to `codex-medium` during profile activation, **as a fallback only** so a user-defined profile literally named `codex-standard` is never shadowed, letting stale `modelProfile.default: codex-standard` configs reach activation instead of blocking startup after the rebuilt profile catalog.
- Fixed interactive goal-mode auto-continuation looping `Error: Agent is already processing…` (`AgentBusyError`) while the session is busy. A wedged/orphaned subagent turn — or an in-progress compaction — can leave the session non-idle while the interactive loop is back at `getUserInput()`; the 800 ms continuation timer then fired `prompt()`, threw `AgentBusyError`, surfaced it via `showError`, and re-armed — spamming the error roughly every 800 ms. The continuation now skips and re-arms while `isStreaming`/`isCompacting`, firing only once the session returns to idle.
- Fixed the built-in `minimax-eco`/`minimax-medium`/`minimax-pro` model profiles 400ing on activation because every role pinned the non-existent `minimax-code/minimax-v3`. All three profiles now pin `minimax-code/minimax-m3`, the canonical `minimax-code` default already present in the bundled models catalog (#656).
- Fixed the native Stop hook letting a deep-interview run terminalize through the ordinary stop path without crystallizing its distilled interview state. A deep-interview mode-state that would release the Stop block (e.g. `active:true` with a `complete`/`completed`/`inactive` phase) is now held until it has actually persisted a final spec — a `spec_path` that still resolves to a real `.gjc/specs/` artifact — and the public-safe diagnostic points the agent at `gjc deep-interview --write --stage final` (optionally `--handoff ralplan`). The guard is scoped to deep-interview only: explicit abort/cancel phases (`failed`/`cancelled`/`canceled`) and the `active:false` demotion/clear outcome remain legitimate terminals, and no other workflow's stop behavior changes (#674).

### Added

- Added three bundled dark TUI migration themes — `claude-code`, `codex`, and `opencode` — whose palettes mirror the Claude Code, OpenAI Codex CLI, and opencode TUIs for easy eye-migration. They join the crustacean defaults (`red-claw` dark, `blue-crab` light) as selectable built-ins via Settings or `/theme`; defaults are unchanged and the new themes keep GJC's default symbol identity. A built-in inventory test now validates every bundled theme against the required `THEME_COLOR_KEYS` token set, name/key equality, var resolution, dark classification, and brand-vs-semantic token separation.
- Documented and regression-guarded the `gjc --tmux` scroll/mouse profile so WSL/Linux launches are not left guessing about mouse-wheel scrolling. The GJC-managed tmux session already applies `mouse on` (plus `set-clipboard on` and a readable copy-mode `mode-style`) scoped to the GJC session only, on macOS/Linux/WSL alike (only native `win32` skips the tmux launch); a new launch-path test asserts a WSL/Linux `--tmux` launch issues session-scoped `set-option ... mouse on` (never global `set -g`) and that `GJC_MOUSE=off` opts out without dropping the ownership tags. `docs/environment-variables.md` now documents the `--tmux` startup env vars (`GJC_LAUNCH_POLICY`, `GJC_TMUX_SESSION`, `GJC_TMUX_COMMAND`, `GJC_TMUX_PROFILE`, `GJC_MOUSE`) and the WSL/Windows Terminal scroll behavior (tmux copy-mode wheel scroll vs. native scrollback, copy-mode keyboard fallback, and that GJC never modifies tmux sessions you started yourself), and `gjc --help` surfaces `GJC_TMUX_PROFILE`/`GJC_MOUSE` (#650).
- Added a subagent-scoped `task.serviceTier` setting (default `"inherit"`) so the service tier / fast mode applied to task-tool subagents can be controlled independently of the main session. `"inherit"` keeps the current behavior (the main session tier is copied into each subagent's isolated settings snapshot), while any explicit value (`none`, `priority`, `openai-only`, `claude-only`, …) overrides only the subagent sessions, which already read `serviceTier` from their own settings. Implemented in `createSubagentSettings` with a focused test covering inherit and explicit-override behavior (#664).

## [0.5.1] - 2026-06-14

### Added

- `gjc --mode rpc` registers each live session in a cross-process registry (`<agent-dir>/rpc-sessions/<id>.json`) on start and removes it on shutdown, so other processes can enumerate running RPC sessions. The Python `gjc_rpc` client exposes `list_sessions()` / `RpcClient.list_sessions()` returning typed `SessionHandle`s and reaps records whose owning process is gone (issue 10; foundation for reattach/issue 09).
- `gjc --mode rpc --listen <socket-path>` runs a persistent Unix-domain-socket RPC server: the `AgentSession` outlives client disconnects (no stdin-EOF teardown) and a client can disconnect and reconnect to the same live session over the socket. The session is registered with `transport: "socket"` and the socket path as its `endpoint`, so it is discoverable/attachable via the registry. The stdio path is unchanged (frame output routes through a swappable sink shared by both transports) (issue 09).

### Fixed

- Subagent/job status panels now freeze a job's elapsed timer the moment it stops running (`completed`/`failed`/`cancelled`/`paused`) instead of counting up against `Date.now()` forever. `AsyncJob` records an `endTime` on the first terminal/pause transition and a shared `jobElapsedMs` helper backs the `subagent` panel, the `job` tool, `/jobs`, and the command-controller job line, so a finished subagent stops at its real duration while siblings keep ticking.
- RPC control-plane hardening (from dogfooding `gjc --mode rpc`): `dispatchRpcCommand` now wraps the command switch so failures return a correlated response carrying the request `id` and the real command name, instead of dropping the id and mislabeling handler exceptions as `parse`; `set_thinking_level`/`set_steering_mode`/`set_follow_up_mode`/`set_interrupt_mode` validate their inputs and reject out-of-contract values instead of silently corrupting session state; `negotiate_unattended` rejects unknown scopes/action classes with `invalid_unattended_declaration` and merges the mandatory `prompt` scope plus its `command.prompt` action floor into the accepted grant (so prompt/`workflow_gate_response` are never locked out); and read-only/control RPC commands no longer consume the unattended `max_tool_calls` budget while wall-time enforcement is preserved. `docs/rpc.md`'s first `workflow_gate` example now matches the canonical `RpcWorkflowGate` shape.
- RPC mode no longer head-of-line-blocks control/cancellation commands behind a long-running command: the stdin loop now dispatches ordered commands through a serial chain (so causal order is preserved — e.g. `get_state` after `bash` still observes the bash result) without blocking the reader, and routes `abort`/`abort_bash`/`abort_retry` on an immediate fast lane so they reach in-flight work. `abort_bash` can now cancel a running `bash`, and a slow `compact`/`handoff`/`login` no longer freezes the whole control plane. Shutdown drains in-flight commands (bounded) so their responses are still emitted on stdin EOF.
- Counted active prompts and agent-initiated custom messages in pre-prompt context maintenance so background task notifications trigger compaction before they can overflow the next model request.
- Bounded monitor task-notification payloads to a compact tail window while preserving full background job output for job inspection.

### Changed

- Expanded coordinator MCP coordination status into a canonical polling snapshot for sessions, session states, turns, questions, reports, and bounded event summaries, and documented that Hermes/coordinator consumption is polling/await rather than push subscription.

## [0.5.0] - 2026-06-13

### Fixed

- Fixed forced `tool_choice` 400s ("tool_choice forces tool use is not compatible with this model") looping after `ast_edit` previews: named queue directives (resolve protocol, eager `todo_write` enforcement, subagent `yield` reminders) now enqueue only when the model supports exact named forcing; otherwise they degrade silently to the existing steer reminder without a forced `tool_choice`, and a runtime-discovered incapability drops the in-flight directive instead of requeueing it.
- `models.yml` compat blocks now accept the `toolChoiceSupport` enum (`none`/`auto`/`required`/`named`) alongside the legacy `supportsToolChoice`/`supportsForcedToolChoice` booleans, mirrored in the generated JSON schema.
### Added

- Made `/model` open to a preset-first landing view: provider-grouped presets with live auth checkmarks, highlight-to-expand tiers, a full clamped role→model preview before applying, and a session/default apply scope choice; typing still jumps straight to model search, "Browse all models" opens the classic tabbed selector, and temporary-only quick-switch bypasses the landing entirely.
- Rebuilt the builtin model profile catalog as 25 profiles: `codex-{eco,medium,pro}` on `gpt-5.5` effort spreads, a single `opencodego` preset, `claude-opus`, `{glm,kimi-coding-plan,mimo,grok,cursor,minimax}-{eco,medium,pro}` trios with thinking levels clamped to provider support, and `opus-codex`/`codex-opencodego` combos. Legacy profile names (including the `*-standard` family and retired Fable presets) were removed clean-break and now fail with the available-profile listing.
- Added a post-`/login` smart preset recommendation: when login succeeds and no profile is active, prompts "Apply <preset> now?" (session-only on confirm); when a profile is active, prints a one-line hint instead. The active profile is tracked in-memory on the session with rollback-safe activation.
- Bundled `kimi-code/kimi-k2.7-code` and `minimax-code/minimax-v3` model entries; MiniMax presets use the canonical `minimax-code` provider id throughout.
- Added a harness receipt JSONL spool exporter for gajae receipt-runtime interop: configured `gjc harness --receipt-spool-dir <dir>` / `GJC_RECEIPT_SPOOL_DIR` now appends persisted native `ReceiptEnvelope` records as `{cursor,envelope}` lines to `spool.jsonl`, with restart-safe 12-digit cursors and installed-package smoke coverage (#545).
- Added Gajae Trinity compatibility golden fixtures and tests that pin ReceiptEnvelope hash basis, validator compatibility, and replayable RPC exchange shape for downstream receipt-runtime interop.
- Optimization Suite v3 Lane 1 (RSS): large resident text in persisted sessions is now backed by an ephemeral session-scoped disk cache (`EphemeralBlobStore`) instead of being pinned in JS heap for the whole session lifetime; canonical JSONL persistence, reload, and export semantics are byte-identical (resident refs never persist). Missing resident text cache blobs now surface a typed `ResidentBlobMissingError` instead of silently leaking `blob:sha256:` refs into provider payloads, UI, or exports. `getEntries()`/`buildSessionContext()` are served from revision-keyed WeakRef caches below the public ownership boundary (callers still receive caller-owned copies). Fixture retained heap −82%, RSS −55%, warm `getEntries()` p95 −80% on 10k-entry sessions; one-shot `exportFromFile()` now closes its session manager.
- Added process-isolated deterministic TUI render-golden capture and fixtures for interactive editor overlays, rich-text resizing, multiplexer viewport repaint, sixel image line preservation, Termux height diffs, and transcript shrink/clear regressions.

### Removed

- Removed the hardcoded OpenAI Codex role-preset action from the model selector; builtin model profiles are now the only preset concept.
- Removed retired Fable model profiles (`claude-fable`, `fable-codex`) after `claude-fable-5` was removed upstream.

### Changed

- Optimization Suite v3 Lane 3 (serialization): session-switch message comparison now uses per-message cached source strings + xxHash64 as an accelerator (source-string compare remains the authority; collision fallback tested) — unchanged-session compares −95% median. The secret obfuscator precomputes a longest-first combined regex (single-pass replace, −70% median/−77% p95 on 100 secrets × 1MiB) with a conservative sequential fallback whenever secrets overlap each other or any replacement/placeholder contains a secret — output bytes are identical in all cases. Intra-line diff rendering gains byte-identical fast paths for identical lines and whitespace-token-aligned prefix/suffix spans (identical −67%, single-token −60%; long lines skip the scan). Mental-model LCS keeps legacy dense-DP tie-break semantics (a Hunt-Szymanski variant was rejected for changing rendered bytes). Provider-visible fork-context seeds use JSON-semantic cloning instead of structuredClone.
- Tightened tool-block rendering to remove vertical padding and rely on Spacer-only separation, reducing transcript noise while preserving stable render-golden output.
- Improved the Bun runtime version guard diagnostic: when the Bun running `gjc` is older than the required version, the error now names the exact detected Bun runtime path and prints a platform-specific upgrade and PATH fix (Windows gets the `irm bun.sh/install.ps1|iex` reinstall plus a `%USERPROFILE%\.bun\bin` PATH hint) instead of a bare `bun upgrade` (#525).
- Aligned the `codex-standard` and `codex-pro` model profiles on the `openai-codex/gpt-5.5` baseline so they no longer default to stale mixed model generations (`gpt-5.4`, `gpt-5.2-codex`, `gpt-5.1-codex-max`, `gpt-5.3-codex-spark`); the profiles now differentiate purely by per-role reasoning effort (#532).
- Reduced the default RPC `get_state` payload by omitting static `dumpTools` and `systemPrompt` fields unless requested via `include: ["tools", "systemPrompt"]` (#539).
- Updated `/model` documentation and generated docs index for the rebuilt preset catalog and preset-first selector.

### Fixed

- Tightened the Windows/psmux tmux provider boundary: `gjc team` now honors `GJC_TMUX_COMMAND` (not just `GJC_TEAM_TMUX_COMMAND`) so the team leader resolves the same multiplexer as `gjc session`/`gjc --tmux`; and when a multiplexer lists a session that lacks GJC's `@gjc-profile` ownership tag, `gjc session status` now returns `gjc_tmux_session_untagged` with a `detail` hint and `gjc team` reports the same cause, instead of a bare `gjc_tmux_session_not_found` / `unmanaged_tmux_session`. Documented that alternative multiplexers such as psmux on Windows are not fully supported because they do not round-trip tmux user options (#531).
- Hardened RPC stdio lifecycle behavior: `gjc --mode rpc` now reports malformed JSONL frames as parse-error responses without killing the session, flushes durable session state before exiting on EOF/shutdown, and has red-team coverage for attached persistence, reload, malformed-frame recovery, and concurrent child-session isolation.
- Hardened the harness RPC submit/router contract so `submit` is no longer advertised or accepted during finalizing/non-idle lifecycle windows, non-idle RPC state reports `submitted:false` with a retryable gate, and degraded owner endpoints fall back to `owner-not-live` without false acceptance (#544).
- Ran estimated context maintenance before sending a new prompt, including tool-output pruning and threshold compaction, so large tool results appended after the last assistant turn cannot push the next model request over the context window.
- `gjc team` now self-heals a missing `@gjc-profile` ownership tag when the current leader pane was genuinely launched by `gjc --tmux` (detected via `GJC_TMUX_LAUNCHED=1`): the session is re-tagged with `set-option` and startup proceeds, instead of hard-failing with `unmanaged_tmux_session` after a mid-startup attach failure or registry race stripped the tag. Sessions without the GJC launch marker are still rejected unchanged, so foreign tmux sessions cannot be hijacked.
- Subagent task receipts and live render output now warn when requested role-agent models are substituted by auth fallback or provider-reported assistant model mismatch, including session model-change annotations for server-side substitutions (#559).
- Converted Cursor wire shell timeouts from millisecond values to bash-tool seconds so delegated Cursor-native shell calls honor the expected timeout units.
- Fixed pi-shell bash fixups on multibyte UTF-8 commands by converting parser source indexes to byte offsets before stripping `head`/`tail` pipelines.

## [0.4.5] - 2026-06-12

### Added

- Added a dim `(ctrl+s to observe sessions)` discoverability hint under the `subagent` await panel header while any awaited subagent is still running, pointing to the full session observer overlay; the hint shows in both collapsed and expanded states and disappears once no subagent is running.
- Added a `phase-rollup` receipt family (receipt-of-receipts) to the harness control plane: a hash-sealed rollup that supersedes N child task receipts at a lifecycle boundary, preserving per-child `{id, status, outputUri, outputSha256, receiptSha256, tokens, costTotal, clonedTokens, lowRoi}` pointers plus aggregate ROI totals, with a pure deterministic builder (`buildPhaseRollupReceipt`) and fail-closed semantic validation.
- Added a pure receipt-ingestion fast path (`ingestReceipts`): fail-closed batch validation + lifecycle transition computation via the existing state machine, plus a deterministic model-facing digest hard-capped at 280 chars — groundwork for LLM-free receipt routing.
- Added advisory spawn-ROI reconciliation (`reconcileSpawnRoi`) and deterministic fork-context mode advice (`adviseForkContextMode`) surfaced in task receipts without changing task success semantics.
- Added the Grok Build provider contract design document.

### Changed

- Reduced compiled CLI startup and native bundle pressure with default-small grammar loading, tokenizer tiering, and compiled fast-help paths.
- Preserved dev/main release metadata and changelog consistency for the 0.4.5 lockstep release.

- Added native `gjc ultragoal steer --kind` support for documented steering mutations beyond `add_subgoal`, including split, reorder, wording revision, ledger annotation, and blocked-goal supersession contracts with structured audit expectations.
### Fixed

- Kept the unified `goal` tool registered and active by default whenever `goal.enabled` is true, including explicit tool subsets and `gjc ultragoal create-goals` arming flows.
- Restored no-argument `gjc` interactive startup instead of launching help.
- Rendered and executed Cursor-native tool calls, including detached/native handler paths and empty-pattern composer grep guards.
- Tool-output pruning no longer rewrites already-sent provider-facing history mid prompt-cache epoch and now persists pruned message updates back into canonical session storage.
- Preserved provider abort root causes in the final TUI abort label, kept replay rendering idempotent, and added a `PI_STREAM_IDLE_TIMEOUT_MS` remediation hint when stream idle watchdogs fire.
- Hardened harness owner recovery/finalize paths and submit-prompt-file handling.

## [0.4.4] - 2026-06-10

### Added

- Made coordinator turns event-backed, adding an MCP coordinator server and the `gjc coordinator` / `gjc mcp-serve` commands backed by durable turn/session state (#479).
- Surfaced awaited sub-agent live streaming status in the await panel (#475).

### Changed

- Refreshed the README brand images (#477).

### Fixed

- Persisted ralplan role-agent artifacts via the CLI, returning receipt-only output to the caller (#474).
- Collected the `ask` tool "Other" custom input inline below the option list (#476).

## [0.4.3] - 2026-06-10

### Added

- Added a `busyPromptMode` setting (`steer` | `queue`, default `steer`) so a prompt submitted while the agent is busy can either steer the active turn or be queued to run after it completes, keeping steering and queued-next-turn semantics distinct. Ctrl+Enter still always queues as a follow-up (#434).
### Fixed

- Fixed a persistent `monitor` notification flood where a cancelled or evicted monitor kept delivering queued `task-notification` follow-ups (surviving process death, log deletion, and `job cancel` returning not-found). Monitors now purge their queued notifications on cancel/terminal/eviction, retain a short tombstone so post-eviction `job cancel` still purges, coalesce rapid duplicate output to the latest state, and close a cancel/trailing-flush race.
- Fixed `ultragoal` execution leaking across concurrent independent GJC sessions. The runtime `goal-mode-request.json` is now stamped with the producing session's `GJC_SESSION_ID`, and the consumer only activates a request that belongs to the current session (another session's request is left intact instead of being consumed/deleted). Legacy unscoped requests remain consumable for single-session compatibility ([#457](https://github.com/Yeachan-Heo/gajae-code/issues/457)).

## [0.4.2] - 2026-06-09

### Changed

- Added conservative `timeout-minutes` values to all CI workflow jobs to prevent indefinite hangs.
- Made coordinator MCP turn waiting state-backed by durable turn/session files, with runtime session sidecar updates for running/completed/error states and Meeseeks guidance that avoids fixed sleep/capture-pane loops.

### Fixed

- Failed stale coordinator turns quickly when their recorded tmux session is gone, clearing active-turn state instead of burning await timeouts.
- Improved the grep limit-reached message to show the current limit value and suggest using `--limit` for more results.
- Passed the active model's `maxTokens` (reserved completion budget) into the auto-compaction threshold and context-usage reserve so prompt packing reserves output for large-window models, keeping the safe input budget below the total context window (e.g. ~272K for a 400K/128K model) instead of filling the whole window ([#442](https://github.com/Yeachan-Heo/gajae-code/issues/442)).
- Fixed a `gjc harness` recovery deadlock where a session created by `start` without `--detach` (persisted as `started` with no owner lease/endpoint) could never get a live owner: `recover` refused to spawn one because no prior endpoint existed, while `start` reported `session-already-exists`. `recover` now bootstraps a fresh owner for a never-started session (no lease, no endpoint, no owner-run evidence) without writing a misleading `vanish` receipt, reported via `bootstrappedOwner: true`. Bootstrap is independent of the vanish classifier's `ownerRequired` verdict (nothing has vanished), so a session started in a non-git workspace (git delta `unknown`) is recovered too, while a deleted worktree is still refused (#421).

## [0.4.1] - 2026-06-07

### Changed

- Hardened the default system prompt with a `<skill-discipline>` block (never ignore skill text, keep read-only/interview skills from mutating, recommend and invoke the matching `/skill` on approval) and tightened `<communication>` to ban permission-begging/deferral phrasing and never announce remaining work instead of doing it (#392).
- Cleaned up the bundled GJC workflow skill docs and defaulted execution handoff to ultragoal while prioritizing ralplan refinement (#395, #396).

## [0.4.0] - 2026-06-06

### Added

- Added an agent-driven RPC workflow lifecycle control plane and a `workflow_gate` contract: `negotiate_unattended`, `workflow_gate`, and `workflow_gate_response` frames are validated by an answer-schema validator, persisted through a durable gate broker, and wired into live session dispatch (#314).
- Added a binding-only `gjc-plugins` sub-skill plugin framework that loads, validates, and activates declarative plugin packs without granting implicit arbitrary execution surface (#347).
- Added generated JSON Schemas for the config and models files (`schemas/config.schema.json`, `schemas/models.schema.json`) with a `check:schemas` drift gate (#377).
- Added `cacheRetention` support in the models config (#381).
- Added an Opus max reasoning preset (#372).

### Changed

- Improved slash command and `skill:*` suggestion ranking so `/team` surfaces the matching skill before weaker fallback candidates.

### Removed

- Removed the optional `@gajae-code/swarm-extension` package and its `gjc-swarm` CLI. The YAML/DAG swarm orchestration extension was a standalone optional feature not imported by any other package; it is no longer built or published.

### Fixed

- Routed unattended deep-interview ask-tool questions through `workflow_gate { kind: "question" }` events, including Round 0 topology and challenge-mode metadata, free-text option/schema shape, headless RPC answers, and synchronous response race handling (#316).
- Preserved harness owner-vanish evidence after prompt acceptance: no-owner `recover` now either restores a detached owner when a prior endpoint exists or returns a public-safe concrete owner-exit reason plus a vanish receipt, and no-owner `observe`/`events` expose the preserved owner-exit summary.
- Fixed LSP and MCP server lifecycle cleanup so clients/servers and their child processes are torn down on abort/exit instead of leaking (#389).
- Preserved session retention across resident session rewrites.
- Fixed harness session lookup testability without changing runtime owner-routing behavior.
- Prevented release catalog file specs from recursing during catalog resolution (#351).
- Reconciled the ultragoal skill mode-state and HUD with the plan/ledger so status reflects real goal progress (#342/#346).

## [0.3.2] - 2026-06-05

### Added

- Added model profiles with a `--mpreset <profile>` CLI flag and a `/model` selector "Profiles" section that activate a named profile's default model plus per-agent-role model overrides in one step, validating required-provider credentials before applying and surfacing a custom provider onboarding wizard for missing API-compatible providers.
- Integrated `ai-slop-cleaner` as an internal Ultragoal sub-skill fragment that runs as the mandatory completion-gate cleanup sweep over a story's changed files, reporting blocking and advisory findings without editing code or mutating `.gjc/` state.
### Changed

- Edit tool diff generation (`generateDiffString`) now uses the native `diffLines` from `@gajae-code/natives` (a byte-identical Rust port of jsdiff) instead of the pure-JS implementation, removing the multi-second Myers blowup on large-file edits (~16x faster on ~1MB files) with identical diff output.

### Fixed

- Reconciled native Ultragoal commands with workflow mode-state and the HUD: `gjc ultragoal create-goals`, `complete-goals`, `checkpoint`, steering, review-blocker recording, and status now sync `.gjc/state/ultragoal-state.json` plus `skill-active-state.json` from the durable `.gjc/ultragoal` plan/ledger, clearing stale active HUD chips after all goals complete.
- Forwarded the parent session id when task subagents validate configured role-agent model overrides, preventing session-scoped OAuth providers from being misread as unauthenticated and falling back to the parent chat model.
- Removed unintended public memory-tool guidance and registration: Hindsight retain/recall/reflect helpers are now compatibility-only, local memory prompt injection no longer advertises `memory://` reads, and regression tests guard the public tool surface.
- Fixed `read` hashline anchors drifting on truncated reads so the `line+hash` anchors consumed by `edit`/`apply_patch` stay correct when a file is read past the truncation boundary.
- Reconciled the ultragoal skill mode-state (`current_phase`/`active`) and HUD chip with the `.gjc/ultragoal` plan/ledger on every `gjc ultragoal` command (`create-goals`/`complete-goals`/`checkpoint`/`steer`/`record-review-blockers`/`status`), so `gjc state ultragoal read`, the skill-tool chain guard, and the HUD no longer sit at a stale `active:true`/`goal-planning` after a run completes (#342). A new `reconcileWorkflowSkillState` performs a session-scoped (`GJC_SESSION_ID`) derived write that bypasses only transition-edge validation while preserving schema/unknown-phase validation, version/checksum stamping, and audit provenance (`owner: gjc-runtime`, `verb: reconcile`); reconciliation is best-effort and surfaces failures via stderr and a `reconcile_failed` ledger event without changing command status/stdout. Removed the duplicate sessionless active-state sync from the `gjc ultragoal` command wrapper.

## [0.3.1] - 2026-06-05
### Added

- Added opt-in crash diagnostics for subprocess failures, with a shared crash taxonomy/report writer, bash/Python/LSP/DAP crash notices, and a native Rust panic-report hook gated by `GJC_NATIVE_CRASH_DIAGNOSTICS` / `GJC_CRASH_DIAGNOSTICS`.
- Started the GJC backend bridge foundation with a shared agent-wire protocol module, event envelopes, RPC command scope matrix, UI request broker, typed unsupported UI results, a guarded `--mode bridge` handshake surface, and RPC mode dispatch refactored onto the shared command dispatcher.
- Documented the experimental `--mode bridge` protocol in `docs/bridge.md` and the `GJC_BRIDGE_*` environment variables in `docs/environment-variables.md` (TLS-mandatory startup, bearer auth, coarse command scopes with a `prompt` floor, single live `AgentSession` per process, bounded event-stream replay with `reset`, and the semantic-not-pixel UI capability matrix), and added bridge event-stream/idempotency regression tests plus a docs-conformance check that pins the docs against the protocol version, scope/command catalog, negotiated capabilities/frame types, and unsupported UI surfaces. The bridge protocol/SDK are experimental (`BRIDGE_PROTOCOL_VERSION` 1) and may change in additive, version-negotiated ways.

### Fixed

- Made opt-in crash diagnostics create/chmod report directories to `0700` and report files to `0600` so captured command, cwd, and stderr previews are private even under a permissive umask.
- Scoped `agent://` and `artifact://` resolution to the caller's artifacts directory plus explicitly authorized parent/child tree directories, removed registry-wide live-session lookup/enumeration, and made missing agent-output metadata sidecars fail closed.
- Fail-closed experimental bridge session endpoints by default for 0.3.1: events, commands, controller ownership, UI responses, host tool results, and host URI results are disabled unless an internal endpoint matrix explicitly enables them; only health/help and the authenticated handshake remain available, with the handshake advertising no enabled session surface by default.
- Render terminal-pasted clipboard image temp paths as compact `[image N]` prompt placeholders while attaching the image payload, instead of inserting raw `/var/folders/.../clipboard-*.png` path text.
- Preserved `gjc harness` `owner-vanished:*` blockers when a replacement owner becomes live so unrecovered prior-owner evidence remains visible until explicit recovery or terminal completion evidence; only safe startup liveness false-negatives such as `detached-owner-not-live` are auto-cleared.
- Fixed the interactive agent unexpectedly stopping after automatic context maintenance instead of resuming the in-flight task. Post-compaction continuation now schedules exactly one source per completion (overflow retry → queued messages → synthetic auto-continue prompt), the threshold/handoff auto-continue prompt skips a redundant pre-send compaction check, overflow retry strips only the context-overflow failed turn (never normal/aborted/silent-abort tails), and non-resumable or superseded continuations log a structured reason instead of stranding the session.
- Fixed the native Stop skill-state hook letting active GJC workflow skills stop prematurely. The Stop hook no longer treats a missing/unreadable mode-state file as terminal for handoff workflows, and handoff skills (`deep-interview`, `ralplan`) now keep blocking Stop even in the `handoff` phase until they are demoted (`active:false`) or cleared, so they always end by offering the next handoff step via the ask tool. Non-handoff skills (`team`, `ultragoal`) retain the fail-open safety valve when their mode-state file is corrupt or invalid.

## [0.3.0] - 2026-06-03

### Added

- Added runtime-enforced Ultragoal executor QA/red-team evidence matrices for completion checkpoints, with plan-first contract coverage, user-surface evidence, adversarial cases, artifact references, scoped Executor red-team guidance, and focused rejection tests for shallow or contradictory QA evidence.
### Fixed

- Made `gjc harness observe` preserve completed RPC owner evidence after the owner exits, including a `completedOwnerExited` diagnostic and durable terminal-result cursor.
- Clarified that `gjc team` requires an existing tmux-backed leader session from `gjc --tmux`, with actionable help, docs, and failure text.
- Kept deep-interview ask options visible for long prompts by adding an opt-in scrollable selector title panel with selector-local `PageUp`/`PageDown` prompt scrolling, while leaving normal ask dialogs and global keybinding configuration unchanged.

## [0.2.5] - 2026-06-02

### Added

- Added Claude Code parity `monitor` plus `CronCreate`, `CronList`, and `CronDelete` tools with frozen upstream schema fixtures, inline scheduling, background output monitoring, and ACP permission coverage.

### Changed

- Pruned bundled built-in themes to `red-claw` and `blue-crab`, with `blue-crab` now the default light-appearance theme.
- Clarified ralplan role-agent handoff guidance so Planner/Architect/Critic return compact artifact receipts after `gjc ralplan --write --json` instead of duplicating full persisted verdict markdown into the parent context.
- Made `web_search` permissionless by default with a keyless DuckDuckGo fallback, active-model-gated native provider selection, and explicit-only legacy provider selection so custom providers no longer auto-hit stray OpenAI/Codex OAuth credentials.

### Fixed

- Fixed the skill HUD rail showing already-handed-off planning workflows so it renders only the currently-active stage. Handoffs now supersede every same-session-scope row of the caller and callee skills (not just the exact `skill::session_id` key), the visible-state read collapses duplicate same-skill rows to the most-recent one (so a handoff demotion drops a stale `active:true` row and on-disk state self-heals), and the HUD renderer collapses the `deep-interview → ralplan → ultragoal` pipeline to its most-recent stage. Activating a later stage (e.g. `gjc ultragoal` after ralplan) now supersedes the earlier one even when the activation path does not run the `handoff` verb, while `team` still coexists with ultragoal.

## [0.2.4] - 2026-06-02

### Added

- Added the bundled `blue-crab` dark theme and a TUI-only `/theme` selector that persists the selected appearance slot while keeping `red-claw` as the default dark theme.
- Added `retry.requestMaxRetries` and `retry.streamMaxRetries` settings plus docs for codex-cli-style provider retry budgets ([#157](https://github.com/Yeachan-Heo/gajae-code/pull/157)).

### Fixed

- Forwarded a caller-provided `User-Agent` on non-Anthropic proxy base URLs so the anthropic-messages provider no longer strips it, fixing HTTP 403 "request was blocked" rejections from WAF-fronted proxy gateways.
- Restored Settings theme live preview without persisting browse state so confirm/cancel governs the rendered theme and in-flight previews cannot overwrite an explicit choice ([#166](https://github.com/Yeachan-Heo/gajae-code/pull/166)).
- Preserved the deep-interview session language so interviews continue in the language they started in ([#168](https://github.com/Yeachan-Heo/gajae-code/pull/168)).
- Purged the inherited OMP/pi-mono history from the embedded coding-agent CHANGELOG so post-update notifications and `/changelog` only surface gajae-code releases ([#161](https://github.com/Yeachan-Heo/gajae-code/pull/161)).
- Wired `goal` tool `drop`/`complete` semantics so completing or dropping a goal restores the pre-goal toolset in the same session, with refreshed ultragoal docs ([#152](https://github.com/Yeachan-Heo/gajae-code/pull/152)).
- Added local-memory empty-payload guidance so the agent does not claim facts were saved or remembered when local memory has no confirmed payload/readback ([#165](https://github.com/Yeachan-Heo/gajae-code/pull/165)).
- Fixed `gjc update` binary fallback downloads to use the current owner release repository and report actionable manual update commands for unsupported fallback targets ([#164](https://github.com/Yeachan-Heo/gajae-code/pull/164)).

## [0.2.3] - 2026-06-01

### Added

- Added `/provider add --preset minimax|minimax-cn|glm` and matching `gjc setup provider --preset ...` shortcuts for OpenAI-compatible MiniMax and GLM/zAI custom-provider onboarding.
- Added a built-in `skill` tool so the agent can chain into another loaded skill on its next turn. Mirrors `/skill:<name>` typing and subagent `autoloadSkills` by dispatching the chained skill's SKILL.md as a user-attribution custom message; controlled by the new `skill.enabled` setting (default true).
- Added explicit fork-context task subagents with sanitized bounded parent-history seeds, global `task.forkContext.enabled`, per-agent `forkContext: allowed`, per-task `inheritContext: true`, audit-visible seed metadata, and fresh provider transport state by default.
- Defaulted the bundled `executor` and `architect` agents to `forkContext: allowed` so callers can opt them into a sanitized parent-context seed via `task({ inheritContext: true })`; per-task selection remains explicit (default false).
- Added a `/context` slash command that surfaces the active session's token usage breakdown via `buildSessionContext` without forking prompt assembly, so users can see active context before sessions overflow ([#150](https://github.com/Yeachan-Heo/gajae-code/pull/150)).
- Added multi-line focused-option rendering to the `ask` tool's `HookSelectorComponent` via an opt-in `wrapFocused` flag so long option labels stay readable in plan-mode and other shared consumers ([#148](https://github.com/Yeachan-Heo/gajae-code/pull/148)).
- Added a sanctioned native deep-interview spec persistence bridge so deep-interview can save final specs under `.gjc/specs/` before deliberate ralplan chaining without caller-side `.gjc` writes ([#134](https://github.com/Yeachan-Heo/gajae-code/pull/134)).
- Added the skill chaining lifecycle's `handoff` state verb with same-turn dispatch and atomic HUD-truth synchronization across callee mode-state, caller mode-state, session active-state, and root active-state ([#132](https://github.com/Yeachan-Heo/gajae-code/pull/132)).

### Changed

- Clarified the README to position team as optional and ultragoal as implementation-through-evidence, so docs readers see workflow handoffs without spelunking into skill sources ([#145](https://github.com/Yeachan-Heo/gajae-code/pull/145)).
- Migrated CI to the gajae self-hosted Linux runner with fork-PR guards, idempotent `fd` symlink setup, and Node 24 pinning for self-hosted jobs ([#124](https://github.com/Yeachan-Heo/gajae-code/pull/124)).

### Fixed

- Enforced the deep-interview phase boundary so active interviews block mutation tools until a handoff/spec is produced.
- Allowed read-only `architect`, `planner`, and `critic` role agents to persist ralplan/state workflow receipts through a restricted `bash` allowlist while blocking general shell and product-file mutations.
- Made settings theme browsing confirm-only so arrowing through themes no longer changes the rendered theme before the displayed/persisted theme name changes.
- Made startup CHANGELOG display deterministic by embedding `packages/coding-agent/CHANGELOG.md` into the binary so post-update launches show the shipped history regardless of cwd or `GJC_PACKAGE_DIR`/`PI_PACKAGE_DIR` overrides.
- Registered `gjc update` as a public root subcommand so it invokes the bundled updater instead of routing into the interactive launcher.
- Fixed local memory backend persistence so manual enqueue/rebuild starts maintenance immediately and prompt injection reads the active session's memory root.
- Aligned release-bump CI tests with the highest bundled upstream Claude family and stopped cancelling TTSR retries when an aborted partial was not persisted, so dev PRs no longer regress on v0.2.2-style release assumptions ([#142](https://github.com/Yeachan-Heo/gajae-code/pull/142)).
- Added a runtime guard in the built-in `skill` tool that rejects re-entry into the currently active skill via a typed active-skill bridge from agent sessions into tool sessions, preventing recursive skill handoffs that prompt-only guidance could not stop ([#129](https://github.com/Yeachan-Heo/gajae-code/pull/129)).

## [0.2.2] - 2026-05-31

### Added

- Added `gjc session` for listing, inspecting, removing, and attaching GJC-managed tmux sessions ([#105](https://github.com/Yeachan-Heo/gajae-code/pull/105)).
- Added a managed tmux session surface so `gjc --tmux` and `gjc team` reuse a scoped GJC session instead of mutating the global tmux environment.
- Added a detached contribution prep workflow surface ([#122](https://github.com/Yeachan-Heo/gajae-code/pull/122)).
- Added stricter `models.yml` validation and docs for OpenAI-compatible proxy providers, bridge-command requirements, and team dry-run state behavior ([#121](https://github.com/Yeachan-Heo/gajae-code/pull/121)).
- Added first-class Azure OpenAI and Amazon Bedrock providers ([#119](https://github.com/Yeachan-Heo/gajae-code/pull/119)).
- Added workflow state receipt routing so skill state writes emit a structured `WorkflowStateReceipt` envelope and sync skill-active HUD chips under `.gjc/state/skill-active-state.json` ([#118](https://github.com/Yeachan-Heo/gajae-code/pull/118)).
- Made `gjc state`, `gjc ralplan`, and `gjc deep-interview` work natively as documented in their SKILL.md files. `gjc state read|write|clear|contract` operates directly on `.gjc/state/` receipts (accepts `--input '<json>'` with `@file` shorthand, `--mode <skill>`, positional `<skill>`, `--session-id`, `--thread-id`, `--turn-id`, `--json`, `--replace`); writes emit a structured `WorkflowStateReceipt` envelope and sync skill-active HUD chips. `gjc ralplan` accepts the documented `--interactive`/`--deliberate`/`--architect`/`--critic` flags and the `--write --stage --stage_n --artifact` artifact persistence shape under `.gjc/plans/ralplan/<run-id>/`. `gjc deep-interview` accepts the documented `--quick`/`--standard`/`--deep` resolution flags ([#125](https://github.com/Yeachan-Heo/gajae-code/pull/125)).
- Added a GJC dogfood skill template ([#114](https://github.com/Yeachan-Heo/gajae-code/pull/114)).

### Changed

- Changed `gjc --tmux` startup to create a fresh GJC-managed tmux session instead of attaching to an existing default session.
- Hardened team message delivery and worker enforcement ([#112](https://github.com/Yeachan-Heo/gajae-code/pull/112)).
- Hardened team worker startup and evidence invariants ([#123](https://github.com/Yeachan-Heo/gajae-code/pull/123)).
- Clarified team state while integration is pending ([#109](https://github.com/Yeachan-Heo/gajae-code/pull/109)).
- Removed goal mode budget semantics ([#106](https://github.com/Yeachan-Heo/gajae-code/pull/106)).
- Removed the unused `gjc question` CLI; the `ask` tool covers blocking-question prompts.
- Dropped the obsolete `loop` and `orchestrate` skills and pinned GJC skills in autocomplete ([#126](https://github.com/Yeachan-Heo/gajae-code/pull/126)).
- Enforced CLI-managed workflow artifacts so planning skills persist via `gjc state` and `gjc ralplan --write` instead of editing `.gjc/` files directly.

### Fixed

- Fixed Anthropic extended-thinking replay recovery after aborted turns ([#115](https://github.com/Yeachan-Heo/gajae-code/pull/115)).
- Fixed `gjc deep-interview` ambiguity threshold default so workflow gating matches the documented contract ([#116](https://github.com/Yeachan-Heo/gajae-code/pull/116)).
- Fixed duplicate PR creation in the team integration path ([#117](https://github.com/Yeachan-Heo/gajae-code/pull/117)).
- Fixed package asset resolution to prefer `GJC_PACKAGE_DIR` over the legacy `PI_PACKAGE_DIR`, preventing update-launch changelog displays from reading stale OMP package assets ([#111](https://github.com/Yeachan-Heo/gajae-code/pull/111)).
## [0.2.1] - 2026-05-30

### Fixed

- Added a `gjc skills` inspection command so installed binaries can list and read embedded workflow skills from any project without relying on source-tree `.gjc` files.
- Fixed first-run API provider onboarding so `models.yml` parent directories are created before writing, and malformed `/provicer` startup invocations now report the intended `/provider add` spelling instead of falling through to model bootstrap.

## [0.2.0] - 2026-05-28

### Added

- Added scoped GJC tmux profile handling for `gjc --tmux` and `gjc team` sessions without mutating global tmux configuration.
- Added GJC team integration hardening for worker turn-end integration requests, auto-rebase/auto-merge conflict surfacing, protected checkpoint classification, and leader/worker-visible integration summaries.
- Added Node 20 release baseline validation to the release/check surface.

### Changed

- Clarified the public workflow contract so `deep-interview` and `ralplan` are invoked through `/skill:<name>`, while `gjc ultragoal` and `gjc team` remain native runtime commands.
- Updated the README hero image and Discord community invite.

### Fixed

- Restored Ultragoal completion receipt export/generation validation and completion gates.
- Fixed workflow bridge guidance and tests so private compatibility bridge commands are not advertised as public skill-loading paths.

## [0.1.3] - 2026-05-28

### Changed

- Released the current dev branch fixes with refreshed 0.1.3 package metadata.

## [0.1.2] - 2026-05-28

### Changed

- Updated package metadata for the Gajae Code npm publication.

### Fixed

- Fixed slash-command autocomplete so skill command matches no longer hide built-in fuzzy candidates like `/model` while typing `/mode`.

## [0.1.1] - 2026-05-28

### Changed

- Restored `gjc team` multi-worker GJC-team parity orchestration with current-window worker panes, GJC-scoped state/API semantics, and `N:agent-type` launches.
- Ported GJC team worker-worktree integration parity so `status`/`resume` auto-checkpoint dirty workers, merge or cherry-pick worker commits, cross-rebase idle workers, and record conflicts under `.gjc` integration artifacts.

### Added

- Added a detached `subagent` control tool for task subagents, with list, inspect, await-with-timeout, and cancel actions.
- Added shared provider onboarding for OpenAI-compatible and Anthropic-compatible API providers through `gjc setup provider` and `/provider add`, with model-list configuration and redacted setup feedback.
- Added shared `/model` onboarding guidance and an interactive `/provider` onboarding chooser so first launch, slash commands, and TUI no-model states point at the same provider setup flows.
- Added a native in-TUI skill HUD rail backed by `.gjc/state/skill-active-state.json`, so active GJC workflow skills are visible without a separate tmux pane.
- Added bundled `executor`, `architect`, `planner`, and `critic` role agents for task delegation, including source-defined prompt files and role-agent discovery coverage.
- Added a native `gjc team` runtime that writes GJC-scoped state, mailboxes, task lifecycle files, and telemetry without delegating to an external team binary
- Added `openai-code` and `gemini` to the web search provider settings so users can configure OpenAI and Gemini web search directly from provider selection
- Added OpenAI (`openai-code`) and Gemini web search options with updated setup descriptions for `gjc /login openai-code` and Gemini OAuth login

### Changed

- Changed normal `task` subagent launches to return immediately as detached background work while keeping generic `job` controls available.
- Changed default interactive `gjc` startup to enter a `gajae_code` tmux session before launching the Gajae Code TUI, with non-interactive modes continuing to run directly.
- Changed `/skill:<name>` handling so canonical skill invocations can be chained in one prompt across interactive and ACP sessions, with autocomplete-only `/name` and `/skill-name` normalization back to the public canonical form.
- Changed interactive `gjc` startup to launch tmux only when `--tmux` is provided, with direct startup as the default.
- Changed GJC default definitions so workflow skills remain source-bundled while repo-visible `.gjc` default artifacts are no longer the source of truth; updated system and Ultragoal guidance to use role-agent delegation and ralplan-first planning when needed.
- Changed bare `gjc setup` to install the normal default workflow skills, while keeping hooks, provider, Python, and speech-to-text setup as explicit optional components.
- Changed `gjc team` startup to use tmux worker panes backed by dedicated detached git worktrees by default, while keeping `--worktree` as a backward-compatible launch override.
- Constrained the visible GJC utility surface to the retained workflow/runtime endpoints and four bundled task agents, with MCP, arbitrary skill, plugin, extension, marketplace, and custom discovery surfaces quarantined from default public use.
- Redesigned the interactive TUI chrome with a minimal opencode-style prompt composer, simple user/gajae transcript labels, a forge-style welcome surface, and compact cwd/pulse indicators tuned for terminal coding-agent ergonomics.
- Changed web search provider credential lookup to use the shared `AuthStorage` pipeline (`getApiKey`/`getOAuthAccess`) for API-key and OAuth auth instead of direct `AgentStorage` access
- Changed the `openai-code` web search provider display label from `OpenAI code` to `OpenAI`
- Updated `anthropic` and `openai`/`gemini` web search option descriptions to reflect their native `web_search`/OAuth requirements
- Changed `/model` selection to a canonical single default-model action, removing the redundant role assignment menu for smol/slow/vision/plan/designer/commit/task models.
- Changed public API-compatible provider setup to require `--api-key-env` and reject raw `--api-key` values.

### Removed

- Removed approved non-critical slash-command handlers for plan, share, browser, copy, todo, changelog, context, branch, fork, handoff, force, and quit while keeping /loop, provider setup/login/logout/model selection, and SSH intact.
- Removed redundant model-selector role assignment options for smol, slow, vision, plan, designer, commit, task, and custom roles so selection uses one canonical default model.
- Removed obvious non-critical plugin, marketplace, extension, and reload-plugin slash-command handlers from the built-in registry while preserving ambiguous slash-command utilities for a later approval pass.
- Removed the auto-QA grievance reporting feature, including the `report_tool_issue` tool, `gjc grievances` command, auto-QA settings/env flags, sharing consent prompt, bundled push endpoint, and persistent install ID correlation path.
- Removed standalone utility feature documentation for plugins, extensions, hooks, marketplace, arbitrary skills, custom tools, task-agent discovery, and TUI/config utility internals from the generated docs index.

### Fixed

- Fixed `gjc ultragoal create-goals` native goal activation so live sessions receive a pending reconciliation request even when the session file already contains an active goal.
- Made `gjc ultragoal` run natively, preserving active goal state across interrupted turns.
- Fixed interactive Escape/interrupt recovery so abort cleanup is bounded and forces the session back to idle when a provider stream, tool, or post-turn task ignores cooperative cancellation.
- Fixed root `gjc --worktree` / `gjc -w` startup so the launch command actually creates and enters the sibling `<repo>.gajae-code-worktrees/<branch-slug>` git worktree before starting the session, using collision-resistant branch slugs and avoiding worktree side effects for help/version launches.
- Fixed root `gjc --worktree <branch>` / `gjc -w <branch>` parsing so named branch worktrees create their own `<branch-slug>` directory instead of reusing the dirty detached worktree for the current branch.
- Wired GJC native UserPromptSubmit/Stop skill-state hooks, including `gjc setup hooks`, so public workflow keywords activate `.gjc/state`, active skill state can block premature Stop events, and active Ultragoal sessions remind steering prompts to use `gjc ultragoal steer`.
- Fixed `gjc ultragoal create-goals` to seed GJC goal mode runtime state automatically, avoiding a separate manual `/goal` setup step.
- Fixed legacy Pi plugin import remapping and stale GJC config-path tests so rebranded `.gjc` discovery contracts pass while preserving legacy compatibility.
- Fixed web search OAuth-backed providers (including OpenAI code and Gemini) to use broker-managed token retrieval and account metadata, avoiding direct token-store refresh behavior that could cause search authentication failures
- Updated Tavily missing-credential feedback to prompt users to configure an API-key provider setting instead of referencing `agent.db` directly
- Refreshed expired OpenAI code provider OAuth tokens during `web_search` execution and persisted the updated credentials so searches continue working after token expiry
- Wired `/login`, `/logout`, `/model`, and `/provider` TUI slash commands through interactive provider/model selectors and existing OAuth flows.
