@gajae-code/coding-agent 0.3.0 → 0.3.1

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 (175) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/types/async/job-manager.d.ts +7 -0
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/commands/deep-interview.d.ts +3 -0
  5. package/dist/types/config/keybindings.d.ts +5 -0
  6. package/dist/types/config/settings-schema.d.ts +4 -4
  7. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  8. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  9. package/dist/types/deep-interview/render-middleware.d.ts +1 -0
  10. package/dist/types/eval/py/executor.d.ts +2 -0
  11. package/dist/types/eval/py/kernel.d.ts +2 -0
  12. package/dist/types/exec/bash-executor.d.ts +10 -0
  13. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  14. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  15. package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
  16. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  17. package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
  18. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  19. package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
  20. package/dist/types/hooks/skill-state.d.ts +21 -0
  21. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  22. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  23. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  24. package/dist/types/internal-urls/types.d.ts +4 -0
  25. package/dist/types/lsp/index.d.ts +10 -10
  26. package/dist/types/modes/bridge/auth.d.ts +12 -0
  27. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  28. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  29. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  30. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  31. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  32. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  33. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  34. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  35. package/dist/types/modes/components/status-line.d.ts +2 -0
  36. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  37. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  38. package/dist/types/modes/index.d.ts +1 -0
  39. package/dist/types/modes/interactive-mode.d.ts +1 -0
  40. package/dist/types/modes/jobs-observer.d.ts +57 -0
  41. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  42. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  43. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  44. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  45. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  46. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  47. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  48. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  49. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  50. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  51. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  52. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  53. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  54. package/dist/types/modes/types.d.ts +1 -0
  55. package/dist/types/sdk.d.ts +2 -0
  56. package/dist/types/session/agent-session.d.ts +11 -1
  57. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
  58. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  59. package/dist/types/task/id.d.ts +7 -0
  60. package/dist/types/task/index.d.ts +5 -0
  61. package/dist/types/task/receipt.d.ts +85 -0
  62. package/dist/types/task/spawn-gate.d.ts +38 -0
  63. package/dist/types/task/types.d.ts +143 -11
  64. package/dist/types/tools/cron.d.ts +6 -0
  65. package/dist/types/tools/index.d.ts +2 -0
  66. package/dist/types/tools/path-utils.d.ts +1 -0
  67. package/dist/types/tools/subagent.d.ts +15 -0
  68. package/package.json +7 -7
  69. package/scripts/build-binary.ts +7 -0
  70. package/src/async/job-manager.ts +36 -0
  71. package/src/cli/args.ts +9 -2
  72. package/src/commands/deep-interview.ts +1 -0
  73. package/src/commands/harness.ts +289 -19
  74. package/src/commands/launch.ts +2 -2
  75. package/src/commands/state.ts +2 -1
  76. package/src/commands/team.ts +22 -4
  77. package/src/config/keybindings.ts +6 -0
  78. package/src/config/settings-schema.ts +6 -3
  79. package/src/dap/client.ts +17 -3
  80. package/src/debug/crash-diagnostics.ts +223 -0
  81. package/src/debug/runtime-gauges.ts +20 -0
  82. package/src/deep-interview/render-middleware.ts +6 -0
  83. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  84. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  85. package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
  86. package/src/eval/py/executor.ts +21 -1
  87. package/src/eval/py/kernel.ts +15 -0
  88. package/src/exec/bash-executor.ts +41 -0
  89. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  90. package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
  91. package/src/gjc-runtime/ralplan-runtime.ts +213 -36
  92. package/src/gjc-runtime/state-migrations.ts +54 -7
  93. package/src/gjc-runtime/state-runtime.ts +461 -64
  94. package/src/gjc-runtime/state-schema.ts +192 -0
  95. package/src/gjc-runtime/state-writer.ts +32 -1
  96. package/src/gjc-runtime/team-runtime.ts +177 -105
  97. package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
  98. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  99. package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
  100. package/src/gjc-runtime/workflow-manifest.ts +3 -1
  101. package/src/harness-control-plane/control-endpoint.ts +19 -8
  102. package/src/harness-control-plane/owner.ts +57 -10
  103. package/src/harness-control-plane/state-machine.ts +2 -1
  104. package/src/hooks/skill-state.ts +176 -26
  105. package/src/internal-urls/agent-protocol.ts +68 -21
  106. package/src/internal-urls/artifact-protocol.ts +12 -17
  107. package/src/internal-urls/docs-index.generated.ts +3 -2
  108. package/src/internal-urls/registry-helpers.ts +19 -16
  109. package/src/internal-urls/types.ts +4 -0
  110. package/src/lsp/client.ts +18 -2
  111. package/src/main.ts +21 -5
  112. package/src/modes/bridge/auth.ts +41 -0
  113. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  114. package/src/modes/bridge/bridge-mode.ts +520 -0
  115. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  116. package/src/modes/bridge/event-stream.ts +70 -0
  117. package/src/modes/components/custom-editor.ts +101 -0
  118. package/src/modes/components/hook-selector.ts +61 -18
  119. package/src/modes/components/jobs-overlay-model.ts +109 -0
  120. package/src/modes/components/jobs-overlay.ts +172 -0
  121. package/src/modes/components/status-line/presets.ts +7 -5
  122. package/src/modes/components/status-line/segments.ts +25 -0
  123. package/src/modes/components/status-line/types.ts +2 -0
  124. package/src/modes/components/status-line.ts +9 -1
  125. package/src/modes/controllers/extension-ui-controller.ts +39 -3
  126. package/src/modes/controllers/input-controller.ts +97 -9
  127. package/src/modes/controllers/selector-controller.ts +29 -0
  128. package/src/modes/index.ts +1 -0
  129. package/src/modes/interactive-mode.ts +27 -0
  130. package/src/modes/jobs-observer.ts +204 -0
  131. package/src/modes/rpc/host-tools.ts +1 -186
  132. package/src/modes/rpc/host-uris.ts +1 -235
  133. package/src/modes/rpc/rpc-client.ts +25 -10
  134. package/src/modes/rpc/rpc-mode.ts +12 -381
  135. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  136. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  137. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  138. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  139. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  140. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  141. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  142. package/src/modes/shared/agent-wire/responses.ts +17 -0
  143. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  144. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  145. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  146. package/src/modes/types.ts +1 -0
  147. package/src/prompts/tools/subagent.md +12 -7
  148. package/src/prompts/tools/task-summary.md +3 -9
  149. package/src/prompts/tools/task.md +5 -1
  150. package/src/sdk.ts +4 -0
  151. package/src/session/agent-session.ts +214 -38
  152. package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
  153. package/src/skill-state/workflow-state-contract.ts +7 -4
  154. package/src/skill-state/workflow-state-version.ts +3 -0
  155. package/src/slash-commands/builtin-registry.ts +8 -0
  156. package/src/task/executor.ts +29 -5
  157. package/src/task/id.ts +33 -0
  158. package/src/task/index.ts +257 -67
  159. package/src/task/output-manager.ts +5 -4
  160. package/src/task/receipt.ts +297 -0
  161. package/src/task/render.ts +48 -131
  162. package/src/task/spawn-gate.ts +132 -0
  163. package/src/task/types.ts +48 -7
  164. package/src/tools/ask.ts +73 -33
  165. package/src/tools/ast-edit.ts +1 -0
  166. package/src/tools/ast-grep.ts +1 -0
  167. package/src/tools/bash.ts +1 -1
  168. package/src/tools/cron.ts +48 -0
  169. package/src/tools/find.ts +4 -1
  170. package/src/tools/index.ts +2 -0
  171. package/src/tools/path-utils.ts +3 -2
  172. package/src/tools/read.ts +1 -0
  173. package/src/tools/search.ts +1 -0
  174. package/src/tools/skill.ts +6 -1
  175. package/src/tools/subagent.ts +237 -84
