Skip to content

fix(coding-agent): answer ACP prompts only once the session admits the next turn - #800

Merged
snimu merged 4 commits into
mainfrom
fix/acp-prompt-waits-for-idle
Aug 20, 2026
Merged

fix(coding-agent): answer ACP prompts only once the session admits the next turn#800
snimu merged 4 commits into
mainfrom
fix/acp-prompt-waits-for-idle

Conversation

@parkerpettit

@parkerpettit parkerpettit commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Linear: ENG-5376

ACP could answer session/prompt after its first idle check even though injected work had restarted the session. An immediate follow-up was then rejected as Agent is already processing.

Without this change:

  1. The current ACP turn finishes.
  2. waitForHeadlessCompletion() observes an idle session.
  3. Injected work, such as a subagent message, starts another turn.
  4. ACP returns end_turn.
  5. The client sends its next prompt and Prime Agent rejects it because the session is busy.

Re-check connection.waitForIdle() before returning the ACP response. This closes the admission race seen in multi-turn Verifiers runs.

#881 fixes a different gap: waitForIdle() did not include a continuation already scheduled by compact.run. One makes the idle check complete; this PR puts a final check at the response boundary.

Verification:

  • ACP response waits for the final admission check
  • an immediate second prompt is accepted

Note

Queue ACP follow-up prompts behind in-flight work and cancel queued turns before delivery

  • ACP prompt handler no longer throws when a prompt is admitted before cancellation; it returns stopReason="cancelled" and passes streamingBehavior:"followUp", queueIfBusy:true to promptAndWait
  • AgentSession.promptAndWait accepts an optional AbortSignal via options.signal; on abort it cancels queued session-owned turn actions matching the agentMessageId before delivery and rejects the completion
  • New daemon server capability owned_prompt_cancellation (default-on) and cancelOwned flag on cancel_prompt_admission commands allow cancelling session-owned prompts that have not started; gated by protocol 7, schema revision 20
  • Daemon schema bumped from 19 to 20 in daemon-protocol.ts; DaemonAgentConnection sends cancelOwned:true when the server advertises the capability
  • Behavioral Change: cancel_prompt_admission with cancelOwned=true now aborts an owned admission's controller in daemon-mode.ts; clients below protocol 7 / schema 20 will not send the flag and owned cancellation is a no-op for them

Macroscope summarized 7ce5609.


Note

Medium Risk
Changes daemon worker lifecycle, persisted launch environment, and ACP turn-boundary semantics that verifiers and embedders depend on; behavior is heavily tested but mis-timed idle or env handling could still break reconnect or scoring.

Overview
ACP session/prompt now awaits connection.waitForIdle() before returning end_turn, so clients are not told the turn finished while injected work (e.g. subagent messages) has already restarted the session and would reject an immediate follow-up with “Agent is already processing.”

ACP session lifecycle and scoring metadata: session/new reserves the single-session slot before the first await, subscribes before getInitialSnapshot(), and fails setup cleanly if the snapshot read errors. After headless completion, a session_info_update carries namespaced quiescence (outstandingSubagents, remainingAutonomousContinuations) plus autonomous state from a live roster snapshot; snapshot failures at emission time propagate instead of reporting a false zero.

Daemon / ACP residency: Normal ACP sessions (with a session file) use resident workers so disconnect/reconnect can reattach; --no-session ACP stays client-owned. Resident creates always forward launchEnv from the caller (model endpoint, tokens, proxy, etc.). The supervisor persists launchEnv on resident worker descriptors for recovery after restart and strips it when promoting to client-owned, with integration tests for env across worker recovery and live IPython namespace across ACP reconnect.

Reviewed by Cursor Bugbot for commit ac97743. Bugbot is set up for automated code reviews on this repo. Configure here.

@parkerpettit
parkerpettit marked this pull request as ready for review August 7, 2026 20:22
@parkerpettit
parkerpettit force-pushed the fix/acp-prompt-waits-for-idle branch from 6a6ab06 to ac97743 Compare August 7, 2026 20:40
@parkerpettit
parkerpettit changed the base branch from main to feat/acp-quiescence-meta August 7, 2026 20:40
@parkerpettit
parkerpettit marked this pull request as draft August 7, 2026 20:40
Comment thread packages/coding-agent/src/modes/acp/acp-mode.ts Outdated
@parkerpettit
parkerpettit marked this pull request as ready for review August 7, 2026 20:42
@snimu

snimu commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Missing CHANGELOG entry: this fixes a user-visible ACP race (immediate follow-up prompt rejected with "Agent is already processing"). Please add a bullet under ## [Unreleased] in packages/coding-agent/CHANGELOG.md.

Comment thread packages/coding-agent/src/modes/acp/acp-mode.ts Outdated
@parkerpettit

Copy link
Copy Markdown
Contributor Author

Reworked per review: ACP prompts now queue behind in-flight work (streamingBehavior: "followUp", queueIfBusy: true) instead of waiting for whole-session idleness; the final waitForIdle() is removed, so the stop reason comes from the status captured after the turn the response gates and the response is never held open by detached work. Regression tests inject real work after the first idle observation; CHANGELOG entry added.

Comment thread packages/coding-agent/src/modes/acp/acp-mode.ts
Comment thread packages/coding-agent/src/modes/daemon/daemon-mode.ts
Comment thread packages/coding-agent/src/core/agent-session.ts Outdated
@parkerpettit
parkerpettit force-pushed the fix/acp-prompt-waits-for-idle branch 2 times, most recently from 3f68e8d to bf2c00e Compare August 10, 2026 22:38
@parkerpettit
parkerpettit force-pushed the fix/acp-prompt-waits-for-idle branch from bf2c00e to 5d7d4cf Compare August 19, 2026 20:36
@parkerpettit
parkerpettit changed the base branch from feat/acp-quiescence-meta to main August 19, 2026 20:42
@macroscopeapp

macroscopeapp Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Macroscope has since reviewed this pull request. An earlier review was skipped by a cost limit; a review has now completed, so that notice no longer applies.

snimu added 3 commits August 20, 2026 10:17
# Conflicts:
#	packages/coding-agent/CHANGELOG.md
# Conflicts:
#	packages/coding-agent/CHANGELOG.md
#	packages/coding-agent/src/modes/acp/acp-mode.ts
#	packages/coding-agent/src/modes/daemon/daemon-protocol.ts
#	packages/coding-agent/test/suite/acp-mode.test.ts
… merge

Renumber owned prompt cancellation to schema revision 20 (17-19 landed on
main), adapt compatibility gating to the requirements-array form, and return
stopReason "cancelled" for a prompt parked behind the terminal lifecycle when
a cancellation drops it before it starts.
@snimu
snimu merged commit 02e217e into main Aug 20, 2026
19 checks passed
@snimu
snimu deleted the fix/acp-prompt-waits-for-idle branch August 20, 2026 09:08
ruttybob added a commit to ruttybob/prime-agent that referenced this pull request Aug 20, 2026
Conflict resolution (3 files, all CHANGELOG):
- packages/{ai,coding-agent,tui}/CHANGELOG.md — keep fork [Unreleased]
  bullets (Z.AI reasoning_effort; mermaid/selection/statusline/cwd/ctrl+c/
  config-scope panes/agents-view fixes), take upstream's finalized
  [0.7.4] sections verbatim

