@gajae-code/coding-agent 0.5.0 → 0.5.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 (125) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/types/async/job-manager.d.ts +26 -0
  3. package/dist/types/cli/args.d.ts +1 -0
  4. package/dist/types/cli/list-models.d.ts +6 -0
  5. package/dist/types/commands/gc.d.ts +26 -0
  6. package/dist/types/config/file-lock-gc.d.ts +5 -0
  7. package/dist/types/config/file-lock.d.ts +7 -0
  8. package/dist/types/coordinator/contract.d.ts +1 -1
  9. package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
  10. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
  11. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
  12. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
  13. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
  14. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
  15. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
  16. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
  17. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
  18. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
  19. package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
  20. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  21. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
  22. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
  23. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
  25. package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
  26. package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
  27. package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
  28. package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
  29. package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
  30. package/dist/types/gjc-runtime/tmux-common.d.ts +11 -0
  31. package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
  32. package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
  33. package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
  34. package/dist/types/harness-control-plane/owner.d.ts +7 -0
  35. package/dist/types/harness-control-plane/storage.d.ts +20 -0
  36. package/dist/types/modes/components/hook-selector.d.ts +7 -1
  37. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  38. package/dist/types/modes/rpc/rpc-mode.d.ts +16 -1
  39. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
  40. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
  41. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
  42. package/dist/types/session/agent-session.d.ts +1 -1
  43. package/dist/types/session/blob-store.d.ts +39 -3
  44. package/dist/types/skill-state/workflow-hud.d.ts +14 -0
  45. package/dist/types/tools/ask.d.ts +15 -1
  46. package/dist/types/tools/subagent.d.ts +6 -0
  47. package/package.json +7 -7
  48. package/src/async/job-manager.ts +52 -0
  49. package/src/cli/args.ts +3 -0
  50. package/src/cli/auth-broker-cli.ts +1 -0
  51. package/src/cli/list-models.ts +13 -1
  52. package/src/cli.ts +1 -0
  53. package/src/commands/gc.ts +22 -0
  54. package/src/commands/harness.ts +7 -3
  55. package/src/config/file-lock-gc.ts +181 -0
  56. package/src/config/file-lock.ts +14 -0
  57. package/src/config/model-profiles.ts +24 -15
  58. package/src/coordinator/contract.ts +1 -0
  59. package/src/coordinator-mcp/server.ts +459 -3
  60. package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
  61. package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
  62. package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
  63. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
  64. package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
  65. package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
  66. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
  67. package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
  68. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
  69. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
  70. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
  71. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
  72. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
  73. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
  74. package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
  75. package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
  76. package/src/defaults/gjc-defaults.ts +7 -0
  77. package/src/defaults/gjc-grok-cli.ts +22 -0
  78. package/src/extensibility/extensions/index.ts +1 -0
  79. package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
  80. package/src/gjc-runtime/deep-interview-recorder.ts +417 -0
  81. package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
  82. package/src/gjc-runtime/deep-interview-state.ts +324 -0
  83. package/src/gjc-runtime/gc-render.ts +70 -0
  84. package/src/gjc-runtime/gc-runtime.ts +403 -0
  85. package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
  86. package/src/gjc-runtime/ralplan-runtime.ts +58 -7
  87. package/src/gjc-runtime/state-renderer.ts +12 -3
  88. package/src/gjc-runtime/state-runtime.ts +46 -29
  89. package/src/gjc-runtime/team-gc.ts +49 -0
  90. package/src/gjc-runtime/team-runtime.ts +179 -2
  91. package/src/gjc-runtime/tmux-common.ts +14 -0
  92. package/src/gjc-runtime/tmux-gc.ts +176 -0
  93. package/src/gjc-runtime/tmux-sessions.ts +49 -1
  94. package/src/gjc-runtime/ultragoal-runtime.ts +12 -0
  95. package/src/harness-control-plane/gc-adapter.ts +184 -0
  96. package/src/harness-control-plane/owner.ts +11 -0
  97. package/src/harness-control-plane/storage.ts +70 -0
  98. package/src/internal-urls/docs-index.generated.ts +14 -8
  99. package/src/main.ts +7 -2
  100. package/src/modes/components/hook-selector.ts +19 -0
  101. package/src/modes/components/model-selector.ts +25 -8
  102. package/src/modes/components/status-line/segments.ts +1 -1
  103. package/src/modes/controllers/command-controller.ts +25 -6
  104. package/src/modes/controllers/extension-ui-controller.ts +3 -0
  105. package/src/modes/controllers/selector-controller.ts +1 -0
  106. package/src/modes/rpc/rpc-mode.ts +151 -33
  107. package/src/modes/shared/agent-wire/command-dispatch.ts +278 -261
  108. package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
  109. package/src/modes/shared/agent-wire/session-registry.ts +109 -0
  110. package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
  111. package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
  112. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  113. package/src/sdk.ts +17 -3
  114. package/src/session/agent-session.ts +77 -8
  115. package/src/session/blob-store.ts +59 -3
  116. package/src/session/session-manager.ts +4 -4
  117. package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
  118. package/src/skill-state/workflow-hud.ts +106 -10
  119. package/src/slash-commands/builtin-registry.ts +3 -2
  120. package/src/task/executor.ts +9 -0
  121. package/src/tools/ask.ts +56 -1
  122. package/src/tools/job.ts +3 -2
  123. package/src/tools/monitor.ts +36 -1
  124. package/src/tools/subagent-render.ts +9 -0
  125. package/src/tools/subagent.ts +26 -2