@@ -186,7 +186,12 @@ export class RpcClient {
186
186
  cwd: this.options.cwd,
187
187
  env: { ...Bun.env, ...this.options.env },
188
188
  stdin: "pipe",
189
+ stderr: "full",
189
190
  });
191
+ const startupStderrPromise = this.#process.stderr
192
+ ? new Response(this.#process.stderr).text().catch(() => "")
193
+ : Promise.resolve("");
194
+ const getStartupStderr = async () => this.#process?.peekStderr() || (await startupStderrPromise);
190
195
 
191
196
  // Wait for the "ready" signal or process exit
192
197
  const { promise: readyPromise, resolve: readyResolve, reject: readyReject } = Promise.withResolvers<void>();
@@ -203,10 +208,20 @@ export class RpcClient {
203
208
  }
204
209
  this.#handleLine(line);
205
210
  }
206
- // Stream ended without ready signal — process exited
211
+ // Stream ended without ready signal — process exited. Wait for the
212
+ // managed process wrapper so stderr is fully drained before reporting.
207
213
  if (!readySettled) {
208
- readySettled = true;
209
- readyReject(new Error(`Agent process exited before ready. Stderr: ${this.#process?.peekStderr() ?? ""}`));
214
+ const proc = this.#process;
215
+ const exitCode = proc ? await proc.exited.catch(() => proc.exitCode ?? -1) : undefined;
216
+ if (!readySettled) {
217
+ const stderr = await getStartupStderr();
218
+ readySettled = true;
219
+ readyReject(
220
+ new Error(
221
+ `Agent process exited${exitCode === undefined ? "" : ` with code ${exitCode}`} before ready. Stderr: ${stderr}`,
222
+ ),
223
+ );
224
+ }
210
225
  }