Upstream payload: model search intent ranking (PrimeIntellect-ai#539), ACP resident
session lifecycle hardening (PrimeIntellect-ai#1494), ACP follow-up prompt queueing
(PrimeIntellect-ai#800), v0.7.4 release prep (version bumps, catalogs).

Fork features verified intact after merge: mermaid transform + settings
toggle, tui Markdown transform hook, config scope panes, cwd statusline,
grok-mermaid dependency, Z.AI supportsReasoningEffort catalog entries.
gogoyqj pushed a commit to gogoyqj/prime-agent that referenced this pull request Aug 21, 2026
…e next turn (PrimeIntellect-ai#800)

* fix(coding-agent): queue ACP prompts behind in-flight work

* fix(acp): keep queued-prompt semantics through the resident lifecycle merge

Renumber owned prompt cancellation to schema revision 20 (17-19 landed on
main), adapt compatibility gating to the requirements-array form, and return
stopReason "cancelled" for a prompt parked behind the terminal lifecycle when
a cancellation drops it before it starts.

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
soponcd added a commit to soponcd/prime-agent-fork that referenced this pull request Aug 29, 2026
* Keep the draft when opening the agents view (#1372)

* feat(coding-agent): auto-stash editor draft when opening the agents view and restore it on session reopen

* fix(coding-agent): skip agents-view auto-stash for whitespace-only drafts

* fix(coding-agent): keep init statuses visible when restoring an on-open stash

* test: stub restorePromptStashOnOpen in startup-run fakes and bound the hook spin

run() now calls this.restorePromptStashOnOpen() right after init(). The
Prime CLI onboarding tests drive the real run() on plain fake objects,
so the missing method made run() reject immediately: three tests failed
and two spun forever in `while (!fakeThis.admitPendingStartupPrompts)
await Promise.resolve()`, hanging the CI shard until the job timeout
cancelled it with no output.

Stub the method in createStartupRunHarness and bound both spin loops so
a future regression fails the test instead of hanging the shard.

* test(coding-agent): trim prompt stash coverage

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* Let subagents be spawned with their own reasoning level (#1510)

* feat(coding-agent): let subagents be spawned with an explicit reasoning level, fixes ENG-5301

* fix(coding-agent): neutral wording for the subagent thinking option in the rlm prompt

* chore(coding-agent): drop a redundant doc comment

* chore(coding-agent): drop the remaining name-restating doc comments in rlm-runtime

* chore(coding-agent): simplify subagent thinking validation

* fix(coding-agent): preserve early thinking validation

* refactor(coding-agent): centralize thinking levels

* refactor(coding-agent): keep thinking validation local

* test(coding-agent): retain thinking option boundaries

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* Normalize daemon socket paths before deriving identity (#1520)

* fix(coding-agent): normalize daemon socket paths at every entry point, fixes ENG-5303

* fix(coding-agent): address review findings on socket-path normalization

- Defer --daemon-socket normalization until after --cwd applies so relative
  socket paths resolve against the requested working directory
- Migrate legacy raw-spelling worker-descriptor namespaces to the canonical
  directory on supervisor construction so existing workers stay adoptable
- Consolidate the remaining private normalizers (package-manager-cli,
  daemon-update-restart) onto the shared normalizeSocketPath
- Wait for killed test supervisors to exit before removing their directories
  to fix ENOTEMPTY flakes in CI

* fix(coding-agent): harden descriptor-namespace adoption and test cleanup

- Adopt a raw-spelling descriptor namespace only after the socket-path lease
  and registry ownership are held, so a rejected startup can never move a live
  supervisor's directory
- Skip unreadable JSON entries per file when identifying a namespace instead
  of aborting the whole scan
- Drop migration-era vocabulary from the helper names and test titles
- Disable the Node compile cache for spawned test supervisors and tolerate
  cleanup races so worker processes exiting late cannot fail the suite

* refactor(coding-agent): drop the descriptor-namespace adoption machinery

Normalization at the entry points prevents namespace forks going forward;
descriptors written under an old raw-spelling key are healed per-field on
load only when they share the directory. Cross-directory adoption kept
accreting ordering hazards (live-daemon rename races, persisted-config
staleness) disproportionate to its value, so a daemon that previously ran
on a non-canonical spelling simply starts fresh namespaces; saved sessions
are unaffected (they live in the session catalog, not the descriptor dir).

* docs(coding-agent): align the changelog with the simplified scope

* chore(coding-agent): trim excessive comments

* fix(coding-agent): normalize the early-launch socket key and drop an incidental test

maybeStartDaemonEarly memoized ensure attempts under the raw --daemon-socket
spelling while main uses the normalized one, so equivalent spellings could
run two concurrent spawn attempts for one daemon. The early kick now derives
the same canonical spelling (resolving relative paths against --cwd).

* chore(coding-agent): simplify socket normalization coverage

* Address final review feedback

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* CI: require a linked Linear ticket on PRs (#1480)

* ci: require a linked Linear ticket or an explicit opt-out on pull requests

* ci: drop the No-Ticket opt-out; every PR links a Linear ticket

* Keep large IPython state from slowing later turns (#1540)

* fix(coding-agent): bound persistent kernel snapshots

* fix(coding-agent): simplify bounded kernel snapshots

* fix(coding-agent): cap individual snapshot variables

* fix(coding-agent): prune oversized state on compaction

* fix(coding-agent): stop snapshots at aggregate limit

* fix(coding-agent): preserve bounded snapshot packing

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* docs: add Trendshift badge (#1500)

* docs: add Trendshift badge

* docs: separate the Trendshift badge

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* fix(coding-agent): rank model search by intent (#539)

Fixes ENG-5388

Co-authored-by: Seth <seth@primeintellect.ai>

* chore(release): prepare v0.7.4 (#1569)

* feat(acp): harden resident session lifecycle (#1494)

* feat(acp): harden resident session lifecycle

* test(daemon): align recovery fixtures with durable context

* fix(acp): validate durable host settings

* fix(daemon): clean up failed resident resources

* fix(daemon): recover owned workers from fresh context

* fix(acp): verify authoritative completion state

* fix(daemon): refresh requested child roster

* fix(acp): read child roster without reattaching

* fix(daemon): preserve telemetry opt-out on recovery

* fix(daemon): scope resident path conflicts

* test(acp): remove runtime publish hook

* fix(acp): drain admitted updates on close

* fix(daemon): merge fresh supervisor defaults

* fix(acp): await autonomous lifecycle settlement

* fix(acp): preserve daemon input fences

* fix(acp): serialize terminal lifecycle turns

* fix(acp): reacquire daemon fences after reconnect

* fix(acp): fail closed across abort cuts

* fix(acp): resume queued input after pause

* fix(acp): invalidate pending pumps on pause

* fix(acp): preserve pause ownership through supervisor

* fix(acp): fail closed on pause cleanup loss

* test(daemon): initialize pause ownership harness

* fix(acp): fence detach pause cleanup

* fix(daemon): preserve detach-all cleanup

* test(daemon): initialize worker pause registry

* fix(daemon): reacquire pauses across release

* fix(daemon): clear stable detach selectors

* fix(daemon): retain detach fences on attach failure

* refactor(daemon): keep descriptor validation private

* docs(changelog): describe ACP lifecycle fencing

* fix(acp): settle restart and failed close lifecycle

* fix(acp): serialize cancel after failed close

* fix(acp): preserve lifecycle reconciliation ordering

* fix(acp): settle deleted child runtimes

* test(acp): consolidate child deletion regressions

* fix(acp): rearm settled child deletion

* fix(acp): preserve recursive terminal settlement

* test(acp): update terminal notice regression

* fix(coding-agent): answer ACP prompts only once the session admits the next turn (#800)

* fix(coding-agent): queue ACP prompts behind in-flight work

* fix(acp): keep queued-prompt semantics through the resident lifecycle merge

Renumber owned prompt cancellation to schema revision 20 (17-19 landed on
main), adapt compatibility gating to the requirements-array form, and return
stopReason "cancelled" for a prompt parked behind the terminal lifecycle when
a cancellation drops it before it starts.

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>

* fix(coding-agent): treat a set-but-empty credential env var as missing (#1513)

* feat: add generic kernel-owned MCP runtime (#1495)

* feat(coding-agent): add generic MCP runtime

* fix(coding-agent): harden MCP lifecycle and overrides

* fix(runtime): close cancelled host request comms

* fix(runtime): preserve MCP SDK result aliases

* fix(runtime): preserve synchronous kernel shutdown

* fix(runtime): allow MCP close retry after cancellation

* feat(coding-agent): manage MCP servers from CLI and TUI

* fix(coding-agent): preserve inherited stdio env names

* fix(coding-agent): reserve built-in MCP identities

* fix(coding-agent): allow cleanup of reserved MCP entries

* test(coding-agent): update MCP command hint

* fix: address MCP reload and cleanup findings

* fix(coding-agent): advertise generic MCP connections

* fix(coding-agent): keep MCP command results visible

* fix(runtime): surface safe stdio startup diagnostics

* fix(runtime): preserve startup errors through cleanup

* fix(coding-agent): drop stored MCP credentials when a server is removed or replaced

* fix(coding-agent): scope credential drops to generic servers and keep quoted empty argv tokens

* docs(coding-agent): document the removed catalog-name override as a breaking change

* fix: close kernel MCP servers during shutdown

* docs: preserve changelog sections after main merge

* fix: harden MCP shutdown boundaries

* fix: bound graceful kernel shutdown

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>

* fix(coding-agent): reject headless idle waiters when a post-compaction continuation cannot start (#1583)

* fix(coding-agent): reject headless idle waiters when a post-compaction continuation cannot start

A continuation that fails to start settled headless idle as a clean finish,
so ACP and print-mode callers reported a turn as completed that never ran.
The settlement is now one-shot with reject support: non-retryable start
failures reject waiters, cancellation and the benign nothing-to-continue
race still resolve, a settled failure is never re-exposed to later waiters,
and interactive waitForIdle is unchanged.

Ports the failure semantics from #881 onto the resident-lifecycle settlement
from #1494. Co-authored-by: Parker Pettit <parkerpettit@users.noreply.github.com>

* docs: cut comments down to single-line load-bearing invariants

* test: keep only the two tests that pin new behavior

* fix(coding-agent): survive kernel cold boots and stop leaking raw zmq EAGAIN (#1587)

* Survive kernel cold boots and stop leaking raw zmq EAGAIN

After an upgrade that changes the kernel runtime deps, the shared venv
reprovisions on first boot. First cells raced that slow start and
failed with libzmq's bare "Operation was not possible or timed out",
succeeding only on retry. ensureKernelPython now reports provisioning
work via onProvisioned, doStart grants such boots a 30s ready budget
(warm boots keep 5s), and probeReady/execute sends translate socket-
teardown rejections into an actionable retriable kernel error carrying
the stderr tail.

* Add the kernel socket-closure translation unit suite

* Propagate provisioning to coalesced callers and sibling processes

Review found onProvisioned only reached the caller that performed venv
work: managers coalesced onto an in-flight bootstrap and lock waiters
that found the venv ready kept the 5s warm budget against a stone-cold
venv. The uncached path now returns {python, provisioned} and the
wrapper fires every caller's callback; a ready venv whose version stamp
is younger than two minutes counts as provisioned so sibling-process
first boots get the cold budget too.

* Grant the cold-boot budget to the port resolve too

Review caught that a cold direct spawn could still fail the 5s
PORTS_RESOLVE_TIMEOUT_MS wait before the 30s ready probe ever ran:
ipykernel rewrites the connection file only after its cold imports
finish binding ports. waitForResolvedConnection now takes the same
cold budget as probeReady when the venv was just provisioned.

* Simplify: flat 30s startup budget instead of cold-boot detection

The cold/warm dual budget required detecting "was this boot cold?"
(onProvisioned plumbing, per-caller propagation, an mtime freshness
heuristic) and three review rounds kept finding edge cases in exactly
that machinery. Crash detection never depended on the timer: both wait
loops observe the exit handler within one 25ms poll. So the timeout
only bounds an alive-but-wedged kernel, where patience is cheap.
Startup now uses one generous 30s budget unconditionally; warm boots
return in under a second and never feel it, cold boots just work, and
all detection machinery is deleted. The zmq socket-teardown error
translation stays.

* fix(coding-agent): close MCP runtime review follow-ups (#1585)

* fix(coding-agent): close MCP runtime review follow-ups

- drop the mcp:<name> credential on every add, not only replaces: a fresh
  add can repoint a name an authored non-catalog skill (e.g. slack) resolves
  via mcp.config, and the stored token must never replay there
- keep the kernel MCP close budget strictly inside the host kill deadline
  (2.5s + 1s dispatch < 5s) and pin the relationship in a test
- include kernel exit in the host's first shutdown race so a kernel that
  dies without shutdown_reply finishes promptly instead of eating the deadline
- close a shutdown race that dropped a just-opened generation from the
  registry without closing it, leaving its server process running
- run kernel-mcp-shutdown.test.ts in test:kernel and the Python runtime
  tests in CI; gate the E2E on module availability instead of an exact
  ipykernel version that silently skipped the suite
- pin mcp>=2,<3, require Python >=3.11 (asyncio.timeout), and resolve the
  streamable-HTTP transport via mcp_base instead of new-only + private imports
- shield sibling generation closes from a failing close in shutdown/reload
- reject a quoted-empty mcp add option value deterministically

* fix: keep SSE-friendly read timeouts and handled shutdown races

Match the SDK factory's timeouts (30s ops / 300s reads) on the http_client
transport path in both the generic runtime and mcp_base, keeping reads above
the configured per-call timeout; an httpx default client caps reads at 5s and
a flat 30s cap drops quiet streams whose server sends no pings. Attach a
rejection handler to the abandoned graceful-reply composite so a late send
failure cannot surface as an unhandled rejection.

* docs: compress comments to single-line invariants

* fix: give the http_client transport its companion client and stop following redirects

The mcp 2.x transport calls client.sse() for server-initiated streams and
reconnects; a plain httpx client has no such method, so the GET stream died
in a silent reconnect loop (AttributeError swallowed by the retry). Both
fallback paths now build an httpx2 client with the SDK-factory timeouts.
Redirects are disabled on these clients: httpx only strips Authorization on
cross-origin redirects, so configured secret headers would follow a
redirecting endpoint. httpx itself is no longer used and leaves the runtime
dependencies (added by #1495 for the code this replaces).

* fix: stop authored integrations honoring mcpServers overrides and fail credential drops closed

An authored McpIntegration resolved its URL through mcp.config, so a
same-named user entry repointed it while its credentials — auth.json tokens
or a bearer-token env var the add-time drop can never reach — followed as
the Authorization header. Authored integrations now always use their class
URL; custom endpoints go through the generic runtime under their own name.

The add flow also persisted the new URL before dropping stored credentials,
and the store records persistence failures instead of throwing, so a failed
or silently skipped write left the old token armed against the new URL
behind a reported success. Drops now happen before the entry is persisted
and drain the store's recorded errors, aborting the add on failure.

* fix(coding-agent): make credential removal disk-verified instead of error-drain heuristics

Three review rounds patched the same invariant at the call site: logout
removes memory before a persist that swallows failures (and silently no-ops
after a load error), so has() gating skipped retries and drainErrors missed
the loadError path entirely. The invariant now lives in the owning layer:
AuthStorage.removeVerified performs the disk removal under lock, throws on
any load or write failure, and only then updates memory — disk-authoritative
and idempotent. dropServerCredentials shrinks to a try/rethrow around it.

Drop-before-persist ordering is deliberate: a settings-flush failure after a
verified drop strands the server on re-login (recoverable); the reverse
order can strand an old token against a new URL (replay).

* fix(coding-agent): keep the stale marker until a verified removal succeeds

A failed removeVerified cleared the stored stale marker first, making a
suppressed credential selectable again while it survived on disk. Also
compress comments to single-line invariants.

* fix: bind MCP OAuth tokens to the endpoint they were issued for

An in-flight login races retargeting: /mcp login captures the provider
for the old URL, awaits the browser, and a concurrent add --force drops
the credential and persists a new URL; the finishing login then stores
an old-endpoint token under the same name and the runtime sends it to
the new URL. No ordering fixes this — the binding token<->endpoint
existed nowhere. Credentials now record the endpoint at issuance,
refreshes carry the original binding forward (no laundering), and both
consumers verify it: the kernel refuses to attach a foreign-bound token
and the host treats it as not authed. Legacy unbound credentials keep
working.

* fix(coding-agent): strip trailing slashes without a backtracking regex

CodeQL flagged the /\/+$/ anchor on stored-credential and settings input
as polynomial; a two-line loop replaces it.

* fix: require endpoint binding for user-declared MCP OAuth credentials

Accepting unbound legacy credentials kept the replay window open: a
pre-update login can finish after a retarget and store an unbound token,
and a refresh would have re-bound it to the current URL. User-declared
servers now require an exact binding (host and kernel), and a refresh
never infers one. Builtin catalog integrations keep accepting stored
credentials — their URLs are code-constant and cannot be retargeted.
Breaking: generic-server OAuth credentials stored before the binding
existed require one /mcp login.

* chore: trim a narrating test comment and compress the _bound_auth docstring

* chore: reframe unbound-credential refusal as standing behavior, not migration wording

* fix: compare MCP endpoint bindings exactly

Trailing-slash normalization ran on the whole URL string, so a slash
that is part of the query unified distinct targets (?tenant=trusted/
matched ?tenant=trusted). Both strings come from the same settings
entry, so any difference means the entry changed: compare exactly.
Also retires the stripTrailingSlashes helper.

* Changelog fragments: conflict-free changelog entries (#1589)

* feat(coding-agent): add changelog fragments with CI check and release-time aggregation (ENG-5408)

* refactor: flag-day cutover — remove [Unreleased] sections entirely

Per review: drop the transition machinery. All four changelogs lose their
[Unreleased] header; coding-agent's pending entries move into a fragment.
release.mjs no longer re-adds the header; the CI check accepts only
fragments (or the no-changelog label). The stray-[Unreleased] absorption
stays in buildReleaseSection so an accidentally merged old-style entry is
folded into the release instead of stranded. Test file removed.

* fix: address bot findings — escape version in dry-run regex, refuse empty fragments

CodeQL: the dry-run preview regex embedded the CLI version argument
unescaped. Macroscope: empty fragments were git rm'd without appearing in
the changelog; the release now aborts on them and the CI check requires
the added fragment to have content. Cursor: normalizeFragment unexported.

* fix(ci): verify fragment content, not just line additions

A whitespace-only fragment passed the additions>0 guard but aborts the
release. The check now reads each candidate fragment's content via its
contents_url (head-repo scoped, so fork PRs work) and requires non-blank
text.

* refactor: decouple CI from release semantics for empty fragments

The fail-closed release abort forced CI to predict exactly what release
would reject, which pulled content fetching and fork-ref handling into
the workflow. Empty fragments have nothing to lose, so the release now
warns and leaves them unconsumed instead of aborting; CI goes back to a
plain added-file presence check. Also trims comments and doc blocks.

* fix(ci): fail closed when a PR exceeds the listFiles cap

Above 3000 changed files the API cannot return the full list, so the
check could miss a src change. The no-changelog label remains the
explicit escape hatch.

* fix(coding-agent): dim the queue-browse header so it reads as a hint, not prompt text (#1575)

* Kernels exit when their owner dies (#1559)

* fix(coding-agent): kernels exit when their owner dies

Set JPY_PARENT_PID so ipykernel's parent poller exits kernels when the
owning process hard-dies (SIGKILL/crash/OOM), add a parent-death
watchdog thread to the forkserver script, and register kernel and
forkserver pids in the orphan process journal so supervisor recovery
can reap them.

fixes ENG-5310

* fix(coding-agent): forked kernels watch the forkserver, not the worker

ipykernel's Unix poller distrusts a parent_handle that differs from
getppid() at startup and falls back to watching for pid-1 reparenting,
which subreapers (systemd --user) never trigger. Forked children now pass
their real parent (the forkserver) whose own watchdog ties it to the
worker, so the death chain holds on every platform. The fork-request env
no longer carries an intentionally-ignored JPY_PARENT_PID.

* fix(coding-agent): race-free forked-kernel signaling via forkserver protocol

Forked kernels were signaled by bare pid from Node (process.kill), which
can hit a reused pid and write a wrong inactive journal record that masks
a sibling manager's active one. The forkserver is the kernels' parent and
waitpid-reaps them, so it now owns kill and liveness: new id-keyed protocol
messages let KernelManager kill/poll through a ForkedKernelHandle, the
Python side only signals a pid found in its un-reaped-children table while
SIGCHLD delivery is excluded (blocked in all threads, handled only by the
main thread outside the check+kill section), and the inactive journal write
is gated on a confirmed outcome — uncertainty leaves the active record for
the supervisor reaper, which verifies process identity before acting.

fixes ENG-5310

* fix(coding-agent): key forkserver kill/liveness by fork request id

Raw-pid keying could alias across forkserver children: a reaped pid
reused by a later fork made kill/alive act on a sibling manager's
kernel. Fork request ids are unique and never reused, so the forkserver
now keeps a bounded id -> (pid, alive) registry (FIFO, 4096) and
kill/alive by id can only ever act on the caller's own incarnation;
evicted ids fail closed. Fork bookkeeping now runs with SIGCHLD blocked
so a fast-exiting child can't be reaped before registration (the forked
child unblocks the inherited mask before running the kernel), and the
inactive orphan-journal write is restricted to the 'signaled' outcome —
the only one that proves the pid still named our child at kill time.

fixes ENG-5310

* chore(coding-agent): trim comments to the non-obvious rationale

* fix(coding-agent): guard kernel teardown against stale starts and unsound journal writes

A stale in-flight doStart (superseded by a public restart) could resume
and tear down or corrupt the successor kernel; a hung forkserver could
stretch startup failure past the 5s budgets via the 10s protocol
timeout; and the forkserver's inactive journal write was unconditional.
Starts now own a generation token bumped by every teardown: stale
resumes and stale failure catches bail without side effects, shutdown
and dispose skip cleanup when superseded mid-await, and liveness probes
during startup are bounded by the remaining budget (timeout counts as
alive so the loop deadline owns failure). The forkserver journal write
now requires an observed exit or confirmed handle-based delivery.

fixes ENG-5310

* fix(coding-agent): kernel start recovery defers to a concurrent teardown

shutdown() now reports whether it performed the cleanup; start recovery
resurrects to idle only as the owning cleanup, so a kill() racing the
recovery can no longer be undone. Replaces the generation+1 idiom and the
remaining inline staleness comparisons with the one startStale predicate.

* fix(coding-agent): never evict live kernels from the forkserver registry

FIFO eviction bounded total forks, not dead entries, so the 4097th fork on
one forkserver dropped the oldest still-running kernel: its liveness read
false (tearing down a healthy kernel) and its kill was unroutable — the
exact orphan leak this change prevents. Eviction now sweeps exited entries
only; live entries are bounded by real concurrent kernels.

* fix: restore main's models.generated.ts

An earlier merge-with-main resolved the generated model catalog as ours,
reverting newer pricing/context data; the watchdog PR must not touch it.

* fix: treat liveness-probe timeouts as unknown and gate direct inactive journal writes on a delivered signal

A forkserver stalled in a slow fork rejects isAlive with a request
timeout; the liveness monitor took any rejection as death and tore down
healthy kernels. ForkServerUnavailable now carries a timedOut flag and
the monitor treats a timed-out probe as unknown (alive), with an
in-flight latch so 1s polls cannot pile up behind a stalled probe.
Proven unavailability (socket death) still counts as dead.

Direct-spawn cleanup wrote an inactive journal record even for a child
that had long exited, which can mask a sibling manager's active record
for a reused pid; it now writes inactive only when kill() delivered a
signal, matching the forked branch's rule. Also retargets the hung-probe
startup test to the probe budget itself (main's cold-boot change raised
the ports budget to 30s, past the test's wall-clock bound).

* fix: report shutdown ownership from the cleanup decision, not a post-cleanup generation check

cleanupResources bumps startGeneration, so the final startStale check read
every non-superseded shutdown as superseded and returned false; startup-failure
recovery then never resurrected the manager to idle, leaving it bricked in
shutdown after a failed ports resolve. Ownership is now captured where the
cleanup decision is made. The superseded-shutdown watchdog test was passing
because of this bug (its parked send short-circuited via waitForKernelExit on
a missing kernel handle); it now parks genuinely and still pins false.

* feat(coding-agent): support ACP MCP programs (#1378)

* feat(coding-agent): support ACP MCP programs

* feat(coding-agent): port ACP MCP servers to kernel runtime

* fix(coding-agent): fence ACP MCP daemon ownership

* fix(coding-agent): release ACP MCP leases on detach

* test(coding-agent): preserve daemon prototype fixtures

* fix(coding-agent): roll back failed ACP MCP claims

* fix(coding-agent): reap ACP MCP transports on release

* fix(coding-agent): isolate ACP MCP release failures

* fix(coding-agent): retry empty-session MCP cleanup

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): render the subagents summary as a bordered tile (#1586)

* Support /fast with OpenAI API-key authentication (#1609)

* feat(coding-agent): support /fast with OpenAI API-key authentication (ENG-5425)

Widens supportsFastMode() to admit GPT-5.4/GPT-5.5/GPT-5.6 on the openai
provider (openai-responses API) in addition to ChatGPT auth, corrects the
GPT-5.6 fast-pricing multiplier from 2.5x to 2x per OpenAI's current
pricing table, and updates the /fast unavailable message.

Resolves discussion #1595.

* docs(ai): trim pricing comments to source reference only

* chore: drop research scratch file from the branch

* chore: restore package-lock.json from main (unrelated npm churn)

* test: update fast-mode unavailable message assertion

* fix(coding-agent): hold goal continuations while subagent work is unsettled (#1610)

* Hold goal continuations while subagent work is unsettled

The harness teaches models to delegate and end their turn, but the goal
continuation hook re-prompted the parent the instant it went idle,
punishing correct waiting with a full-context call per turn (#1598).
The hook now defers while _hasUnsettledRlmQuiescenceWork() reports
outstanding descendant work and resumes via the existing goal-context
admission once the last run settles.

* Add the changelog fragment and the continuation-quiescence unit suite

* Make the continuation resume wake, retry, and queue in order

Review round: the resume went through _runOrQueueGoalContext, which
admits with wake:false (an idle parent never woke), front:true (the
continuation jumped ahead of the settling child's terminal notice), and
the deferral flag was cleared before an admit that throws under an
admission pause (goal stranded idle). The helper now admits directly
without front and with the wake enabled, keeps the deferral until the
admit succeeds, early-returns while admission is paused, and the pause
release retries it.

* Respect abort suspension and goal replacement in the resume

Review round two: the resume admit could clear the post-abort pump
suspension via its immediate wake, starting a goal turn right after the
user aborted — it now holds the deferral while the pump is suspended
and resumeQueuedWork retries it. A deferral no longer leaks onto a
replacement goal: _clearQueuedGoalContexts and _startGoal drop it.

* Count resumed continuations

Review: the resumed continuation skipped the continuationsUsed
increment, so resumed cycles reported a stale count. The resume now
mirrors the hook, rolling the state back when admission throws so the
retry re-counts.

* refactor(agent): typed codes for Agent.continue precondition failures (#1588)

* refactor(agent): typed codes for Agent.continue precondition failures

Classifying continuation-start failures by error message text
(includes("already processing"), includes("continue from")) breaks
silently when wording changes. Agent.continue() now throws
AgentContinueError with a stable code — busy or nothing-to-continue —
and the post-compaction classifier switches on the code. Unknown errors
still reject headless idle waiters.

* test(coding-agent): retarget queue retry test to AgentContinueError

The queue characterization suite also reaches the reschedule path with a
plain Error; it now throws the typed busy error like the compaction suite.

* chore: merge main and convert changelog entry to fragment

* Delete packages/ai/.changes/fix-typed-continue-preconditions.md

unrelated to PR

---------

Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>

* fix(coding-agent): keep the working-status elapsed timer across session re-entry (#1605)

* Keep the working-status elapsed timer across session re-entry

The loader anchored its elapsed display on Date.now() at loader start,
so leaving a session (agents view, detach) and coming back restarted
"Waiting · Ns" at zero while the daemon kept working. The timer now
anchors on the in-flight turn: the first user message of the turn live,
and the newest user message of the restored transcript on attach.
Steering messages mid-turn keep the original anchor; agent_end clears
it so idle loaders fall back to Date.now().

* Stub the turn-start restore in the session-render harness

* Anchor on the run's first starter and cover resync

Review round: the transcript scan now finds the earliest run starter
(user, agent-session, or heartbeat prompt) of the trailing in-flight
run, stopping at the previous run-ending assistant message, so steering
messages and custom-started runs anchor correctly; a scan that finds
nothing clears the stale anchor. session_resynced refreshes the anchor
from the snapshot. Live message_start shares the same starter
predicate. Dedicated unit suite removed and comments slimmed per
maintainer request.

* Share one run-start predicate across core and TUI

Review flagged three spellings of "message starts an agent run".
startsAgentRun now lives in agent-messages.ts; agent-session's
_isPromptTurnStartMessage and the interactive-mode duplicates use it.

* test(coding-agent): cover working timer restoration

fixes #1605

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): session_before_refine extension hook (#1558)

* feat(coding-agent): session_before_refine extension hook

Let extensions customize continual-harness refinement the same way
session_before_compact customizes compaction. The hook fires before the
planning LLM call for /refine and auto-refine with the planning inputs
(trigger, instructions, scope, planning harness state, refinement
history, serialized conversation). An extension can return a
RefinementProposal to replace the built-in planner (edits still pass
apply-time validation and baseline conflict rejection), return
{ skip: true } to suppress the round, or return nothing to fall back to
the default planner. Rollback refinements bypass the hook.

Includes examples/extensions/custom-refinement.ts (planning with a
cheaper model, covering discussion #1464) and documents the existing
refine_complete event.

* Update queue-test refine call assertions for the trigger argument

* Propagate the auto trigger and skip semantics through serialized refine paths

Serialized auto-refine now reaches session_before_refine with trigger
"auto", an extension skip there stamps the cooldown without emitting
refine_failed, and a skipped explicit refine.run surfaces the
RefineSkippedError instead of passing as a silent reviewer decline. The
example planner now labels entry ids with their scope and tells the
model other-scope entries are read-only.

* fix(coding-agent): consume skipped auto-refine rounds

* fix(coding-agent): report skipped refine during disposal

* fix(coding-agent): normalize extension refine proposals

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): surface refinement status and outcomes (#447)

* feat(coding-agent): surface refinement and queue prompts

* fix(coding-agent): preserve internal prompt handoff semantics

* fix(coding-agent): preserve refinement queue order

* fix: preserve queued prompt semantics

* feat(coding-agent): show exact refinement edits

* chore: restore main's package-lock.json

* test: assert isRefining stays false while a public refine waits for idle

* review: bump daemon schema to 17, drop duplicate attach-terminal refinement lines, tighten comments

* test: executable compatibility assertions for revision-17 refinement additions

* fix: mirror compaction_outcome handling for refinement_outcome in print mode and context rebuilds

* Render refinement outcomes like compaction and skill messages

Replace the bespoke checkmark rendering with the shared custom-message
pattern: a boxed bold [refinement] label on the customMessage background,
collapsed to a single summary line and expanded through the same
tool-output toggle (ctrl+o) that compaction summaries and skill
invocations use, instead of a separate edit-diff axis.

* Strip the live-status plumbing; keep only the durable outcome message

The apply phase is sub-second, so a "Refining" loader, agents-view
status label, daemon status string, heartbeat deferral, and an
isRefining wire flag were all periphery for something users can barely
see. Drop refinement_start/end events and every isRefining touchpoint
(daemon protocol schema stays at 16 — no wire change at all), and slim
the outcome-message validator to the shallow envelope checks the
compaction outcome uses. The feature is now just the persisted
[refinement] transcript card.

* Show a live loader for user-issued /refine by reusing existing edges

No new events or state: start the loader when the /refine slash-command
message reaches the transcript and stop it on the already-wire-visible
refine_complete/refine_failed events, mirroring the compaction loader.

* Extract the shared expandable custom-message card skeleton

Compaction, skill, and now refinement cards each copied the same Box
subclass with an expanded flag, setExpanded, invalidate, and bold-label
formatting. Pull that into ExpandableCustomMessageBox + a
customMessageLabel helper and rebase all three components onto it, so
the refinement card only carries its edit-row/diff formatting.

* Emit refine_failed when a queued /refine command fails

A failed /refine persisted its command-error row without emitting
refine_failed, so the TUI loader kept spinning. Emit it from the queued
command catch, matching the refine.run and auto-refine failure paths.

* Align the refinement card layout with compaction and shield its loader

Collapsed card now renders the [refinement] label line above the
summary line, matching the compaction card structure. The refine loader
joins compaction/retry in the syncWorkingLoader ownership guard so
periodic reconcile paths (subagent updates, connection refreshes)
cannot clear it mid-refine.

* Truncate the collapsed refinement summary instead of wrapping

Long summaries wrapped the collapsed line onto a second row. Render the
collapsed line through a width-aware component that ellipsizes the
summary while keeping the edit count and expand hint visible.

* Remount the refine loader after compaction and hard-clip the collapsed line

A compaction that starts mid-refine owns the status container; on
compaction_end syncWorkingLoader now remounts the refine loader instead
of bailing on the ownership guard. The collapsed outcome line is also
clipped to the render width after assembly so extreme narrow viewports
cannot wrap it.

* fix: settle the /refine loader on its own result row and discard it on teardown

refine_complete carries no request identity, so an agent or auto
refinement settling while a queued user /refine waited on it killed the
loader early. The /refine result row is the user refine's settle edge
(emitted after refine() returns, error row on failure), so the loader
stops there — and on refine_failed, which covers a failed result-row
append. Session switches and stop() now discard the loader timer, which
previously leaked when the view never received a settle event.

* fix(coding-agent): correlate refine loader settlement

* fix: emit refine_failed only for the refinement itself

The queued /refine catch emitted refine_failed for any error in the
command try, including a result-row persist failure after refine()
succeeded — reporting a completed harness update as failed. The emit now
wraps only the parse+refine call; the outer catch keeps its row-append
duty as the correlated settle edge.

* fix(coding-agent): remove refine loader on teardown

* fix(coding-agent): retain outcomes when refine audit write fails

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* fix(ai): follow MCP protected-resource OAuth discovery (#1591)

* fix(ai): discover path-scoped MCP OAuth resources

* fix(ai): preserve canonical MCP OAuth resources

* fix(ai): allow same-origin OAuth tenant issuers

* fix(daemon): skip failed workers in heartbeat catalog (#1621)

* fix(daemon): skip failed workers in heartbeat catalog

* Add the changelog fragment

* fix(coding-agent): refresh MCP providers after add (#1629)

* fix(coding-agent): refresh MCP providers after add

* fix(coding-agent): preserve legacy MCP login guidance

* chore(release): prepare v0.8.0 (#1618)

* chore(release): prepare v0.8.0

* chore(release): include heartbeat catalog fix

* chore(release): include MCP provider refresh fix

* chore(ai): regenerate the model catalog from live provider catalogs (#1632)

Commits the current `npm run generate-models` output. Net 1252 -> 1260
models. Highlights: z-ai/glm-5.3 on OpenRouter and Prime Inference,
deepseek-v4-flash-vision-exp (OpenRouter, Vercel AI Gateway, OpenCode
Go), thinkingmachines inkling free routes and two stealth previews
(ox-alpha, x-preview-f); Vercel AI Gateway renamed its grok vendor
prefix from xai/ to spacexai/ (13 ids); repricing on gpt-5.6-sol
(5/30 -> 2.5/15), gemini-3.6-flash (halved), gemini-flash-lite-latest
(raised), kimi-k2.7-code, minimax-m2.5, and mistral-small-3.2; retired
deepseek-v4-flash-free on OpenCode.

All provider fetches succeeded during generation; no provider section
was emptied by a failed source.

Co-authored-by: eliebak <eliebak@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Highlight the whole ipython cell so multi-line strings keep color (#1692)

The expanded view highlighted each line separately, so highlight.js
lost its inside-a-string state and only the first line of a
triple-quoted string was colored. One whole-cell highlightCode pass
(already line-split and ANSI-self-contained per line) fixes it; the
per-line %magic/!bash overrides are kept.

* feat(coding-agent): default RLM max depth to 2 (#1493)

* feat(coding-agent): default RLM max depth to 2

* test(coding-agent): pin max depth in prompt update case

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>

* fix: preserve ACP quiescence and Chat reasoning lineage (#1612)

* fix(acp): await terminal quiescence before prompt completion

* docs(changelog): add ACP quiescence fragment

* fix(ai): preserve chat reasoning details on replay

* fix(ai): emit valid reasoning-only chat messages

* fix: preserve streamed reasoning and ACP cancellation

Fixes #1612.

* fix(acp): retain ownership through terminal publication

Fixes #1612.

* ENG-5528 (#1747)

* Update README with arXiv

* Add Citation section and arXiv badge to README

* Fix README subtitle: RLM Agent -> RLM Harness

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* chore(ai): regenerate the model catalog from live provider catalogs (#1759)

* chore(ai): regenerate the model catalog from live provider catalogs

Commits the current `npm run generate-models` output. Net 1260 -> 1231
models. The big mover: models.dev rescoped cloudflare-ai-gateway to
proxied third-party models only (57 -> 31 ids), dropping the
workers-ai/@cf/* mirrors (still served by cloudflare-workers-ai) and
legacy OpenAI ids (gpt-4, gpt-4-turbo, o1, o1-pro, o3-pro, gpt-5-pro,
gpt-5.2/5.3 family, plain gpt-5.6). Other changes: devstral-2512 and
MiniMax M2.7/M3 free routes on OpenRouter, longcat-2.0 on OpenCode Go,
gpt-oss-safeguard-120b on Vercel AI Gateway; retired inclusionai
ling/ring and nvidia nemotron free routes. Repricing: gpt-5.6/gpt-5.6-sol
5/30 -> 4/20 (OpenAI + Azure), kimi-k2.6 raised to 0.95/4, glm-5.1/5.2
raised, deepseek-v4-pro halved on OpenRouter.

The removed workers-ai/@cf/moonshotai/kimi-k2.6 gateway id is what broke
main's CI (TS2345 in packages/ai tests); this updates the affected
tests, the gateway default model (now claude-sonnet-4.5), and the
provider docs to match the rescoped catalog.

All provider fetches succeeded during generation; no provider section
was emptied by a failed source.

* fix(ai): restore kimi-k2.6 output limit on Prime Inference and drop a history-narrating test comment

OpenRouter stopped publishing max_completion_tokens for moonshotai/kimi-k2.6,
which collapsed the Prime Inference entry to the 8192 default. models.dev
(moonshotai first-party and openrouter) both list output = context = 262144;
pin that in PRIME_INFERENCE_MODEL_METADATA like the other gap-fills.

The openrouter-provider entry still shows the 4096 fallback; that fallback is
pre-existing generator behavior affecting 53 models (kimi-k2.5 included on
main today) and deserves its own change rather than a rider here.

* fix(ai): restore minimax-m2.7 output limit on Prime Inference

OpenRouter also stopped publishing max_completion_tokens for
minimax/minimax-m2.7, collapsing the Prime Inference entry to the 8192
default the same way as kimi-k2.6. models.dev (minimax first-party)
lists output = 131072; pin it in PRIME_INFERENCE_MODEL_METADATA.

* fix(acp): preserve assistant message boundaries (#1781)

* chore: prepare v0.8.1 release (#1794)

* Async bash() tool for the kernel runtime (#1684)

* feat(coding-agent): add async-by-default bash() to the kernel runtime

* chore(coding-agent): tighten bash() comments and changelog fragment

* fix(coding-agent): address bash() review findings — exact tail retention, foreground-status lifecycle with group anchoring, post-kill forkserver reap, Windows start-id parity, executor-free await

* fix(coding-agent): tree-kill bash() process trees on Windows and make running reflect group liveness

* fix(coding-agent): carry bash() status pipe as stdin so strict-POSIX shells accept single-digit fd redirections

* fix(coding-agent): close status fds when wake-pipe creation fails and gate bash() commands on orphan-journal registration

* fix(coding-agent): kill full process trees in the Windows orphan reaper via taskkill /T

* fix(coding-agent): kill one-shot await bash() on cancel, resolve Windows helpers via System32, retry taskkill after reap, and fail closed on orphan-journal enrollment

* fix(coding-agent): confirm group exit before a cancelled one-shot bash() await propagates and complete-write orphan-journal records

* fix(coding-agent): shield the cancelled-await bash() confirm-exit from re-cancellation, fail clearly without bash on Windows, and fence slow pumps out of result assembly

* fix(coding-agent): flag the pump's read-to-commit window so the bash() drain fence cannot miss an in-flight chunk

* fix(coding-agent): resolve taskkill via absolute System32 path in the host orphan reaper

* fix(coding-agent): write the inactive orphan-journal record only after a delivered kill so undelivered signals leave the host reaper in charge

* fix(coding-agent): count an already-exited leader as delivered in the Windows bash() reap so clean exits still journal inactive

* fix(coding-agent): resolve trusted absolute shell host-side and close Windows journal/reap gaps

* fix(coding-agent): best-effort-kill pid-only orphan records in daemon force-stop and recovery reaps

* fix(coding-agent): resolve the kernel bash shell only from trusted absolute paths on Windows

* fix(coding-agent): contain Windows bash children with kill-on-close job objects

* fix(coding-agent): fall back to taskkill when job termination fails

* fix(coding-agent): start Windows bash children suspended and fail closed when job containment cannot be established

* fix(coding-agent): kill the whole tree for enriched orphan records on Windows force-stop

stopTrackedProcess signals only the shell pid on Windows; use killOrphanProcess (taskkill /T) after the identity check, matching the owned-session-worker and daemon-supervisor reapers.

* fix(coding-agent): skip the liveness check when the orphan identity is stale

A reused pid looked like a failed kill after the identity re-check skipped taskkill, blocking worker cleanup with a spurious failure.

* fix(coding-agent): journal Windows bash() children before job assignment so a kernel kill mid-spawn cannot leak an unjournaled suspended process

* fix(coding-agent): pre-create the Windows bash() job before spawn and contain the child before the journal start-id query

* fix(coding-agent): create Windows bash() children atomically inside the kill-on-close job via PROC_THREAD_ATTRIBUTE_JOB_LIST, closing the Popen-to-assignment leak window

* chore(coding-agent): compress the atomic-job-association comments to one-line rationale

* fix(coding-agent): retain the Windows bash() process handle through job cleanup and taskkill fallbacks so a recycled pid can never be killed

* fix(coding-agent): serialize Windows bash() abort cleanup under the kill lock so shutdown taskkill can never race the process-handle close

* Minimal CPython REPL runtime (rlm.repl) (#1685)

* feat(coding-agent): add async-by-default bash() to the kernel runtime

* chore(coding-agent): tighten bash() comments and changelog fragment

* feat(coding-agent): add a standalone CPython REPL runtime speaking JSON lines over stdio

* chore(coding-agent): tighten REPL runtime comments

* feat(coding-agent): run the kernel on the REPL runtime over stdio

* fix(coding-agent): reject pending host requests on kernel teardown

The runtime now fails pending host_request futures when stdin hits EOF or
a shutdown request arrives, so a cell awaiting a host reply can no longer
block _serve from consuming the shutdown and wedge the process. Startup
and busy-kernel messages plus the ipython tool description now say Python
kernel, matching the default REPL runtime client.

* chore(coding-agent): describe the kernel-heavy test tag client-neutrally

* feat(coding-agent): make the REPL runtime the only kernel

Removes the Jupyter/ipykernel/ZMQ kernel client, the fork server, the
PRIME_AGENT_KERNEL escape hatch, and the %%bash/%cd/%env cell rewriting.
Magic-style cells (%/%%/!) return a one-line error naming the Python or
bash() replacement. The kernel venv no longer installs ipykernel;
existing venvs are rebuilt once via the bootstrap schema bump. Old
snapshot artifacts still restore through the runtime's pickle compat.

* fix(coding-agent): close MCP transports in REPL shutdown

* fix(coding-agent): address bash() review findings — exact tail retention, foreground-status lifecycle with group anchoring, post-kill forkserver reap, Windows start-id parity, executor-free await

* fix(coding-agent): tree-kill bash() process trees on Windows and make running reflect group liveness

* fix(runtime): address repl runtime review findings

- revalidate targeted interrupts through SIGINT delivery so a stale signal
  cannot cancel a later request
- preserve a parked untargeted interrupt while another request is inflight
- reject non-string interrupt ids and validate snapshot options
  (prune_oversized bool, max_bytes/max_variable_bytes present => int)
- guard str(exc) in error/skip/fail reasons so a broken __str__ cannot
  kill the runtime
- fail the snapshot when the manifest write fails (before any pruning)
- report 'snapshot not found' reason on restore of a missing path
- wait unbounded for the output-pump drain marker so done never precedes
  captured output and marker bytes never leak
- document that aggregate-cap skips are not pruned

* fix(coding-agent): carry bash() status pipe as stdin so strict-POSIX shells accept single-digit fd redirections

* fix(coding-agent): address host-swap review findings — runtime-neutral kernel wording, %cd/%env rewrite fidelity, linear-time magic regexes, restart supersession guard

* fix(coding-agent): reject stale column-0 line magics on any cell line, not just the first

* fix(coding-agent): close status fds when wake-pipe creation fails and gate bash() commands on orphan-journal registration

* fix(coding-agent): strip quoted %cd targets, use UTF-16-safe %env parsing, and expand $var/${var} values PEP-215-style

* fix(coding-agent): fail rlm.run fast outside a live kernel so vitest workers cannot hang on a comm that never replies

* fix(coding-agent): clear the active cell id before terminal events so late background output is never tagged with a finished cell

* fix(coding-agent): update legacy IPython-era tests to assert cutover behavior

* fix(coding-agent): describe the column-0 magic scan without referencing the removed rewrite backend

* fix(coding-agent): consume parked interrupts on compile failures and survive user-closed stdio in the drain path

* fix(coding-agent): redact bare %env listings, prefer '=' in %env parsing, echo %cd targets, and keep graceful shutdowns owning their cleanup

* fix(coding-agent): reject kernel startup promptly on spawn errors and commit %cd previous-dir tracking only after chdir succeeds

* fix(coding-agent): trim ParsedIpythonBashCell to the body field its callers use

* fix(coding-agent): kill full process trees in the Windows orphan reaper via taskkill /T

* fix(coding-agent): survive compile-phase crashes and fd-reuse drain hangs in the REPL runtime

* fix(coding-agent): shield snapshot prune deletions from interrupts and pin the host comm probe to its real seam

* fix(coding-agent): route list_names through the per-request backstop and skip non-string namespace keys

* fix(coding-agent): reject snapshot requests whose manifest path aliases the payload path

* fix(coding-agent): reject negative snapshot size caps that would prune every user variable

* fix(coding-agent): kill one-shot await bash() on cancel, resolve Windows helpers via System32, retry taskkill after reap, and fail closed on orphan-journal enrollment

* fix(coding-agent): attribute REPL stream output at write time so raw fd and user-thread bytes never carry another cell's id

* fix(coding-agent): surface unattributed REPL stream output as separate background output instead of dropping or merging it

* fix(coding-agent): confirm group exit before a cancelled one-shot bash() await propagates and complete-write orphan-journal records

* fix(coding-agent): expose a working sys.stdout.buffer on the tagged REPL writer routing bytes through the null-attributed raw channel

* fix(coding-agent): mark between-cell background output truncated when the pending buffer cap is hit

* fix(coding-agent): shield the cancelled-await bash() confirm-exit from re-cancellation, fail clearly without bash on Windows, and fence slow pumps out of result assembly

* fix(coding-agent): survive hostile protocol lines in the REPL reader thread and interrupt without pthread_kill on Windows

* fix(coding-agent): send the protocol shutdown from dispose() so the runtime closes MCP servers and kills live bash groups before the hard kill

* fix(coding-agent): flag the pump's read-to-commit window so the bash() drain fence cannot miss an in-flight chunk

* fix(coding-agent): guard the whole REPL request line against hostile input, reject duplicate in-flight ids, complete short buffer-pipe writes, and clean the snapshot temp file on KeyboardInterrupt

* fix(coding-agent): drop stale between-cell background output on kernel teardown so it cannot surface after a restart

* fix(coding-agent): assert the tagged channel only in the large buffer-write REPL test since cross-channel ordering is not guaranteed

* fix(coding-agent): resolve taskkill via absolute System32 path in the host orphan reaper

* fix(coding-agent): write the inactive orphan-journal record only after a delivered kill so undelivered signals leave the host reaper in charge

* fix(coding-agent): keep a REPL request interrupt-targetable through its trailing-expression repr and output drain until done is emitted

* fix(coding-agent): count an already-exited leader as delivered in the Windows bash() reap so clean exits still journal inactive

* fix(coding-agent): reject NaN and Infinity in emit() payloads before they corrupt REPL protocol framing

* chore(coding-agent): correct the stale ipykernel requirement on the python override doc

* chore(coding-agent): drop migration-era wording from kernel comments

* fix(coding-agent): resolve trusted absolute shell host-side and close Windows journal/reap gaps

* fix(coding-agent): best-effort-kill pid-only orphan records in daemon force-stop and recovery reaps

* refactor(coding-agent): drop the magic-cell rejection gate

%-prefixed and !-prefixed cells now reach CPython and fail with its own
SyntaxError, which names the offending line. The rejection layer guarded
against model-produced IPython syntax, but with the shipped prompt fresh
sessions do not produce it, and the plain SyntaxError is protection enough.
parseIpythonBashCell stays: transcripts from ipykernel-era sessions render
%%bash cells with shell highlighting forever.

* fix(coding-agent): report a committed destructive snapshot as success even when an interrupt was parked

* chore(ai): drop stray live-regen drift in models.generated.ts

ad657d7ec accidentally committed generate-models output produced by a local
build; the catalog belongs to its own regen PRs.

* test(coding-agent): expect a plain SyntaxError for legacy %%bash cells after the magic-rejection gate removal

* fix(coding-agent): consume a finishing-targeted interrupt after a completed state request instead of failing the done

* fix(coding-agent): exit the REPL runtime when its owner dies even during a busy cell

* fix(coding-agent): resolve the kernel bash shell only from trusted absolute paths on Windows

* fix(coding-agent): contain Windows bash children with kill-on-close job objects

* test(coding-agent): cover owner death during a non-yielding REPL cell

* fix(coding-agent): skip concurrently deleted names during snapshot instead of aborting

* fix(coding-agent): fall back to taskkill when job termination fails

* fix(coding-agent): stop telling REPL-default users they need ipykernel

* fix(coding-agent): clear the SIGINT target when its request finishes

* test(coding-agent): drop migration-era wording from the %%bash rejection comment

* fix(coding-agent): start Windows bash children suspended and fail closed when job containment cannot be established

* fix(coding-agent): re-assert the kernel SIGINT handler between cells so a cell rebinding SIGINT cannot break interrupts

* fix(coding-agent): kill the whole tree for enriched orphan records on Windows force-stop

stopTrackedProcess signals only the shell pid on Windows; use killOrphanProcess (taskkill /T) after the identity check, matching the owned-session-worker and daemon-supervisor reapers.

* docs(coding-agent): describe current magic-cell behavior in release notes

Magic-style cells fail with a plain SyntaxError; the migration error and %cd/%env rewrites were removed, so the notes stop claiming them.

* fix(coding-agent): skip the liveness check when the orphan identity is stale

A reused pid looked like a failed kill after the identity re-check skipped taskkill, blocking worker cleanup with a spurious failure.

* fix(coding-agent): journal Windows bash() children before job assignment so a kernel kill mid-spawn cannot leak an unjournaled suspended process

* fix(coding-agent): pre-create the Windows bash() job before spawn and contain the child before the journal start-id query

* fix(coding-agent): create Windows bash() children atomically inside the kill-on-close job via PROC_THREAD_ATTRIBUTE_JOB_LIST, closing the Popen-to-assignment leak window

* chore(coding-agent): compress the atomic-job-association comments to one-line rationale

* fix(coding-agent): send duplicate-id protocol errors outside the interrupt lock and reject int payloads in the captured stdout buffer

* docs(coding-agent): update the kernel provisioner docstring for the REPL/IPython split

* fix(coding-agent): retain the Windows bash() process handle through job cleanup and taskkill fallbacks so a recycled pid can never be killed

* fix(coding-agent): serialize Windows bash() abort cleanup under the kill lock so shutdown taskkill can never race the process-handle close

* fix(coding-agent): commit REPL snapshot payload and manifest atomically via unique temp files with guaranteed cleanup and parked-SIGINT restore

* fix(coding-agent): stage REPL restore before an interrupt-parked apply and recover committed snapshot/restore results from protocol interrupts so a committed operation is never misreported as failed

* chore(coding-agent): compress restore-shield rationale comments to one line each

* fix(coding-agent): surface unattributed background output in IPython cell details and render it after the traceback in the expanded cell view

* docs(coding-agent): replace stale Jupyter comm vocabulary in rlm-runtime failure modes and focused validation with stdio-transport equivalents

* docs(coding-agent): describe the REPL trust boundary and runtime-bootstrap failure in stdio terms and drop the removed TokenUsage export

* fix(runtime): address REPL review findings

* fix(runtime): preserve near-cap snapshots

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* test(coding-agent): cover root sibling agent messaging (#1698)

* test(coding-agent): cover root sibling messaging

* test(coding-agent): isolate root sibling fixture

* chore(ai): regenerate the model catalog from live provider catalogs (#1878)

* chore(ai): regenerate the model catalog from live provider catalogs

* fix(ai): correct OpenCode Go Qwen API mapping and drop dev/ Prime routes

- opencode-go qwen models mislabeled @ai-sdk/anthropic by models.dev now
  map to openai-completions at /v1 (generalizes the qwen3.5/3.6 fix;
  affects qwen3.8-flash today)
- isPrimeInferencePrivateModel also filters dev/ routes, removing
  dev/kimi-k3-high-throughput ($0 dev route) from the public catalog

* fix(coding-agent): trust supervisor-approved session renames in worker mode (#1841)

* refactor(coding-agent): centralize the session-path predicate (#1846)

* refactor(coding-agent): remove test-only config cache reset APIs (#1849)

* refactor(coding-agent): read messageCount instead of a shadow flag (#1852)

* refactor(coding-agent): derive available connection models on read (#1853)

* Remove unused kernel seams (#1835)

* fix(runtime): ordered bash output completion sentinel (#1838)

* fix(runtime): fence bash output completion

* docs(runtime): document post-fence output visibility in bash() docstring

* fix(runtime): emit bash completion fence via absolute printf path

A shell function named `command` shadows `\command -p printf` (the
backslash only defeats aliases), swallowing both fence frames and
wedging the await behind background jobs until shell death. A
slash-qualified printf resolved on the system default utility PATH
bypasses function and alias lookup in every POSIX shell.

Also scope the changelog claim: EXIT-trap/background output lands after
the completion marker by design and stays visible via handle.output().

* docs(runtime): soften the fence-printf comment per review

* refactor(coding-agent): centralize agent-message admission in AgentSession (#1861)

* refactor(coding-agent): query agent peers on demand instead of broadcasting mirrors

* refactor(coding-agent): centralize agent-message admission in AgentSession

* fix(coding-agent): respond to unknown worker commands instead of dropping them

Legacy worker_sync_agent_peers commands from an older supervisor fell
through handleWorkerCommand without a response, forcing the requester
to wait out its full 5s timeout on every peer sync. A default case now
writes an immediate failure for any unhandled worker command.

* fix(daemon): re-check cron job runnability after prompt admission fence

runCronJob snapshotted the runnable job before promptHeartbeat/
promptUntilAccepted, which can wait on the session admission fence. A job
cancelled or completed during that wait was still delivered. Re-check via
the admissionCommitted hook and skip when the job is no longer runnable.

* fix(daemon): skip cron delivery when the job content changed during admission

updateRlmHeartbeat mutates prompt/deliveryMode in place under the same job
id, so the existence-only admissionCommitted re-check still delivered the
stale snapshot captured before the admission fence wait. Compare the
refreshed job's prompt and deliveryMode and skip on mismatch; the skip
reschedules nextRunAt so the updated instruction is delivered next cycle.

* fix(coding-agent): re-check prompt admission after async input normalization

_prompt ran admissionCommitted before _normalizeSubmission, but async
extension input handlers are awaited between that check and
_admitSessionInput, so a cron job cancelled or updated during the await
still delivered its stale prompt. Re-run the callback after the awaited
normalization so invalidated content is not admitted.

* fix(coding-agent): dispatcher-owned host-reply envelope (#1836)

* fix host reply result envelope

* docs(coding-agent): drop a stale handler-contract sentence from the merge

* fix(coding-agent): repair corrupt REPL protocol frames (#1839)

* refactor(coding-agent): unify kernel shutdown

* docs(coding-agent): carry over shutdown rationale comments

* fix(coding-agent): repair corrupt REPL protocol

* fix(coding-agent): cancel the final kernel snapshot before disposal cleanup

* fix(coding-agent): block new executions from splicing ahead of the final dispose snapshot

flushSnapshotForDispose captured the execution queue while the kernel
state was still 'running', so a concurrent execute() (which has no
per-cell timeout) could enqueue after the capture; captureSnapshot then
queued behind it and its timeout never started, hanging dispose() and
shutdown({snapshot:true}) forever. Claim the flush before capturing the
queue: non-internal requests are rejected while the final snapshot is
flushing, keeping every wait on the dispose path bounded.

* fix(coding-agent): close protocol-repair races and bound the repair

- Gate queued requests behind an in-flight repair: a request whose queue
  slot opens during a repair releases the slot and requeues after the
  repair, so queued executes/snapshots can no lon…
PR9000 pushed a commit to PR9000/prime-agent that referenced this pull request Sep 1, 2026
…e next turn (PrimeIntellect-ai#800)

* fix(coding-agent): queue ACP prompts behind in-flight work

* fix(acp): keep queued-prompt semantics through the resident lifecycle merge

Renumber owned prompt cancellation to schema revision 20 (17-19 landed on
main), adapt compatibility gating to the requirements-array form, and return
stopReason "cancelled" for a prompt parked behind the terminal lifecycle when
a cancellation drops it before it starts.

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>
phytal added a commit to zeroset-inc/prime-agent that referenced this pull request Sep 4, 2026
* feat(daemon): supervisor-owned rlm spawn ledger as family authority (#1387)

* feat(daemon): add rlm spawn ledger module

* feat(daemon): record rlm spawns, renames, and deletions in the spawn ledger

* test(daemon): cover rlm spawn ledger semantics, seeding, and wiring

* fix(daemon): harden rlm spawn ledger reads, writes, and depth checks

- enforce spawn invariants at append (never write what the reader rejects)
- verify depth monotonicity only between ledger-known depths; drop+log a
  contradictory edge instead of failing the whole family
- tolerate one torn final line (no trailing newline) on read; truncate it
  before the next append; interior malformed lines stay fail-closed
- skip v:1 records with unknown ops (forward compat); v!==1 fails loudly
- shared registry seed source (header-id read, tolerant LWW registry parse)
- canonicalSessionPath for all path keys; realpath the sessions dir
- flush() to await durable appends; document multi-writer O_APPEND reality

* feat(daemon): serve supervisor siblings from the spawn ledger

- supervisor holds its own ledger instance; the three catalog.siblings
  call sites (named create, saved rename reservation, saved-name check)
  now read ledger-backed siblings
- offline rename_saved_session at the supervisor appends a ledger rename
- worker spawn appends are awaited before admission returns (no self-heal
  exists for a lost spawn record)
- ledger delete is appended after the registry tombstone succeeds

* test(daemon): cover ledger hardening and supervisor siblings wiring

* fix(daemon): await and self-heal the rlm ledger delete

- recordRlmSubagentDeletion awaits the ledger delete (tombstone-first
  ordering kept)
- a retried deletion over an existing tombstone finishes a ledger delete
  lost to a crash instead of leaving a permanent ghost live edge
- TODO at the spawn-append failure log: revisit failing admission once
  the ledger is the messaging authority

* fix(daemon): address PR #1387 bot findings on the rlm spawn ledger

- await the ledger rename at the active-session rename write point so a
  rename is durable before its name reservation is released
- sessionRow strips header-claimed parentSessionPath/rlmDepth: topology
  in ledger rows is exclusively ledger-sourced (fork headers no longer
  leak a parent onto root rows)
- seeding is atomic: collect all records, publish via temp file + rename;
  an interrupted seed leaves no ledger file and re-seeds next time; a
  racing live append wins and suppresses the seed
- siblings() falls back to a lone root-shaped row when the target's edge
  was reconciliation-dropped (parent file gone) but the child file exists
- drop the unused public rlmLedgerFamily/rlmLedgerSiblings wrappers on
  AgentDaemon (tests use the ledger via daemon internals)

* fix(daemon): byte-exact torn-tail repair and no-clobber seed publish

- truncateTornTailSync works on raw buffers with byte offsets: string
  indices diverge from byte offsets on multi-byte UTF-8 names, so the
  old truncate could cut into a preceding valid record and poison the
  ledger; also hardened cross-process with a byte-stable double read
  and same-fd fstat/ftruncate (residual race stays documented)
- seed publish uses linkSync (EEXIST => live append wins, seed dropped)
  instead of existsSync+renameSync, whose clobbering rename could lose
  a racing append with no self-heal

* fix(daemon): bound the repair-path read and add a link-less seed publish fallback

- readAllSync and the torn-tail repair enforce RLM_LEDGER_MAX_BYTES
  before any file-sized allocation, throwing the same loud bounded-read
  error as replaySync (outside the swallowing repair try-block)
- seed publish falls back to check-then-rename when linkSync fails with
  anything but EEXIST (filesystems without hard links); EEXIST still
  means the racing live append wins; fallback path is logged

* fix(daemon): never publish a seed that exceeds the ledger read bounds

A seed past RLM_LEDGER_MAX_BYTES/RECORDS would publish a ledger every
replaySync refuses to read. Check the single serialized payload against
both bounds before publishing and skip seeding entirely (flat families,
the documented degradation mode) — logged, not thrown, so the guard
cannot recreate the seedAttempted-sticks failure shape.

* refactor(daemon): consolidate rlm subagent metadata onto the spawn ledger (#1390)

* feat(daemon): add per-child rlm subagent display files

* feat(daemon): serve passive rlm subagents from the ledger and stop writing registries

* refactor(daemon): drop the unconsumed catalog siblings walk

* test(daemon): cover display files and legacy registry metadata fallback

* refactor(daemon): share rlm subagent metadata field spreading

* refactor(daemon): derive the legacy registry entry type from the passive entry

* fix(daemon): resolve deleted-child paths for job cleanup and harden display writes

* fix(daemon): fail admission on lost spawn records and never clobber-publish seeds

* chore(ai): refresh the generated model catalog from live provider catalogs (#1445)

Commits the current `npm run generate-models` output. 92 models added,
33 removed (net 1163 -> 1222) across 14 providers. Highlights: DeepSeek
V4 Flash/Pro dated snapshots (workers-ai, fireworks, huggingface),
gemini-3.7-flash (google, copilot), grok-4.5/4.6 and kimi-k3 on copilot,
glm-5.3 on opencode-go, and the GPT-5.x family on cloudflare-ai-gateway;
retired claude-opus-4-1 aliases, gemini-2.0-flash, and ling-3.0-flash
routes.

The build regenerates this file from live catalogs anyway; committing
keeps the checked-in file from drifting further and keeps local keyless
test runs representative.

* Add Ctrl+J to toggle edit diffs independently of tool output (#1388)

* feat(coding-agent): add ctrl+j toggle to expand edit diffs independently of tool output

* feat(coding-agent): make ctrl+j sole owner of edit-diff visibility and hint the summary line

* fix(coding-agent): stop duplicating the ctrl+j hint on collapsed edits

Collapsed built-in edits showed the hint twice: on the edit header and on
the summary line. The header hint now renders only when the diff is
expanded (where no summary line exists); collapsed rows keep the single
hint on the summary line.

* fix(coding-agent): keep the ctrl+j hint visible while no summary line renders

Gating the header hint on expansion assumed the collapsed summary line
always carries the cue, but that summary only mounts once a successful
result with a countable diff lands. During the preview-only window and on
error rows the diff was expandable with no visible hint.

The header now keeps the hint whenever the summary line is absent
(mirroring its mount condition) and yields it once the summary renders,
so exactly one hint is visible in every state.

* fix(coding-agent): advertise the collapse key on expanded ipython diff headers

* fix(coding-agent): move the expanded-diff collapse hint to the truncated cell header

* feat(coding-agent): always-visible edit summary with inline Ctrl+J diff (#1392)

* feat(coding-agent): add ctrl+j toggle to expand edit diffs independently of tool output

* feat(coding-agent): make ctrl+j sole owner of edit-diff visibility and hint the summary line

* feat(coding-agent): always show the edit summary line and render the diff inline beneath it

* fix(coding-agent): suppress edit summary on failed edits, unify summary path formatting, trim dead exports

* fix(coding-agent): color the edit header as error when execution fails after a successful preview

* fix(coding-agent): always show the ctrl+j hint on edit summary rows

The edit-diff hint was threaded through showExpandHint, the flag that
restricts the ctrl+o hint to the latest tool row. Since the agent almost
always runs more tools after an edit, edit rows stopped being "latest"
immediately and the ctrl+j hint effectively never appeared.

The ctrl+j hint now renders on every edit summary row, matching the
always-visible thinking (ctrl+t) and agent-message (ctrl+p) hints. The
latest-row gating still applies to the ctrl+o hint on the header line.

* fix(coding-agent): stop duplicating the ctrl+j hint on collapsed edits

Collapsed built-in edits showed the hint twice: on the edit header and on
the summary line. The header hint now renders only when the diff is
expanded (where no summary line exists); collapsed rows keep the single
hint on the summary line.

* docs(coding-agent): correct the ctrl+j hint comment to match showHint

The hint renders on every tool row, but within a row only on the last
file's summary line — the comment claimed every summary row.

* fix(coding-agent): keep the ctrl+j hint visible while no summary line renders

Gating the header hint on expansion assumed the collapsed summary line
always carries the cue, but that summary only mounts once a successful
result with a countable diff lands. During the preview-only window and on
error rows the diff was expandable with no visible hint.

The header now keeps the hint whenever the summary line is absent
(mirroring its mount condition) and yields it once the summary renders,
so exactly one hint is visible in every state.

* fix(coding-agent): advertise the collapse key on expanded ipython diff headers

* fix(coding-agent): move the expanded-diff collapse hint to the truncated cell header

* fix(coding-agent): stabilize summary-line truncation across the ctrl+j toggle and reuse countChangedLines

* Agents view: hint 'type to search sessions' instead of 'type to start' (#1367)

* fix(coding-agent): say 'type to search sessions' in the agents view splash

* refactor(coding-agent): drop the dead 'type to start' splash fallback

* refactor(coding-agent): change the splash hint fallback in place instead

* fix(daemon): move supervisor authority records out of $TMPDIR (#1449)

* refactor(daemon): extract a safe lease-renew loop for the shutdown admission

RenewableRegistryRecord owns the shutdown-admission lease-renew loop: the
unref()'d interval, a single-flight refresh shared by timer-fired and
direct assertOrRenew calls (whose rejection reaches direct awaiters), a
stopped/lost re-check inside the guarded section so a renew that loses a
race with stop() can never write, and disposal. This replaces
DaemonShutdownAdmission's hand-rolled loop, which had a latent race:
direct assertOrRenew calls bypassed the single-flight slot, so release()
could return while a renew was still queued on the registry guard and the
record could be rewritten after removal. Public semantics (assertOrRenew,
release awaiting in-flight refresh, lease timings) are unchanged.

* fix(daemon): move the supervisor registry out of $TMPDIR

macOS com.apple.bsd.dirhelper deletes files older than three days under
$TMPDIR daily at 03:35. The supervisor ownership registry (owner.json/
scope.json, startup fences, the shutdown admission) lived there only
because it rode along with the socket directory, whose location is forced
by the 104-byte sun_path limit — a constraint JSON records do not share.
Any supervisor alive past three days lost its record and wedged
permanently with supervisor_generation_stale on every command.

The registry now defaults to ~/.prime/supervisor-owners: durable, global
per user (ownerConflicts must see every daemon on the box, so it must not
shard per agent dir), with the PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_
REGISTRY_DIR override unchanged. All registry tenants move together.

No record migration: records are per-process-lifetime. A running old-build
daemon keeps its tmpdir records and works until restarted; its leftover
records self-clean via dirhelper within three days. During the overlap a
new-build daemon sees an empty registry and fails at socket bind instead,
which surfaces like any bind conflict and unwinds acquire cleanly.

* fix(daemon): disambiguate the two ownership-lost error messages

Both daemon-supervisor.ts (this.ownership undefined: never acquired or
already released) and DaemonSupervisorOwnershipLostError (record on disk
missing or replaced) emitted the identical 'no longer owns its registry
entry' string, making the failure mode impossible to tell apart from the
message alone. Each message is now distinct and appends the socket path,
the registry dir where available, and the remedy (restart the daemon;
sessions are preserved). Both keep code: supervisor_generation_stale.

* test(daemon): cover the release-overtakes-renew admission race

Pin the RenewableRegistryRecord hardening at its consumer: a direct
assertOrRenew queued behind a held registry guard while release() runs
must reject with the admission-lost error and must not rewrite
shutdown-admission.json after removal.

* fix(daemon): read the legacy $TMPDIR registry during the move window

A new-build CLI could not see a still-running pre-move daemon's owner
record: persistDaemonStartupFenceFromOwner scanned only the new (empty)
registry and threw AFTER prepare_update_restart had already drained and
fenced the old supervisor, whose updateRestartPhase never leaves
'prepared' without a shutdown — wedging the standard upgrade path.
The worker-auth validation (assertDaemonSupervisorOwnerCurrent) had the
same blind spot for a new-build worker under an old-build supervisor.

Owner-record READS now fall back to the legacy tmpdir location
(read-only, no abandoned-dir reclaim — old-build daemons own that
location's lifecycle; unlocked relative to old-build writers, acceptable
because records are rename-atomic). Writes, including fences, go only to
the new registry. The fallback is resolved structurally where registryDir
is resolved: it exists iff no env override and no explicit registryDir,
so tests with explicit registries can never leak reads to the machine's
real tmpdir. Remove after one release.

acquire's conflict scan keeps no fallback: real socket contention is
still caught at bind by the socket lease.

* test(daemon): cover the worker-auth legacy fallback; filter legacy fence matches

Add the missing test for assertDaemonSupervisorOwnerCurrent's legacy
read (a pre-move owner claim validates through an injected legacy dir and
never falls back for an explicit registry), replace the silent
processStartId early-out in the fence-via-legacy test with a visible
assertion, and filter legacy fence matches by the caller-held token/pid
so stale legacy leftovers cannot produce a spurious multiple-owners
failure.

* fix(daemon): drop deleted rlm children's kernel state and dedupe artifact paths (#1450)

* refactor: canonical session artifact path helpers in session-manager

* fix(daemon): drop a deleted rlm child's nested artifact dir, keep the transcript

* feat(daemon): bounded per-parent reaper for tombstoned rlm child artifact dirs

* fix(daemon): re-sweep artifact dirs resurrected by teardown snapshots

* test(daemon): artifact-dir deletion, best-effort rm, reaper bounds, path helper equivalence

* fix(daemon): guard degenerate session-file names in the artifact reaper

Also sweep every tombstoned edge on retry-heal, skip the chmod rm-failure test as root, and pin the depth-2 transcript boundary.

* docs: trim redundant policy comments to repo discipline

* fix(daemon): re-sweep artifact dirs even when child teardown throws

* fix(daemon): never let jobs-store errors mask a child deletion

Pre-round-2 a cancel throw on the healthy path propagated as a deletion
failure. Swallowing it is deliberate: both tombstones are durable by this
point, the reaper and retry-heal converge on leftover state, and a deletion
should not fail over jobs-store bookkeeping.

* revert(daemon): drop the orphan-artifact reaper, keep the deletion hook

New children never orphan artifact dirs once the deletion hook exists; the pre-fix garbage is a one-time mess not worth a permanent mechanism in the deletion path. The degenerate-basename guard keeps direct coverage via deleteSessionArtifacts.

* fix(ai): stabilize Cloudflare gateway model test

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): show subagent model and effort in agents view (#1479)

* Show subagent reasoning effort in agents view

* Fix scoped subagent effort labels

* Fix agents view effort regression coverage

* Hide disabled subagent effort labels

* Prioritize subagent model in agents view

* Preserve legacy subagent effort labels

* Regenerate the model catalog (#1481)

* chore(ai): regenerate the model catalog

fixes ENG-5286

* fix(coding-agent): repoint the Cerebras default after zai-glm-4.7 left the catalog

Also finish the Cloudflare gateway Claude id rename in the handoff test.

fixes ENG-5286

* Keep handoff models aligned with catalog

---------

Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>
Co-authored-by: Seth <seth@primeintellect.ai>

* Resume interrupted work after an automatic compaction (#1279)

* fix(coding-agent): resume interrupted work after auto-compaction

A threshold compaction that intentionally stopped a mid-task tool loop
never resumed the loop when the compaction itself failed or was skipped
(resumeAfterFailure only fired for requested compactions). And with an
active goal, a threshold stop landing after an assistant text turn left
the goal stuck active forever after a successful compaction, because
nothing re-entered the loop and continue() cannot resume from an
assistant-last context.

Widen the failure-resume gate to threshold compactions (overflow stays
excluded on purpose) and queue the goal continuation as a session input
before compaction, mirroring the autonomous-mode compensation, with the
goal taking exclusive priority over autonomous continuation to match
_getContinuationMessages.

* fix(coding-agent): withdraw the queued goal continuation on cancelled compaction

The goal continuation is admitted as a session input before a threshold
compaction runs, so when the user cancelled that compaction the finally
block's session-input pump still delivered it and restarted the agent.
Cancel the queued action on the aborted branch only (skip/fail must keep
delivering it), roll back the queue-time continuationsUsed increment only
when an action was actually cancelled so a stale marker cannot corrupt
completed-goal bookkeeping, and trim the previously added comments down
to the genuinely subtle rationale.

* test(coding-agent): make compaction continuation fixture deterministic

* test(coding-agent): remove ineffective faux usage overrides

* test(ai): use stable Cloudflare Anthropic model id

---------

Co-authored-by: Alex Zhang <alex.lx.zhang@gmail.com>
Co-authored-by: az <altzhang@mit.edu>

* chore(release): prepare v0.7.3 (#1492)

* fix(coding-agent): reset daemon root depth (#1496)

* prompt: make long-running RLM work nonblocking, visible, and clear (#1188)

* prompt: disincentivize blocking sleep in kernel, bash cells, and bash tool

Add a control-loop rule to the RLM system prompt forbidding blocking
sleep patterns across all execution modes: time.sleep() loops in Python,
sleep in %%bash cells, and sleep in the bash tool. A blocked cell or
command holds the turn open, wastes wall-clock, and prevents user
interaction. The agent should instead kick off work, record its handle,
end the turn, and check results later.

Expands PR #1034 which only covered time.sleep() in Python to also
cover sleep in bash cells and the bash tool.

closes #1034

* prompt: bound total wait instead of poll interval

'Short intervals' permitted sleep(5)-loops that block the turn just as
long as one big sleep — the harm is total blocked wall-clock, not
interval size. Allow only a single bounded wait when completion is
imminent; any longer wait or any sleep loop means end the turn.

* prompt: make long-running RLM work nonblocking

* prompt: add proactive progress and clear technical prose

* prompt: limit user progress updates to root agents

* prompt: emphasize parallel work and outcome updates

* prompt: clarify safe async shell usage

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* fix(coding-agent): preserve goal continuation after compaction (#1316)

* feat(coding-agent): add async bash() to IPython kernel

Add an async bash() function to the RLM bootstrap code in ipython.ts
that uses asyncio.create_subprocess_exec to run shell commands without
blocking the kernel event loop. Unlike %%bash cells (which block the
kernel until the command finishes), await bash('...') keeps the kernel
responsive to interrupts and other messages while the process runs.

Supports optional timeout (raises TimeoutError) and cwd parameters.
Returns a _PrimeAgentBashResult with stdout, stderr, and returncode.

Update the RLM system prompt to prefer await bash('...') over %%bash
cells.

* fix(coding-agent): bound async bash subprocesses

* fix(coding-agent): fail closed for bash on Windows

* fix(coding-agent): render async bash output

* fix(coding-agent): clean completed bash sessions

* fix(coding-agent): validate bash output bounds

* fix(coding-agent): bound rendered bash output

* fix(coding-agent): escalate bash group cleanup

* fix(coding-agent): preserve bounded UTF-8 output

* docs(coding-agent): qualify async bash platforms

* fix(coding-agent): clarify bash truncation units

* refactor(coding-agent): keep async bash POSIX-only

* docs(coding-agent): guide background bash tasks

closes #1034

* docs(coding-agent): simplify background bash guidance

* fix(coding-agent): preserve goal continuation after compaction

* docs(coding-agent): explain continuation deduplication

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* chore: remove redundant comments and unused internals (#1505)

* chore: remove redundant comments

* chore(tui): remove redundant export comments

* chore(coding-agent): remove redundant example comments

* chore(ai): remove redundant comments

* chore(coding-agent): remove obsolete core comments

* chore(coding-agent): remove redundant test comments

* chore(coding-agent): remove redundant test comments

* chore(coding-agent): remove redundant rpc test comments

* chore(coding-agent): remove redundant CLI comments

* chore(agent): remove redundant comments

* chore(tui): remove redundant source comments

* chore(coding-agent): remove redundant mode comments

* chore(coding-agent): remove redundant mode comments

* chore(coding-agent): remove redundant test comments

* chore(ai): remove redundant source comments

* chore(coding-agent): remove redundant session comments

* chore(coding-agent): remove redundant core comments

* chore(coding-agent): remove redundant test comments

* chore(tests): remove redundant test comments

* chore: remove unused internal helpers

* docs: restore public API contracts

* chore(coding-agent): restore invariant documentation

* chore(coding-agent): clarify agent family contracts

* style(coding-agent): format restored contracts

* fix: restore intentional catch explanations

* prompt: remove reference to unshipped async bash() kernel helper (#1508)

* fix(coding-agent): keep Shift+Enter newline working where it sends a raw \n (#1522)

* fix(coding-agent): let a raw \n insert a newline instead of toggling edit diffs

0.7.3 added app.edits.expand with the default ctrl+j (#1388). The custom
editor dispatches app actions before the base editor handling, and a raw
"\n" byte decodes as ctrl+j — but that byte is exactly what Shift+Enter
sends in terminals that map it to a literal newline (the mapping our own
keys.ts comments recommend for Ghostty), and ctrl+j is itself a
traditional newline key. Since 0.7.3, that input toggled edit diffs and
Shift+Enter stopped producing newlines outside kitty-protocol terminals.

Skip app-action dispatch for the raw "\n" byte in the editor and in the
subagent-line key handler so it reaches the newline handling; ctrl+j
still triggers the toggle via the kitty CSI-u encoding, which is
unambiguous.

* Trim the collision comments to one line each

* Keep the draft when opening the agents view (#1372)

* feat(coding-agent): auto-stash editor draft when opening the agents view and restore it on session reopen

* fix(coding-agent): skip agents-view auto-stash for whitespace-only drafts

* fix(coding-agent): keep init statuses visible when restoring an on-open stash

* test: stub restorePromptStashOnOpen in startup-run fakes and bound the hook spin

run() now calls this.restorePromptStashOnOpen() right after init(). The
Prime CLI onboarding tests drive the real run() on plain fake objects,
so the missing method made run() reject immediately: three tests failed
and two spun forever in `while (!fakeThis.admitPendingStartupPrompts)
await Promise.resolve()`, hanging the CI shard until the job timeout
cancelled it with no output.

Stub the method in createStartupRunHarness and bound both spin loops so
a future regression fails the test instead of hanging the shard.

* test(coding-agent): trim prompt stash coverage

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* Let subagents be spawned with their own reasoning level (#1510)

* feat(coding-agent): let subagents be spawned with an explicit reasoning level, fixes ENG-5301

* fix(coding-agent): neutral wording for the subagent thinking option in the rlm prompt

* chore(coding-agent): drop a redundant doc comment

* chore(coding-agent): drop the remaining name-restating doc comments in rlm-runtime

* chore(coding-agent): simplify subagent thinking validation

* fix(coding-agent): preserve early thinking validation

* refactor(coding-agent): centralize thinking levels

* refactor(coding-agent): keep thinking validation local

* test(coding-agent): retain thinking option boundaries

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* Normalize daemon socket paths before deriving identity (#1520)

* fix(coding-agent): normalize daemon socket paths at every entry point, fixes ENG-5303

* fix(coding-agent): address review findings on socket-path normalization

- Defer --daemon-socket normalization until after --cwd applies so relative
  socket paths resolve against the requested working directory
- Migrate legacy raw-spelling worker-descriptor namespaces to the canonical
  directory on supervisor construction so existing workers stay adoptable
- Consolidate the remaining private normalizers (package-manager-cli,
  daemon-update-restart) onto the shared normalizeSocketPath
- Wait for killed test supervisors to exit before removing their directories
  to fix ENOTEMPTY flakes in CI

* fix(coding-agent): harden descriptor-namespace adoption and test cleanup

- Adopt a raw-spelling descriptor namespace only after the socket-path lease
  and registry ownership are held, so a rejected startup can never move a live
  supervisor's directory
- Skip unreadable JSON entries per file when identifying a namespace instead
  of aborting the whole scan
- Drop migration-era vocabulary from the helper names and test titles
- Disable the Node compile cache for spawned test supervisors and tolerate
  cleanup races so worker processes exiting late cannot fail the suite

* refactor(coding-agent): drop the descriptor-namespace adoption machinery

Normalization at the entry points prevents namespace forks going forward;
descriptors written under an old raw-spelling key are healed per-field on
load only when they share the directory. Cross-directory adoption kept
accreting ordering hazards (live-daemon rename races, persisted-config
staleness) disproportionate to its value, so a daemon that previously ran
on a non-canonical spelling simply starts fresh namespaces; saved sessions
are unaffected (they live in the session catalog, not the descriptor dir).

* docs(coding-agent): align the changelog with the simplified scope

* chore(coding-agent): trim excessive comments

* fix(coding-agent): normalize the early-launch socket key and drop an incidental test

maybeStartDaemonEarly memoized ensure attempts under the raw --daemon-socket
spelling while main uses the normalized one, so equivalent spellings could
run two concurrent spawn attempts for one daemon. The early kick now derives
the same canonical spelling (resolving relative paths against --cwd).

* chore(coding-agent): simplify socket normalization coverage

* Address final review feedback

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* CI: require a linked Linear ticket on PRs (#1480)

* ci: require a linked Linear ticket or an explicit opt-out on pull requests

* ci: drop the No-Ticket opt-out; every PR links a Linear ticket

* Keep large IPython state from slowing later turns (#1540)

* fix(coding-agent): bound persistent kernel snapshots

* fix(coding-agent): simplify bounded kernel snapshots

* fix(coding-agent): cap individual snapshot variables

* fix(coding-agent): prune oversized state on compaction

* fix(coding-agent): stop snapshots at aggregate limit

* fix(coding-agent): preserve bounded snapshot packing

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* docs: add Trendshift badge (#1500)

* docs: add Trendshift badge

* docs: separate the Trendshift badge

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* fix(coding-agent): rank model search by intent (#539)

Fixes ENG-5388

Co-authored-by: Seth <seth@primeintellect.ai>

* chore(release): prepare v0.7.4 (#1569)

* feat(acp): harden resident session lifecycle (#1494)

* feat(acp): harden resident session lifecycle

* test(daemon): align recovery fixtures with durable context

* fix(acp): validate durable host settings

* fix(daemon): clean up failed resident resources

* fix(daemon): recover owned workers from fresh context

* fix(acp): verify authoritative completion state

* fix(daemon): refresh requested child roster

* fix(acp): read child roster without reattaching

* fix(daemon): preserve telemetry opt-out on recovery

* fix(daemon): scope resident path conflicts

* test(acp): remove runtime publish hook

* fix(acp): drain admitted updates on close

* fix(daemon): merge fresh supervisor defaults

* fix(acp): await autonomous lifecycle settlement

* fix(acp): preserve daemon input fences

* fix(acp): serialize terminal lifecycle turns

* fix(acp): reacquire daemon fences after reconnect

* fix(acp): fail closed across abort cuts

* fix(acp): resume queued input after pause

* fix(acp): invalidate pending pumps on pause

* fix(acp): preserve pause ownership through supervisor

* fix(acp): fail closed on pause cleanup loss

* test(daemon): initialize pause ownership harness

* fix(acp): fence detach pause cleanup

* fix(daemon): preserve detach-all cleanup

* test(daemon): initialize worker pause registry

* fix(daemon): reacquire pauses across release

* fix(daemon): clear stable detach selectors

* fix(daemon): retain detach fences on attach failure

* refactor(daemon): keep descriptor validation private

* docs(changelog): describe ACP lifecycle fencing

* fix(acp): settle restart and failed close lifecycle

* fix(acp): serialize cancel after failed close

* fix(acp): preserve lifecycle reconciliation ordering

* fix(acp): settle deleted child runtimes

* test(acp): consolidate child deletion regressions

* fix(acp): rearm settled child deletion

* fix(acp): preserve recursive terminal settlement

* test(acp): update terminal notice regression

* fix(coding-agent): answer ACP prompts only once the session admits the next turn (#800)

* fix(coding-agent): queue ACP prompts behind in-flight work

* fix(acp): keep queued-prompt semantics through the resident lifecycle merge

Renumber owned prompt cancellation to schema revision 20 (17-19 landed on
main), adapt compatibility gating to the requirements-array form, and return
stopReason "cancelled" for a prompt parked behind the terminal lifecycle when
a cancellation drops it before it starts.

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>

* fix(coding-agent): treat a set-but-empty credential env var as missing (#1513)

* feat: add generic kernel-owned MCP runtime (#1495)

* feat(coding-agent): add generic MCP runtime

* fix(coding-agent): harden MCP lifecycle and overrides

* fix(runtime): close cancelled host request comms

* fix(runtime): preserve MCP SDK result aliases

* fix(runtime): preserve synchronous kernel shutdown

* fix(runtime): allow MCP close retry after cancellation

* feat(coding-agent): manage MCP servers from CLI and TUI

* fix(coding-agent): preserve inherited stdio env names

* fix(coding-agent): reserve built-in MCP identities

* fix(coding-agent): allow cleanup of reserved MCP entries

* test(coding-agent): update MCP command hint

* fix: address MCP reload and cleanup findings

* fix(coding-agent): advertise generic MCP connections

* fix(coding-agent): keep MCP command results visible

* fix(runtime): surface safe stdio startup diagnostics

* fix(runtime): preserve startup errors through cleanup

* fix(coding-agent): drop stored MCP credentials when a server is removed or replaced

* fix(coding-agent): scope credential drops to generic servers and keep quoted empty argv tokens

* docs(coding-agent): document the removed catalog-name override as a breaking change

* fix: close kernel MCP servers during shutdown

* docs: preserve changelog sections after main merge

* fix: harden MCP shutdown boundaries

* fix: bound graceful kernel shutdown

---------

Co-authored-by: Sebastian <sebastian@primeintellect.ai>

* fix(coding-agent): reject headless idle waiters when a post-compaction continuation cannot start (#1583)

* fix(coding-agent): reject headless idle waiters when a post-compaction continuation cannot start

A continuation that fails to start settled headless idle as a clean finish,
so ACP and print-mode callers reported a turn as completed that never ran.
The settlement is now one-shot with reject support: non-retryable start
failures reject waiters, cancellation and the benign nothing-to-continue
race still resolve, a settled failure is never re-exposed to later waiters,
and interactive waitForIdle is unchanged.

Ports the failure semantics from #881 onto the resident-lifecycle settlement
from #1494. Co-authored-by: Parker Pettit <parkerpettit@users.noreply.github.com>

* docs: cut comments down to single-line load-bearing invariants

* test: keep only the two tests that pin new behavior

* fix(coding-agent): survive kernel cold boots and stop leaking raw zmq EAGAIN (#1587)

* Survive kernel cold boots and stop leaking raw zmq EAGAIN

After an upgrade that changes the kernel runtime deps, the shared venv
reprovisions on first boot. First cells raced that slow start and
failed with libzmq's bare "Operation was not possible or timed out",
succeeding only on retry. ensureKernelPython now reports provisioning
work via onProvisioned, doStart grants such boots a 30s ready budget
(warm boots keep 5s), and probeReady/execute sends translate socket-
teardown rejections into an actionable retriable kernel error carrying
the stderr tail.

* Add the kernel socket-closure translation unit suite

* Propagate provisioning to coalesced callers and sibling processes

Review found onProvisioned only reached the caller that performed venv
work: managers coalesced onto an in-flight bootstrap and lock waiters
that found the venv ready kept the 5s warm budget against a stone-cold
venv. The uncached path now returns {python, provisioned} and the
wrapper fires every caller's callback; a ready venv whose version stamp
is younger than two minutes counts as provisioned so sibling-process
first boots get the cold budget too.

* Grant the cold-boot budget to the port resolve too

Review caught that a cold direct spawn could still fail the 5s
PORTS_RESOLVE_TIMEOUT_MS wait before the 30s ready probe ever ran:
ipykernel rewrites the connection file only after its cold imports
finish binding ports. waitForResolvedConnection now takes the same
cold budget as probeReady when the venv was just provisioned.

* Simplify: flat 30s startup budget instead of cold-boot detection

The cold/warm dual budget required detecting "was this boot cold?"
(onProvisioned plumbing, per-caller propagation, an mtime freshness
heuristic) and three review rounds kept finding edge cases in exactly
that machinery. Crash detection never depended on the timer: both wait
loops observe the exit handler within one 25ms poll. So the timeout
only bounds an alive-but-wedged kernel, where patience is cheap.
Startup now uses one generous 30s budget unconditionally; warm boots
return in under a second and never feel it, cold boots just work, and
all detection machinery is deleted. The zmq socket-teardown error
translation stays.

* fix(coding-agent): close MCP runtime review follow-ups (#1585)

* fix(coding-agent): close MCP runtime review follow-ups

- drop the mcp:<name> credential on every add, not only replaces: a fresh
  add can repoint a name an authored non-catalog skill (e.g. slack) resolves
  via mcp.config, and the stored token must never replay there
- keep the kernel MCP close budget strictly inside the host kill deadline
  (2.5s + 1s dispatch < 5s) and pin the relationship in a test
- include kernel exit in the host's first shutdown race so a kernel that
  dies without shutdown_reply finishes promptly instead of eating the deadline
- close a shutdown race that dropped a just-opened generation from the
  registry without closing it, leaving its server process running
- run kernel-mcp-shutdown.test.ts in test:kernel and the Python runtime
  tests in CI; gate the E2E on module availability instead of an exact
  ipykernel version that silently skipped the suite
- pin mcp>=2,<3, require Python >=3.11 (asyncio.timeout), and resolve the
  streamable-HTTP transport via mcp_base instead of new-only + private imports
- shield sibling generation closes from a failing close in shutdown/reload
- reject a quoted-empty mcp add option value deterministically

* fix: keep SSE-friendly read timeouts and handled shutdown races

Match the SDK factory's timeouts (30s ops / 300s reads) on the http_client
transport path in both the generic runtime and mcp_base, keeping reads above
the configured per-call timeout; an httpx default client caps reads at 5s and
a flat 30s cap drops quiet streams whose server sends no pings. Attach a
rejection handler to the abandoned graceful-reply composite so a late send
failure cannot surface as an unhandled rejection.

* docs: compress comments to single-line invariants

* fix: give the http_client transport its companion client and stop following redirects

The mcp 2.x transport calls client.sse() for server-initiated streams and
reconnects; a plain httpx client has no such method, so the GET stream died
in a silent reconnect loop (AttributeError swallowed by the retry). Both
fallback paths now build an httpx2 client with the SDK-factory timeouts.
Redirects are disabled on these clients: httpx only strips Authorization on
cross-origin redirects, so configured secret headers would follow a
redirecting endpoint. httpx itself is no longer used and leaves the runtime
dependencies (added by #1495 for the code this replaces).

* fix: stop authored integrations honoring mcpServers overrides and fail credential drops closed

An authored McpIntegration resolved its URL through mcp.config, so a
same-named user entry repointed it while its credentials — auth.json tokens
or a bearer-token env var the add-time drop can never reach — followed as
the Authorization header. Authored integrations now always use their class
URL; custom endpoints go through the generic runtime under their own name.

The add flow also persisted the new URL before dropping stored credentials,
and the store records persistence failures instead of throwing, so a failed
or silently skipped write left the old token armed against the new URL
behind a reported success. Drops now happen before the entry is persisted
and drain the store's recorded errors, aborting the add on failure.

* fix(coding-agent): make credential removal disk-verified instead of error-drain heuristics

Three review rounds patched the same invariant at the call site: logout
removes memory before a persist that swallows failures (and silently no-ops
after a load error), so has() gating skipped retries and drainErrors missed
the loadError path entirely. The invariant now lives in the owning layer:
AuthStorage.removeVerified performs the disk removal under lock, throws on
any load or write failure, and only then updates memory — disk-authoritative
and idempotent. dropServerCredentials shrinks to a try/rethrow around it.

Drop-before-persist ordering is deliberate: a settings-flush failure after a
verified drop strands the server on re-login (recoverable); the reverse
order can strand an old token against a new URL (replay).

* fix(coding-agent): keep the stale marker until a verified removal succeeds

A failed removeVerified cleared the stored stale marker first, making a
suppressed credential selectable again while it survived on disk. Also
compress comments to single-line invariants.

* fix: bind MCP OAuth tokens to the endpoint they were issued for

An in-flight login races retargeting: /mcp login captures the provider
for the old URL, awaits the browser, and a concurrent add --force drops
the credential and persists a new URL; the finishing login then stores
an old-endpoint token under the same name and the runtime sends it to
the new URL. No ordering fixes this — the binding token<->endpoint
existed nowhere. Credentials now record the endpoint at issuance,
refreshes carry the original binding forward (no laundering), and both
consumers verify it: the kernel refuses to attach a foreign-bound token
and the host treats it as not authed. Legacy unbound credentials keep
working.

* fix(coding-agent): strip trailing slashes without a backtracking regex

CodeQL flagged the /\/+$/ anchor on stored-credential and settings input
as polynomial; a two-line loop replaces it.

* fix: require endpoint binding for user-declared MCP OAuth credentials

Accepting unbound legacy credentials kept the replay window open: a
pre-update login can finish after a retarget and store an unbound token,
and a refresh would have re-bound it to the current URL. User-declared
servers now require an exact binding (host and kernel), and a refresh
never infers one. Builtin catalog integrations keep accepting stored
credentials — their URLs are code-constant and cannot be retargeted.
Breaking: generic-server OAuth credentials stored before the binding
existed require one /mcp login.

* chore: trim a narrating test comment and compress the _bound_auth docstring

* chore: reframe unbound-credential refusal as standing behavior, not migration wording

* fix: compare MCP endpoint bindings exactly

Trailing-slash normalization ran on the whole URL string, so a slash
that is part of the query unified distinct targets (?tenant=trusted/
matched ?tenant=trusted). Both strings come from the same settings
entry, so any difference means the entry changed: compare exactly.
Also retires the stripTrailingSlashes helper.

* Changelog fragments: conflict-free changelog entries (#1589)

* feat(coding-agent): add changelog fragments with CI check and release-time aggregation (ENG-5408)

* refactor: flag-day cutover — remove [Unreleased] sections entirely

Per review: drop the transition machinery. All four changelogs lose their
[Unreleased] header; coding-agent's pending entries move into a fragment.
release.mjs no longer re-adds the header; the CI check accepts only
fragments (or the no-changelog label). The stray-[Unreleased] absorption
stays in buildReleaseSection so an accidentally merged old-style entry is
folded into the release instead of stranded. Test file removed.

* fix: address bot findings — escape version in dry-run regex, refuse empty fragments

CodeQL: the dry-run preview regex embedded the CLI version argument
unescaped. Macroscope: empty fragments were git rm'd without appearing in
the changelog; the release now aborts on them and the CI check requires
the added fragment to have content. Cursor: normalizeFragment unexported.

* fix(ci): verify fragment content, not just line additions

A whitespace-only fragment passed the additions>0 guard but aborts the
release. The check now reads each candidate fragment's content via its
contents_url (head-repo scoped, so fork PRs work) and requires non-blank
text.

* refactor: decouple CI from release semantics for empty fragments

The fail-closed release abort forced CI to predict exactly what release
would reject, which pulled content fetching and fork-ref handling into
the workflow. Empty fragments have nothing to lose, so the release now
warns and leaves them unconsumed instead of aborting; CI goes back to a
plain added-file presence check. Also trims comments and doc blocks.

* fix(ci): fail closed when a PR exceeds the listFiles cap

Above 3000 changed files the API cannot return the full list, so the
check could miss a src change. The no-changelog label remains the
explicit escape hatch.

* fix(coding-agent): dim the queue-browse header so it reads as a hint, not prompt text (#1575)

* Kernels exit when their owner dies (#1559)

* fix(coding-agent): kernels exit when their owner dies

Set JPY_PARENT_PID so ipykernel's parent poller exits kernels when the
owning process hard-dies (SIGKILL/crash/OOM), add a parent-death
watchdog thread to the forkserver script, and register kernel and
forkserver pids in the orphan process journal so supervisor recovery
can reap them.

fixes ENG-5310

* fix(coding-agent): forked kernels watch the forkserver, not the worker

ipykernel's Unix poller distrusts a parent_handle that differs from
getppid() at startup and falls back to watching for pid-1 reparenting,
which subreapers (systemd --user) never trigger. Forked children now pass
their real parent (the forkserver) whose own watchdog ties it to the
worker, so the death chain holds on every platform. The fork-request env
no longer carries an intentionally-ignored JPY_PARENT_PID.

* fix(coding-agent): race-free forked-kernel signaling via forkserver protocol

Forked kernels were signaled by bare pid from Node (process.kill), which
can hit a reused pid and write a wrong inactive journal record that masks
a sibling manager's active one. The forkserver is the kernels' parent and
waitpid-reaps them, so it now owns kill and liveness: new id-keyed protocol
messages let KernelManager kill/poll through a ForkedKernelHandle, the
Python side only signals a pid found in its un-reaped-children table while
SIGCHLD delivery is excluded (blocked in all threads, handled only by the
main thread outside the check+kill section), and the inactive journal write
is gated on a confirmed outcome — uncertainty leaves the active record for
the supervisor reaper, which verifies process identity before acting.

fixes ENG-5310

* fix(coding-agent): key forkserver kill/liveness by fork request id

Raw-pid keying could alias across forkserver children: a reaped pid
reused by a later fork made kill/alive act on a sibling manager's
kernel. Fork request ids are unique and never reused, so the forkserver
now keeps a bounded id -> (pid, alive) registry (FIFO, 4096) and
kill/alive by id can only ever act on the caller's own incarnation;
evicted ids fail closed. Fork bookkeeping now runs with SIGCHLD blocked
so a fast-exiting child can't be reaped before registration (the forked
child unblocks the inherited mask before running the kernel), and the
inactive orphan-journal write is restricted to the 'signaled' outcome —
the only one that proves the pid still named our child at kill time.

fixes ENG-5310

* chore(coding-agent): trim comments to the non-obvious rationale

* fix(coding-agent): guard kernel teardown against stale starts and unsound journal writes

A stale in-flight doStart (superseded by a public restart) could resume
and tear down or corrupt the successor kernel; a hung forkserver could
stretch startup failure past the 5s budgets via the 10s protocol
timeout; and the forkserver's inactive journal write was unconditional.
Starts now own a generation token bumped by every teardown: stale
resumes and stale failure catches bail without side effects, shutdown
and dispose skip cleanup when superseded mid-await, and liveness probes
during startup are bounded by the remaining budget (timeout counts as
alive so the loop deadline owns failure). The forkserver journal write
now requires an observed exit or confirmed handle-based delivery.

fixes ENG-5310

* fix(coding-agent): kernel start recovery defers to a concurrent teardown

shutdown() now reports whether it performed the cleanup; start recovery
resurrects to idle only as the owning cleanup, so a kill() racing the
recovery can no longer be undone. Replaces the generation+1 idiom and the
remaining inline staleness comparisons with the one startStale predicate.

* fix(coding-agent): never evict live kernels from the forkserver registry

FIFO eviction bounded total forks, not dead entries, so the 4097th fork on
one forkserver dropped the oldest still-running kernel: its liveness read
false (tearing down a healthy kernel) and its kill was unroutable — the
exact orphan leak this change prevents. Eviction now sweeps exited entries
only; live entries are bounded by real concurrent kernels.

* fix: restore main's models.generated.ts

An earlier merge-with-main resolved the generated model catalog as ours,
reverting newer pricing/context data; the watchdog PR must not touch it.

* fix: treat liveness-probe timeouts as unknown and gate direct inactive journal writes on a delivered signal

A forkserver stalled in a slow fork rejects isAlive with a request
timeout; the liveness monitor took any rejection as death and tore down
healthy kernels. ForkServerUnavailable now carries a timedOut flag and
the monitor treats a timed-out probe as unknown (alive), with an
in-flight latch so 1s polls cannot pile up behind a stalled probe.
Proven unavailability (socket death) still counts as dead.

Direct-spawn cleanup wrote an inactive journal record even for a child
that had long exited, which can mask a sibling manager's active record
for a reused pid; it now writes inactive only when kill() delivered a
signal, matching the forked branch's rule. Also retargets the hung-probe
startup test to the probe budget itself (main's cold-boot change raised
the ports budget to 30s, past the test's wall-clock bound).

* fix: report shutdown ownership from the cleanup decision, not a post-cleanup generation check

cleanupResources bumps startGeneration, so the final startStale check read
every non-superseded shutdown as superseded and returned false; startup-failure
recovery then never resurrected the manager to idle, leaving it bricked in
shutdown after a failed ports resolve. Ownership is now captured where the
cleanup decision is made. The superseded-shutdown watchdog test was passing
because of this bug (its parked send short-circuited via waitForKernelExit on
a missing kernel handle); it now parks genuinely and still pins false.

* feat(coding-agent): support ACP MCP programs (#1378)

* feat(coding-agent): support ACP MCP programs

* feat(coding-agent): port ACP MCP servers to kernel runtime

* fix(coding-agent): fence ACP MCP daemon ownership

* fix(coding-agent): release ACP MCP leases on detach

* test(coding-agent): preserve daemon prototype fixtures

* fix(coding-agent): roll back failed ACP MCP claims

* fix(coding-agent): reap ACP MCP transports on release

* fix(coding-agent): isolate ACP MCP release failures

* fix(coding-agent): retry empty-session MCP cleanup

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): render the subagents summary as a bordered tile (#1586)

* Support /fast with OpenAI API-key authentication (#1609)

* feat(coding-agent): support /fast with OpenAI API-key authentication (ENG-5425)

Widens supportsFastMode() to admit GPT-5.4/GPT-5.5/GPT-5.6 on the openai
provider (openai-responses API) in addition to ChatGPT auth, corrects the
GPT-5.6 fast-pricing multiplier from 2.5x to 2x per OpenAI's current
pricing table, and updates the /fast unavailable message.

Resolves discussion #1595.

* docs(ai): trim pricing comments to source reference only

* chore: drop research scratch file from the branch

* chore: restore package-lock.json from main (unrelated npm churn)

* test: update fast-mode unavailable message assertion

* fix(coding-agent): hold goal continuations while subagent work is unsettled (#1610)

* Hold goal continuations while subagent work is unsettled

The harness teaches models to delegate and end their turn, but the goal
continuation hook re-prompted the parent the instant it went idle,
punishing correct waiting with a full-context call per turn (#1598).
The hook now defers while _hasUnsettledRlmQuiescenceWork() reports
outstanding descendant work and resumes via the existing goal-context
admission once the last run settles.

* Add the changelog fragment and the continuation-quiescence unit suite

* Make the continuation resume wake, retry, and queue in order

Review round: the resume went through _runOrQueueGoalContext, which
admits with wake:false (an idle parent never woke), front:true (the
continuation jumped ahead of the settling child's terminal notice), and
the deferral flag was cleared before an admit that throws under an
admission pause (goal stranded idle). The helper now admits directly
without front and with the wake enabled, keeps the deferral until the
admit succeeds, early-returns while admission is paused, and the pause
release retries it.

* Respect abort suspension and goal replacement in the resume

Review round two: the resume admit could clear the post-abort pump
suspension via its immediate wake, starting a goal turn right after the
user aborted — it now holds the deferral while the pump is suspended
and resumeQueuedWork retries it. A deferral no longer leaks onto a
replacement goal: _clearQueuedGoalContexts and _startGoal drop it.

* Count resumed continuations

Review: the resumed continuation skipped the continuationsUsed
increment, so resumed cycles reported a stale count. The resume now
mirrors the hook, rolling the state back when admission throws so the
retry re-counts.

* refactor(agent): typed codes for Agent.continue precondition failures (#1588)

* refactor(agent): typed codes for Agent.continue precondition failures

Classifying continuation-start failures by error message text
(includes("already processing"), includes("continue from")) breaks
silently when wording changes. Agent.continue() now throws
AgentContinueError with a stable code — busy or nothing-to-continue —
and the post-compaction classifier switches on the code. Unknown errors
still reject headless idle waiters.

* test(coding-agent): retarget queue retry test to AgentContinueError

The queue characterization suite also reaches the reschedule path with a
plain Error; it now throws the typed busy error like the compaction suite.

* chore: merge main and convert changelog entry to fragment

* Delete packages/ai/.changes/fix-typed-continue-preconditions.md

unrelated to PR

---------

Co-authored-by: Seth Karten <32787133+sethkarten@users.noreply.github.com>

* fix(coding-agent): keep the working-status elapsed timer across session re-entry (#1605)

* Keep the working-status elapsed timer across session re-entry

The loader anchored its elapsed display on Date.now() at loader start,
so leaving a session (agents view, detach) and coming back restarted
"Waiting · Ns" at zero while the daemon kept working. The timer now
anchors on the in-flight turn: the first user message of the turn live,
and the newest user message of the restored transcript on attach.
Steering messages mid-turn keep the original anchor; agent_end clears
it so idle loaders fall back to Date.now().

* Stub the turn-start restore in the session-render harness

* Anchor on the run's first starter and cover resync

Review round: the transcript scan now finds the earliest run starter
(user, agent-session, or heartbeat prompt) of the trailing in-flight
run, stopping at the previous run-ending assistant message, so steering
messages and custom-started runs anchor correctly; a scan that finds
nothing clears the stale anchor. session_resynced refreshes the anchor
from the snapshot. Live message_start shares the same starter
predicate. Dedicated unit suite removed and comments slimmed per
maintainer request.

* Share one run-start predicate across core and TUI

Review flagged three spellings of "message starts an agent run".
startsAgentRun now lives in agent-messages.ts; agent-session's
_isPromptTurnStartMessage and the interactive-mode duplicates use it.

* test(coding-agent): cover working timer restoration

fixes #1605

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): session_before_refine extension hook (#1558)

* feat(coding-agent): session_before_refine extension hook

Let extensions customize continual-harness refinement the same way
session_before_compact customizes compaction. The hook fires before the
planning LLM call for /refine and auto-refine with the planning inputs
(trigger, instructions, scope, planning harness state, refinement
history, serialized conversation). An extension can return a
RefinementProposal to replace the built-in planner (edits still pass
apply-time validation and baseline conflict rejection), return
{ skip: true } to suppress the round, or return nothing to fall back to
the default planner. Rollback refinements bypass the hook.

Includes examples/extensions/custom-refinement.ts (planning with a
cheaper model, covering discussion #1464) and documents the existing
refine_complete event.

* Update queue-test refine call assertions for the trigger argument

* Propagate the auto trigger and skip semantics through serialized refine paths

Serialized auto-refine now reaches session_before_refine with trigger
"auto", an extension skip there stamps the cooldown without emitting
refine_failed, and a skipped explicit refine.run surfaces the
RefineSkippedError instead of passing as a silent reviewer decline. The
example planner now labels entry ids with their scope and tells the
model other-scope entries are read-only.

* fix(coding-agent): consume skipped auto-refine rounds

* fix(coding-agent): report skipped refine during disposal

* fix(coding-agent): normalize extension refine proposals

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* feat(coding-agent): surface refinement status and outcomes (#447)

* feat(coding-agent): surface refinement and queue prompts

* fix(coding-agent): preserve internal prompt handoff semantics

* fix(coding-agent): preserve refinement queue order

* fix: preserve queued prompt semantics

* feat(coding-agent): show exact refinement edits

* chore: restore main's package-lock.json

* test: assert isRefining stays false while a public refine waits for idle

* review: bump daemon schema to 17, drop duplicate attach-terminal refinement lines, tighten comments

* test: executable compatibility assertions for revision-17 refinement additions

* fix: mirror compaction_outcome handling for refinement_outcome in print mode and context rebuilds

* Render refinement outcomes like compaction and skill messages

Replace the bespoke checkmark rendering with the shared custom-message
pattern: a boxed bold [refinement] label on the customMessage background,
collapsed to a single summary line and expanded through the same
tool-output toggle (ctrl+o) that compaction summaries and skill
invocations use, instead of a separate edit-diff axis.

* Strip the live-status plumbing; keep only the durable outcome message

The apply phase is sub-second, so a "Refining" loader, agents-view
status label, daemon status string, heartbeat deferral, and an
isRefining wire flag were all periphery for something users can barely
see. Drop refinement_start/end events and every isRefining touchpoint
(daemon protocol schema stays at 16 — no wire change at all), and slim
the outcome-message validator to the shallow envelope checks the
compaction outcome uses. The feature is now just the persisted
[refinement] transcript card.

* Show a live loader for user-issued /refine by reusing existing edges

No new events or state: start the loader when the /refine slash-command
message reaches the transcript and stop it on the already-wire-visible
refine_complete/refine_failed events, mirroring the compaction loader.

* Extract the shared expandable custom-message card skeleton

Compaction, skill, and now refinement cards each copied the same Box
subclass with an expanded flag, setExpanded, invalidate, and bold-label
formatting. Pull that into ExpandableCustomMessageBox + a
customMessageLabel helper and rebase all three components onto it, so
the refinement card only carries its edit-row/diff formatting.

* Emit refine_failed when a queued /refine command fails

A failed /refine persisted its command-error row without emitting
refine_failed, so the TUI loader kept spinning. Emit it from the queued
command catch, matching the refine.run and auto-refine failure paths.

* Align the refinement card layout with compaction and shield its loader

Collapsed card now renders the [refinement] label line above the
summary line, matching the compaction card structure. The refine loader
joins compaction/retry in the syncWorkingLoader ownership guard so
periodic reconcile paths (subagent updates, connection refreshes)
cannot clear it mid-refine.

* Truncate the collapsed refinement summary instead of wrapping

Long summaries wrapped the collapsed line onto a second row. Render the
collapsed line through a width-aware component that ellipsizes the
summary while keeping the edit count and expand hint visible.

* Remount the refine loader after compaction and hard-clip the collapsed line

A compaction that starts mid-refine owns the status container; on
compaction_end syncWorkingLoader now remounts the refine loader instead
of bailing on the ownership guard. The collapsed outcome line is also
clipped to the render width after assembly so extreme narrow viewports
cannot wrap it.

* fix: settle the /refine loader on its own result row and discard it on teardown

refine_complete carries no request identity, so an agent or auto
refinement settling while a queued user /refine waited on it killed the
loader early. The /refine result row is the user refine's settle edge
(emitted after refine() returns, error row on failure), so the loader
stops there — and on refine_failed, which covers a failed result-row
append. Session switches and stop() now discard the loader timer, which
previously leaked when the view never received a settle event.

* fix(coding-agent): correlate refine loader settlement

* fix: emit refine_failed only for the refinement itself

The queued /refine catch emitted refine_failed for any error in the
command try, including a result-row persist failure after refine()
succeeded — reporting a completed harness update as failed. The emit now
wraps only the parse+refine call; the outer catch keeps its row-append
duty as the correlated settle edge.

* fix(coding-agent): remove refine loader on teardown

* fix(coding-agent): retain outcomes when refine audit write fails

---------

Co-authored-by: Seth <seth@primeintellect.ai>

* fix(ai): follow MCP protected-resource OAuth discovery (#1591)

* fix(ai): discover path-scoped MCP OAuth resources

* fix(ai): preserve canonical MCP OAuth resources

* fix(ai): allow same-origin OAuth tenant issuers

* fix(daemon): skip failed workers in heartbeat catalog (#1621)

* fix(daemon): skip failed workers in heartbeat catalog

* Add the changelog fragment

* fix(coding-agent): refresh MCP providers after add (#1629)

* fix(coding-agent): refresh MCP providers after add

* fix(coding-agent): preserve legacy MCP login guidance

* chore(release): prepare v0.8.0 (#1618)

* chore(release): prepare v0.8.0

* chore(release): include heartbeat catalog fix

* chore(release): include MCP provider refresh fix

* chore(ai): regenerate the model catalog from live provider catalogs (#1632)

Commits the current `npm run generate-models` output. Net 1252 -> 1260
models. Highlights: z-ai/glm-5.3 on OpenRouter and Prime Inference,
deepseek-v4-flash-vision-exp (OpenRouter, Vercel AI Gateway, OpenCode
Go), thinkingmachines inkling free routes and two stealth previews
(ox-alpha, x-preview-f); Vercel AI Gateway renamed its grok vendor
prefix from xai/ to spacexai/ (13 ids); repricing on gpt-5.6-sol
(5/30 -> 2.5/15), gemini-3.6-flash (halved), gemini-flash-lite-latest
(raised), kimi-k2.7-code, minimax-m2.5, and mistral-small-3.2; retired
deepseek-v4-flash-free on OpenCode.

All provider fetches succeeded during generation; no provider section
was emptied by a failed source.

Co-authored-by: eliebak <eliebak@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Highlight the whole ipython cell so multi-line strings keep color (#1692)

The expanded view highlighted each line separately, so highlight.js
lost its inside-a-string state and only the first line of a
triple-quoted string was colored. One whole-cell highlightCode pass
(already line-split and ANSI-self-contained per line) fixes it; the
per-line %magic/!bash overrides are kept.

* feat(coding-agent): default RLM max depth to 2 (#1493)

* feat(coding-agent): default RLM max depth to 2

* test(coding-agent): pin max depth in prompt update case

---------

Co-authored-by:…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants