@dreb/coding-agent 2.34.3 → 2.36.0

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 (80) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts +3 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +3 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +30 -2
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +172 -130
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/export-html/template.js +1 -1
  12. package/dist/core/footer-data-provider.d.ts.map +1 -1
  13. package/dist/core/footer-data-provider.js +5 -102
  14. package/dist/core/footer-data-provider.js.map +1 -1
  15. package/dist/core/git-branch.d.ts +19 -0
  16. package/dist/core/git-branch.d.ts.map +1 -0
  17. package/dist/core/git-branch.js +109 -0
  18. package/dist/core/git-branch.js.map +1 -0
  19. package/dist/core/sdk.d.ts.map +1 -1
  20. package/dist/core/sdk.js +1 -1
  21. package/dist/core/sdk.js.map +1 -1
  22. package/dist/core/session-manager.d.ts +21 -0
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +117 -42
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/core/settings-manager.d.ts +6 -0
  27. package/dist/core/settings-manager.d.ts.map +1 -1
  28. package/dist/core/settings-manager.js +8 -0
  29. package/dist/core/settings-manager.js.map +1 -1
  30. package/dist/{modes/interactive → core}/tab-title.d.ts +12 -3
  31. package/dist/core/tab-title.d.ts.map +1 -0
  32. package/dist/{modes/interactive → core}/tab-title.js +58 -16
  33. package/dist/core/tab-title.js.map +1 -0
  34. package/dist/core/tools/search.d.ts.map +1 -1
  35. package/dist/core/tools/search.js +4 -1
  36. package/dist/core/tools/search.js.map +1 -1
  37. package/dist/core/tools/subagent.d.ts +63 -2
  38. package/dist/core/tools/subagent.d.ts.map +1 -1
  39. package/dist/core/tools/subagent.js +459 -73
  40. package/dist/core/tools/subagent.js.map +1 -1
  41. package/dist/core/tools/web.d.ts +1 -1
  42. package/dist/core/tools/web.d.ts.map +1 -1
  43. package/dist/core/tools/web.js +60 -16
  44. package/dist/core/tools/web.js.map +1 -1
  45. package/dist/main.d.ts +10 -0
  46. package/dist/main.d.ts.map +1 -1
  47. package/dist/main.js +54 -4
  48. package/dist/main.js.map +1 -1
  49. package/dist/modes/index.d.ts +2 -2
  50. package/dist/modes/index.d.ts.map +1 -1
  51. package/dist/modes/index.js +1 -1
  52. package/dist/modes/index.js.map +1 -1
  53. package/dist/modes/interactive/components/session-selector.d.ts +1 -1
  54. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  55. package/dist/modes/interactive/components/session-selector.js +4 -40
  56. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  57. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  58. package/dist/modes/interactive/interactive-mode.js +2 -2
  59. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  60. package/dist/modes/rpc/index.d.ts +2 -2
  61. package/dist/modes/rpc/index.d.ts.map +1 -1
  62. package/dist/modes/rpc/index.js.map +1 -1
  63. package/dist/modes/rpc/rpc-client.d.ts +114 -6
  64. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  65. package/dist/modes/rpc/rpc-client.js +140 -2
  66. package/dist/modes/rpc/rpc-client.js.map +1 -1
  67. package/dist/modes/rpc/rpc-mode.d.ts +97 -0
  68. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  69. package/dist/modes/rpc/rpc-mode.js +656 -27
  70. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  71. package/dist/modes/rpc/rpc-types.d.ts +312 -1
  72. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  73. package/dist/modes/rpc/rpc-types.js.map +1 -1
  74. package/docs/dashboard.md +254 -0
  75. package/docs/development.md +8 -5
  76. package/docs/rpc.md +652 -5
  77. package/examples/sdk/12-full-control.ts +1 -1
  78. package/package.json +1 -1
  79. package/dist/modes/interactive/tab-title.d.ts.map +0 -1
  80. package/dist/modes/interactive/tab-title.js.map +0 -1
@@ -4,7 +4,7 @@
4
4
  * Usage:
5
5
  * import { RpcClient } from "@dreb/coding-agent/rpc";
6
6
  */
7
- export type { ModelInfo, RpcClientOptions, RpcEventListener } from "./rpc-client.js";
7
+ export type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from "./rpc-client.js";
8
8
  export { RpcClient } from "./rpc-client.js";
9
- export type { RpcCommand, RpcCommandType, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcResponse, RpcSessionInfo, RpcSessionState, RpcSlashCommand, } from "./rpc-types.js";
9
+ export type { RpcAgentTypeInfo, RpcBackgroundAgentInfo, RpcCommand, RpcCommandType, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcResponse, RpcSessionInfo, RpcSessionState, RpcSettingsSetResult, RpcSettingsSnapshot, RpcSettingsUpdate, RpcSlashCommand, RpcTreeNode, } from "./rpc-types.js";
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EACX,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,eAAe,EACf,eAAe,GACf,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport type {\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSlashCommand,\n} from \"./rpc-types.js\";\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EACX,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,WAAW,EACX,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,WAAW,GACX,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport type {\n\tRpcAgentTypeInfo,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n} from \"./rpc-types.js\";\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport type {\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSlashCommand,\n} from \"./rpc-types.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport type {\n\tRpcAgentTypeInfo,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcResponse,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n} from \"./rpc-types.js\";\n"]}
@@ -3,12 +3,12 @@
3
3
  *
4
4
  * Spawns the agent in RPC mode and provides a typed API for all operations.
5
5
  */
6
- import type { AgentEvent, AgentMessage, ThinkingLevel } from "@dreb/agent-core";
6
+ import type { AgentMessage, ThinkingLevel } from "@dreb/agent-core";
7
7
  import type { ImageContent } from "@dreb/ai";
8
- import type { SessionStats } from "../../core/agent-session.js";
8
+ import type { AgentSessionEvent, SessionStats } from "../../core/agent-session.js";
9
9
  import type { BashResult } from "../../core/bash-executor.js";
10
10
  import type { CompactionResult } from "../../core/compaction/index.js";