@@ -1,4 +1,5 @@
1
1
  import type { AgentTool } from "@gajae-code/agent-core";
2
+ import { ThinkingLevel } from "@gajae-code/agent-core";
2
3
  import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
3
4
  import { Snowflake } from "@gajae-code/utils";
4
5
  import type { ExtensionUIContext } from "../../../extensibility/extensions";
@@ -127,313 +128,329 @@ export async function dispatchRpcCommand(
127
128
  const denied = preflight();
128
129
  if (denied) return denied;
129
130
 
130
- switch (command.type) {
131
- case "prompt": {
132
- session
133
- .prompt(command.message, {
134
- images: command.images,
135
- streamingBehavior: command.streamingBehavior,
136
- })
137
- .catch(e => output(rpcError(id, "prompt", serializeRpcDispatchError(e))));
138
- return reconcile() ?? rpcSuccess(id, "prompt");
139
- }
140
-
141
- case "steer": {
142
- await session.steer(command.message, command.images);
143
- return rpcSuccess(id, "steer");
144
- }
145
-
146
- case "follow_up": {
147
- await session.followUp(command.message, command.images);
148
- return rpcSuccess(id, "follow_up");
149
- }
150
-
151
- case "abort": {
152
- await session.abort();
153
- return rpcSuccess(id, "abort");
154
- }
131
+ try {
132
+ switch (command.type) {
133
+ case "prompt": {
134
+ session
135
+ .prompt(command.message, {
136
+ images: command.images,
137
+ streamingBehavior: command.streamingBehavior,
138
+ })
139
+ .catch(e => output(rpcError(id, "prompt", serializeRpcDispatchError(e))));
140
+ return reconcile() ?? rpcSuccess(id, "prompt");
141
+ }
155
142
 
156
- case "abort_and_prompt": {
157
- await session.abort();
158
- session
159
- .prompt(command.message, { images: command.images })
160
- .catch(e => output(rpcError(id, "abort_and_prompt", e.message)));
161
- return rpcSuccess(id, "abort_and_prompt");
162
- }
143
+ case "steer": {
144
+ await session.steer(command.message, command.images);
145
+ return rpcSuccess(id, "steer");
146
+ }
163
147
 
164
- case "new_session": {
165
- const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
166
- const cancelled = !(await session.newSession(options));
167
- return rpcSuccess(id, "new_session", { cancelled });
168
- }
148
+ case "follow_up": {
149
+ await session.followUp(command.message, command.images);
150
+ return rpcSuccess(id, "follow_up");
151
+ }
169
152
 
170
- case "get_state": {
171
- const state: RpcSessionState = {
172
- model: session.model,
173
- thinkingLevel: session.thinkingLevel,
174
- isStreaming: session.isStreaming,
175
- isCompacting: session.isCompacting,
176
- steeringMode: session.steeringMode,
177
- followUpMode: session.followUpMode,
178
- interruptMode: session.interruptMode,
179
- sessionFile: session.sessionFile,
180
- sessionId: session.sessionId,
181
- sessionName: session.sessionName,
182
- autoCompactionEnabled: session.autoCompactionEnabled,
183
- messageCount: session.messages.length,
184
- queuedMessageCount: session.queuedMessageCount,
185
- todoPhases: session.getTodoPhases(),
186
- contextUsage: session.getContextUsage(),
187
- };
188
- const include = new Set(command.include ?? []);
189
- if (include.has("systemPrompt")) {
190
- state.systemPrompt = session.systemPrompt;
191
- }
192
- if (include.has("tools") || include.has("dumpTools")) {
193
- state.dumpTools = session.agent.state.tools.map(tool => ({
194
- name: tool.name,
195
- description: tool.description,
196
- parameters: tool.parameters,
197
- }));
153
+ case "abort": {
154
+ await session.abort();
155
+ return rpcSuccess(id, "abort");
198
156
  }
199
- return rpcSuccess(id, "get_state", state);
200
- }
201
157
 
202
- case "set_todos": {
203
- session.setTodoPhases(command.phases);
204
- return rpcSuccess(id, "set_todos", { todoPhases: session.getTodoPhases() });
205
- }
158
+ case "abort_and_prompt": {
159
+ await session.abort();
160
+ session
161
+ .prompt(command.message, { images: command.images })
162
+ .catch(e => output(rpcError(id, "abort_and_prompt", e.message)));
163
+ return rpcSuccess(id, "abort_and_prompt");
164
+ }
206
165
 
207
- case "set_host_tools": {
208
- const tools = normalizeHostToolDefinitions(command.tools);
209
- const rpcTools = hostToolRegistry.setTools(tools);
210
- await session.refreshRpcHostTools(rpcTools);
211
- return rpcSuccess(id, "set_host_tools", { toolNames: tools.map(tool => tool.name) });
212
- }
166
+ case "new_session": {
167
+ const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
168
+ const cancelled = !(await session.newSession(options));
169
+ return rpcSuccess(id, "new_session", { cancelled });
170
+ }
213
171
 
214
- case "set_host_uri_schemes": {
215
- try {
216
- const schemes = hostUriRegistry.setSchemes(command.schemes);
217
- return rpcSuccess(id, "set_host_uri_schemes", { schemes });
218
- } catch (err) {
219
- return rpcError(id, "set_host_uri_schemes", err instanceof Error ? err.message : String(err));
172
+ case "get_state": {
173
+ const state: RpcSessionState = {
174
+ model: session.model,
175
+ thinkingLevel: session.thinkingLevel,
176
+ isStreaming: session.isStreaming,
177
+ isCompacting: session.isCompacting,
178
+ steeringMode: session.steeringMode,
179
+ followUpMode: session.followUpMode,
180
+ interruptMode: session.interruptMode,
181
+ sessionFile: session.sessionFile,
182
+ sessionId: session.sessionId,
183
+ sessionName: session.sessionName,
184
+ autoCompactionEnabled: session.autoCompactionEnabled,
185
+ messageCount: session.messages.length,
186
+ queuedMessageCount: session.queuedMessageCount,
187
+ todoPhases: session.getTodoPhases(),
188
+ contextUsage: session.getContextUsage(),
189
+ };
190
+ const include = new Set(command.include ?? []);
191
+ if (include.has("systemPrompt")) {
192
+ state.systemPrompt = session.systemPrompt;
193
+ }
194
+ if (include.has("tools") || include.has("dumpTools")) {
195
+ state.dumpTools = session.agent.state.tools.map(tool => ({
196
+ name: tool.name,
197
+ description: tool.description,
198
+ parameters: tool.parameters,
199
+ }));
200
+ }
201
+ return rpcSuccess(id, "get_state", state);
220
202
  }
221
- }
222
203
 
223
- case "set_model": {
224
- const models = session.getAvailableModels();
225
- const model = models.find(m => m.provider === command.provider && m.id === command.modelId);
226
- if (!model) {
227
- return rpcError(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
204
+ case "set_todos": {
205
+ session.setTodoPhases(command.phases);
206
+ return rpcSuccess(id, "set_todos", { todoPhases: session.getTodoPhases() });
228
207
  }
229
- await session.setModel(model);
230
- return rpcSuccess(id, "set_model", model);
231
- }
232
208
 
233
- case "cycle_model": {
234
- const result = await session.cycleModel();
235
- if (!result) {
236
- return rpcSuccess(id, "cycle_model", null);
209
+ case "set_host_tools": {
210
+ const tools = normalizeHostToolDefinitions(command.tools);
211
+ const rpcTools = hostToolRegistry.setTools(tools);
212
+ await session.refreshRpcHostTools(rpcTools);
213
+ return rpcSuccess(id, "set_host_tools", { toolNames: tools.map(tool => tool.name) });
237
214
  }
238
- return rpcSuccess(id, "cycle_model", result);
239
- }
240
215
 
241
- case "get_available_models": {
242
- const models = session.getAvailableModels();
243
- return rpcSuccess(id, "get_available_models", { models });
244
- }
216
+ case "set_host_uri_schemes": {
217
+ try {
218
+ const schemes = hostUriRegistry.setSchemes(command.schemes);
219
+ return rpcSuccess(id, "set_host_uri_schemes", { schemes });
220
+ } catch (err) {
221
+ return rpcError(id, "set_host_uri_schemes", err instanceof Error ? err.message : String(err));
222
+ }
223
+ }
245
224
 
246
- case "set_thinking_level": {
247
- session.setThinkingLevel(command.level);
248
- return rpcSuccess(id, "set_thinking_level");
249
- }
225
+ case "set_model": {
226
+ const models = session.getAvailableModels();
227
+ const model = models.find(m => m.provider === command.provider && m.id === command.modelId);
228
+ if (!model) {
229
+ return rpcError(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
230
+ }
231
+ await session.setModel(model);
232
+ return rpcSuccess(id, "set_model", model);
233
+ }
250
234
 
251
- case "cycle_thinking_level": {
252
- const level = session.cycleThinkingLevel();
253
- if (!level) {
254
- return rpcSuccess(id, "cycle_thinking_level", null);
235
+ case "cycle_model": {
236
+ const result = await session.cycleModel();
237
+ if (!result) {
238
+ return rpcSuccess(id, "cycle_model", null);
239
+ }
240
+ return rpcSuccess(id, "cycle_model", result);
255
241
  }
256
- return rpcSuccess(id, "cycle_thinking_level", { level });
257
- }
258
242
 
259
- case "set_steering_mode": {
260
- session.setSteeringMode(command.mode);
261
- return rpcSuccess(id, "set_steering_mode");
262
- }
243
+ case "get_available_models": {
244
+ const models = session.getAvailableModels();
245
+ return rpcSuccess(id, "get_available_models", { models });
246
+ }
263
247
 
264
- case "set_follow_up_mode": {
265
- session.setFollowUpMode(command.mode);
266
- return rpcSuccess(id, "set_follow_up_mode");
267
- }
248
+ case "set_thinking_level": {
249
+ if (!(Object.values(ThinkingLevel) as string[]).includes(command.level as string)) {
250
+ return rpcError(id, "set_thinking_level", `Invalid thinking level: ${String(command.level)}`);
251
+ }
252
+ session.setThinkingLevel(command.level);
253
+ return rpcSuccess(id, "set_thinking_level");
254
+ }
268
255
 
269
- case "set_interrupt_mode": {
270
- session.setInterruptMode(command.mode);
271
- return rpcSuccess(id, "set_interrupt_mode");
272
- }
256
+ case "cycle_thinking_level": {
257
+ const level = session.cycleThinkingLevel();
258
+ if (!level) {
259
+ return rpcSuccess(id, "cycle_thinking_level", null);
260
+ }
261
+ return rpcSuccess(id, "cycle_thinking_level", { level });
262
+ }
273
263
 
274
- case "compact": {
275
- const result = await session.compact(command.customInstructions);
276
- return rpcSuccess(id, "compact", result);
277
- }
264
+ case "set_steering_mode": {
265
+ if ((command.mode as string) !== "all" && (command.mode as string) !== "one-at-a-time") {
266
+ return rpcError(id, "set_steering_mode", `Invalid steering mode: ${String(command.mode)}`);
267
+ }
268
+ session.setSteeringMode(command.mode);
269
+ return rpcSuccess(id, "set_steering_mode");
270
+ }
278
271
 
279
- case "set_auto_compaction": {
280
- session.setAutoCompactionEnabled(command.enabled);
281
- return rpcSuccess(id, "set_auto_compaction");
282
- }
272
+ case "set_follow_up_mode": {
273
+ if ((command.mode as string) !== "all" && (command.mode as string) !== "one-at-a-time") {
274
+ return rpcError(id, "set_follow_up_mode", `Invalid follow-up mode: ${String(command.mode)}`);
275
+ }
276
+ session.setFollowUpMode(command.mode);
277
+ return rpcSuccess(id, "set_follow_up_mode");
278
+ }
283
279
 
284
- case "set_auto_retry": {
285
- session.setAutoRetryEnabled(command.enabled);
286
- return rpcSuccess(id, "set_auto_retry");
287
- }
280
+ case "set_interrupt_mode": {
281
+ if ((command.mode as string) !== "immediate" && (command.mode as string) !== "wait") {
282
+ return rpcError(id, "set_interrupt_mode", `Invalid interrupt mode: ${String(command.mode)}`);
283
+ }
284
+ session.setInterruptMode(command.mode);
285
+ return rpcSuccess(id, "set_interrupt_mode");
286
+ }
288
287
 
289
- case "abort_retry": {
290
- session.abortRetry();
291
- return rpcSuccess(id, "abort_retry");
292
- }
288
+ case "compact": {
289
+ const result = await session.compact(command.customInstructions);
290
+ return rpcSuccess(id, "compact", result);
291
+ }
293
292
 
294
- case "bash": {
295
- const result = await session.executeBash(command.command);
296
- return reconcile() ?? rpcSuccess(id, "bash", result);
297
- }
293
+ case "set_auto_compaction": {
294
+ session.setAutoCompactionEnabled(command.enabled);
295
+ return rpcSuccess(id, "set_auto_compaction");
296
+ }
298
297
 
299
- case "abort_bash": {
300
- session.abortBash();
301
- return rpcSuccess(id, "abort_bash");
302
- }
298
+ case "set_auto_retry": {
299
+ session.setAutoRetryEnabled(command.enabled);
300
+ return rpcSuccess(id, "set_auto_retry");
301
+ }
303
302
 
304
- case "get_session_stats": {
305
- const stats = session.getSessionStats();
306
- return rpcSuccess(id, "get_session_stats", stats);
307
- }
303
+ case "abort_retry": {
304
+ session.abortRetry();
305
+ return rpcSuccess(id, "abort_retry");
306
+ }
308
307
 
309
- case "export_html": {
310
- const path = await session.exportToHtml(command.outputPath);
311
- return rpcSuccess(id, "export_html", { path });
312
- }
308
+ case "bash": {
309
+ const result = await session.executeBash(command.command);
310
+ return reconcile() ?? rpcSuccess(id, "bash", result);
311
+ }
313
312
 
314
- case "switch_session": {
315
- const cancelled = !(await session.switchSession(command.sessionPath));
316
- return rpcSuccess(id, "switch_session", { cancelled });
317
- }
313
+ case "abort_bash": {
314
+ session.abortBash();
315
+ return rpcSuccess(id, "abort_bash");
316
+ }
318
317
 
319
- case "branch": {
320
- const result = await session.branch(command.entryId);
321
- return rpcSuccess(id, "branch", { text: result.selectedText, cancelled: result.cancelled });
322
- }
318
+ case "get_session_stats": {
319
+ const stats = session.getSessionStats();
320
+ return rpcSuccess(id, "get_session_stats", stats);
321
+ }
323
322
 
324
- case "get_branch_messages": {
325
- const messages = session.getUserMessagesForBranching();
326
- return rpcSuccess(id, "get_branch_messages", { messages });
327
- }
323
+ case "export_html": {
324
+ const path = await session.exportToHtml(command.outputPath);
325
+ return rpcSuccess(id, "export_html", { path });
326
+ }
328
327
 
329
- case "get_last_assistant_text": {
330
- const text = session.getLastAssistantText();
331
- return rpcSuccess(id, "get_last_assistant_text", { text });
332
- }
328
+ case "switch_session": {
329
+ const cancelled = !(await session.switchSession(command.sessionPath));
330
+ return rpcSuccess(id, "switch_session", { cancelled });
331
+ }
333
332
 
334
- case "set_session_name": {
335
- const name = command.name.trim();
336
- if (!name) {
337
- return rpcError(id, "set_session_name", "Session name cannot be empty");
333
+ case "branch": {
334
+ const result = await session.branch(command.entryId);
335
+ return rpcSuccess(id, "branch", { text: result.selectedText, cancelled: result.cancelled });
338
336
  }
339
- const applied = await session.setSessionName(name, "user");
340
- if (!applied) {
341
- return rpcError(id, "set_session_name", "Session name cannot be empty");
337
+
338
+ case "get_branch_messages": {
339
+ const messages = session.getUserMessagesForBranching();
340
+ return rpcSuccess(id, "get_branch_messages", { messages });
342
341
  }
343
- return rpcSuccess(id, "set_session_name");
344
- }
345
342
 
346
- case "handoff": {
347
- const result = await session.handoff(command.customInstructions);
348
- return rpcSuccess(id, "handoff", result ? { savedPath: result.savedPath } : null);
349
- }
343
+ case "get_last_assistant_text": {
344
+ const text = session.getLastAssistantText();
345
+ return rpcSuccess(id, "get_last_assistant_text", { text });
346
+ }
350
347
 
351
- case "get_messages": {
352
- return rpcSuccess(id, "get_messages", { messages: session.messages });
353
- }
348
+ case "set_session_name": {
349
+ const name = command.name.trim();
350
+ if (!name) {
351
+ return rpcError(id, "set_session_name", "Session name cannot be empty");
352
+ }
353
+ const applied = await session.setSessionName(name, "user");
354
+ if (!applied) {
355
+ return rpcError(id, "set_session_name", "Session name cannot be empty");
356
+ }
357
+ return rpcSuccess(id, "set_session_name");
358
+ }
354
359
 
355
- case "get_login_providers": {
356
- const providers = getOAuthProviders().map(provider => ({
357
- id: provider.id,
358
- name: provider.name,
359
- available: provider.available,
360
- authenticated: session.modelRegistry.authStorage.hasAuth(provider.id),
361
- }));
362
- return rpcSuccess(id, "get_login_providers", { providers });
363
- }
360
+ case "handoff": {
361
+ const result = await session.handoff(command.customInstructions);
362
+ return rpcSuccess(id, "handoff", result ? { savedPath: result.savedPath } : null);
363
+ }
364
364
 
365
- case "login": {
366
- const knownProvider = getOAuthProviders().find(p => p.id === command.providerId);
367
- if (!knownProvider) {
368
- return rpcError(id, "login", `Unknown OAuth provider: ${command.providerId}`);
369
- }
370
- const uiCtx = createUiContext();
371
- let authEmitted = false;
372
- try {
373
- await session.modelRegistry.authStorage.login(command.providerId, {
374
- onAuth: info => {
375
- authEmitted = true;
376
- output({
377
- type: "extension_ui_request",
378
- id: Snowflake.next() as string,
379
- method: "open_url",
380
- url: info.url,
381
- instructions: info.instructions,
382
- } as RpcExtensionUIRequest);
383
- },
384
- onProgress: message => {
385
- uiCtx.notify(message, "info");
386
- },
387
- onPrompt: () => {
388
- if (!authEmitted) {
389
- return Promise.reject(
390
- new Error(
391
- `Provider '${command.providerId}' requires interactive prompts ` +
392
- "which are not supported in RPC mode. Use the terminal UI to log in.",
393
- ),
394
- );
395
- }
396
- return new Promise<string>(() => {});
397
- },
398
- });
399
- await session.modelRegistry.refresh();
400
- return rpcSuccess(id, "login", { providerId: command.providerId });
401
- } catch (err: unknown) {
402
- return rpcError(id, "login", err instanceof Error ? err.message : String(err));
365
+ case "get_messages": {
366
+ return rpcSuccess(id, "get_messages", { messages: session.messages });
403
367
  }
404
- }
405
368
 
406
- case "negotiate_unattended": {
407
- if (!unattendedControlPlane) {
408
- return rpcError(id, "negotiate_unattended", "unattended mode is not available on this session");
369
+ case "get_login_providers": {
370
+ const providers = getOAuthProviders().map(provider => ({
371
+ id: provider.id,
372
+ name: provider.name,
373
+ available: provider.available,
374
+ authenticated: session.modelRegistry.authStorage.hasAuth(provider.id),
375
+ }));
376
+ return rpcSuccess(id, "get_login_providers", { providers });
409
377
  }
410
- try {
411
- const accepted = unattendedControlPlane.negotiate(command.declaration);
412
- return rpcSuccess(id, "negotiate_unattended", accepted);
413
- } catch (err) {
414
- return typedError("negotiate_unattended", err);
378
+
379
+ case "login": {
380
+ const knownProvider = getOAuthProviders().find(p => p.id === command.providerId);
381
+ if (!knownProvider) {
382
+ return rpcError(id, "login", `Unknown OAuth provider: ${command.providerId}`);
383
+ }
384
+ const uiCtx = createUiContext();
385
+ let authEmitted = false;
386
+ try {
387
+ await session.modelRegistry.authStorage.login(command.providerId, {
388
+ onAuth: info => {
389
+ authEmitted = true;
390
+ output({
391
+ type: "extension_ui_request",
392
+ id: Snowflake.next() as string,
393
+ method: "open_url",
394
+ url: info.url,
395
+ instructions: info.instructions,
396
+ } as RpcExtensionUIRequest);
397
+ },
398
+ onProgress: message => {
399
+ uiCtx.notify(message, "info");
400
+ },
401
+ onPrompt: () => {
402
+ if (!authEmitted) {
403
+ return Promise.reject(
404
+ new Error(
405
+ `Provider '${command.providerId}' requires interactive prompts ` +
406
+ "which are not supported in RPC mode. Use the terminal UI to log in.",
407
+ ),
408
+ );
409
+ }
410
+ return new Promise<string>(() => {});
411
+ },
412
+ });
413
+ await session.modelRegistry.refresh();
414
+ return rpcSuccess(id, "login", { providerId: command.providerId });
415
+ } catch (err: unknown) {
416
+ return rpcError(id, "login", err instanceof Error ? err.message : String(err));
417
+ }
415
418
  }
416
- }
417
419
 
418
- case "workflow_gate_response": {
419
- if (!unattendedControlPlane) {
420
- return rpcError(id, "workflow_gate_response", "workflow gates are not available on this session");
420
+ case "negotiate_unattended": {
421
+ if (!unattendedControlPlane) {
422
+ return rpcError(id, "negotiate_unattended", "unattended mode is not available on this session");
423
+ }
424
+ try {
425
+ const accepted = unattendedControlPlane.negotiate(command.declaration);
426
+ return rpcSuccess(id, "negotiate_unattended", accepted);
427
+ } catch (err) {
428
+ return typedError("negotiate_unattended", err);
429
+ }
421
430
  }
422
- try {
423
- const resolution = await unattendedControlPlane.resolveGate({
424
- gate_id: command.gate_id,
425
- answer: command.answer,
426
- idempotency_key: command.idempotency_key,
427
- });
428
- return rpcSuccess(id, "workflow_gate_response", resolution);
429
- } catch (err) {
430
- return typedError("workflow_gate_response", err);
431
+
432
+ case "workflow_gate_response": {
433
+ if (!unattendedControlPlane) {
434
+ return rpcError(id, "workflow_gate_response", "workflow gates are not available on this session");
435
+ }
436
+ try {
437
+ const resolution = await unattendedControlPlane.resolveGate({
438
+ gate_id: command.gate_id,
439
+ answer: command.answer,
440
+ idempotency_key: command.idempotency_key,
441
+ });
442
+ return rpcSuccess(id, "workflow_gate_response", resolution);
443
+ } catch (err) {
444
+ return typedError("workflow_gate_response", err);
445
+ }
431
446
  }
432
- }
433
447
 
434
- default: {
435
- const unknownCommand = command as { type: string };
436
- return rpcError(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
448
+ default: {
449
+ const unknownCommand = command as { type: string };
450
+ return rpcError(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
451
+ }
437
452
  }
453
+ } catch (err) {
454
+ return rpcError(id, command.type, serializeRpcDispatchError(err));
438
455
  }
439
456
  }
@@ -17,12 +17,26 @@ import type { OpenGateInput } from "./workflow-gate-broker";
17
17
  /** "Other (type your own)" sentinel, mirroring the interactive ask tool. */
18
18
  export const GATE_OTHER_OPTION = "Other (type your own)";
19
19
 
20
+ /** Optional structured deep-interview round metadata supplied by the agent. */
21
+ export interface AskGateDeepInterviewState {
22
+ round_id?: string;
23
+ round: number;
24
+ component: string;
25
+ dimension: string;
26
+ ambiguity: number;
27
+ }
28
+
20
29
  export interface AskGateQuestion {
21
30
  id: string;
22
31
  question: string;
23
32
  options: Array<{ label: string }>;
24
33
  multi?: boolean;
25
34
  recommended?: number;
35
+ /**
36
+ * Structured round metadata. When present it is the authoritative source for gate
37
+ * `stage_state`; when absent, the question text is regex-parsed as a fallback.
38
+ */
39
+ deepInterview?: AskGateDeepInterviewState;
26
40
  }
27
41
 
28
42
  export interface AskGateResult {
@@ -130,6 +144,19 @@ function questionAnswerSchema(question: AskGateQuestion, labels: string[]): RpcJ
130
144
  };
131
145
  }
132
146
 
147
+ /** Build `stage_state` round metadata from the structured param (authoritative when present). */
148
+ function structuredDeepInterviewState(meta: AskGateDeepInterviewState): Record<string, unknown> {
149
+ const state: Record<string, unknown> = {
150
+ deep_interview_metadata: true,
151
+ round: meta.round,
152
+ component: meta.component,
153
+ dimension: meta.dimension,
154
+ ambiguity: meta.ambiguity,
155
+ };
156
+ if (meta.round_id !== undefined) state.round_id = meta.round_id;
157
+ return state;
158
+ }
159
+
133
160
  /** Build the `workflow_gate` open-input for one deep-interview question. */
134
161
  export function questionToGate(question: AskGateQuestion): OpenGateInput {
135
162
  const labels = question.options.map(o => o.label);
@@ -151,7 +178,9 @@ export function questionToGate(question: AskGateQuestion): OpenGateInput {
151
178
  multi: question.multi ?? false,
152
179
  options: labels,
153
180
  other_option: GATE_OTHER_OPTION,
154
- ...deepInterviewQuestionState(question.question),
181
+ ...(question.deepInterview
182
+ ? structuredDeepInterviewState(question.deepInterview)
183
+ : deepInterviewQuestionState(question.question)),
155
184
  },
156
185
  },
157
186
  };