- read now treats an xd://-mounted inspect_image as available (top-level
predicate OR mounted device gated by the effective mode), so default
xdev sessions with a text-only model keep metadata-guidance reads
instead of inlining images the provider boundary would scrub
- advisor tool session stops inheriting the primary's isToolActive and
xdevRegistry: advisors cannot execute xd:// devices, so their reads
inline images again
- setModelWithProviderSessionReset is now async and awaited at every
callsite, so retry-fallback model switches cannot race the
inspect_image tool-slate reconcile
- regression tests for both xd:// availability directions
- Added a prepareToolCall phase to the agent loop running before tool scheduling for validation and hooks.
- Updated BeforeToolCallContext and result types to support argument replacement instead of in-place mutation.
- Updated coding-agent extension handling and runner to track emitted tool calls and re-evaluate approvals on input revisions.
- Added comprehensive test coverage for argument replacement, concurrency resolution, and schema validation.
- read now derives its image behavior from actual tool availability
(session.isToolActive) with the mode computation as fallback, so
restricted sessions whose explicit slate omits inspect_image (e.g.
subagents) never get metadata-only reads pointing at an absent tool
- reconcile passes the post-change availability into the read
description sync, keeping the advertised prompt correct across flips
in both directions and when tool construction fails
- flat quoted-dotted inspect_image.mode is normalized into the nested
target during migration instead of being silently dropped when a
legacy flat enabled key is present
- regression tests for all three: availability-driven read behavior,
flat+flat migration, description advertising
- Reconcile inspect_image centrally from setModelWithProviderSessionReset
so retry-fallback model changes (turn-recovery.ts) that bypass
syncAfterModelChange cannot leave a stale tool set
- Apply persisted inspect_image.mode changes immediately from the
settings selector via a new handleSettingChange branch
- Refresh the read tool's advertised description during reconciliation,
before applyActiveToolsByName rebuilds the prompt, instead of only
lazily on the next image read
- Fix the flat (quoted-dotted) enabled->mode migration to write the
nested target form the resolver actually reads
- Add committed regression tests: tri-state x capability matrix,
override precedence, and enabled->mode migration (nested, flat, and
explicit-mode-wins)
Replace the inspect_image.enabled boolean with inspect_image.mode
(auto|on|off, default auto). In auto the tool is registered only when
the active model lacks native image input, so vision-capable models
(e.g. kimi-code/k3) read images inline with their own capabilities
instead of delegating to a separate vision model. on/off force
registration regardless of model capability.
- New utils/inspect-image-mode.ts resolves the effective state from the
/vision session override, the persisted setting, and model capability
- read tool re-evaluates the effective state per image read and
re-renders its description, so it returns decoded image blocks again
whenever inspect_image is hidden
- /vision [on|off|auto|status] slash command (modeled on /computer)
overrides the mode for the current session only
- Tool set is reconciled on model switch with a status notice when
inspect_image appears/disappears
- Legacy inspect_image.enabled true/false migrates to mode on/off
- Add an LRU cache to `scanSessionFile` in `session-listing.ts` keyed by file path, stat identity, and scan mode.
- Add a match key union probe in `CustomEditor` in `custom-editor.ts` to bypass per-action lookups on plain text input.
- Add tests covering cache hits, size and mtime invalidations, and negative result caching.
- task.maxEffort only clamped the initial thinking level; a retry
fallback candidate could clamp back up to its model floor and run a
low-capped spawn at high.
- The ceiling now rides the session as thinkingLevelCeiling: clamped in
ModelControls (constructor, setThinkingLevel, auto classifier,
restore) and in applyRetryFallbackCandidate; fallback candidates whose
floor exceeds the ceiling are skipped.
- Effort value import moved to @oh-my-pi/pi-catalog/effort; changelog
attribution added.
- Review follow-up for PR #6794.
Render the Advisor spend next to the primary-model cost as `$2.67 (sub) + $0.41 (adv)`, leaving the status line unchanged until an Advisor cost exists.
Record the cost from finalized advisor `message_end` events in a per-session ledger instead of deriving it from the live advisor transcript, so an in-session compaction or any other history rewrite no longer resets the reported spend. The ledger is cleared for a new session and once a different-session switch commits, and survives a switch that rolls back.
- Cleared the retained soft-requirement lifecycle alongside the deferred
hard choice: clearDeferredToolDirectives() owns both, is called from
clearAllQueues/reset and session-scoped tool-state cleanup, with a
regression covering reminder re-injection after a queue clear.
- Allowed void-returning pre-model gates via the named AgentBeforeModelCall
type and normalized gate results in the loop and Agent dispatcher.
- Documented that the first gate installed mid-run applies from the next
run; corrected the onToolChoiceRejected contract docs; documented the
cross-run lifetime of ToolChoiceQueue's in-flight claim.
- Removed the unused addBeforeModelContextBuild hook.
- Relocated both packages' changelog entries out of the released 17.1.4
sections into Unreleased with PR attribution, folding the never-shipped
Fixed bullet into Added and noting the input-event timing change.
Moved first-wins MCP tool-name deduplication and origin-aware warnings into one shared helper used by startup extension registration, SDK custom-tool assembly, and deferred refreshes.
Added an SDK startup regression proving colliding MCP proxy tools keep the first origin instead of silently overwriting it.
Fixes#6786
Deduplicated semantically identical MCP endpoints across provider-specific names while preserving provider priority and canonical direct names.
Kept the first registration on sanitized tool-name collisions and logged both origins.
Fixes#6786
A context rebuild that recreated the failed turn's message object made the
identity-keyed active-context removal miss, so the scheduled retry
continuation rejected the terminal assistant error message locally
("Cannot continue from message role: assistant") before any provider
request. auto_retry_end never fired, retryPromise stayed pending, and the
in-flight prompt() plus the TUI retry indicator hung until a manual
follow-up.
The retry path now strips a still-failed assistant tail positionally after
the backoff (generation-guarded, never in preserveFailedTurn mode), and a
continuation that still fails locally closes the retry saga with a failed
auto_retry_end via the new scheduleAgentContinue onError hook.
Fixes#5382
A pre-model gate can defer a claimed hard tool choice for the next call. Branch transitions cleared the coding-agent queue but left that agent-owned value alive, allowing an obsolete forced tool to cross into the replacement transcript.
Expose the narrow deferred-choice reset at the Agent owner and invoke it from the shared session-scoped tool-state cleanup used by both branch paths. Failed session switches retain their existing rollback behavior.
Signed-off-by: Christian Stewart <christian@aperture.us>
The agent loop had no place to refuse a provider request. A host that needs to
act on the assembled context before it is billed, checking that the prompt still
fits the window, that a budget boundary has not been crossed, or that the
session should hand off instead of spending, could only observe the request
after the fact, when the tokens were already committed.
Add `AgentLoopConfig.beforeModelCall`, asked once per turn beside the deadline
check and before `turn_start` is emitted. A `stop` result ends the stream with
no turn open, so nothing has to synthesise a cancellation event and no consumer
is left holding a half-open turn. Placing it there also keeps `turn_end`'s
contract intact: that event carries the assistant message for a completed turn,
and a gated stop has no assistant message to report.
`syncContextBeforeModelCall` keeps its existing void contract and its job of
refreshing prompt and tool state, so implementations typed as returning void are
unaffected.
`Agent.setBeforeModelCall` installs the host's callback, and `addBeforeModelCall`
registers an additional callback without displacing the host's, returning a
disposer so an extension can attach and detach independently. A supplied
`reason` is logged where the loop stops.
Signed-off-by: Christian Stewart <christian@aperture.us>
Retained complete native web-search call/result pairs through leaked-thinking projection at their original source anchors.
Kept opaque server-tool blocks atomic during persistence, stripped them on reparent, and covered custom continuations plus compaction retention.
Fixes#6703
Scoped the Cursor server-execution marker gate to the stream-stall path so an unmarked client-side Cursor tool call (todo/MCP) followed by a reasonless abort recovers via its synthetic executed:false result instead of settling the turn.
Fixes#6668
Continued from synthetic unexecuted tool results when a reasonless request abort arrives after a complete streamed tool call. Preserved deliberate user, lifecycle, and streaming-edit guard abort behavior.
Fixes#6668
The prewalk arm/switch guard compared model identity only (modelsAreEqual /
provider+id), discarding the resolved thinkingLevel. A legal same-model target
at a cheaper effort (e.g. prewalk: "@task" resolving to the active model at a
lower level) was dropped as a no-op, so the session ran the expensive effort for
the whole run while still paying the plan/continue nudges — silently on the
session path, logger.debug only on the subagent path.
Compare (provider, id, effective thinking level) via a shared prewalkWouldBeNoop
helper. Effort-only deltas on the same model now switch; a genuine no-op emits a
user-visible notice on the session path and never arms on the subagent path.
Fixes#6659
- Removed hash-delimited placeholder parsing and stored-session aliases.
- Simplified replay and display restoration to the double-dollar format.
- Replaced legacy compatibility tests with an inert-token regression.
Advisor provider-identity refresh was only invoked from resetSessionState(), so transitions that update the primary identity without re-priming the advisor (branch with skipConversationRestore, fork) left the advisor emitting the previous session id/metadata/telemetry. Move the refresh into #syncAgentSessionId() via a new SessionAdvisors.refreshProviderIdentity() so it fires on every provider-session change regardless of conversation restore. Add a fork regression test.
The advisor overflow-compaction one-shot calls compact() directly, bypassing the advisor Agent and its metadata resolver, so it omitted metadata.user_id. Resolve the advisor's session metadata per candidate provider (after credential selection) and pass it into the compact() options alongside sessionId/promptCacheKey. Add a regression test asserting the direct compaction request carries the advisor session id.
Rebind the live advisor Agent's provider session id, prompt cache key, credential resolver, metadata resolver, and telemetry identity whenever AgentSession crosses a conversation boundary. Add a regression test proving /new preserves the advisor Agent while assigning and emitting a distinct new provider session UUID.
The advisor Agent is constructed separately in session-advisors.ts with
its own advisorProviderSessionId, but unlike AgentSession it never had a
metadata resolver installed. Its outbound requests therefore omitted the
metadata.user_id session identity that main and subagent requests carry,
so custom Anthropic-compatible proxies saw advisor traffic with no stable
session id to route or attribute on.
Extract buildSessionMetadata into session/session-metadata.ts and install
it as the advisor agent's metadata resolver, scoped to the advisor's own
provider session id and resolved live so token refreshes surface the
current account_uuid.
Fixes#6625
Passed session-scoped settings through Edit and Write generated-file checks and fell back to schema defaults when no global singleton exists.
Guarded inline image sizing against an uninitialized global settings proxy and added isolated-session regression coverage.
Fixes#6549
- Add usage history, reporting, and client summary endpoints to the auth broker.
- Implement SQLite persistence, batching, and periodic flushing for observed client usage.
- Integrate usage reporting into the coding agent session for completed assistant messages.
- Update stats aggregator and dashboard to retrieve usage history snapshots from the broker.
Persisted native server-tool calls and web-search results in assistant content, replayed them only to the issuing Anthropic provider, and retained Umans gateway filtering.
Fixes#6495
Raced session_stop handlers against the active settle signal and discarded cancellation without timeout errors.
Added runner and AgentSession regressions for pre-dispatch and in-flight aborts, timeout preservation, and stale continuation suppression.
Fixes#6489
- Added `LIVE_DELEGATION_MESSAGE_TYPE` constant and delegation message handling for voice sessions.
- Implemented turn-based transcript coalescing with user and assistant turn counters.
- Added transcript display row with normalized rendering in the live visualizer.
- Refactored controller to send delegation messages via `sendCustomMessage` with configurable frame styling.
- Removed microphone permission error reporting from silence detection logic.
- Switched from `miniaudio` to `maudio` Rust crate and added `AudioCapture` and `AudioPlayback` native classes.
- Removed browser-side audio infrastructure including Web Audio API, audio worklet processor, and WebRTC runtime.
- Migrated STT recorder and transcriber modules to use native `AudioCapture` with callback-based streaming.
- Replaced streaming audio player with native `AudioPlayback` that writes PCM directly without TypeScript intermediaries.
- Removed ffmpeg, wav, and platform-specific playback commands from the audio toolchain.