@oh-my-pi/pi-coding-agent 17.1.6 → 17.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/{CHANGELOG-x9zt79k8.md → CHANGELOG-5k4dq4g6.md} +23 -0
  3. package/dist/cli.js +3149 -3092
  4. package/dist/types/config/settings-schema.d.ts +16 -1
  5. package/dist/types/cursor.d.ts +2 -1
  6. package/dist/types/extensibility/extensions/runner.d.ts +4 -0
  7. package/dist/types/extensibility/shared-events.d.ts +18 -1
  8. package/dist/types/modes/components/custom-editor.d.ts +5 -0
  9. package/dist/types/session/agent-session-types.d.ts +5 -5
  10. package/dist/types/session/agent-session.d.ts +26 -1
  11. package/dist/types/session/model-controls.d.ts +1 -1
  12. package/dist/types/session/session-tools.d.ts +44 -6
  13. package/dist/types/session/streaming-output.d.ts +7 -2
  14. package/dist/types/session/turn-recovery.d.ts +1 -1
  15. package/dist/types/tools/index.d.ts +8 -3
  16. package/dist/types/tools/output-meta.d.ts +5 -0
  17. package/dist/types/tools/read.d.ts +9 -1
  18. package/dist/types/tools/xdev.d.ts +53 -67
  19. package/dist/types/utils/cpuprofile.d.ts +51 -0
  20. package/dist/types/utils/inspect-image-mode.d.ts +29 -0
  21. package/dist/types/utils/profile-tree.d.ts +47 -0
  22. package/dist/types/utils/sample-profile.d.ts +67 -0
  23. package/package.json +12 -12
  24. package/src/config/settings-schema.ts +15 -1
  25. package/src/config/settings.ts +35 -0
  26. package/src/cursor.ts +4 -3
  27. package/src/discovery/builtin-rules/index.ts +2 -0
  28. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  29. package/src/extensibility/extensions/runner.ts +26 -0
  30. package/src/extensibility/extensions/wrapper.ts +74 -42
  31. package/src/extensibility/hooks/tool-wrapper.ts +11 -4
  32. package/src/extensibility/shared-events.ts +18 -1
  33. package/src/modes/components/custom-editor.ts +39 -16
  34. package/src/modes/components/tips.txt +2 -1
  35. package/src/modes/components/tool-execution.ts +8 -7
  36. package/src/modes/controllers/extension-ui-controller.ts +4 -4
  37. package/src/modes/controllers/selector-controller.ts +7 -2
  38. package/src/sdk.ts +47 -50
  39. package/src/session/agent-session-types.ts +5 -5
  40. package/src/session/agent-session.ts +123 -7
  41. package/src/session/model-controls.ts +5 -5
  42. package/src/session/session-listing.ts +66 -4
  43. package/src/session/session-tools.ts +158 -52
  44. package/src/session/streaming-output.ts +18 -6
  45. package/src/session/turn-recovery.ts +4 -4
  46. package/src/slash-commands/builtin-registry.ts +69 -0
  47. package/src/tools/bash.ts +16 -9
  48. package/src/tools/index.ts +36 -16
  49. package/src/tools/output-meta.ts +20 -0
  50. package/src/tools/read.ts +87 -13
  51. package/src/tools/write.ts +16 -7
  52. package/src/tools/xdev.ts +198 -210
  53. package/src/utils/cpuprofile.ts +235 -0
  54. package/src/utils/inspect-image-mode.ts +39 -0
  55. package/src/utils/profile-tree.ts +111 -0
  56. package/src/utils/sample-profile.ts +437 -0