11
- import type { RpcSessionInfo, RpcSessionState, RpcSlashCommand } from "./rpc-types.js";
11
+ import type { RpcAgentTypeInfo, RpcBackgroundAgentInfo, RpcExtensionUIResponse, RpcPendingMessages, RpcResources, RpcSessionInfo, RpcSessionState, RpcSettingsSetResult, RpcSettingsSnapshot, RpcSettingsUpdate, RpcSlashCommand, RpcTreeNode } from "./rpc-types.js";
12
12
  export interface RpcClientOptions {
13
13
  /** Path to the CLI entry point (default: searches for dist/cli.js) */
14
14
  cliPath?: string;
@@ -43,12 +43,28 @@ export interface ModelInfo {
43
43
  contextWindow: number;
44
44
  reasoning: boolean;
45
45
  }
46
- export type RpcEventListener = (event: AgentEvent) => void;
46
+ /**
47
+ * Listener for events streamed by the RPC server. The wire carries the full
48
+ * {@link AgentSessionEvent} union (core AgentEvents plus session-level events like
49
+ * background_agent_start/end/event, tasks_update, suggest_next).
50
+ */
51
+ export type RpcEventListener = (event: AgentSessionEvent) => void;
52
+ export type RpcExitInfo = {
53
+ code: number | null;
54
+ signal: NodeJS.Signals | null;
55
+ error?: undefined;
56
+ } | {
57
+ code?: undefined;
58
+ signal?: undefined;
59
+ error: Error;
60
+ };
61
+ export type RpcExitListener = (info: RpcExitInfo) => void;
47
62
  export declare class RpcClient {
48
63
  private options;
49
64
  private process;
50
65
  private stopReadingStdout;
51
66
  private eventListeners;
67
+ private exitListeners;
52
68
  private pendingRequests;
53
69
  private requestId;
54
70
  private stderr;
@@ -67,6 +83,10 @@ export declare class RpcClient {
67
83
  * Subscribe to agent events.
68
84
  */
69
85
  onEvent(listener: RpcEventListener): () => void;
86
+ /**
87
+ * Subscribe to child process death/spawn-failure notifications.
88
+ */
89
+ onExit(listener: RpcExitListener): () => void;
70
90
  /**
71
91
  * Get collected stderr output (useful for debugging).
72
92
  */
@@ -101,6 +121,18 @@ export declare class RpcClient {
101
121
  * Get current session state.
102
122
  */
103
123
  getState(): Promise<RpcSessionState>;
124
+ /**
125
+ * Get loaded context/resources metadata (paths/names only, no file contents).
126
+ */
127
+ getResources(): Promise<RpcResources>;
128
+ /**
129
+ * Get current git branch for the agent cwd.
130
+ */
131
+ getGitBranch(): Promise<string | null>;
132
+ /**
133
+ * Get cached same-day cost across all sessions, primed by the server on first call.
134
+ */
135
+ getDailyCost(): Promise<number>;
104
136
  /**
105
137
  * Set model by provider and ID.
106
138
  */
@@ -160,6 +192,14 @@ export declare class RpcClient {
160
192
  * Set follow-up mode.
161
193
  */
162
194
  setFollowUpMode(mode: "all" | "one-at-a-time"): Promise<void>;
195
+ /**
196
+ * Get pending steering and follow-up messages without clearing them.
197
+ */
198
+ getPendingMessages(): Promise<RpcPendingMessages>;
199
+ /**
200
+ * Clear pending steering and follow-up messages, returning the cleared text.
201
+ */
202
+ clearPendingMessages(): Promise<RpcPendingMessages>;
163
203
  /**
164
204
  * Compact session context.
165
205
  */
@@ -168,6 +208,10 @@ export declare class RpcClient {
168
208
  * Set auto-compaction enabled/disabled.
169
209
  */
170
210
  setAutoCompaction(enabled: boolean): Promise<void>;
211
+ /**
212
+ * Abort in-progress compaction.
213
+ */
214
+ abortCompaction(): Promise<void>;
171
215
  /**
172
216
  * Set auto-retry enabled/disabled.
173
217
  */
@@ -213,6 +257,13 @@ export declare class RpcClient {
213
257
  switchSession(sessionPath: string): Promise<{
214
258
  cancelled: boolean;
215
259
  }>;
260
+ /**
261
+ * Delete a session file, trying trash first and falling back to unlink.
262
+ * Throws on failure, including when attempting to delete the active session.
263
+ */
264
+ deleteSession(sessionPath: string): Promise<{
265
+ method: "trash" | "unlink";
266
+ }>;
216
267
  /**
217
268
  * Fork from a specific message.
218
269
  * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)
@@ -228,6 +279,29 @@ export declare class RpcClient {
228
279
  entryId: string;
229
280
  text: string;
230
281
  }>>;
282
+ /**
283
+ * Get the session tree and current leaf.
284
+ */
285
+ getTree(): Promise<{
286
+ roots: RpcTreeNode[];
287
+ leafId: string | null;
288
+ }>;
289
+ /**
290
+ * Navigate to a session tree entry.
291
+ * @param targetId Entry ID to navigate to
292
+ * @param options Navigation options; timeoutMs is client-side only and is not sent over RPC
293
+ * @returns Object with `cancelled: true` if navigation was cancelled, and `editorText` when re-editing a message
294
+ */
295
+ navigateTree(targetId: string, options?: {
296
+ summarize?: boolean;
297
+ customInstructions?: string;
298
+ replaceInstructions?: boolean;
299
+ label?: string;
300
+ timeoutMs?: number;
301
+ }): Promise<{
302
+ cancelled: boolean;
303
+ editorText?: string;
304
+ }>;
231
305
  /**
232
306
  * Get text of last assistant message.
233
307
  */
@@ -249,10 +323,43 @@ export declare class RpcClient {
249
323
  * Returns sessions sorted by most recently modified first.
250
324
  */
251
325
  listSessions(): Promise<RpcSessionInfo[]>;
326
+ /**
327
+ * List sessions across all projects.
328
+ * Returns sessions sorted by most recently modified first. May be slow with many sessions.
329
+ */
330
+ listAllSessions(): Promise<RpcSessionInfo[]>;
331
+ /**
332
+ * List background subagents tracked by the server's registry (running and
333
+ * recently completed), including session dir/file paths where known.
334
+ */
335
+ listBackgroundAgents(): Promise<RpcBackgroundAgentInfo[]>;
336
+ /**
337
+ * List discoverable subagent types for the server's current working directory.
338
+ */
339
+ listAgentTypes(): Promise<RpcAgentTypeInfo[]>;
340
+ /**
341
+ * Answer an extension UI request (select/confirm/input/editor) previously
342
+ * received as an `extension_ui_request` event. Fire-and-forget: the server
343
+ * does not send a response to this message.
344
+ */
345
+ sendExtensionUIResponse(response: RpcExtensionUIResponse): void;
252
346
  /**
253
347
  * Get the dreb version.
254
348
  */
255
349
  getVersion(): Promise<string>;
350
+ /**
351
+ * Get the persistent default settings (SettingsManager-backed).
352
+ * These seed fresh runtimes — for live session state use getState().
353
+ */
354
+ getSettings(): Promise<RpcSettingsSnapshot>;
355
+ /**
356
+ * Update persistent default settings. Validates the whole payload before applying
357
+ * anything (atomic: on any invalid field, nothing changes). Does NOT touch live
358
+ * session state — use setModel/setThinkingLevel/etc. for that.
359
+ * Returns the full settings snapshot after the write, plus any loud warnings
360
+ * (for example project-level agent model overrides shadowing global writes).
361
+ */
362
+ setSettings(settings: RpcSettingsUpdate): Promise<RpcSettingsSetResult>;
256
363
  /**
257
364
  * Wait for agent to become idle (no streaming).
258
365
  * Resolves when agent_end event is received.
@@ -261,17 +368,18 @@ export declare class RpcClient {
261
368
  /**
262
369
  * Collect events until agent becomes idle.
263
370
  */
264
- collectEvents(timeout?: number): Promise<AgentEvent[]>;
371
+ collectEvents(timeout?: number): Promise<AgentSessionEvent[]>;
265
372
  /**
266
373
  * Send prompt and wait for completion, returning all events.
267
374
  */
268
- promptAndWait(message: string, images?: ImageContent[], timeout?: number): Promise<AgentEvent[]>;
375
+ promptAndWait(message: string, images?: ImageContent[], timeout?: number): Promise<AgentSessionEvent[]>;
269
376
  /**
270
377
  * Mark the client dead and reject every in-flight request with `message`.
271
378
  * Shared by the 'exit' and 'error' handlers so both failure paths surface
272
379
  * loudly and consistently instead of leaving callers hanging.
273
380
  */
274
381
  private failPendingRequests;
382
+ private notifyExitListeners;
275
383
  private handleLine;
276
384
  private send;
277
385
  private getData;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC Client for programmatic access to the coding agent.\n *\n * Spawns the agent in RPC mode and provides a typed API for all operations.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { AgentEvent, AgentMessage, ThinkingLevel } from \"@dreb/agent-core\";\nimport type { ImageContent } from \"@dreb/ai\";\nimport type { SessionStats } from \"../../core/agent-session.js\";\nimport type { BashResult } from \"../../core/bash-executor.js\";\nimport type { CompactionResult } from \"../../core/compaction/index.js\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.js\";\nimport type { RpcCommand, RpcResponse, RpcSessionInfo, RpcSessionState, RpcSlashCommand } from \"./rpc-types.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Grace period (ms) start() waits for the child to initialize before treating\n * it as alive. An 'error'/'exit' event during this window rejects start() early.\n */\nconst INIT_GRACE_MS = 100;\n\n/** Distributive Omit that works with union types */\ntype DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;\n\n/** RpcCommand without the id field (for internal send) */\ntype RpcCommandBody = DistributiveOmit<RpcCommand, \"id\">;\n\nexport interface RpcClientOptions {\n\t/** Path to the CLI entry point (default: searches for dist/cli.js) */\n\tcliPath?: string;\n\t/** Working directory for the agent */\n\tcwd?: string;\n\t/** Environment variables */\n\tenv?: Record<string, string>;\n\t/** Provider to use */\n\tprovider?: string;\n\t/** Model ID to use */\n\tmodel?: string;\n\t/** Additional CLI arguments */\n\targs?: string[];\n\t/**\n\t * Run the agent child process under this OS user id.\n\t * Forwarded directly to `child_process.spawn`. The parent process must hold\n\t * `CAP_SETUID` (typically by running as root) for this to succeed; otherwise\n\t * the spawn fails and `start()` rejects. Omitted when unset (default behavior).\n\t */\n\tuid?: number;\n\t/**\n\t * Run the agent child process under this OS group id.\n\t * Forwarded directly to `child_process.spawn`. The parent process must hold\n\t * `CAP_SETGID` (typically by running as root) for this to succeed; otherwise\n\t * the spawn fails and `start()` rejects. Omitted when unset (default behavior).\n\t */\n\tgid?: number;\n}\n\nexport interface ModelInfo {\n\tprovider: string;\n\tid: string;\n\tcontextWindow: number;\n\treasoning: boolean;\n}\n\nexport type RpcEventListener = (event: AgentEvent) => void;\n\n// ============================================================================\n// RPC Client\n// ============================================================================\n\nexport class RpcClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate stopReadingStdout: (() => void) | null = null;\n\tprivate eventListeners: RpcEventListener[] = [];\n\tprivate pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =\n\t\tnew Map();\n\tprivate requestId = 0;\n\tprivate stderr = \"\";\n\tprivate _dead = false;\n\tprivate spawnError: Error | null = null;\n\n\tconstructor(private options: RpcClientOptions = {}) {}\n\n\t/**\n\t * Start the RPC agent process.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.process) {\n\t\t\tthrow new Error(\"Client already started\");\n\t\t}\n\n\t\tconst cliPath = this.options.cliPath ?? \"dist/cli.js\";\n\t\tconst args = [\"--mode\", \"rpc\"];\n\n\t\tif (this.options.provider) {\n\t\t\targs.push(\"--provider\", this.options.provider);\n\t\t}\n\t\tif (this.options.model) {\n\t\t\targs.push(\"--model\", this.options.model);\n\t\t}\n\t\tif (this.options.args) {\n\t\t\targs.push(...this.options.args);\n\t\t}\n\n\t\tthis.process = spawn(\"node\", [cliPath, ...args], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv: { ...process.env, ...this.options.env },\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t...(this.options.uid != null ? { uid: this.options.uid } : {}),\n\t\t\t...(this.options.gid != null ? { gid: this.options.gid } : {}),\n\t\t});\n\n\t\tthis._dead = false;\n\t\tthis.spawnError = null;\n\n\t\t// Collect stderr for debugging\n\t\tthis.process.stderr?.on(\"data\", (data) => {\n\t\t\tthis.stderr += data.toString();\n\t\t});\n\n\t\t// Detect process exit and spawn failures for the whole session lifecycle —\n\t\t// reject pending requests so callers don't hang. Capture the process\n\t\t// reference so stale handlers from a previous process don't clobber state\n\t\t// after a stop()/start() cycle.\n\t\tconst procRef = this.process;\n\t\tprocRef.on(\"exit\", (code, signal) => {\n\t\t\t// Guard: skip if this handler belongs to an old, already-stopped process\n\t\t\tif (this.process !== procRef) return;\n\t\t\tthis.failPendingRequests(`RPC process exited with code ${code}, signal ${signal}`);\n\t\t});\n\n\t\t// Spawn failures surface asynchronously as an 'error' event rather than a\n\t\t// thrown exception (e.g. EPERM when dropping to a uid/gid the parent lacks\n\t\t// CAP_SETUID/CAP_SETGID for, or unsupported on Windows). Without a listener\n\t\t// an 'error' event would crash the process; record the cause so start() can\n\t\t// fail loudly and a later send() can report the real reason instead of a\n\t\t// generic \"not running\".\n\t\tprocRef.on(\"error\", (err) => {\n\t\t\t// Guard: skip if this handler belongs to an old, already-stopped process\n\t\t\tif (this.process !== procRef) return;\n\t\t\tthis.spawnError = err;\n\t\t\tthis.failPendingRequests(`RPC process failed to spawn: ${err.message}`);\n\t\t});\n\n\t\t// Set up strict JSONL reader for stdout.\n\t\tthis.stopReadingStdout = attachJsonlLineReader(this.process.stdout!, (line) => {\n\t\t\tthis.handleLine(line);\n\t\t});\n\n\t\t// Give the process a moment to initialize, but settle as soon as an\n\t\t// 'error' or 'exit' event arrives. Racing the grace period against those\n\t\t// events (instead of a blind fixed sleep) guarantees that a failed\n\t\t// privilege drop (uid/gid EPERM) rejects start() loudly no matter when\n\t\t// libuv delivers the event — a fixed sleep could expire first and let\n\t\t// start() resolve despite the child never running.\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst onError = (err: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\treject(new Error(`Agent process failed to spawn: ${err.message}. Stderr: ${this.stderr}`));\n\t\t\t};\n\t\t\tconst onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n\t\t\t\tcleanup();\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`Agent process exited immediately with code ${code}, signal ${signal}. Stderr: ${this.stderr}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t}, INIT_GRACE_MS);\n\t\t\tconst cleanup = () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tprocRef.off(\"error\", onError);\n\t\t\t\tprocRef.off(\"exit\", onExit);\n\t\t\t};\n\t\t\tprocRef.once(\"error\", onError);\n\t\t\tprocRef.once(\"exit\", onExit);\n\t\t});\n\n\t\t// Belt-and-suspenders: a process that exited exactly as the grace timer\n\t\t// fired (so the race resolved instead of rejecting) still surfaces loudly.\n\t\tif (this.process.exitCode !== null) {\n\t\t\tthrow new Error(`Agent process exited immediately with code ${this.process.exitCode}. Stderr: ${this.stderr}`);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.process) return;\n\n\t\tthis.stopReadingStdout?.();\n\t\tthis.stopReadingStdout = null;\n\t\tthis.process.kill(\"SIGTERM\");\n\n\t\t// Wait for process to exit\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.process?.kill(\"SIGKILL\");\n\t\t\t\tresolve();\n\t\t\t}, 1000);\n\n\t\t\tthis.process?.on(\"exit\", () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tthis.process = null;\n\t\tthis._dead = true;\n\t\tthis.pendingRequests.clear();\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: RpcEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst index = this.eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Get collected stderr output (useful for debugging).\n\t */\n\tgetStderr(): string {\n\t\treturn this.stderr;\n\t}\n\n\t// =========================================================================\n\t// Command Methods\n\t// =========================================================================\n\n\t/**\n\t * Send a prompt to the agent.\n\t * Returns immediately after sending; use onEvent() to receive streaming events.\n\t * Use waitForIdle() to wait for completion.\n\t */\n\tasync prompt(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"prompt\", message, images });\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t */\n\tasync steer(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"steer\", message, images });\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t */\n\tasync followUp(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"follow_up\", message, images });\n\t}\n\n\t/**\n\t * Abort current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tawait this.send({ type: \"abort\" });\n\t}\n\n\t/**\n\t * Start a new session, optionally with parent tracking.\n\t * @param parentSession - Optional parent session path for lineage tracking\n\t * @returns Object with `cancelled: true` if an extension cancelled the new session\n\t */\n\tasync newSession(parentSession?: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"new_session\", parentSession });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current session state.\n\t */\n\tasync getState(): Promise<RpcSessionState> {\n\t\tconst response = await this.send({ type: \"get_state\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set model by provider and ID.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<{ provider: string; id: string }> {\n\t\tconst response = await this.send({ type: \"set_model\", provider, modelId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Cycle to next model.\n\t */\n\tasync cycleModel(): Promise<{\n\t\tmodel: { provider: string; id: string };\n\t\tthinkingLevel: ThinkingLevel;\n\t\tisScoped: boolean;\n\t} | null> {\n\t\tconst response = await this.send({ type: \"cycle_model\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get list of available models.\n\t */\n\tasync getAvailableModels(): Promise<ModelInfo[]> {\n\t\tconst response = await this.send({ type: \"get_available_models\" });\n\t\treturn this.getData<{ models: ModelInfo[] }>(response).models;\n\t}\n\n\t/**\n\t * Resolve a model pattern using the same logic as CLI/TUI.\n\t * Runs server-side so API keys never leave the process.\n\t * Returns null if no match found.\n\t */\n\tasync resolveModel(pattern: string): Promise<{ model: ModelInfo; warning?: string } | null> {\n\t\tconst response = await this.send({ type: \"resolve_model\", pattern });\n\t\treturn this.getData<{ model: ModelInfo; warning?: string } | null>(response);\n\t}\n\n\t/**\n\t * Hatch a new buddy companion. Runs inside the agent process\n\t * so API keys never leave the process boundary.\n\t */\n\tasync buddyHatch(): Promise<import(\"../../core/buddy/buddy-types.js\").BuddyState> {\n\t\tconst response = await this.send({ type: \"buddy_hatch\" });\n\t\treturn this.getData<{ state: import(\"../../core/buddy/buddy-types.js\").BuddyState }>(response).state;\n\t}\n\n\t/**\n\t * Reroll the buddy companion. Runs inside the agent process\n\t * so API keys never leave the process boundary.\n\t */\n\tasync buddyReroll(): Promise<import(\"../../core/buddy/buddy-types.js\").BuddyState> {\n\t\tconst response = await this.send({ type: \"buddy_reroll\" });\n\t\treturn this.getData<{ state: import(\"../../core/buddy/buddy-types.js\").BuddyState }>(response).state;\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\tawait this.send({ type: \"set_thinking_level\", level });\n\t}\n\n\t/**\n\t * Cycle thinking level.\n\t */\n\tasync cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> {\n\t\tconst response = await this.send({ type: \"cycle_thinking_level\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set steering mode.\n\t */\n\tasync setSteeringMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_steering_mode\", mode });\n\t}\n\n\t/**\n\t * Set follow-up mode.\n\t */\n\tasync setFollowUpMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_follow_up_mode\", mode });\n\t}\n\n\t/**\n\t * Compact session context.\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tconst response = await this.send({ type: \"compact\", customInstructions });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set auto-compaction enabled/disabled.\n\t */\n\tasync setAutoCompaction(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_compaction\", enabled });\n\t}\n\n\t/**\n\t * Set auto-retry enabled/disabled.\n\t */\n\tasync setAutoRetry(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_retry\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress retry.\n\t */\n\tasync abortRetry(): Promise<void> {\n\t\tawait this.send({ type: \"abort_retry\" });\n\t}\n\n\t/**\n\t * Execute a bash command.\n\t */\n\tasync bash(command: string): Promise<BashResult> {\n\t\tconst response = await this.send({ type: \"bash\", command });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Abort running bash command.\n\t */\n\tasync abortBash(): Promise<void> {\n\t\tawait this.send({ type: \"abort_bash\" });\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<SessionStats> {\n\t\tconst response = await this.send({ type: \"get_session_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get performance statistics.\n\t */\n\tasync getPerformanceStats(): Promise<{\n\t\tmodels: Array<{ provider: string; modelId: string; median: number; mean: number; count: number }>;\n\t}> {\n\t\tconst response = await this.send({ type: \"get_performance_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t */\n\tasync exportHtml(outputPath?: string): Promise<{ path: string }> {\n\t\tconst response = await this.send({ type: \"export_html\", outputPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Switch to a different session file.\n\t * @returns Object with `cancelled: true` if an extension cancelled the switch\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"switch_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Fork from a specific message.\n\t * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)\n\t */\n\tasync fork(entryId: string): Promise<{ text: string; cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"fork\", entryId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get messages available for forking.\n\t */\n\tasync getForkMessages(): Promise<Array<{ entryId: string; text: string }>> {\n\t\tconst response = await this.send({ type: \"get_fork_messages\" });\n\t\treturn this.getData<{ messages: Array<{ entryId: string; text: string }> }>(response).messages;\n\t}\n\n\t/**\n\t * Get text of last assistant message.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_last_assistant_text\" });\n\t\treturn this.getData<{ text: string | null }>(response).text;\n\t}\n\n\t/**\n\t * Set the session display name.\n\t */\n\tasync setSessionName(name: string): Promise<void> {\n\t\tawait this.send({ type: \"set_session_name\", name });\n\t}\n\n\t/**\n\t * Get all messages in the session.\n\t */\n\tasync getMessages(): Promise<AgentMessage[]> {\n\t\tconst response = await this.send({ type: \"get_messages\" });\n\t\treturn this.getData<{ messages: AgentMessage[] }>(response).messages;\n\t}\n\n\t/**\n\t * Get available commands (extension commands, prompt templates, skills).\n\t */\n\tasync getCommands(): Promise<RpcSlashCommand[]> {\n\t\tconst response = await this.send({ type: \"get_commands\" });\n\t\treturn this.getData<{ commands: RpcSlashCommand[] }>(response).commands;\n\t}\n\n\t/**\n\t * List sessions for the current working directory.\n\t * Returns sessions sorted by most recently modified first.\n\t */\n\tasync listSessions(): Promise<RpcSessionInfo[]> {\n\t\tconst response = await this.send({ type: \"list_sessions\" });\n\t\treturn this.getData<{ sessions: RpcSessionInfo[] }>(response).sessions;\n\t}\n\n\t/**\n\t * Get the dreb version.\n\t */\n\tasync getVersion(): Promise<string> {\n\t\tconst response = await this.send({ type: \"get_version\" });\n\t\treturn this.getData<{ version: string }>(response).version;\n\t}\n\n\t// =========================================================================\n\t// Helpers\n\t// =========================================================================\n\n\t/**\n\t * Wait for agent to become idle (no streaming).\n\t * Resolves when agent_end event is received.\n\t */\n\twaitForIdle(timeout = 60000): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Collect events until agent becomes idle.\n\t */\n\tcollectEvents(timeout = 60000): Promise<AgentEvent[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst events: AgentEvent[] = [];\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout collecting events. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tevents.push(event);\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve(events);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Send prompt and wait for completion, returning all events.\n\t */\n\tasync promptAndWait(message: string, images?: ImageContent[], timeout = 60000): Promise<AgentEvent[]> {\n\t\tconst eventsPromise = this.collectEvents(timeout);\n\t\tawait this.prompt(message, images);\n\t\treturn eventsPromise;\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\t/**\n\t * Mark the client dead and reject every in-flight request with `message`.\n\t * Shared by the 'exit' and 'error' handlers so both failure paths surface\n\t * loudly and consistently instead of leaving callers hanging.\n\t */\n\tprivate failPendingRequests(message: string): void {\n\t\tthis._dead = true;\n\t\tfor (const pending of this.pendingRequests.values()) {\n\t\t\tpending.reject(new Error(message));\n\t\t}\n\t\tthis.pendingRequests.clear();\n\t}\n\n\tprivate handleLine(line: string): void {\n\t\ttry {\n\t\t\tconst data = JSON.parse(line);\n\n\t\t\t// Check if it's a response to a pending request\n\t\t\tif (data.type === \"response\" && data.id && this.pendingRequests.has(data.id)) {\n\t\t\t\tconst pending = this.pendingRequests.get(data.id)!;\n\t\t\t\tthis.pendingRequests.delete(data.id);\n\t\t\t\tpending.resolve(data as RpcResponse);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise it's an event\n\t\t\tfor (const listener of this.eventListeners) {\n\t\t\t\tlistener(data as AgentEvent);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore non-JSON lines\n\t\t}\n\t}\n\n\tprivate async send(command: RpcCommandBody): Promise<RpcResponse> {\n\t\tif (this._dead || !this.process?.stdin) {\n\t\t\t// Surface the real spawn cause (e.g. uid/gid EPERM) if one was captured,\n\t\t\t// rather than a generic message that hides why the process is gone.\n\t\t\tthrow new Error(\n\t\t\t\tthis.spawnError ? `RPC process not running: ${this.spawnError.message}` : \"RPC process not running\",\n\t\t\t);\n\t\t}\n\n\t\tconst id = `req_${++this.requestId}`;\n\t\tconst fullCommand = { ...command, id } as RpcCommand;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\treject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));\n\t\t\t}, 30000);\n\n\t\t\tthis.pendingRequests.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(response);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.process!.stdin!.write(serializeJsonLine(fullCommand));\n\t\t});\n\t}\n\n\tprivate getData<T>(response: RpcResponse): T {\n\t\tif (!response.success) {\n\t\t\tconst errorResponse = response as Extract<RpcResponse, { success: false }>;\n\t\t\tthrow new Error(errorResponse.error);\n\t\t}\n\t\t// Type assertion: we trust response.data matches T based on the command sent.\n\t\t// This is safe because each public method specifies the correct T for its command.\n\t\tconst successResponse = response as Extract<RpcResponse, { success: true; data: unknown }>;\n\t\treturn successResponse.data as T;\n\t}\n}\n"]}
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC Client for programmatic access to the coding agent.\n *\n * Spawns the agent in RPC mode and provides a typed API for all operations.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { AgentMessage, ThinkingLevel } from \"@dreb/agent-core\";\nimport type { ImageContent } from \"@dreb/ai\";\nimport type { AgentSessionEvent, SessionStats } from \"../../core/agent-session.js\";\nimport type { BashResult } from \"../../core/bash-executor.js\";\nimport type { CompactionResult } from \"../../core/compaction/index.js\";\nimport { attachJsonlLineReader, serializeJsonLine } from \"./jsonl.js\";\nimport type {\n\tRpcAgentTypeInfo,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcExtensionUIResponse,\n\tRpcPendingMessages,\n\tRpcResources,\n\tRpcResponse,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n} from \"./rpc-types.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Grace period (ms) start() waits for the child to initialize before treating\n * it as alive. An 'error'/'exit' event during this window rejects start() early.\n */\nconst INIT_GRACE_MS = 100;\n\n/** Distributive Omit that works with union types */\ntype DistributiveOmit<T, K extends keyof T> = T extends unknown ? Omit<T, K> : never;\n\n/** RpcCommand without the id field (for internal send) */\ntype RpcCommandBody = DistributiveOmit<RpcCommand, \"id\">;\n\nexport interface RpcClientOptions {\n\t/** Path to the CLI entry point (default: searches for dist/cli.js) */\n\tcliPath?: string;\n\t/** Working directory for the agent */\n\tcwd?: string;\n\t/** Environment variables */\n\tenv?: Record<string, string>;\n\t/** Provider to use */\n\tprovider?: string;\n\t/** Model ID to use */\n\tmodel?: string;\n\t/** Additional CLI arguments */\n\targs?: string[];\n\t/**\n\t * Run the agent child process under this OS user id.\n\t * Forwarded directly to `child_process.spawn`. The parent process must hold\n\t * `CAP_SETUID` (typically by running as root) for this to succeed; otherwise\n\t * the spawn fails and `start()` rejects. Omitted when unset (default behavior).\n\t */\n\tuid?: number;\n\t/**\n\t * Run the agent child process under this OS group id.\n\t * Forwarded directly to `child_process.spawn`. The parent process must hold\n\t * `CAP_SETGID` (typically by running as root) for this to succeed; otherwise\n\t * the spawn fails and `start()` rejects. Omitted when unset (default behavior).\n\t */\n\tgid?: number;\n}\n\nexport interface ModelInfo {\n\tprovider: string;\n\tid: string;\n\tcontextWindow: number;\n\treasoning: boolean;\n}\n\n/**\n * Listener for events streamed by the RPC server. The wire carries the full\n * {@link AgentSessionEvent} union (core AgentEvents plus session-level events like\n * background_agent_start/end/event, tasks_update, suggest_next).\n */\nexport type RpcEventListener = (event: AgentSessionEvent) => void;\n\nexport type RpcExitInfo =\n\t| { code: number | null; signal: NodeJS.Signals | null; error?: undefined }\n\t| { code?: undefined; signal?: undefined; error: Error };\n\nexport type RpcExitListener = (info: RpcExitInfo) => void;\n\n// ============================================================================\n// RPC Client\n// ============================================================================\n\nexport class RpcClient {\n\tprivate process: ChildProcess | null = null;\n\tprivate stopReadingStdout: (() => void) | null = null;\n\tprivate eventListeners: RpcEventListener[] = [];\n\tprivate exitListeners = new Set<RpcExitListener>();\n\tprivate pendingRequests: Map<string, { resolve: (response: RpcResponse) => void; reject: (error: Error) => void }> =\n\t\tnew Map();\n\tprivate requestId = 0;\n\tprivate stderr = \"\";\n\tprivate _dead = false;\n\tprivate spawnError: Error | null = null;\n\n\tconstructor(private options: RpcClientOptions = {}) {}\n\n\t/**\n\t * Start the RPC agent process.\n\t */\n\tasync start(): Promise<void> {\n\t\tif (this.process) {\n\t\t\tthrow new Error(\"Client already started\");\n\t\t}\n\n\t\tconst cliPath = this.options.cliPath ?? \"dist/cli.js\";\n\t\tconst args = [\"--mode\", \"rpc\"];\n\n\t\tif (this.options.provider) {\n\t\t\targs.push(\"--provider\", this.options.provider);\n\t\t}\n\t\tif (this.options.model) {\n\t\t\targs.push(\"--model\", this.options.model);\n\t\t}\n\t\tif (this.options.args) {\n\t\t\targs.push(...this.options.args);\n\t\t}\n\n\t\tthis.process = spawn(\"node\", [cliPath, ...args], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv: { ...process.env, ...this.options.env },\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t...(this.options.uid != null ? { uid: this.options.uid } : {}),\n\t\t\t...(this.options.gid != null ? { gid: this.options.gid } : {}),\n\t\t});\n\n\t\tthis._dead = false;\n\t\tthis.spawnError = null;\n\n\t\t// Collect stderr for debugging\n\t\tthis.process.stderr?.on(\"data\", (data) => {\n\t\t\tthis.stderr += data.toString();\n\t\t});\n\n\t\t// Detect process exit and spawn failures for the whole session lifecycle —\n\t\t// reject pending requests so callers don't hang. Capture the process\n\t\t// reference so stale handlers from a previous process don't clobber state\n\t\t// after a stop()/start() cycle.\n\t\tconst procRef = this.process;\n\t\tprocRef.on(\"exit\", (code, signal) => {\n\t\t\t// Guard: skip if this handler belongs to an old, already-stopped process\n\t\t\tif (this.process !== procRef) return;\n\t\t\tthis.failPendingRequests(`RPC process exited with code ${code}, signal ${signal}`);\n\t\t\tthis.notifyExitListeners({ code, signal });\n\t\t});\n\n\t\t// Spawn failures surface asynchronously as an 'error' event rather than a\n\t\t// thrown exception (e.g. EPERM when dropping to a uid/gid the parent lacks\n\t\t// CAP_SETUID/CAP_SETGID for, or unsupported on Windows). Without a listener\n\t\t// an 'error' event would crash the process; record the cause so start() can\n\t\t// fail loudly and a later send() can report the real reason instead of a\n\t\t// generic \"not running\".\n\t\tprocRef.on(\"error\", (err) => {\n\t\t\t// Guard: skip if this handler belongs to an old, already-stopped process\n\t\t\tif (this.process !== procRef) return;\n\t\t\tthis.spawnError = err;\n\t\t\tthis.failPendingRequests(`RPC process failed to spawn: ${err.message}`);\n\t\t\tthis.notifyExitListeners({ error: err });\n\t\t});\n\n\t\t// Set up strict JSONL reader for stdout.\n\t\tthis.stopReadingStdout = attachJsonlLineReader(this.process.stdout!, (line) => {\n\t\t\tthis.handleLine(line);\n\t\t});\n\n\t\t// Give the process a moment to initialize, but settle as soon as an\n\t\t// 'error' or 'exit' event arrives. Racing the grace period against those\n\t\t// events (instead of a blind fixed sleep) guarantees that a failed\n\t\t// privilege drop (uid/gid EPERM) rejects start() loudly no matter when\n\t\t// libuv delivers the event — a fixed sleep could expire first and let\n\t\t// start() resolve despite the child never running.\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst onError = (err: Error) => {\n\t\t\t\tcleanup();\n\t\t\t\treject(new Error(`Agent process failed to spawn: ${err.message}. Stderr: ${this.stderr}`));\n\t\t\t};\n\t\t\tconst onExit = (code: number | null, signal: NodeJS.Signals | null) => {\n\t\t\t\tcleanup();\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`Agent process exited immediately with code ${code}, signal ${signal}. Stderr: ${this.stderr}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t}, INIT_GRACE_MS);\n\t\t\tconst cleanup = () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tprocRef.off(\"error\", onError);\n\t\t\t\tprocRef.off(\"exit\", onExit);\n\t\t\t};\n\t\t\tprocRef.once(\"error\", onError);\n\t\t\tprocRef.once(\"exit\", onExit);\n\t\t});\n\n\t\t// Belt-and-suspenders: a process that exited exactly as the grace timer\n\t\t// fired (so the race resolved instead of rejecting) still surfaces loudly.\n\t\tif (this.process.exitCode !== null) {\n\t\t\tthrow new Error(`Agent process exited immediately with code ${this.process.exitCode}. Stderr: ${this.stderr}`);\n\t\t}\n\t}\n\n\t/**\n\t * Stop the RPC agent process.\n\t */\n\tasync stop(): Promise<void> {\n\t\tif (!this.process) return;\n\n\t\tthis.stopReadingStdout?.();\n\t\tthis.stopReadingStdout = null;\n\t\tthis.process.kill(\"SIGTERM\");\n\n\t\t// Wait for process to exit\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.process?.kill(\"SIGKILL\");\n\t\t\t\tresolve();\n\t\t\t}, 1000);\n\n\t\t\tthis.process?.on(\"exit\", () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\n\t\tthis.process = null;\n\t\tthis._dead = true;\n\t\tthis.pendingRequests.clear();\n\t}\n\n\t/**\n\t * Subscribe to agent events.\n\t */\n\tonEvent(listener: RpcEventListener): () => void {\n\t\tthis.eventListeners.push(listener);\n\t\treturn () => {\n\t\t\tconst index = this.eventListeners.indexOf(listener);\n\t\t\tif (index !== -1) {\n\t\t\t\tthis.eventListeners.splice(index, 1);\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Subscribe to child process death/spawn-failure notifications.\n\t */\n\tonExit(listener: RpcExitListener): () => void {\n\t\tthis.exitListeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.exitListeners.delete(listener);\n\t\t};\n\t}\n\n\t/**\n\t * Get collected stderr output (useful for debugging).\n\t */\n\tgetStderr(): string {\n\t\treturn this.stderr;\n\t}\n\n\t// =========================================================================\n\t// Command Methods\n\t// =========================================================================\n\n\t/**\n\t * Send a prompt to the agent.\n\t * Returns immediately after sending; use onEvent() to receive streaming events.\n\t * Use waitForIdle() to wait for completion.\n\t */\n\tasync prompt(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"prompt\", message, images });\n\t}\n\n\t/**\n\t * Queue a steering message to interrupt the agent mid-run.\n\t */\n\tasync steer(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"steer\", message, images });\n\t}\n\n\t/**\n\t * Queue a follow-up message to be processed after the agent finishes.\n\t */\n\tasync followUp(message: string, images?: ImageContent[]): Promise<void> {\n\t\tawait this.send({ type: \"follow_up\", message, images });\n\t}\n\n\t/**\n\t * Abort current operation.\n\t */\n\tasync abort(): Promise<void> {\n\t\tawait this.send({ type: \"abort\" });\n\t}\n\n\t/**\n\t * Start a new session, optionally with parent tracking.\n\t * @param parentSession - Optional parent session path for lineage tracking\n\t * @returns Object with `cancelled: true` if an extension cancelled the new session\n\t */\n\tasync newSession(parentSession?: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"new_session\", parentSession });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current session state.\n\t */\n\tasync getState(): Promise<RpcSessionState> {\n\t\tconst response = await this.send({ type: \"get_state\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get loaded context/resources metadata (paths/names only, no file contents).\n\t */\n\tasync getResources(): Promise<RpcResources> {\n\t\tconst response = await this.send({ type: \"get_resources\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get current git branch for the agent cwd.\n\t */\n\tasync getGitBranch(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_git_branch\" });\n\t\treturn this.getData<{ branch: string | null }>(response).branch;\n\t}\n\n\t/**\n\t * Get cached same-day cost across all sessions, primed by the server on first call.\n\t */\n\tasync getDailyCost(): Promise<number> {\n\t\tconst response = await this.send({ type: \"get_daily_cost\" });\n\t\treturn this.getData<{ cost: number }>(response).cost;\n\t}\n\n\t/**\n\t * Set model by provider and ID.\n\t */\n\tasync setModel(provider: string, modelId: string): Promise<{ provider: string; id: string }> {\n\t\tconst response = await this.send({ type: \"set_model\", provider, modelId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Cycle to next model.\n\t */\n\tasync cycleModel(): Promise<{\n\t\tmodel: { provider: string; id: string };\n\t\tthinkingLevel: ThinkingLevel;\n\t\tisScoped: boolean;\n\t} | null> {\n\t\tconst response = await this.send({ type: \"cycle_model\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get list of available models.\n\t */\n\tasync getAvailableModels(): Promise<ModelInfo[]> {\n\t\tconst response = await this.send({ type: \"get_available_models\" });\n\t\treturn this.getData<{ models: ModelInfo[] }>(response).models;\n\t}\n\n\t/**\n\t * Resolve a model pattern using the same logic as CLI/TUI.\n\t * Runs server-side so API keys never leave the process.\n\t * Returns null if no match found.\n\t */\n\tasync resolveModel(pattern: string): Promise<{ model: ModelInfo; warning?: string } | null> {\n\t\tconst response = await this.send({ type: \"resolve_model\", pattern });\n\t\treturn this.getData<{ model: ModelInfo; warning?: string } | null>(response);\n\t}\n\n\t/**\n\t * Hatch a new buddy companion. Runs inside the agent process\n\t * so API keys never leave the process boundary.\n\t */\n\tasync buddyHatch(): Promise<import(\"../../core/buddy/buddy-types.js\").BuddyState> {\n\t\tconst response = await this.send({ type: \"buddy_hatch\" });\n\t\treturn this.getData<{ state: import(\"../../core/buddy/buddy-types.js\").BuddyState }>(response).state;\n\t}\n\n\t/**\n\t * Reroll the buddy companion. Runs inside the agent process\n\t * so API keys never leave the process boundary.\n\t */\n\tasync buddyReroll(): Promise<import(\"../../core/buddy/buddy-types.js\").BuddyState> {\n\t\tconst response = await this.send({ type: \"buddy_reroll\" });\n\t\treturn this.getData<{ state: import(\"../../core/buddy/buddy-types.js\").BuddyState }>(response).state;\n\t}\n\n\t/**\n\t * Set thinking level.\n\t */\n\tasync setThinkingLevel(level: ThinkingLevel): Promise<void> {\n\t\tawait this.send({ type: \"set_thinking_level\", level });\n\t}\n\n\t/**\n\t * Cycle thinking level.\n\t */\n\tasync cycleThinkingLevel(): Promise<{ level: ThinkingLevel } | null> {\n\t\tconst response = await this.send({ type: \"cycle_thinking_level\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set steering mode.\n\t */\n\tasync setSteeringMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_steering_mode\", mode });\n\t}\n\n\t/**\n\t * Set follow-up mode.\n\t */\n\tasync setFollowUpMode(mode: \"all\" | \"one-at-a-time\"): Promise<void> {\n\t\tawait this.send({ type: \"set_follow_up_mode\", mode });\n\t}\n\n\t/**\n\t * Get pending steering and follow-up messages without clearing them.\n\t */\n\tasync getPendingMessages(): Promise<RpcPendingMessages> {\n\t\tconst response = await this.send({ type: \"get_pending_messages\" });\n\t\treturn this.getData<RpcPendingMessages>(response);\n\t}\n\n\t/**\n\t * Clear pending steering and follow-up messages, returning the cleared text.\n\t */\n\tasync clearPendingMessages(): Promise<RpcPendingMessages> {\n\t\tconst response = await this.send({ type: \"clear_pending_messages\" });\n\t\treturn this.getData<RpcPendingMessages>(response);\n\t}\n\n\t/**\n\t * Compact session context.\n\t */\n\tasync compact(customInstructions?: string): Promise<CompactionResult> {\n\t\tconst response = await this.send({ type: \"compact\", customInstructions });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Set auto-compaction enabled/disabled.\n\t */\n\tasync setAutoCompaction(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_compaction\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress compaction.\n\t */\n\tasync abortCompaction(): Promise<void> {\n\t\tawait this.send({ type: \"abort_compaction\" });\n\t}\n\n\t/**\n\t * Set auto-retry enabled/disabled.\n\t */\n\tasync setAutoRetry(enabled: boolean): Promise<void> {\n\t\tawait this.send({ type: \"set_auto_retry\", enabled });\n\t}\n\n\t/**\n\t * Abort in-progress retry.\n\t */\n\tasync abortRetry(): Promise<void> {\n\t\tawait this.send({ type: \"abort_retry\" });\n\t}\n\n\t/**\n\t * Execute a bash command.\n\t */\n\tasync bash(command: string): Promise<BashResult> {\n\t\tconst response = await this.send({ type: \"bash\", command });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Abort running bash command.\n\t */\n\tasync abortBash(): Promise<void> {\n\t\tawait this.send({ type: \"abort_bash\" });\n\t}\n\n\t/**\n\t * Get session statistics.\n\t */\n\tasync getSessionStats(): Promise<SessionStats> {\n\t\tconst response = await this.send({ type: \"get_session_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get performance statistics.\n\t */\n\tasync getPerformanceStats(): Promise<{\n\t\tmodels: Array<{ provider: string; modelId: string; median: number; mean: number; count: number }>;\n\t}> {\n\t\tconst response = await this.send({ type: \"get_performance_stats\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t */\n\tasync exportHtml(outputPath?: string): Promise<{ path: string }> {\n\t\tconst response = await this.send({ type: \"export_html\", outputPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Switch to a different session file.\n\t * @returns Object with `cancelled: true` if an extension cancelled the switch\n\t */\n\tasync switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"switch_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Delete a session file, trying trash first and falling back to unlink.\n\t * Throws on failure, including when attempting to delete the active session.\n\t */\n\tasync deleteSession(sessionPath: string): Promise<{ method: \"trash\" | \"unlink\" }> {\n\t\tconst response = await this.send({ type: \"delete_session\", sessionPath });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Fork from a specific message.\n\t * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)\n\t */\n\tasync fork(entryId: string): Promise<{ text: string; cancelled: boolean }> {\n\t\tconst response = await this.send({ type: \"fork\", entryId });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get messages available for forking.\n\t */\n\tasync getForkMessages(): Promise<Array<{ entryId: string; text: string }>> {\n\t\tconst response = await this.send({ type: \"get_fork_messages\" });\n\t\treturn this.getData<{ messages: Array<{ entryId: string; text: string }> }>(response).messages;\n\t}\n\n\t/**\n\t * Get the session tree and current leaf.\n\t */\n\tasync getTree(): Promise<{ roots: RpcTreeNode[]; leafId: string | null }> {\n\t\tconst response = await this.send({ type: \"get_tree\" });\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Navigate to a session tree entry.\n\t * @param targetId Entry ID to navigate to\n\t * @param options Navigation options; timeoutMs is client-side only and is not sent over RPC\n\t * @returns Object with `cancelled: true` if navigation was cancelled, and `editorText` when re-editing a message\n\t */\n\tasync navigateTree(\n\t\ttargetId: string,\n\t\toptions?: {\n\t\t\tsummarize?: boolean;\n\t\t\tcustomInstructions?: string;\n\t\t\treplaceInstructions?: boolean;\n\t\t\tlabel?: string;\n\t\t\ttimeoutMs?: number;\n\t\t},\n\t): Promise<{ cancelled: boolean; editorText?: string }> {\n\t\tconst { timeoutMs = 300000, ...commandOptions } = options ?? {};\n\t\tconst response = await this.send({ type: \"navigate_tree\", targetId, ...commandOptions }, timeoutMs);\n\t\treturn this.getData(response);\n\t}\n\n\t/**\n\t * Get text of last assistant message.\n\t */\n\tasync getLastAssistantText(): Promise<string | null> {\n\t\tconst response = await this.send({ type: \"get_last_assistant_text\" });\n\t\treturn this.getData<{ text: string | null }>(response).text;\n\t}\n\n\t/**\n\t * Set the session display name.\n\t */\n\tasync setSessionName(name: string): Promise<void> {\n\t\tawait this.send({ type: \"set_session_name\", name });\n\t}\n\n\t/**\n\t * Get all messages in the session.\n\t */\n\tasync getMessages(): Promise<AgentMessage[]> {\n\t\tconst response = await this.send({ type: \"get_messages\" });\n\t\treturn this.getData<{ messages: AgentMessage[] }>(response).messages;\n\t}\n\n\t/**\n\t * Get available commands (extension commands, prompt templates, skills).\n\t */\n\tasync getCommands(): Promise<RpcSlashCommand[]> {\n\t\tconst response = await this.send({ type: \"get_commands\" });\n\t\treturn this.getData<{ commands: RpcSlashCommand[] }>(response).commands;\n\t}\n\n\t/**\n\t * List sessions for the current working directory.\n\t * Returns sessions sorted by most recently modified first.\n\t */\n\tasync listSessions(): Promise<RpcSessionInfo[]> {\n\t\tconst response = await this.send({ type: \"list_sessions\" });\n\t\treturn this.getData<{ sessions: RpcSessionInfo[] }>(response).sessions;\n\t}\n\n\t/**\n\t * List sessions across all projects.\n\t * Returns sessions sorted by most recently modified first. May be slow with many sessions.\n\t */\n\tasync listAllSessions(): Promise<RpcSessionInfo[]> {\n\t\tconst response = await this.send({ type: \"list_all_sessions\" });\n\t\treturn this.getData<{ sessions: RpcSessionInfo[] }>(response).sessions;\n\t}\n\n\t/**\n\t * List background subagents tracked by the server's registry (running and\n\t * recently completed), including session dir/file paths where known.\n\t */\n\tasync listBackgroundAgents(): Promise<RpcBackgroundAgentInfo[]> {\n\t\tconst response = await this.send({ type: \"list_background_agents\" });\n\t\treturn this.getData<{ agents: RpcBackgroundAgentInfo[] }>(response).agents;\n\t}\n\n\t/**\n\t * List discoverable subagent types for the server's current working directory.\n\t */\n\tasync listAgentTypes(): Promise<RpcAgentTypeInfo[]> {\n\t\tconst response = await this.send({ type: \"list_agent_types\" });\n\t\treturn this.getData<{ agentTypes: RpcAgentTypeInfo[] }>(response).agentTypes;\n\t}\n\n\t/**\n\t * Answer an extension UI request (select/confirm/input/editor) previously\n\t * received as an `extension_ui_request` event. Fire-and-forget: the server\n\t * does not send a response to this message.\n\t */\n\tsendExtensionUIResponse(response: RpcExtensionUIResponse): void {\n\t\tif (this._dead || !this.process?.stdin) {\n\t\t\tconst cause = this.spawnError ? ` Spawn error: ${this.spawnError.message}.` : \"\";\n\t\t\tthrow new Error(`RPC process is not running.${cause} Stderr: ${this.stderr}`);\n\t\t}\n\t\tthis.process.stdin.write(serializeJsonLine(response));\n\t}\n\n\t/**\n\t * Get the dreb version.\n\t */\n\tasync getVersion(): Promise<string> {\n\t\tconst response = await this.send({ type: \"get_version\" });\n\t\treturn this.getData<{ version: string }>(response).version;\n\t}\n\n\t/**\n\t * Get the persistent default settings (SettingsManager-backed).\n\t * These seed fresh runtimes — for live session state use getState().\n\t */\n\tasync getSettings(): Promise<RpcSettingsSnapshot> {\n\t\tconst response = await this.send({ type: \"get_settings\" });\n\t\treturn this.getData<RpcSettingsSnapshot>(response);\n\t}\n\n\t/**\n\t * Update persistent default settings. Validates the whole payload before applying\n\t * anything (atomic: on any invalid field, nothing changes). Does NOT touch live\n\t * session state — use setModel/setThinkingLevel/etc. for that.\n\t * Returns the full settings snapshot after the write, plus any loud warnings\n\t * (for example project-level agent model overrides shadowing global writes).\n\t */\n\tasync setSettings(settings: RpcSettingsUpdate): Promise<RpcSettingsSetResult> {\n\t\tconst response = await this.send({ type: \"set_settings\", settings });\n\t\treturn this.getData<RpcSettingsSetResult>(response);\n\t}\n\n\t// =========================================================================\n\t// Helpers\n\t// =========================================================================\n\n\t/**\n\t * Wait for agent to become idle (no streaming).\n\t * Resolves when agent_end event is received.\n\t */\n\twaitForIdle(timeout = 60000): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout waiting for agent to become idle. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Collect events until agent becomes idle.\n\t */\n\tcollectEvents(timeout = 60000): Promise<AgentSessionEvent[]> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst events: AgentSessionEvent[] = [];\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tunsubscribe();\n\t\t\t\treject(new Error(`Timeout collecting events. Stderr: ${this.stderr}`));\n\t\t\t}, timeout);\n\n\t\t\tconst unsubscribe = this.onEvent((event) => {\n\t\t\t\tevents.push(event);\n\t\t\t\tif (event.type === \"agent_end\") {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tunsubscribe();\n\t\t\t\t\tresolve(events);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Send prompt and wait for completion, returning all events.\n\t */\n\tasync promptAndWait(message: string, images?: ImageContent[], timeout = 60000): Promise<AgentSessionEvent[]> {\n\t\tconst eventsPromise = this.collectEvents(timeout);\n\t\tawait this.prompt(message, images);\n\t\treturn eventsPromise;\n\t}\n\n\t// =========================================================================\n\t// Internal\n\t// =========================================================================\n\n\t/**\n\t * Mark the client dead and reject every in-flight request with `message`.\n\t * Shared by the 'exit' and 'error' handlers so both failure paths surface\n\t * loudly and consistently instead of leaving callers hanging.\n\t */\n\tprivate failPendingRequests(message: string): void {\n\t\tthis._dead = true;\n\t\tfor (const pending of this.pendingRequests.values()) {\n\t\t\tpending.reject(new Error(message));\n\t\t}\n\t\tthis.pendingRequests.clear();\n\t}\n\n\tprivate notifyExitListeners(info: RpcExitInfo): void {\n\t\tfor (const listener of this.exitListeners) {\n\t\t\tlistener(info);\n\t\t}\n\t}\n\n\tprivate handleLine(line: string): void {\n\t\ttry {\n\t\t\tconst data = JSON.parse(line);\n\n\t\t\t// Check if it's a response to a pending request\n\t\t\tif (data.type === \"response\" && data.id && this.pendingRequests.has(data.id)) {\n\t\t\t\tconst pending = this.pendingRequests.get(data.id)!;\n\t\t\t\tthis.pendingRequests.delete(data.id);\n\t\t\t\tpending.resolve(data as RpcResponse);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Otherwise it's an event\n\t\t\tfor (const listener of this.eventListeners) {\n\t\t\t\tlistener(data as AgentSessionEvent);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore non-JSON lines\n\t\t}\n\t}\n\n\tprivate async send(command: RpcCommandBody, timeoutMs = 30000): Promise<RpcResponse> {\n\t\tif (this._dead || !this.process?.stdin) {\n\t\t\t// Surface the real spawn cause (e.g. uid/gid EPERM) if one was captured,\n\t\t\t// rather than a generic message that hides why the process is gone.\n\t\t\tthrow new Error(\n\t\t\t\tthis.spawnError ? `RPC process not running: ${this.spawnError.message}` : \"RPC process not running\",\n\t\t\t);\n\t\t}\n\n\t\tconst id = `req_${++this.requestId}`;\n\t\tconst fullCommand = { ...command, id } as RpcCommand;\n\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\tthis.pendingRequests.delete(id);\n\t\t\t\treject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));\n\t\t\t}, timeoutMs);\n\n\t\t\tthis.pendingRequests.set(id, {\n\t\t\t\tresolve: (response) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\tresolve(response);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tclearTimeout(timeout);\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.process!.stdin!.write(serializeJsonLine(fullCommand));\n\t\t});\n\t}\n\n\tprivate getData<T>(response: RpcResponse): T {\n\t\tif (!response.success) {\n\t\t\tconst errorResponse = response as Extract<RpcResponse, { success: false }>;\n\t\t\tthrow new Error(errorResponse.error);\n\t\t}\n\t\t// Type assertion: we trust response.data matches T based on the command sent.\n\t\t// This is safe because each public method specifies the correct T for its command.\n\t\tconst successResponse = response as Extract<RpcResponse, { success: true; data: unknown }>;\n\t\treturn successResponse.data as T;\n\t}\n}\n"]}
@@ -21,6 +21,7 @@ export class RpcClient {
21
21
  process = null;
22
22
  stopReadingStdout = null;
23
23
  eventListeners = [];
24
+ exitListeners = new Set();
24
25
  pendingRequests = new Map();
25
26
  requestId = 0;
26
27
  stderr = "";
@@ -70,6 +71,7 @@ export class RpcClient {
70
71
  if (this.process !== procRef)
71
72
  return;
72
73
  this.failPendingRequests(`RPC process exited with code ${code}, signal ${signal}`);
74
+ this.notifyExitListeners({ code, signal });
73
75
  });
74
76
  // Spawn failures surface asynchronously as an 'error' event rather than a
75
77
  // thrown exception (e.g. EPERM when dropping to a uid/gid the parent lacks
@@ -83,6 +85,7 @@ export class RpcClient {
83
85
  return;
84
86
  this.spawnError = err;
85
87
  this.failPendingRequests(`RPC process failed to spawn: ${err.message}`);
88
+ this.notifyExitListeners({ error: err });
86
89
  });
87
90
  // Set up strict JSONL reader for stdout.
88
91
  this.stopReadingStdout = attachJsonlLineReader(this.process.stdout, (line) => {
@@ -157,6 +160,15 @@ export class RpcClient {
157
160
  }
158
161
  };
159
162
  }
163
+ /**
164
+ * Subscribe to child process death/spawn-failure notifications.
165
+ */
166
+ onExit(listener) {
167
+ this.exitListeners.add(listener);
168
+ return () => {
169
+ this.exitListeners.delete(listener);
170
+ };
171
+ }
160
172
  /**
161
173
  * Get collected stderr output (useful for debugging).
162
174
  */
@@ -208,6 +220,27 @@ export class RpcClient {
208
220
  const response = await this.send({ type: "get_state" });
209
221
  return this.getData(response);
210
222
  }
223
+ /**
224
+ * Get loaded context/resources metadata (paths/names only, no file contents).
225
+ */
226
+ async getResources() {
227
+ const response = await this.send({ type: "get_resources" });
228
+ return this.getData(response);
229
+ }
230
+ /**
231
+ * Get current git branch for the agent cwd.
232
+ */
233
+ async getGitBranch() {
234
+ const response = await this.send({ type: "get_git_branch" });
235
+ return this.getData(response).branch;
236
+ }
237
+ /**
238
+ * Get cached same-day cost across all sessions, primed by the server on first call.
239
+ */
240
+ async getDailyCost() {
241
+ const response = await this.send({ type: "get_daily_cost" });
242
+ return this.getData(response).cost;
243
+ }
211
244
  /**
212
245
  * Set model by provider and ID.
213
246
  */
@@ -279,6 +312,20 @@ export class RpcClient {
279
312
  async setFollowUpMode(mode) {
280
313
  await this.send({ type: "set_follow_up_mode", mode });
281
314
  }
315
+ /**
316
+ * Get pending steering and follow-up messages without clearing them.
317
+ */
318
+ async getPendingMessages() {
319
+ const response = await this.send({ type: "get_pending_messages" });
320
+ return this.getData(response);
321
+ }
322
+ /**
323
+ * Clear pending steering and follow-up messages, returning the cleared text.
324
+ */
325
+ async clearPendingMessages() {
326
+ const response = await this.send({ type: "clear_pending_messages" });
327
+ return this.getData(response);
328
+ }
282
329
  /**
283
330
  * Compact session context.
284
331
  */
@@ -292,6 +339,12 @@ export class RpcClient {
292
339
  async setAutoCompaction(enabled) {
293
340
  await this.send({ type: "set_auto_compaction", enabled });
294
341
  }
342
+ /**
343
+ * Abort in-progress compaction.
344
+ */
345
+ async abortCompaction() {
346
+ await this.send({ type: "abort_compaction" });
347
+ }
295
348
  /**
296
349
  * Set auto-retry enabled/disabled.
297
350
  */
@@ -346,6 +399,14 @@ export class RpcClient {
346
399
  const response = await this.send({ type: "switch_session", sessionPath });
347
400
  return this.getData(response);
348
401
  }
402
+ /**
403
+ * Delete a session file, trying trash first and falling back to unlink.
404
+ * Throws on failure, including when attempting to delete the active session.
405
+ */
406
+ async deleteSession(sessionPath) {
407
+ const response = await this.send({ type: "delete_session", sessionPath });
408
+ return this.getData(response);
409
+ }
349
410
  /**
350
411
  * Fork from a specific message.
351
412
  * @returns Object with `text` (the message text) and `cancelled` (if extension cancelled)
@@ -361,6 +422,24 @@ export class RpcClient {
361
422
  const response = await this.send({ type: "get_fork_messages" });
362
423
  return this.getData(response).messages;
363
424
  }
425
+ /**
426
+ * Get the session tree and current leaf.
427
+ */
428
+ async getTree() {
429
+ const response = await this.send({ type: "get_tree" });
430
+ return this.getData(response);
431
+ }
432
+ /**
433
+ * Navigate to a session tree entry.
434
+ * @param targetId Entry ID to navigate to
435
+ * @param options Navigation options; timeoutMs is client-side only and is not sent over RPC
436
+ * @returns Object with `cancelled: true` if navigation was cancelled, and `editorText` when re-editing a message
437
+ */
438
+ async navigateTree(targetId, options) {
439
+ const { timeoutMs = 300000, ...commandOptions } = options ?? {};
440
+ const response = await this.send({ type: "navigate_tree", targetId, ...commandOptions }, timeoutMs);
441
+ return this.getData(response);
442
+ }
364
443
  /**
365
444
  * Get text of last assistant message.
366
445
  */
@@ -396,6 +475,41 @@ export class RpcClient {
396
475
  const response = await this.send({ type: "list_sessions" });
397
476
  return this.getData(response).sessions;
398
477
  }
478
+ /**
479
+ * List sessions across all projects.
480
+ * Returns sessions sorted by most recently modified first. May be slow with many sessions.
481
+ */
482
+ async listAllSessions() {
483
+ const response = await this.send({ type: "list_all_sessions" });
484
+ return this.getData(response).sessions;
485
+ }
486
+ /**
487
+ * List background subagents tracked by the server's registry (running and
488
+ * recently completed), including session dir/file paths where known.
489
+ */
490
+ async listBackgroundAgents() {
491
+ const response = await this.send({ type: "list_background_agents" });
492
+ return this.getData(response).agents;
493
+ }
494
+ /**
495
+ * List discoverable subagent types for the server's current working directory.
496
+ */
497
+ async listAgentTypes() {
498
+ const response = await this.send({ type: "list_agent_types" });
499
+ return this.getData(response).agentTypes;
500
+ }
501
+ /**
502
+ * Answer an extension UI request (select/confirm/input/editor) previously
503
+ * received as an `extension_ui_request` event. Fire-and-forget: the server
504
+ * does not send a response to this message.
505
+ */
506
+ sendExtensionUIResponse(response) {
507
+ if (this._dead || !this.process?.stdin) {
508
+ const cause = this.spawnError ? ` Spawn error: ${this.spawnError.message}.` : "";
509
+ throw new Error(`RPC process is not running.${cause} Stderr: ${this.stderr}`);
510
+ }
511
+ this.process.stdin.write(serializeJsonLine(response));
512
+ }
399
513
  /**
400
514
  * Get the dreb version.
401
515
  */
@@ -403,6 +517,25 @@ export class RpcClient {
403
517
  const response = await this.send({ type: "get_version" });
404
518
  return this.getData(response).version;
405
519
  }
520
+ /**
521
+ * Get the persistent default settings (SettingsManager-backed).
522
+ * These seed fresh runtimes — for live session state use getState().
523
+ */
524
+ async getSettings() {
525
+ const response = await this.send({ type: "get_settings" });
526
+ return this.getData(response);
527
+ }
528
+ /**
529
+ * Update persistent default settings. Validates the whole payload before applying
530
+ * anything (atomic: on any invalid field, nothing changes). Does NOT touch live
531
+ * session state — use setModel/setThinkingLevel/etc. for that.
532
+ * Returns the full settings snapshot after the write, plus any loud warnings
533
+ * (for example project-level agent model overrides shadowing global writes).
534
+ */
535
+ async setSettings(settings) {
536
+ const response = await this.send({ type: "set_settings", settings });
537
+ return this.getData(response);
538
+ }
406
539
  // =========================================================================
407
540
  // Helpers
408
541
  // =========================================================================
@@ -468,6 +601,11 @@ export class RpcClient {
468
601
  }
469
602
  this.pendingRequests.clear();
470
603
  }
604
+ notifyExitListeners(info) {
605
+ for (const listener of this.exitListeners) {
606
+ listener(info);
607
+ }
608
+ }
471
609
  handleLine(line) {
472
610
  try {
473
611
  const data = JSON.parse(line);
@@ -487,7 +625,7 @@ export class RpcClient {
487
625
  // Ignore non-JSON lines
488
626
  }
489
627
  }
490
- async send(command) {
628
+ async send(command, timeoutMs = 30000) {
491
629
  if (this._dead || !this.process?.stdin) {
492
630
  // Surface the real spawn cause (e.g. uid/gid EPERM) if one was captured,
493
631
  // rather than a generic message that hides why the process is gone.
@@ -499,7 +637,7 @@ export class RpcClient {
499
637
  const timeout = setTimeout(() => {
500
638
  this.pendingRequests.delete(id);
501
639
  reject(new Error(`Timeout waiting for response to ${command.type}. Stderr: ${this.stderr}`));
502
- }, 30000);
640
+ }, timeoutMs);
503
641
  this.pendingRequests.set(id, {
504
642
  resolve: (response) => {
505
643
  clearTimeout(timeout);