`/goal <objective>`, `/plan <prompt>` and `/vibe <prompt>` promote the
composer draft into the first turn, but built their submission from the
draft *text* only:
this.onInputCallback(this.startPendingSubmission({ text: objective }));
The editor-submit path in `InputController` passes
`editor.pendingImages`/`pendingImageLinks` alongside the text; these four
call sites did not. A draft holding pasted screenshots therefore reached
the model with its positional `[Image #N, WxH]` markers intact and every
image payload missing, so the agent saw markers pointing at nothing and
`read "Image #1"` resolved against an empty list.
The payload was not only dropped, it also outlived the draft: the mode
commands cleared the composer with `editor.setText("")`, which leaves
`pendingImages` attached. The orphans then rode along with whatever the
user typed next, one index off, which is how a later message can attach a
screenshot the user never re-pasted.
Measured on 259 image-bearing user messages across 10 local session logs
(v17.2.x): 36 of 37 messages submitted as a goal objective lost every
image, against 190 of 198 preserved on the ordinary submit path.
Fix:
- `#takeDraftImages()` detaches the composer's pending images and links,
and all four mode-command submissions spread it into
`startPendingSubmission` (`cancelPendingSubmission` already restores
them when a submission is cancelled).
- `/goal`, `/guided-goal`, `/plan` and `/vibe` clear the draft with
`editor.clearDraft()` instead of `editor.setText("")`, so images can
never outlive the text they were pasted into (the streaming branch of
`/goal` never submits, so its draft must die whole).
Tests: two regression cases in `goal-mode-integration.test.ts` assert the
objective submission carries the image and empties the composer; both
fail on the previous behaviour with `images: undefined`. The `/plan`,
`/goal` and `/guided-goal` slash stubs now model `clearDraft`.
- Reworked the `/guided-goal` command to send a hidden interview brief instead of a modal popup flow.
- Removed the deprecated `guided-setup.ts` module and system prompt template.
- Updated goal tool availability and activation logic to support goal creation during the interview.
- Replaced existing tests and added new verification for the updated guided-goal workflow.
- Added the `xd://` virtual device protocol (`internal-urls/xd-protocol.ts`, `tools/xdev.ts`): tools declaring `loadMode: "discoverable"` are unmounted from the request tools array and driven via `read xd://` (list/docs+schema) and `write xd://<tool>` (execute), gated by the `tools.xdev` setting (default on) and inlined into the system prompt.
- Merged the `irc`, `job`, and `launch` tools into a single `hub` tool (`tools/hub/`, `async/job-manager.ts`): messaging keeps `send`/`inbox`/`list`, job control maps to `wait`/`cancel`/`jobs`, process supervision keeps `start`/`logs`/`stop`/`restart`/`describe` with `ps`, and the unified `wait` races background jobs against peer messages; SDK `IrcTool`/`JobTool`/`LaunchTool` are replaced by `HubTool`.
- Removed the hidden `resolve` tool in favor of the `xd://resolve`/`xd://reject`/`xd://propose` resolution devices, auto-including `write` whenever a deferrable tool or plan mode is present.
- Removed the BM25 tool-discovery system: the `search_tool_bm25` tool, the `tool-discovery` module, the `tools.discoveryMode`/`mcp.discoveryMode`/`mcp.discoveryDefaultServers`/`tools.essentialOverride` settings, per-tool MCP selection, and the `mcp_tool_selection` message type.
- Unified tool presentation on `ToolLoadMode` (`essential`|`discoverable`), replacing the custom-tool `xdev?: boolean` opt-out; custom, extension, MCP, RPC host, image-generation, and TTS tools now default to `discoverable`, and added a `satisfies` predicate to `SoftToolRequirement`.
- Removed the standalone `ssh` command tool and `ssh/ssh-executor` (the `ssh://` read/write/search protocol stays), and made `--tools` address hidden built-ins.
- Updated collab-web to render `xd://` dispatches and `hub` op families, dropped the `search_tool_bm25`/`ssh`/`report-finding` renderers, refreshed tool docs and prompts, and migrated the affected tests and changelogs.
- Minted the guided-goal Codex side session id once per interview in handleGuidedGoalCommand and threaded it through every turn via GuidedGoalTurnOptions.sideSessionId, so a multi-question interview shares a single websocket-only Codex socket instead of opening a fresh one each turn (which could trip websocket_connection_limit_reached and fall back to the rejected SSE path).
- Exported newGuidedGoalSessionId; runGuidedGoalTurn mints its own id only when no side session id is supplied (one-shot callers, tests).
- Added regression coverage asserting the supplied side session id is reused across turns.
Fixes#5304
- Passed the session provider transport (providerSessionState + preferWebsockets) and an isolated session id to the /guided-goal interview completion so websocket-only Codex models (gpt-5.6-luna/sol/terra) get a websocket session instead of an SSE fallback the Codex /responses endpoint rejects with "Model not found".
- Added regression coverage asserting the guided-goal request inherits the session transport with an isolated session id.
Fixes#5304
Normalized extension custom-message payloads before session state or persistence, including bare string sendMessage shorthands. Skipped legacy bare custom_message entries during context rebuilds and dropped malformed custom/hook messages before LLM conversion. Added regression coverage for the poisoned-session resume crash.\n\nFixes #4345
- Moved authentication logic to `perplexity-auth.ts` to share logic between search providers and CLI commands.
- Updated authentication priority to prefer browser cookies over OAuth tokens during search operations.
- Modified the `token` CLI command to display active OAuth tokens when both an OAuth token and an API key are configured.
- Added comprehensive unit tests in `perplexity.test.ts` to verify authentication priority and precedence.
- Replaced usage of `ReturnType<typeof setTimeout>` and `ReturnType<typeof setInterval>` with the explicit `Timer` type across the codebase.
- Updated several type definitions and function signatures to use concrete types instead of inferred return types for improved clarity and maintainability.
- Migrated all wire protocol, schema definitions, and tools validation from Zod to ArkType across multiple packages.
- Updated extension runtimes, custom tools loader, and TypeBox compatibility shim to expose and use ArkType instances.
- Added a comprehensive ArkType migration guide, validation parity tests, and helper utilities.
- Removed redundant PDF asset routing and parsing implementations from the read tool.
- Introduced advisory note output as `<advisory>` tags with optional severity and guidance.
- Updated session transcript formatting to `### Session update` and inline watched role labels.
- Added shared `escapeXmlText` utility and escaped XML-sensitive text in advisor outputs.
- Added one-shot success run token metrics and one-shot statistics reporting.
- Replaced Bun.sleep and wall-clock timing with fake timers (vi.useFakeTimers), release gates, and deterministic polling across 15+ test files to eliminate flakiness and improve speed.
- Consolidated per-test fixture setup into beforeAll/afterAll lifecycle hooks across 20+ test files, reducing redundant initialization and improving test performance by reusing shared immutable fixtures.
- Stubbed network calls in ModelRegistry and test discovery to prevent unintended outbound requests during test execution.
- Replaced subprocess-based test coordination (file markers, Bun.sleep polling) with in-memory fakes (FakeWebSocket, FakeLspServer, VirtualClock) for deterministic, fast test execution.
Reviewer flagged a race left open by the streaming guard added in #2455:
getUserInput() arms onInputCallback and schedules an 800 ms goal
continuation timer; when /goal set takes the streaming branch (or any
extension/hook starts a turn inside that window), the timer fired
unchecked. The downstream onInputCallback resolved the main waiter with
a goal-continuation submission, submitInteractiveInput called
session.promptCustomMessage without a streamingBehavior, and the same
AgentBusyError the PR set out to fix resurfaced.
Make the continuation timer streaming-aware: at fire time, bail out
when session.isStreaming || isCompacting || hasPostPromptWork is true.
Reuses the auto-submit busy check loop mode already relies on (renamed
#isLoopAutoSubmitBlocked -> #isAutoSubmitBlocked since both flows have
the same notion of 'agent is busy, do not submit'). The next agent_end
in #handleGoalSessionEvent reschedules normally.
Fixes#2454
runGuidedGoalTurn sent the rendered interview transcript to the plan/slow
provider as raw text, so a secret typed into the rough goal or an answer
bypassed the session's redaction contract. Route the transcript through the
session obfuscator before the request and deobfuscate the echoed question /
objective before it is displayed or the goal starts (no-op when no secrets
are configured).
Stopped goal objective commands from resolving the interactive input waiter while the agent is already streaming. The goal context still uses steer immediately, and the next idle goal continuation submits the objective work without AgentBusyError spam.
Added goal-mode integration coverage for both initial and replacement objective commands during streaming.
Fixes#2454
- Shared immutable model registries and auth storage via beforeAll/afterAll.
- Swapped fixed-delay settle sleeps for predicate polling and signals.
- Stubbed network/timers to drop wall-clock waits in registry and history tests.
- Added resetDisplay invalidation tests and startup-timing breakdown lines.
Allowed /goal set to replace the current active goal instead of rejecting and discarding the command input.
Added goal runtime and interactive-mode regression coverage for active replacements.
Fixes#1293
- get op now returns paused goals (was returning null when enabled=false)
- complete op now works on paused goals; previously required enabled=true
which always failed after an interrupt set enabled=false
- create op now allowed after previous goal status is 'complete'; was
incorrectly blocked by the same guard as 'dropped' check
- goal tool is re-added to the active tool set on session reload when a
paused/active goal is persisted to disk; sdk.ts:1599 excludes 'goal'
from initial active tools unconditionally, so restoreModeFromSession
now re-adds it and saves #goalModePreviousTools for later cleanup
- goal_updated event for 'dropped' status now triggers #exitGoalMode
before clearing goalModeEnabled, ensuring the previous tool set is
restored when the agent drops a goal via the tool
- added 'resume' and 'drop' ops to goal tool schema and execute path
- updated goal.md prompt to document new ops and the paused-goal workflow
Fixes#1249
- Added an internal accounting-state guard and used it to skip goal usage flushing when accounting was inactive.
- Updated goal abort handling to return early unless accounting or pause logic was required, then paused only a cloned active goal state before committing.
- Aligned related tests/types by tightening OpenAI helper typing and using Tool typings for the goal tool registry.
- Added GoalRuntime with wall-clock and token accounting, budget steering, and lifecycle operations (create, pause, resume, drop, complete).
- Exposed goal tool as a hidden agent tool, activated only when goal mode is enabled.
- Integrated goal continuation loop in InteractiveMode with auto-submit between turns.
- Added status line segment and theme icons for goal mode state.