@@ -3941,11 +3941,26 @@ export declare const SETTINGS_SCHEMA: {
3941
3941
  readonly "inspect_image.enabled": {
3942
3942
  readonly type: "boolean";
3943
3943
  readonly default: false;
3944
+ };
3945
+ readonly "inspect_image.mode": {
3946
+ readonly type: "enum";
3947
+ readonly values: readonly ["auto", "on", "off"];
3948
+ readonly default: "auto";
3944
3949
  readonly ui: {
3945
3950
  readonly tab: "tools";
3946
3951
  readonly group: "Available Tools";
3947
3952
  readonly label: "Inspect Image";
3948
- readonly description: "Enable the inspect_image tool, delegating image understanding to a vision-capable model";
3953
+ readonly description: "Controls the inspect_image tool, which delegates image understanding to a vision-capable model. 'auto' exposes it only when the active model lacks native image input; 'on' always exposes it; 'off' never does.";
3954
+ readonly options: readonly [{
3955
+ readonly value: "auto";
3956
+ readonly label: "Auto (only for models without vision)";
3957
+ }, {
3958
+ readonly value: "on";
3959
+ readonly label: "On";
3960
+ }, {
3961
+ readonly value: "off";
3962
+ readonly label: "Off";
3963
+ }];
3949
3964
  };
3950
3965
  };
3951
3966
  readonly "computer.enabled": {
@@ -5,7 +5,8 @@ interface CursorExecBridgeOptions {
5
5
  cwd: string;
6
6
  getCwd?: () => string;
7
7
  tools: Map<string, AgentTool>;
8
- getTool?: (name: string) => AgentTool | undefined;
8
+ /** Resolves execution overrides (mounted-device permission wrappers) before the canonical map. */
9
+ getExecutableTool?: (name: string) => AgentTool | undefined;
9
10
  getToolContext?: () => AgentToolContext | undefined;
10
11
  emitEvent?: (event: AgentEvent) => void;
11
12
  /**
@@ -69,6 +69,10 @@ export declare class ExtensionRunner {
69
69
  private readonly modelRegistry;
70
70
  private readonly settings?;
71
71
  private readonly localProtocolOptions?;
72
+ /** Records that the loop already emitted `tool_call` for this dispatch. */
73
+ markToolCallEmitted(toolCallId: string, toolName: string): void;
74
+ /** Consumes a {@link markToolCallEmitted} marker; true when the loop already emitted. */
75
+ consumeToolCallEmitted(toolCallId: string, toolName: string): boolean;
72
76
  constructor(extensions: Extension[], runtime: ExtensionRuntime, cwd: string, sessionManager: SessionManager, modelRegistry: ModelRegistry, getMemory?: () => MemoryRuntimeContext | undefined, settings?: Settings | undefined, localProtocolOptions?: LocalProtocolOptions | undefined);
73
77
  initialize(actions: ExtensionActions, contextActions: ExtensionContextActions, commandContextActions?: ExtensionCommandContextActions, uiContext?: ExtensionUIContext): void;
74
78
  /**
@@ -228,13 +228,30 @@ export interface TodoReminderEvent {
228
228
  }
229
229
  /**
230
230
  * Return type for `tool_call` handlers.
231
- * Allows handlers to block tool execution.
231
+ * Allows handlers to block tool execution or revise the input the tool runs with.
232
232
  */
233
233
  export interface ToolCallEventResult {
234
234
  /** If true, block the tool from executing */
235
235
  block?: boolean;
236
236
  /** Reason for blocking (returned to LLM as error) */
237
237
  reason?: string;
238
+ /**
239
+ * Replacement input the tool executes with, instead of the original arguments. Ignored when
240
+ * `block` is true. This is the raw execution input passed to the tool's `execute` (the handler
241
+ * owns its correctness) — not the normalized `event.input` view, which may carry derived
242
+ * gate-only fields (e.g. hashline `edit` `path`/`paths`) that are not real parameters. When
243
+ * multiple handlers set `input`, the last one wins; handlers do not observe each other's
244
+ * revisions (each sees the original `event.input`). Not applied to `computer` tool calls.
245
+ *
246
+ * For model-issued tool calls the event fires at arg-prep time in the agent loop, before
247
+ * concurrency scheduling, `tool_execution_start`, and the approval gate: the revision is
248
+ * revalidated against the tool schema and becomes what the loop schedules, displays, persists,
249
+ * and executes — the user always approves what actually runs. For dispatches the loop never
250
+ * sees (nested `write xd://` device calls, Cursor direct execution) the tool wrapper applies
251
+ * the revision before its own approval gate; a revised nested xd:// input forfeits the outer
252
+ * write gate's approval and faces the full prompt again.
253
+ */
254
+ input?: Record<string, unknown>;
238
255
  }
239
256
  /**
240
257
  * Return type for `tool_result` handlers.
@@ -103,6 +103,11 @@ export declare class CustomEditor extends Editor {
103
103
  * reset the editor text and all pending draft-image state. The shared tail of
104
104
  * every "message submitted" path; pass no argument for a plain discard. */
105
105
  clearDraft(historyText?: string): void;
106
+ /** Replace the composer draft with a restored historical prompt: sets the text and
107
+ * re-attaches the message's images so positional `[Image #N]` markers resolve on
108
+ * resubmit instead of degrading to literal text (esc-esc branch, `/tree`). Source
109
+ * links are unknown for restored drafts, so every link slot is `undefined`. */
110
+ setDraft(text: string, images?: readonly ImageContent[]): void;
106
111
  /** Treat image/paste markers as indivisible: a stray backspace deletes the whole token
107
112
  * instead of corrupting `[Paste #1, +30 lines]` into plain text. */
108
113
  atomicTokenPattern: RegExp;
@@ -15,7 +15,7 @@ import type { Skill, SkillWarning } from "../extensibility/skills.js";
15
15
  import type { FileSlashCommand } from "../extensibility/slash-commands.js";
16
16
  import type { SecretObfuscator } from "../secrets/obfuscator.js";
17
17
  import type { ConfiguredThinkingLevel } from "../thinking.js";
18
- import type { XdevRegistry } from "../tools/xdev.js";
18
+ import type { XdevState } from "../tools/xdev.js";
19
19
  import type { SessionManager } from "./session-manager.js";
20
20
  /** Maximum time the interactive shutdown path waits for Mnemopi consolidation. */
21
21
  export declare const SHUTDOWN_CONSOLIDATE_BUDGET_MS = 1500;
@@ -121,6 +121,8 @@ export interface AgentSessionConfig {
121
121
  createMemoryTools?: () => Promise<AgentTool[]>;
122
122
  /** Creates the built-in `computer` tool for session-scoped runtime enablement (see {@link AgentSession.setComputerToolEnabled}). */
123
123
  createComputerTool?: () => Promise<AgentTool | null>;
124
+ /** Creates the built-in `inspect_image` tool for session-scoped runtime enablement (see {@link AgentSession.setInspectImageMode}). */
125
+ createInspectImageTool?: () => Promise<AgentTool | null>;
124
126
  /** Model registry for API key resolution and model discovery. */
125
127
  modelRegistry: ModelRegistry;
126
128
  /** Tool registry for LSP and settings. */
@@ -164,10 +166,8 @@ export interface AgentSessionConfig {
164
166
  name: string;
165
167
  summary: string;
166
168
  }>;
167
- /** Session-owned `xd://` registry. */
168
- xdevRegistry?: XdevRegistry;
169
- /** Discoverable tools mounted under `xd://` in the initial enabled set. */
170
- initialMountedXdevToolNames?: string[];
169
+ /** `xd://` presentation state backed by the canonical tool map. */
170
+ xdev?: XdevState;
171
171
  /** Names pinned top-level during runtime repartitioning. */
172
172
  presentationPinnedToolNames?: ReadonlySet<string>;
173
173
  /** Accessor for live MCP server instructions. */
@@ -46,6 +46,7 @@ import { type AskToolDetails, type AskToolInput } from "../tools/ask.js";
46
46
  import type { CheckpointState, CompletedRewindState } from "../tools/checkpoint.js";
47
47
  import { type PlanProposalHandler } from "../tools/resolve.js";
48
48
  import type { TodoPhase } from "../tools/todo.js";
49
+ import type { InspectImageMode } from "../utils/inspect-image-mode.js";
49
50
  import type { VibeModeState } from "../vibe/state.js";
50
51
  import type { AgentSessionEventListener } from "./agent-session-events.js";
51
52
  import type { AgentSessionConfig, AgentSessionDisposeOptions, AsyncJobSnapshot, CommandMetadataChangedListener, ContextUsageBreakdown, FollowUpOptions, FreshSessionResult, HandoffResult, ModelCycleResult, Prewalk, PromptOptions, ResolvedRoleModel, RestoredQueuedMessage, RoleModelCycle, RoleModelCycleResult, SessionHandoffOptions, SessionOAuthAccountList, SessionStats, UsageFallbackConfirmation } from "./agent-session-types.js";
@@ -283,6 +284,26 @@ export declare class AgentSession {
283
284
  * tool (e.g. restricted child sessions have no factory).
284
285
  */
285
286
  setComputerToolEnabled(enabled: boolean): Promise<boolean>;
287
+ /**
288
+ * Session-scoped inspect_image mode (`/vision`). `auto` clears the override
289
+ * and returns to the persisted `inspect_image.mode` setting; `on`/`off`
290
+ * force the tool for this session only. See {@link SessionTools.setInspectImageMode}.
291
+ */
292
+ setInspectImageMode(mode: InspectImageMode): Promise<boolean>;
293
+ /** Effective inspect_image state for `/vision status`. */
294
+ inspectImageState(): {
295
+ mode: InspectImageMode;
296
+ active: boolean;
297
+ model: string | undefined;
298
+ };
299
+ /** Session-scoped `/vision` override; undefined means "follow the persisted setting". */
300
+ getInspectImageModeOverride(): InspectImageMode | undefined;
301
+ /**
302
+ * Reconciles the inspect_image tool set after the persisted
303
+ * `inspect_image.mode` setting changed (e.g. via the settings selector), so
304
+ * the new value takes effect immediately instead of on the next model switch.
305
+ */
306
+ applyInspectImageModeChange(): Promise<boolean>;
286
307
  /** Cancels the local rollout-memory startup owned by this session. */
287
308
  cancelLocalMemoryStartup(): void;
288
309
  /** Starts a new local rollout-memory generation and cancels its predecessor. */
@@ -766,10 +787,12 @@ export declare class AgentSession {
766
787
  * @param entryId ID of the entry to branch from
767
788
  * @returns Object with:
768
789
  * - selectedText: The text of the selected user message (for editor pre-fill)
790
+ * - selectedImages: Image attachments of the selected user message (for editor draft restore)
769
791
  * - cancelled: True if a hook cancelled the branch
770
792
  */
771
793
  branch(entryId: string): Promise<{
772
794
  selectedText: string;
795
+ selectedImages: ImageContent[];
773
796
  cancelled: boolean;
774
797
  }>;
775
798
  branchFromBtw(question: string, assistantMessage: AssistantMessage): Promise<{
@@ -783,7 +806,7 @@ export declare class AgentSession {
783
806
  * @param targetId The entry ID to navigate to
784
807
  * @param options.summarize Whether user wants to summarize abandoned branch
785
808
  * @param options.customInstructions Custom instructions for summarizer
786
- * @returns Result with editorText (if user message) and cancelled status
809
+ * @returns Result with editorText/editorImages (if user message) and cancelled status
787
810
  */
788
811
  navigateTree(targetId: string, options?: {
789
812
  summarize?: boolean;
@@ -810,6 +833,8 @@ export declare class AgentSession {
810
833
  reanswerAskResult?: AgentToolResult<AskToolDetails>;
811
834
  }): Promise<{
812
835
  editorText?: string;
836
+ /** Image attachments of the target user message, parallel to the positional `[Image #N]` markers in {@link editorText}. */
837
+ editorImages?: ImageContent[];
813
838
  cancelled: boolean;
814
839
  aborted?: boolean;
815
840
  summaryEntry?: BranchSummaryEntry;
@@ -21,7 +21,7 @@ export interface ModelControlsHost {
21
21
  promptGeneration(): number;
22
22
  resolveActiveEditMode(): EditMode;
23
23
  syncAfterModelChange(previousEditMode: EditMode): Promise<void>;
24
- setModelWithProviderSessionReset(model: Model): void;
24
+ setModelWithProviderSessionReset(model: Model): Promise<void>;
25
25
  clearActiveRetryFallback(): void;
26
26
  clearInheritedProviderPromptCacheKey(): void;
27
27
  magicKeywordEnabled(keyword: "orchestrate" | "ultrathink" | "workflow"): boolean;
@@ -7,8 +7,9 @@ import type { ExtensionRunner } from "../extensibility/extensions/index.js";
7
7
  import { type Skill, type SkillWarning } from "../extensibility/skills.js";
8
8
  import { type LocalProtocolOptions } from "../internal-urls/index.js";
9
9
  import type { MemoryBackendStartOptions } from "../memory-backend/types.js";
10
- import { type XdevRegistry } from "../tools/xdev.js";
10
+ import { type XdevState } from "../tools/xdev.js";
11
11
  import { type EditMode } from "../utils/edit-mode.js";
12
+ import { type InspectImageMode } from "../utils/inspect-image-mode.js";
12
13
  import type { ClientBridge } from "./client-bridge.js";
13
14
  import type { CustomMessage } from "./messages.js";
14
15
  import type { SessionManager } from "./session-manager.js";
@@ -33,12 +34,17 @@ export interface SessionToolsHost {
33
34
  emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void;
34
35
  notifyCommandMetadataChanged(): void;
35
36
  localProtocolOptions(): LocalProtocolOptions;
37
+ /** Session-scoped `/vision` override; undefined means "follow the persisted setting". */
38
+ getInspectImageModeOverride(): InspectImageMode | undefined;
39
+ setInspectImageModeOverride(mode: InspectImageMode | undefined): void;
36
40
  }
37
41
  interface SessionToolsOptions {
38
42
  autoApprove?: boolean;
39
43
  toolRegistry?: Map<string, AgentTool>;
40
44
  createVibeTools?: () => AgentTool[];
41
45
  createComputerTool?: () => Promise<AgentTool | null>;
46
+ /** Creates the built-in `inspect_image` tool for session-scoped runtime enablement (see {@link SessionTools.setInspectImageMode}). */
47
+ createInspectImageTool?: () => Promise<AgentTool | null>;
42
48
  builtInToolNames?: Iterable<string>;
43
49
  presentationPinnedToolNames?: ReadonlySet<string>;
44
50
  ensureWriteRegistered?: () => Promise<boolean>;
@@ -47,8 +53,7 @@ interface SessionToolsOptions {
47
53
  }>;
48
54
  getLocalCalendarDate?: () => string;
49
55
  getMcpServerInstructions?: () => Map<string, string> | undefined;
50
- xdevRegistry?: XdevRegistry;
51
- initialMountedXdevToolNames?: string[];
56
+ xdev?: XdevState;
52
57
  setActiveToolNames?: (names: Iterable<string>) => void;
53
58
  baseSystemPrompt: string[];
54
59
  skills?: Skill[];
@@ -99,13 +104,13 @@ export declare class SessionTools {
99
104
  get skillsSettings(): SkillsSettings | undefined;
100
105
  /** Drops cached per-session ACP `allow_always`/`reject_always` decisions. */
101
106
  clearAcpPermissionDecisions(): void;
102
- /** Re-wraps active and mounted tools after the ACP client changes. */
107
+ /** Drops cached ACP decisions and re-wraps active tools after the client changes. */
103
108
  refreshAcpPermissionGates(): void;
104
109
  /** Names of tools currently exposed at the top level. */
105
110
  getActiveToolNames(): string[];
106
111
  /** Enabled top-level and discoverable tool names. */
107
112
  getEnabledToolNames(): string[];
108
- /** Names of dynamic tools mounted under `xd://`. */
113
+ /** Names currently presented as `xd://` devices. */
109
114
  getMountedXdevToolNames(): string[];
110
115
  /** Whether the edit tool is registered. */
111
116
  get hasEditTool(): boolean;
@@ -139,7 +144,7 @@ export declare class SessionTools {
139
144
  * Restore an enabled tool set with its exact top-level versus `xd://` partition.
140
145
  *
141
146
  * Both inputs are required because {@link setActiveToolsByName} only receives the
142
- * enabled name list and classifies mounts from the current `#mountedXdevToolNames`.
147
+ * enabled name list and classifies mounts from the current presentation set.
143
148
  * Rollback/restore callers must pass the snapshotted mounted subset so names that
144
149
  * were top-level stay pinned (`#runtimeSelectedToolNames`) and names that were under
145
150
  * `xd://` remain mount-eligible, even when the live mount set has drifted.
@@ -166,6 +171,39 @@ export declare class SessionTools {
166
171
  * tool (e.g. restricted child sessions have no factory).
167
172
  */
168
173
  setComputerToolEnabled(enabled: boolean): Promise<boolean>;
174
+ /** Current effective inspect_image state for `/vision status`. */
175
+ inspectImageState(): {
176
+ mode: InspectImageMode;
177
+ active: boolean;
178
+ model: string | undefined;
179
+ };
180
+ /**
181
+ * Brings the active tool set in line with the effective inspect_image state
182
+ * (mode setting, `/vision` override, active-model image capability).
183
+ * Mirrors {@link setComputerToolEnabled}: enabling builds the tool through
184
+ * the config factory on first use and reuses the registry entry afterwards.
185
+ * Idempotent — safe to call from every model/settings change path.
186
+ *
187
+ * @returns false when the tool should be active but this session cannot
188
+ * build it (e.g. restricted child sessions have no factory).
189
+ */
190
+ reconcileInspectImageTool(): Promise<boolean>;
191
+ /**
192
+ * Reconciles inspect_image after a model change and surfaces a notice when
193
+ * the visible tool set actually flipped. Called from every model-change
194
+ * path — including retry-fallback switches that bypass
195
+ * {@link syncAfterModelChange}.
196
+ */
197
+ reconcileInspectImageAfterModelChange(): Promise<void>;
198
+ /**
199
+ * Session-scoped `/vision` override. `auto` clears the override so the
200
+ * persisted `inspect_image.mode` setting (itself possibly `auto`) decides;
201
+ * `on`/`off` force the tool for this session only. Takes effect before the
202
+ * next model call.
203
+ *
204
+ * @returns false when `on` was requested but the tool cannot be built here.
205
+ */
206
+ setInspectImageMode(mode: InspectImageMode): Promise<boolean>;
169
207
  /** Rebuilds the stable base prompt for the current tools and model. */
170
208
  refreshBaseSystemPrompt(): Promise<void>;
171
209
  /** Applies one-turn memory prompt injection before an agent run. */
@@ -34,12 +34,17 @@ export interface OutputSummary {
34
34
  export interface OutputSinkOptions {
35
35
  artifactPath?: string;
36
36
  artifactId?: string;
37
- /** Tail buffer budget (bytes). Default DEFAULT_MAX_BYTES. */
37
+ /**
38
+ * Total inline body budget (bytes). Default DEFAULT_MAX_BYTES. The head
39
+ * window and rolling tail window share this budget, so a composed
40
+ * `dump()` body never exceeds it (plus the elision marker).
41
+ */
38
42
  spillThreshold?: number;
39
43
  /**
40
44
  * When > 0, the sink keeps the first `headBytes` of output in addition to
41
45
  * the rolling tail window. Output between the two windows is elided
42
- * (middle elision). Default 0 = tail-only behavior.
46
+ * (middle elision). Clamped to `spillThreshold / 2` so the tail keeps at
47
+ * least half the inline budget. Default 0 = tail-only behavior.
43
48
  */
44
49
  headBytes?: number;
45
50
  /**
@@ -43,7 +43,7 @@ export interface TurnRecoveryHost {
43
43
  waitForSessionMessagePersistence(message: AssistantMessage): Promise<void>;
44
44
  appendSessionMessage(message: AssistantMessage): void;
45
45
  sessionMessageAlreadyPersisted(message: AssistantMessage): boolean;
46
- setModelWithProviderSessionReset(model: Model): void;
46
+ setModelWithProviderSessionReset(model: Model): Promise<void>;
47
47
  resetCurrentResponsesProviderSession(reason: string): void;
48
48
  maybeAutoRedeemCodexReset(): Promise<boolean>;
49
49
  runAutoCompaction(reason: "overflow" | "threshold" | "idle" | "incomplete", willRetry: boolean, deferred?: boolean, allowDefer?: boolean, options?: {
@@ -24,12 +24,13 @@ import type { ToolChoiceQueue } from "../session/tool-choice-queue.js";
24
24
  import type { AgentOutputManager } from "../task/output-manager.js";
25
25
  import { type StructuredSubagentSchemaMode } from "../task/types.js";
26
26
  import type { EventBus } from "../utils/event-bus.js";
27
+ import { type InspectImageMode } from "../utils/inspect-image-mode.js";
27
28
  import type { WorkspaceTree } from "../workspace-tree.js";
28
29
  import { type BuiltinToolName, type HiddenToolName } from "./builtin-names.js";
29
30
  import { type CheckpointState, type CompletedRewindState } from "./checkpoint.js";
30
31
  import type { PlanProposalHandler } from "./resolve.js";
31
32
  import { type TodoPhase } from "./todo.js";
32
- import { XdevRegistry } from "./xdev.js";
33
+ import { type XdevState } from "./xdev.js";
33
34
  export * from "../edit/index.js";
34
35
  export * from "../goals/index.js";
35
36
  export * from "../lsp/index.js";
@@ -204,8 +205,10 @@ export interface ToolSession {
204
205
  isToolActive?: (name: string) => boolean;
205
206
  /** Update the active built-in tool predicate when a session changes tools mid-run. */
206
207
  setActiveToolNames?: (names: Iterable<string>) => void;
207
- /** Tools mounted under `xd://` (set by createTools when `tools.xdev` is active); read/write consult it at execute time. */
208
- xdevRegistry?: XdevRegistry;
208
+ /** Canonical map containing every registered tool exactly once. */
209
+ toolRegistry?: Map<string, Tool>;
210
+ /** `xd://` presentation state backed by {@link toolRegistry}. */
211
+ xdev?: XdevState;
209
212
  /** Agent registry for IRC routing across live sessions. */
210
213
  agentRegistry?: AgentRegistry;
211
214
  /** Idle→parked→revive lifecycle owner; lets the hub kill a non-job-backed agent registration. Default: AgentLifecycleManager.global(). */
@@ -227,6 +230,8 @@ export interface ToolSession {
227
230
  getActiveModelString?: () => string | undefined;
228
231
  /** Get the current session model object (provider/api capabilities), regardless of how it was chosen. */
229
232
  getActiveModel?: () => Model | undefined;
233
+ /** Session-scoped inspect_image mode override set by `/vision`; wins over the persisted setting. */
234
+ getInspectImageModeOverride?: () => InspectImageMode | undefined;
230
235
  /** Get the session's live per-family service tiers (undefined = none). Source of truth for subagent `tier.subagent: inherit`. */
231
236
  getServiceTierByFamily?: () => ServiceTierByFamily | undefined;
232
237
  /** Auth storage for passing to subagents (avoids re-discovery) */
@@ -197,6 +197,11 @@ export declare function stripOutputNotice(text: string, meta: OutputMeta | undef
197
197
  * middle elision with the same per-user configuration.
198
198
  */
199
199
  export declare function resolveOutputSinkHeadBytes(s: Settings | undefined): number;
200
+ /**
201
+ * Resolve the `enforceInlineByteCap` budget for streaming tools (bash/ssh)
202
+ * from session settings: the user's spill threshold plus notice slack.
203
+ */
204
+ export declare function resolveInlineByteCapBudget(s: Settings | undefined): number;
200
205
  /**
201
206
  * Resolve the per-line column cap from session settings. Shared by streaming
202
207
  * executors (bash/python/ssh/eval via OutputSink) and the `read` tool's
@@ -56,12 +56,20 @@ export declare class ReadTool implements AgentTool<typeof readSchema, ReadToolDe
56
56
  readonly approval: (args: unknown) => ToolTier;
57
57
  readonly label = "Read";
58
58
  readonly loadMode = "essential";
59
- readonly description: string;
59
+ description: string;
60
60
  readonly parameters: import("arktype/internal/variants/object.ts").ObjectType<{
61
61
  path: string;
62
62
  }, {}>;
63
63
  readonly strict = true;
64
64
  constructor(session: ToolSession);
65
+ /**
66
+ * Re-evaluate the effective inspect_image state; it can flip when the model
67
+ * or the `/vision` override changes after this tool was constructed. Keeps
68
+ * the behavior branch and the advertised description in lockstep. Called
69
+ * per image read and by tool reconciliation before prompt rebuilds (which
70
+ * passes the post-change availability as `availableOverride`).
71
+ */
72
+ syncInspectImageState(availableOverride?: boolean): boolean;
65
73
  execute(_toolCallId: string, params: ReadParams, signal?: AbortSignal, _onUpdate?: AgentToolUpdateCallback<ReadToolDetails>, _toolContext?: AgentToolContext): Promise<AgentToolResult<ReadToolDetails>>;
66
74
  }
67
75
  interface ReadRenderArgs {
@@ -9,15 +9,20 @@
9
9
  * read xd://<tool> → tool docs + JSON parameter schema
10
10
  * write xd://<tool> → execute: `content` is the JSON args object
11
11
  *
12
+ * Direct and device dispatch share one canonical tool map. The mounted-name
13
+ * set controls presentation only; dispatch accepts the enabled union of
14
+ * top-level active and mounted names. Listing and prompt docs stay
15
+ * mounted-only because top-level tools already ship their schemas.
16
+ *
12
17
  * Args go through the same machinery as native tool calls: validated with
13
18
  * pi-ai's `validateToolArguments` (the schema is returned on mismatch, so a
14
19
  * malformed call self-corrects without a round trip) and streamed through
15
20
  * the write tool's existing incremental `content` decoding for live render
16
21
  * previews. Compared to a dispatcher def this still costs zero *schema
17
22
  * duplication* — one wire schema per tool instead of one per dispatcher
18
- * branch — but full docs + schema for every mounted device are inlined into
19
- * the system prompt (`XdevRegistry.docsAll()`) so no discovery `read` is
20
- * needed before first use; `read xd://<tool>` remains for on-demand re-fetch.
23
+ * branch — but full docs + schema for every mounted device can be inlined
24
+ * into the system prompt, so no discovery read is needed before first use;
25
+ * `read xd://<tool>` remains for on-demand re-fetch.
21
26
  *
22
27
  * Rendering: the write renderer draws NOTHING until the streamed `path` is
23
28
  * known and provably does not target `xd://`; device writes then delegate to
@@ -54,8 +59,7 @@ export type XdevDocsMode = "inline" | "builtins" | "catalog";
54
59
  * while the `xd://` transport is active. Discoverable tools mount unless they
55
60
  * are pinned top-level by {@link XDEV_KEEP_TOP_LEVEL} or carry the transport
56
61
  * itself ({@link XDEV_TRANSPORT_TOOLS}); essential tools never do. The caller
57
- * gates this on the transport being active (a session-owned
58
- * {@link XdevRegistry} existing).
62
+ * gates this on the transport being active.
59
63
  */
60
64
  export declare function isMountableUnderXdev(tool: {
61
65
  name: string;
@@ -72,69 +76,51 @@ export interface XdevDispatch {
72
76
  }
73
77
  /** Wire the wrapped-renderer lookup. Called once by `renderers.ts`. */
74
78
  export declare function setXdevRendererLookup(lookup: (name: string) => ToolRenderer | undefined): void;
75
- /**
76
- * Registry of tools mounted under `xd://` for one session. `createTools`
77
- * mounts discoverable built-ins first; SDK assembly adds custom tools that do
78
- * not opt out. `read`/`write` consult it at execute time.
79
- */
80
- export declare class XdevRegistry {
81
- #private;
82
- constructor(builtins: Iterable<Tool>);
83
- /**
84
- * Replace the dynamic mount set while preserving the built-in devices. Order
85
- * follows `tools`; names absent from it are dropped. A built-in device is
86
- * never shadowed by a same-named dynamic entry.
87
- */
88
- reconcile(tools: Iterable<Tool>): void;
89
- get size(): number;
90
- /** Mounted tools in catalog order: built-ins first, then dynamic mounts. */
91
- list(): readonly Tool[];
92
- get(name: string): Tool | undefined;
93
- /** `{name, summary}` pairs for prompt templates and /tools display. */
94
- entries(): Array<{
95
- name: string;
96
- summary: string;
97
- }>;
98
- /** `read xd://` listing with one device per line. */
99
- listing(): string;
100
- /** Docs + schema for one device; throws with the listing when unknown. */
101
- docs(name: string): string;
102
- /**
103
- * Char budget for the full docs inlined into the system prompt. Large MCP
104
- * catalogs previously shipped every schema top-level; without a cap they
105
- * would bloat every request. Devices past the budget fall back to a
106
- * one-line summary — their docs stay one `read xd://<tool>` away.
107
- */
108
- static readonly DOCS_TOTAL_BUDGET = 48000;
109
- /** A single device's docs above this size never inline: one pathological
110
- * MCP description must not starve every later device. */
111
- static readonly DOCS_PER_DEVICE_CAP = 10000;
112
- /** Description cap for EXTERNAL devices (dynamic mounts: MCP, custom,
113
- * extension, …) in the system-prompt embedding. Built-in devices inline
114
- * their full curated docs; external descriptions are server-controlled
115
- * prose the model can re-fetch, so only the lede earns prompt space. */
116
- static readonly EXTERNAL_DESCRIPTION_CAP = 200;
117
- /**
118
- * Docs + schema for mounted devices, nested under `##` headings for
119
- * system-prompt embedding. Inlines full docs in catalog order (built-ins
120
- * first) until {@link DOCS_TOTAL_BUDGET} is spent; the rest are listed by
121
- * name + summary with a pointer to on-demand `read xd://<tool>` docs.
122
- * Dynamic mounts embed at most {@link EXTERNAL_DESCRIPTION_CAP} description
123
- * chars (schema always intact); `read xd://<tool>` returns the full text.
124
- */
125
- docsAll(mode?: XdevDocsMode, inlinePatterns?: readonly string[]): string;
126
- /** Docs for selected mounted devices under the configured prompt-doc policy. */
127
- docsFor(names: Iterable<string>, mode: XdevDocsMode, inlinePatterns?: readonly string[]): string;
128
- /**
129
- * Execute a device write: `content` is the JSON args object (empty, `?`, or
130
- * `help` returns docs). Args validate against the wrapped tool's schema —
131
- * the schema comes back in the error on mismatch.
132
- */
133
- dispatch(name: string, content: string, toolCallId: string, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, context?: AgentToolContext): Promise<{
134
- result: AgentToolResult<unknown>;
135
- xdev: XdevDispatch;
136
- }>;
79
+ /** Shared tool state consumed by the `xd://` presentation layer. */
80
+ export interface XdevState {
81
+ /** Canonical session tool map; direct and device dispatch read the same instances. */
82
+ readonly tools: Map<string, Tool>;
83
+ /** Ordered names currently presented as mounted devices. */
84
+ readonly mountedNames: Set<string>;
85
+ /** Names originating from built-in factories, used only for prompt presentation. */
86
+ readonly builtInNames: Set<string>;
87
+ /** Whether a name is active at the top level. */
88
+ readonly isActive: (name: string) => boolean;
89
+ /** Optional execution-only decorator, such as the ACP permission gate. */
90
+ decorateExecution?(tool: Tool): Tool;
137
91
  }
92
+ /** Full-doc character budget for system-prompt mounted-device sections. */
93
+ export declare const XDEV_DOCS_TOTAL_BUDGET = 48000;
94
+ /** Per-device cap preventing one pathological description from starving later devices. */
95
+ export declare const XDEV_DOCS_PER_DEVICE_CAP = 10000;
96
+ /** Description cap for external mounted tools; their full docs remain readable on demand. */
97
+ export declare const XDEV_EXTERNAL_DESCRIPTION_CAP = 200;
98
+ /** Resolve any enabled tool through the canonical session map. */
99
+ export declare function resolveXdevTool(state: XdevState, name: string): Tool | undefined;
100
+ /** Resolve a mounted tool for top-level fallback execution. */
101
+ export declare function resolveMountedXdevTool(state: XdevState, name: string): Tool | undefined;
102
+ /** Resolve a mounted tool with its execution-only permission decorator. */
103
+ export declare function resolveMountedXdevExecutable(state: XdevState, name: string): Tool | undefined;
104
+ /** Mounted tools in presentation order, resolved from the canonical map. */
105
+ export declare function listXdevTools(state: XdevState): Tool[];
106
+ /** `{name, summary}` pairs for prompt templates and `/tools` display. */
107
+ export declare function xdevEntries(state: XdevState): Array<{
108
+ name: string;
109
+ summary: string;
110
+ }>;
111
+ /** `read xd://` listing with one device per line. */
112
+ export declare function xdevListing(state: XdevState): string;
113
+ /** Docs + schema for any enabled tool. */
114
+ export declare function xdevDocs(state: XdevState, name: string): string;
115
+ /** Docs + schema for mounted devices under the configured prompt-doc policy. */
116
+ export declare function xdevDocsAll(state: XdevState, mode?: XdevDocsMode, inlinePatterns?: readonly string[]): string;
117
+ /** Docs for selected mounted devices under the configured prompt-doc policy. */
118
+ export declare function xdevDocsFor(state: XdevState, names: Iterable<string>, mode: XdevDocsMode, inlinePatterns?: readonly string[]): string;
119
+ /** Execute an enabled canonical tool through `write xd://<tool>`. */
120
+ export declare function dispatchXdevTool(state: XdevState, name: string, content: string, toolCallId: string, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback, context?: AgentToolContext): Promise<{
121
+ result: AgentToolResult<unknown>;
122
+ xdev: XdevDispatch;
123
+ }>;
138
124
  /**
139
125
  * Streaming-safe call preview for an `xd://` write: forwards the decoded inner
140
126
  * args to the mounted tool's renderer (session instance first, then the static
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Parser and renderer for V8 `.cpuprofile` files (emitted by `node --cpu-prof`,
3
+ * `bun --cpu-prof`, Chrome DevTools, and the CDP `Profiler` domain).
4
+ *
5
+ * The raw file is a JSON blob with a flat node table and up to millions of
6
+ * sample/timestamp entries — useless to read directly. `renderCpuProfile`
7
+ * converts it into a compact bottleneck summary:
8
+ *
9
+ * - the hot-path call tree, pruned to frames with meaningful self time,
10
+ * with pass-through chains collapsed and direct recursion flattened
11
+ * - `(idle)` time excluded from on-CPU totals
12
+ * - a profile-wide "top functions by self time" table
13
+ *
14
+ * Consumed by the read tool: `*.cpuprofile` reads show the summary, `:raw`
15
+ * returns the original JSON.
16
+ */
17
+ /** Matches paths the read tool should treat as V8 CPU profiles. */
18
+ export declare function isCpuProfilePath(filePath: string): boolean;
19
+ /** Call-site metadata of one profile node. `lineNumber` is 0-based. */
20
+ export interface CpuProfileCallFrame {
21
+ functionName: string;
22
+ url?: string;
23
+ lineNumber?: number;
24
+ }
25
+ /** One node in the flat profile tree; `children` are node ids. */
26
+ export interface CpuProfileNode {
27
+ id: number;
28
+ callFrame: CpuProfileCallFrame;
29
+ hitCount?: number;
30
+ children?: number[];
31
+ }
32
+ /** Parsed V8 CPU profile. `startTime`/`endTime`/`timeDeltas` are microseconds. */
33
+ export interface CpuProfile {
34
+ nodes: CpuProfileNode[];
35
+ startTime: number;
36
+ endTime: number;
37
+ samples?: number[];
38
+ timeDeltas?: number[];
39
+ }
40
+ /**
41
+ * Parse a `.cpuprofile` JSON blob. Accepts both the bare profile and the CDP
42
+ * `Profiler.stop` result shape (`{ profile: {...} }`). Returns null when the
43
+ * text is not a structurally valid V8 CPU profile.
44
+ */
45
+ export declare function parseCpuProfile(text: string): CpuProfile | null;
46
+ /**
47
+ * Render a V8 CPU profile as an agent-friendly bottleneck summary.
48
+ * Returns null when `text` is not a CPU profile (caller falls back to the
49
+ * plain-text path).
50
+ */
51
+ export declare function renderCpuProfile(text: string): string | null;