211
226
  })().catch((err: Error) => {
212
227
  if (!readySettled) {
@@ -216,12 +231,12 @@ export class RpcClient {
216
231
  });
217
232
 
218
233
  // Also race against process exit (in case stdout closes before we read it)
219
- void this.#process.exited.then((exitCode: number) => {
234
+ void this.#process.exited.then(async (exitCode: number) => {
220
235
  if (!readySettled) {
236
+ const stderr = await getStartupStderr();
237
+ if (readySettled) return;
221
238
  readySettled = true;
222
- readyReject(
223
- new Error(`Agent process exited with code ${exitCode}. Stderr: ${this.#process?.peekStderr() ?? ""}`),
224
- );
239
+ readyReject(new Error(`Agent process exited with code ${exitCode}. Stderr: ${stderr}`));
225
240
  }
226
241
  });
227
242
 
@@ -229,9 +244,9 @@ export class RpcClient {
229
244
  const readyTimeout = this.#startTimeout(30000, () => {
230
245
  if (readySettled) return;
231
246
  readySettled = true;
232
- readyReject(
233
- new Error(`Timeout waiting for agent to become ready. Stderr: ${this.#process?.peekStderr() ?? ""}`),
234
- );
247
+ void getStartupStderr().then(stderr => {
248
+ readyReject(new Error(`Timeout waiting for agent to become ready. Stderr: ${stderr}`));
249
+ });
235
250
  });
236
251
 
