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