237
252
  try {
@@ -10,7 +10,6 @@
10
10
  * - Events: AgentSessionEvent objects streamed as they occur
11
11
  * - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
12
12
  */
13
- import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
14
13
  import { $env, readJsonl, Snowflake } from "@gajae-code/utils";
15
14
  import type {
16
15
  ExtensionUIContext,
@@ -20,6 +19,8 @@ import type {
20
19
  import { type Theme, theme } from "../../modes/theme/theme";
21
20
  import type { AgentSession } from "../../session/agent-session";
22
21
  import { initializeExtensions } from "../runtime-init";
22
+ import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
23
+ import { rpcError as error } from "../shared/agent-wire/responses";
23
24
  import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
24
25
  import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
25
26
  import type {
@@ -28,11 +29,9 @@ import type {
28
29
  RpcExtensionUIResponse,
29
30
  RpcHostToolCallRequest,
30
31
  RpcHostToolCancelRequest,
31
- RpcHostToolDefinition,
32
32
  RpcHostUriCancelRequest,
33
33
  RpcHostUriRequest,
34
34
  RpcResponse,
35
- RpcSessionState,
36
35
  } from "./rpc-types";
37
36
 
38
37
  // Re-export types for consumers
@@ -54,30 +53,6 @@ type RpcOutput = (
54
53
  | object,
55
54
  ) => void;
56
55
 
57
- function normalizeHostToolDefinitions(tools: RpcHostToolDefinition[]): RpcHostToolDefinition[] {
58
- return tools.map((tool, index) => {
59
- const name = typeof tool.name === "string" ? tool.name.trim() : "";
60
- if (!name) {
61
- throw new Error(`Host tool at index ${index} must provide a non-empty name`);
62
- }
63
- const description = typeof tool.description === "string" ? tool.description.trim() : "";
64
- if (!description) {
65
- throw new Error(`Host tool "${name}" must provide a non-empty description`);
66
- }
67
- if (!tool.parameters || typeof tool.parameters !== "object" || Array.isArray(tool.parameters)) {
68
- throw new Error(`Host tool "${name}" must provide a JSON Schema object`);
69
- }
70
- const label = typeof tool.label === "string" && tool.label.trim() ? tool.label.trim() : name;
71
- return {
72
- name,
73
- label,
74
- description,
75
- parameters: tool.parameters,
76
- hidden: tool.hidden === true,
77
- };
78
- });
79
- }
80
-
81
56
  function parseValueDialogResponse(
82
57
  response: RpcExtensionUIResponse,
83
58
  dialogOptions: ExtensionUIDialogOptions | undefined,
@@ -181,21 +156,6 @@ export async function runRpcMode(
181
156
  };
182
157
  const emitRpcTitles = shouldEmitRpcTitles();
183
158
 
184
- const success = <T extends RpcCommand["type"]>(
185
- id: string | undefined,
186
- command: T,
187
- data?: object | null,
188
- ): RpcResponse => {
189
- if (data === undefined) {
190
- return { id, type: "response", command, success: true } as RpcResponse;
191
- }
192
- return { id, type: "response", command, success: true, data } as RpcResponse;
193
- };
194
-
195
- const error = (id: string | undefined, command: string, message: string): RpcResponse => {
196
- return { id, type: "response", command, success: false, error: message };
197
- };
198
-
199
159
  const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
200
160
  const hostToolBridge = new RpcHostToolBridge(output);
201
161
  const hostUriBridge = new RpcHostUriBridge(output);
@@ -450,345 +410,16 @@ export async function runRpcMode(
450
410
  output(event);
451
411
  });
452
412
 
453
- // Handle a single command
454
- const handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {
455
- const id = command.id;
456
-
457
- switch (command.type) {
458
- // =================================================================
459
- // Prompting
460
- // =================================================================
461
-
462
- case "prompt": {
463
- // Don't await - events will stream
464
- // Extension commands are executed immediately, file prompt templates are expanded
465
- // If streaming and streamingBehavior specified, queues via steer/followUp
466
- session
467
- .prompt(command.message, {
468
- images: command.images,
469
- streamingBehavior: command.streamingBehavior,
470
- })
471
- .catch(e => output(error(id, "prompt", e.message)));
472
- return success(id, "prompt");
473
- }
474
-
475
- case "steer": {
476
- await session.steer(command.message, command.images);
477
- return success(id, "steer");
478
- }
479
-
480
- case "follow_up": {
481
- await session.followUp(command.message, command.images);
482
- return success(id, "follow_up");
483
- }
484
-
485
- case "abort": {
486
- await session.abort();
487
- return success(id, "abort");
488
- }
489
-
490
- case "abort_and_prompt": {
491
- await session.abort();
492
- session
493
- .prompt(command.message, { images: command.images })
494
- .catch(e => output(error(id, "abort_and_prompt", e.message)));
495
- return success(id, "abort_and_prompt");
496
- }
497
-
498
- case "new_session": {
499
- const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
500
- const cancelled = !(await session.newSession(options));
501
- return success(id, "new_session", { cancelled });
502
- }
503
-
504
- // =================================================================
505
- // State
506
- // =================================================================
507
-
508
- case "get_state": {
509
- const state: RpcSessionState = {
510
- model: session.model,
511
- thinkingLevel: session.thinkingLevel,
512
- isStreaming: session.isStreaming,
513
- isCompacting: session.isCompacting,
514
- steeringMode: session.steeringMode,
515
- followUpMode: session.followUpMode,
516
- interruptMode: session.interruptMode,
517
- sessionFile: session.sessionFile,
518
- sessionId: session.sessionId,
519
- sessionName: session.sessionName,
520
- autoCompactionEnabled: session.autoCompactionEnabled,
521
- messageCount: session.messages.length,
522
- queuedMessageCount: session.queuedMessageCount,
523
- todoPhases: session.getTodoPhases(),
524
- systemPrompt: session.systemPrompt,
525
- dumpTools: session.agent.state.tools.map(tool => ({
526
- name: tool.name,
527
- description: tool.description,
528
- parameters: tool.parameters,
529
- })),
530
- contextUsage: session.getContextUsage(),
531
- };
532
- return success(id, "get_state", state);
533
- }
534
-
535
- case "set_todos": {
536
- session.setTodoPhases(command.phases);
537
- return success(id, "set_todos", { todoPhases: session.getTodoPhases() });
538
- }
539
-
540
- case "set_host_tools": {
541
- const tools = normalizeHostToolDefinitions(command.tools);
542
- const rpcTools = hostToolBridge.setTools(tools);
543
- await session.refreshRpcHostTools(rpcTools);
544
- return success(id, "set_host_tools", { toolNames: tools.map(tool => tool.name) });
545
- }
546
-
547
- case "set_host_uri_schemes": {
548
- try {
549
- const schemes = hostUriBridge.setSchemes(command.schemes);
550
- return success(id, "set_host_uri_schemes", { schemes });
551
- } catch (err) {
552
- return error(id, "set_host_uri_schemes", err instanceof Error ? err.message : String(err));
553
- }
554
- }
555
-
556
- // =================================================================
557
- // Model
558
- // =================================================================
559
-
560
- case "set_model": {
561
- const models = session.getAvailableModels();
562
- const model = models.find(m => m.provider === command.provider && m.id === command.modelId);
563
- if (!model) {
564
- return error(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
565
- }
566
- await session.setModel(model);
567
- return success(id, "set_model", model);
568
- }
569
-
570
- case "cycle_model": {
571
- const result = await session.cycleModel();
572
- if (!result) {
573
- return success(id, "cycle_model", null);
574
- }
575
- return success(id, "cycle_model", result);
576
- }
577
-
578
- case "get_available_models": {
579
- const models = session.getAvailableModels();
580
- return success(id, "get_available_models", { models });
581
- }
582
-
583
- // =================================================================
584
- // Thinking
585
- // =================================================================
586
-
587
- case "set_thinking_level": {
588
- session.setThinkingLevel(command.level);
589
- return success(id, "set_thinking_level");
590
- }
591
-
592
- case "cycle_thinking_level": {
593
- const level = session.cycleThinkingLevel();
594
- if (!level) {
595
- return success(id, "cycle_thinking_level", null);
596
- }
597
- return success(id, "cycle_thinking_level", { level });
598
- }
599
-
600
- // =================================================================
601
- // Queue Modes
602
- // =================================================================
603
-
604
- case "set_steering_mode": {
605
- session.setSteeringMode(command.mode);
606
- return success(id, "set_steering_mode");
607
- }
608
-
609
- case "set_follow_up_mode": {
610
- session.setFollowUpMode(command.mode);
611
- return success(id, "set_follow_up_mode");
612
- }
613
-
614
- case "set_interrupt_mode": {
615
- session.setInterruptMode(command.mode);
616
- return success(id, "set_interrupt_mode");
617
- }
618
-
619
- // =================================================================
620
- // Compaction
621
- // =================================================================
622
-
623
- case "compact": {
624
- const result = await session.compact(command.customInstructions);
625
- return success(id, "compact", result);
626
- }
627
-
628
- case "set_auto_compaction": {
629
- session.setAutoCompactionEnabled(command.enabled);
630
- return success(id, "set_auto_compaction");
631
- }
632
-
633
- // =================================================================
634
- // Retry
635
- // =================================================================
636
-
637
- case "set_auto_retry": {
638
- session.setAutoRetryEnabled(command.enabled);
639
- return success(id, "set_auto_retry");
640
- }
641
-
642
- case "abort_retry": {
643
- session.abortRetry();
644
- return success(id, "abort_retry");
645
- }
646
-
647
- // =================================================================
648
- // Bash
649
- // =================================================================
650
-
651
- case "bash": {
652
- const result = await session.executeBash(command.command);
653
- return success(id, "bash", result);
654
- }
655
-
656
- case "abort_bash": {
657
- session.abortBash();
658
- return success(id, "abort_bash");
659
- }
660
-
661
- // =================================================================
662
- // Session
663
- // =================================================================
664
-
665
- case "get_session_stats": {
666
- const stats = session.getSessionStats();
667
- return success(id, "get_session_stats", stats);
668
- }
669
-
670
- case "export_html": {
671
- const path = await session.exportToHtml(command.outputPath);
672
- return success(id, "export_html", { path });
673
- }
674
-
675
- case "switch_session": {
676
- const cancelled = !(await session.switchSession(command.sessionPath));
677
- return success(id, "switch_session", { cancelled });
678
- }
679
-
680
- case "branch": {
681
- const result = await session.branch(command.entryId);
682
- return success(id, "branch", { text: result.selectedText, cancelled: result.cancelled });
683
- }
684
-
685
- case "get_branch_messages": {
686
- const messages = session.getUserMessagesForBranching();
687
- return success(id, "get_branch_messages", { messages });
688
- }
689
-
690
- case "get_last_assistant_text": {
691
- const text = session.getLastAssistantText();
692
- return success(id, "get_last_assistant_text", { text });
693
- }
694
-
695
- case "set_session_name": {
696
- const name = command.name.trim();
697
- if (!name) {
698
- return error(id, "set_session_name", "Session name cannot be empty");
699
- }
700
- const applied = await session.setSessionName(name, "user");
701
- if (!applied) {
702
- return error(id, "set_session_name", "Session name cannot be empty");
703
- }
704
- return success(id, "set_session_name");
705
- }
706
-
707
- case "handoff": {
708
- const result = await session.handoff(command.customInstructions);
709
- return success(id, "handoff", result ? { savedPath: result.savedPath } : null);
710
- }
711
-
712
- // =================================================================
713
- // Messages
714
- // =================================================================
715
-
716
- case "get_messages": {
717
- return success(id, "get_messages", { messages: session.messages });
718
- }
719
-
720
- // =================================================================
721
- // Login
722
- // =================================================================
723
-
724
- case "get_login_providers": {
725
- const providers = getOAuthProviders().map(provider => ({
726
- id: provider.id,
727
- name: provider.name,
728
- available: provider.available,
729
- authenticated: session.modelRegistry.authStorage.hasAuth(provider.id),
730
- }));
731
- return success(id, "get_login_providers", { providers });
732
- }
733
-
734
- case "login": {
735
- const knownProvider = getOAuthProviders().find(p => p.id === command.providerId);
736
- if (!knownProvider) {
737
- return error(id, "login", `Unknown OAuth provider: ${command.providerId}`);
738
- }
739
- const uiCtx = new RpcExtensionUIContext(pendingExtensionRequests, output);
740
- // Track whether onAuth has fired. Providers that use OAuthCallbackFlow
741
- // always call onAuth first (emit browser URL), then onManualCodeInput as
742
- // a fallback. Providers that require interactive input (API-key paste,
743
- // GitHub Enterprise URL, device-code entry) call onPrompt before onAuth.
744
- // We use this ordering to self-classify at runtime — no static allowlist.
745
- let authEmitted = false;
746
- try {
747
- await session.modelRegistry.authStorage.login(command.providerId, {
748
- onAuth: info => {
749
- authEmitted = true;
750
- output({
751
- type: "extension_ui_request",
752
- id: Snowflake.next() as string,
753
- method: "open_url",
754
- url: info.url,
755
- instructions: info.instructions,
756
- } as RpcExtensionUIRequest);
757
- },
758
- onProgress: message => {
759
- uiCtx.notify(message, "info");
760
- },
761
- onPrompt: () => {
762
- if (!authEmitted) {
763
- // onPrompt called before any auth URL — provider requires
764
- // interactive input that cannot be satisfied headlessly.
765
- return Promise.reject(
766
- new Error(
767
- `Provider '${command.providerId}' requires interactive prompts ` +
768
- "which are not supported in RPC mode. Use the terminal UI to log in.",
769
- ),
770
- );
771
- }
772
- // onAuth has already fired — we are inside OAuthCallbackFlow's
773
- // manual-redirect fallback race. Returning a never-settling promise
774
- // lets the race block until the callback server wins; a rejection
775
- // would be caught as null and spin the while(true) loop.
776
- return new Promise<string>(() => {});
777
- },
778
- });
779
- await session.modelRegistry.refresh();
780
- return success(id, "login", { providerId: command.providerId });
781
- } catch (err: unknown) {
782
- return error(id, "login", err instanceof Error ? err.message : String(err));
783
- }
784
- }
785
-
786
- default: {
787
- const unknownCommand = command as { type: string };
788
- return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
789
- }
790
- }
791
- };
413
+ // Handle a single command through the shared agent-wire dispatcher so RPC
414
+ // and bridge mode use one command surface.
415
+ const handleCommand = (command: RpcCommand): Promise<RpcResponse> =>
416
+ dispatchRpcCommand(command, {
417
+ session,
418
+ output,
419
+ hostToolRegistry: hostToolBridge,
420
+ hostUriRegistry: hostUriBridge,
421
+ createUiContext: () => new RpcExtensionUIContext(pendingExtensionRequests, output),
422
+ });
792
423
 
793
424
  /**
794
425
  * Check if shutdown was requested and perform shutdown if so.