@gajae-code/coding-agent 0.5.1 → 0.5.3

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 (165) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +6 -0
  4. package/dist/types/cli/setup-cli.d.ts +8 -1
  5. package/dist/types/commands/setup.d.ts +7 -0
  6. package/dist/types/config/file-lock.d.ts +24 -2
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/models-config-schema.d.ts +5 -0
  9. package/dist/types/config/settings-schema.d.ts +62 -0
  10. package/dist/types/dap/client.d.ts +2 -1
  11. package/dist/types/edit/read-file.d.ts +6 -0
  12. package/dist/types/eval/js/context-manager.d.ts +3 -0
  13. package/dist/types/eval/js/executor.d.ts +1 -0
  14. package/dist/types/exec/bash-executor.d.ts +2 -0
  15. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  16. package/dist/types/gjc-runtime/tmux-sessions.d.ts +7 -1
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  19. package/dist/types/lsp/types.d.ts +2 -0
  20. package/dist/types/modes/bridge/bridge-mode.d.ts +1 -0
  21. package/dist/types/modes/components/model-selector.d.ts +2 -0
  22. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  23. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  24. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -0
  25. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  26. package/dist/types/modes/interactive-mode.d.ts +1 -1
  27. package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
  28. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  29. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  30. package/dist/types/modes/theme/theme.d.ts +1 -0
  31. package/dist/types/modes/types.d.ts +1 -1
  32. package/dist/types/runtime/process-lifecycle.d.ts +108 -0
  33. package/dist/types/runtime-mcp/transports/stdio.d.ts +1 -0
  34. package/dist/types/runtime-mcp/types.d.ts +2 -0
  35. package/dist/types/session/agent-session.d.ts +17 -1
  36. package/dist/types/session/artifacts.d.ts +4 -1
  37. package/dist/types/session/history-storage.d.ts +2 -2
  38. package/dist/types/session/session-manager.d.ts +10 -1
  39. package/dist/types/session/streaming-output.d.ts +5 -0
  40. package/dist/types/setup/credential-import.d.ts +79 -0
  41. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +76 -0
  42. package/dist/types/task/executor.d.ts +1 -0
  43. package/dist/types/task/render.d.ts +1 -1
  44. package/dist/types/tools/bash.d.ts +1 -0
  45. package/dist/types/tools/browser/tab-supervisor.d.ts +9 -0
  46. package/dist/types/tools/sqlite-reader.d.ts +2 -1
  47. package/dist/types/tools/subagent-render.d.ts +7 -1
  48. package/dist/types/tools/subagent.d.ts +21 -0
  49. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  50. package/dist/types/web/search/index.d.ts +4 -4
  51. package/dist/types/web/search/provider.d.ts +16 -20
  52. package/dist/types/web/search/providers/base.d.ts +2 -1
  53. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  54. package/dist/types/web/search/types.d.ts +14 -2
  55. package/package.json +7 -7
  56. package/scripts/build-binary.ts +7 -0
  57. package/src/async/job-manager.ts +153 -39
  58. package/src/cli/args.ts +2 -0
  59. package/src/cli/fast-help.ts +2 -0
  60. package/src/cli/setup-cli.ts +138 -3
  61. package/src/commands/setup.ts +5 -1
  62. package/src/commands/ultragoal.ts +3 -1
  63. package/src/config/file-lock-gc.ts +14 -2
  64. package/src/config/file-lock.ts +63 -13
  65. package/src/config/model-profile-activation.ts +15 -3
  66. package/src/config/model-profiles.ts +15 -15
  67. package/src/config/model-registry.ts +21 -1
  68. package/src/config/models-config-schema.ts +1 -0
  69. package/src/config/settings-schema.ts +62 -0
  70. package/src/dap/client.ts +105 -64
  71. package/src/dap/session.ts +44 -7
  72. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  73. package/src/edit/read-file.ts +19 -1
  74. package/src/eval/js/context-manager.ts +228 -65
  75. package/src/eval/js/executor.ts +2 -0
  76. package/src/eval/js/index.ts +1 -0
  77. package/src/eval/js/worker-core.ts +10 -6
  78. package/src/eval/py/executor.ts +68 -19
  79. package/src/eval/py/kernel.ts +46 -22
  80. package/src/eval/py/runner.py +68 -14
  81. package/src/exec/bash-executor.ts +49 -13
  82. package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
  83. package/src/gjc-runtime/launch-tmux.ts +3 -4
  84. package/src/gjc-runtime/ralplan-runtime.ts +174 -12
  85. package/src/gjc-runtime/state-runtime.ts +2 -1
  86. package/src/gjc-runtime/state-writer.ts +254 -7
  87. package/src/gjc-runtime/tmux-gc.ts +88 -38
  88. package/src/gjc-runtime/tmux-sessions.ts +44 -6
  89. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  90. package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
  91. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  92. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  93. package/src/harness-control-plane/owner.ts +3 -2
  94. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  95. package/src/hooks/skill-state.ts +121 -2
  96. package/src/internal-urls/artifact-protocol.ts +10 -1
  97. package/src/internal-urls/docs-index.generated.ts +14 -10
  98. package/src/lsp/client.ts +64 -26
  99. package/src/lsp/defaults.json +1 -0
  100. package/src/lsp/index.ts +2 -1
  101. package/src/lsp/lspmux.ts +33 -9
  102. package/src/lsp/types.ts +2 -0
  103. package/src/main.ts +14 -4
  104. package/src/modes/acp/acp-agent.ts +4 -2
  105. package/src/modes/bridge/bridge-mode.ts +23 -1
  106. package/src/modes/components/assistant-message.ts +10 -2
  107. package/src/modes/components/bash-execution.ts +5 -1
  108. package/src/modes/components/eval-execution.ts +5 -1
  109. package/src/modes/components/history-search.ts +5 -2
  110. package/src/modes/components/model-selector.ts +60 -2
  111. package/src/modes/components/oauth-selector.ts +5 -0
  112. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  113. package/src/modes/components/runtime-mcp-add-wizard.ts +58 -7
  114. package/src/modes/components/skill-message.ts +24 -16
  115. package/src/modes/components/tool-execution.ts +6 -0
  116. package/src/modes/controllers/extension-ui-controller.ts +33 -6
  117. package/src/modes/controllers/input-controller.ts +5 -0
  118. package/src/modes/controllers/selector-controller.ts +86 -2
  119. package/src/modes/interactive-mode.ts +11 -1
  120. package/src/modes/rpc/rpc-mode.ts +132 -18
  121. package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
  122. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  123. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  124. package/src/modes/theme/defaults/claude-code.json +100 -0
  125. package/src/modes/theme/defaults/codex.json +100 -0
  126. package/src/modes/theme/defaults/index.ts +6 -0
  127. package/src/modes/theme/defaults/opencode.json +102 -0
  128. package/src/modes/theme/theme.ts +2 -2
  129. package/src/modes/types.ts +1 -1
  130. package/src/modes/utils/ui-helpers.ts +5 -2
  131. package/src/prompts/agents/executor.md +5 -2
  132. package/src/runtime/process-lifecycle.ts +400 -0
  133. package/src/runtime-mcp/manager.ts +164 -50
  134. package/src/runtime-mcp/transports/http.ts +12 -11
  135. package/src/runtime-mcp/transports/stdio.ts +64 -38
  136. package/src/runtime-mcp/types.ts +3 -0
  137. package/src/sdk.ts +39 -1
  138. package/src/session/agent-session.ts +190 -33
  139. package/src/session/artifacts.ts +17 -2
  140. package/src/session/blob-store.ts +36 -2
  141. package/src/session/history-storage.ts +32 -11
  142. package/src/session/session-manager.ts +99 -31
  143. package/src/session/streaming-output.ts +54 -3
  144. package/src/setup/credential-import.ts +429 -0
  145. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  146. package/src/slash-commands/builtin-registry.ts +30 -3
  147. package/src/slash-commands/helpers/fast-status-report.ts +111 -0
  148. package/src/task/executor.ts +7 -1
  149. package/src/task/render.ts +18 -7
  150. package/src/tools/archive-reader.ts +10 -1
  151. package/src/tools/ask.ts +4 -2
  152. package/src/tools/bash.ts +11 -4
  153. package/src/tools/browser/tab-supervisor.ts +22 -0
  154. package/src/tools/browser.ts +38 -4
  155. package/src/tools/cron.ts +1 -1
  156. package/src/tools/read.ts +11 -12
  157. package/src/tools/sqlite-reader.ts +19 -5
  158. package/src/tools/subagent-render.ts +119 -29
  159. package/src/tools/subagent.ts +147 -7
  160. package/src/tools/ultragoal-ask-guard.ts +39 -0
  161. package/src/web/search/index.ts +25 -25
  162. package/src/web/search/provider.ts +178 -87
  163. package/src/web/search/providers/base.ts +2 -1
  164. package/src/web/search/providers/openai-compatible.ts +151 -0
  165. package/src/web/search/types.ts +47 -22
@@ -13,7 +13,7 @@
13
13
 
14
14
  import * as fs from "node:fs/promises";
15
15
  import * as path from "node:path";
16
- import { $pickenv, readLines, Snowflake } from "@gajae-code/utils";
16
+ import { $pickenv, logger, readLines, Snowflake } from "@gajae-code/utils";
17
17
  import type {
18
18
  ExtensionUIContext,
19
19
  ExtensionUIDialogOptions,
@@ -27,7 +27,7 @@ import { AgentWireFrameSequencer, toAgentWireEventFrame } from "../shared/agent-
27
27
  import { rpcError as error } from "../shared/agent-wire/responses";
28
28
  import { registerRpcSession, unregisterRpcSession } from "../shared/agent-wire/session-registry";
29
29
  import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
30
- import { UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
30
+ import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
31
31
  import { FileGateStore } from "../shared/agent-wire/workflow-gate-broker";
32
32
  import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
33
33
  import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
@@ -82,6 +82,82 @@ export function shouldEmitRpcTitlesForTest(): boolean {
82
82
 
83
83
  const shouldEmitRpcTitles = shouldEmitRpcTitlesForTest;
84
84
 
85
+ /**
86
+ * Cancellation commands bypass the ordered serial chain because they must
87
+ * interrupt in-flight work — they cannot wait behind the very command they are
88
+ * meant to abort.
89
+ */
90
+ export const RPC_CANCELLATION_COMMANDS: ReadonlySet<RpcCommand["type"]> = new Set<RpcCommand["type"]>([
91
+ "abort",
92
+ "abort_bash",
93
+ "abort_retry",
94
+ ]);
95
+
96
+ /**
97
+ * Safe read-only commands that bypass the ordered serial chain so they never
98
+ * head-of-line-block behind a long-running ordered command like
99
+ * `bash`/`compact`/`handoff`/`login` (#606, issue 13 — the partial fix only
100
+ * fast-laned cancellation).
101
+ *
102
+ * Every command listed here has a dispatch handler that is **fully synchronous
103
+ * and side-effect-free**: on the single-threaded event loop it runs to
104
+ * completion between the await points of any in-flight ordered command, reading
105
+ * live state without mutating it. Because such a read performs no causal write,
106
+ * jumping ahead of an earlier *queued* ordered command is observably harmless —
107
+ * there is no state change to reorder. Read payloads are additionally
108
+ * snapshotted inside the handler (e.g. `get_messages` returns a shallow copy of
109
+ * `session.messages`) so a fast-lane read can never serialize a half-mutated
110
+ * array that an ordered turn/compaction is rewriting in place.
111
+ *
112
+ * Deliberately excluded (kept ordered): every async/long command and every
113
+ * mutating command. In particular the control-flag setters (`set_thinking_level`,
114
+ * `cycle_thinking_level`, `set_steering_mode`, `set_follow_up_mode`,
115
+ * `set_interrupt_mode`, `set_auto_compaction`, `set_auto_retry`) stay ordered.
116
+ * Their handlers are synchronous, so fast-laning one ahead of an already-queued
117
+ * `prompt`/`bash` would apply the new mode *before* that earlier command runs —
118
+ * the earlier command would then observe the later setter's value, a
119
+ * causal-order (arrival-order) regression. Mutations therefore stay on the
120
+ * chain, and new command types default to ordered (fail-safe).
121
+ */
122
+ export const RPC_SAFE_READ_CONTROL_COMMANDS: ReadonlySet<RpcCommand["type"]> = new Set<RpcCommand["type"]>([
123
+ // Pure synchronous reads — snapshot live state at processing time, never mutate.
124
+ "get_state",
125
+ "get_session_stats",
126
+ "get_available_models",
127
+ "get_branch_messages",
128
+ "get_last_assistant_text",
129
+ "get_messages",
130
+ "get_login_providers",
131
+ ]);
132
+
133
+ /** True when a command may bypass the ordered serial chain and run immediately. */
134
+ export function isFastLaneRpcCommand(type: RpcCommand["type"]): boolean {
135
+ return RPC_CANCELLATION_COMMANDS.has(type) || RPC_SAFE_READ_CONTROL_COMMANDS.has(type);
136
+ }
137
+
138
+ /**
139
+ * Schedules inbound RPC commands: fast-lane commands run immediately while
140
+ * everything else runs through a serial chain so causal order is preserved. The
141
+ * read loop never blocks, which is what lets a fast-lane command reach a
142
+ * long-running ordered command instead of being head-of-line-blocked behind it.
143
+ */
144
+ export function createRpcCommandScheduler(
145
+ run: (command: RpcCommand) => Promise<void>,
146
+ track: (task: Promise<void>) => void,
147
+ ): { dispatch: (command: RpcCommand) => void } {
148
+ let orderedChain: Promise<void> = Promise.resolve();
149
+ return {
150
+ dispatch(command: RpcCommand): void {
151
+ if (isFastLaneRpcCommand(command.type)) {
152
+ track(run(command));
153
+ return;
154
+ }
155
+ orderedChain = orderedChain.then(() => run(command));
156
+ track(orderedChain);
157
+ },
158
+ };
159
+ }
160
+
85
161
  function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "exceeded" | "aborted" | "info" {
86
162
  if (event.includes("denied")) return "denied";
87
163
  if (event.includes("exceeded")) return "exceeded";
@@ -91,6 +167,43 @@ function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "e
91
167
  return "info";
92
168
  }
93
169
 
170
+ export class RpcListenRefusedError extends Error {
171
+ constructor(socketPath: string) {
172
+ super(
173
+ `RPC --listen refused: a live server is already listening on ${socketPath}. ` +
174
+ "Stop it first or choose a different --listen path.",
175
+ );
176
+ this.name = "RpcListenRefusedError";
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Probe whether a unix-domain socket path has a live server accepting
182
+ * connections. Returns `true` when a connection succeeds (a previous owner is
183
+ * still alive), and returns `false` only for known missing/stale endpoints
184
+ * (ENOENT / ECONNREFUSED). Unexpected probe failures fail closed as "alive" so
185
+ * `--listen` startup refuses to unlink a path it could not safely classify.
186
+ */
187
+ export async function isUnixSocketAlive(socketPath: string): Promise<boolean> {
188
+ try {
189
+ const socket = await Bun.connect({
190
+ unix: socketPath,
191
+ socket: { data() {}, open() {}, error() {}, close() {} },
192
+ });
193
+ socket.end();
194
+ return true;
195
+ } catch (err) {
196
+ const code = err && typeof err === "object" ? (err as { code?: unknown }).code : undefined;
197
+ if (code === "ENOENT" || code === "ECONNREFUSED") return false;
198
+ logger.warn("RPC --listen socket probe failed closed", {
199
+ socketPath,
200
+ code: typeof code === "string" ? code : undefined,
201
+ error: err instanceof Error ? err.message : String(err),
202
+ });
203
+ return true;
204
+ }
205
+ }
206
+
94
207
  export function requestRpcEditor(
95
208
  pendingRequests: Map<string, PendingExtensionRequest>,
96
209
  output: RpcOutput,
@@ -230,6 +343,7 @@ export async function runRpcMode(
230
343
  emitFrame: gate => output(gate),
231
344
  store: gateStore,
232
345
  audit: recordAudit,
346
+ providerSupportsTokenCostMetrics: modelSupportsTokenCostMetrics(session.model),
233
347
  getUsageSnapshot: () => {
234
348
  const stats = session.getSessionStats();
235
349
  return { tokens: stats.tokens.total, costUsd: stats.cost };
@@ -537,14 +651,13 @@ export async function runRpcMode(
537
651
  unattendedControlPlane,
538
652
  });
539
653
 
540
- // Cancellation commands must interrupt in-flight work, so they bypass the ordered
541
- // queue and run immediately. Everything else runs through a serial chain so causal
542
- // order is preserved (e.g. `get_state` after `bash` still observes the bash result)
543
- // while the read loop itself never blocks that is what lets a cancellation command
544
- // reach a long-running `bash`/`compact`/`handoff`/`login` instead of being
545
- // head-of-line-blocked behind it (issue 13).
546
- const CANCELLATION_COMMANDS = new Set<RpcCommand["type"]>(["abort", "abort_bash", "abort_retry"]);
547
- let orderedChain: Promise<void> = Promise.resolve();
654
+ // Fast-lane commands (cancellation + safe read/control, see
655
+ // isFastLaneRpcCommand) bypass the ordered serial chain and run immediately;
656
+ // everything else runs through a serial chain so causal order is preserved
657
+ // (e.g. an ordered `set_model` after `bash` still applies after the bash
658
+ // result) while the read loop itself never blocks — that is what lets a
659
+ // fast-lane command reach a long-running `bash`/`compact`/`handoff`/`login`
660
+ // instead of being head-of-line-blocked behind it (issue 13).
548
661
  const runCommand = async (command: RpcCommand): Promise<void> => {
549
662
  try {
550
663
  output(await handleCommand(command));
@@ -556,14 +669,7 @@ export async function runRpcMode(
556
669
  inFlightCommands.add(task);
557
670
  void task.finally(() => inFlightCommands.delete(task));
558
671
  };
559
- const dispatchCommand = (command: RpcCommand): void => {
560
- if (CANCELLATION_COMMANDS.has(command.type)) {
561
- trackCommand(runCommand(command));
562
- return;
563
- }
564
- orderedChain = orderedChain.then(() => runCommand(command));
565
- trackCommand(orderedChain);
566
- };
672
+ const { dispatch: dispatchCommand } = createRpcCommandScheduler(runCommand, trackCommand);
567
673
 
568
674
  /**
569
675
  * Check if shutdown was requested and perform shutdown if so.
@@ -620,6 +726,14 @@ export async function runRpcMode(
620
726
  if (options?.listen) {
621
727
  const socketPath = options.listen;
622
728
  await fs.mkdir(path.dirname(socketPath), { recursive: true }).catch(() => {});
729
+ // Refuse to clobber a live previous owner: probe the path first and only
730
+ // unlink a stale endpoint. A second `--listen` on the same path must not
731
+ // remove the socket another running server is still serving (#606).
732
+ // Unexpected probe failures are treated as alive, so this also refuses
733
+ // rather than unlinking a socket path we could not safely classify.
734
+ if (await isUnixSocketAlive(socketPath)) {
735
+ throw new RpcListenRefusedError(socketPath);
736
+ }
623
737
  await fs.rm(socketPath, { force: true }).catch(() => {});
624
738
  await registerRpcSession({
625
739
  sessionId: session.sessionId,
@@ -363,7 +363,10 @@ export async function dispatchRpcCommand(
363
363
  }
364
364
 
365
365
  case "get_messages": {
366
- return rpcSuccess(id, "get_messages", { messages: session.messages });
366
+ // Fast-lane read: snapshot the live array so a concurrent ordered
367
+ // turn/compaction mutating session.messages in place cannot make this
368
+ // response serialize a half-rewritten array (#606, issue 13).
369
+ return rpcSuccess(id, "get_messages", { messages: [...session.messages] });
367
370
  }
368
371
 
369
372
  case "get_login_providers": {
@@ -447,7 +450,7 @@ export async function dispatchRpcCommand(
447
450
 
448
451
  default: {
449
452
  const unknownCommand = command as { type: string };
450
- return rpcError(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
453
+ return rpcError(id, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
451
454
  }
452
455
  }
453
456
  } catch (err) {
@@ -93,6 +93,9 @@ export class RpcHostToolBridge {
93
93
  }
94
94
 
95
95
  setTools(tools: RpcHostToolDefinition[]): AgentTool[] {
96
+ if (tools.some(tool => tool.name === "ask")) {
97
+ throw new Error('RPC host tool "ask" is reserved and cannot be supplied by the host');
98
+ }
96
99
  this.#definitions = new Map(tools.map(tool => [tool.name, tool]));
97
100
  return tools.map(tool => new RpcHostToolAdapter(tool, this));
98
101
  }
@@ -14,6 +14,7 @@
14
14
  * Also implements the dispatch-facing {@link RpcUnattendedControlPlane} so the
15
15
  * RPC server can route `negotiate_unattended` + `workflow_gate_response` here.
16
16
  */
17
+ import type { Model } from "@gajae-code/ai";
17
18
  import type {
18
19
  RpcCommand,
19
20
  RpcUnattendedAccepted,
@@ -38,6 +39,20 @@ import { type GateStore, MemoryGateStore, type OpenGateInput, WorkflowGateBroker
38
39
  */
39
40
  const CHARGED_COMMAND_TYPES = new Set<RpcCommand["type"]>(["bash", "prompt", "steer", "follow_up", "abort_and_prompt"]);
40
41
 
42
+ /**
43
+ * Derive an explicit `providerSupportsTokenCostMetrics` capability from the
44
+ * active model so unattended negotiation fails closed when token/cost usage
45
+ * cannot be accounted for (#606). Callers that omit a model — or whose model is
46
+ * configured to suppress streaming usage (`compat.supportsUsageInStreaming:
47
+ * false`) — get `false`, which the controller refuses with
48
+ * `unsupported_budget_metric`.
49
+ */
50
+ export function modelSupportsTokenCostMetrics(model: Model | undefined): boolean {
51
+ if (!model) return false;
52
+ const compat = model.compat as { supportsUsageInStreaming?: boolean } | undefined;
53
+ return compat?.supportsUsageInStreaming !== false;
54
+ }
55
+
41
56
  /** Minimal surface a skill runtime / ask tool uses to emit a gate and await its answer. */
42
57
  export interface WorkflowGateEmitter {
43
58
  /** True only when unattended mode has been negotiated. */
@@ -92,7 +107,7 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
92
107
  this.#rejectAllPending(new Error(`unattended run aborted: ${reason}`));
93
108
  },
94
109
  },
95
- providerSupportsTokenCostMetrics: this.opts.providerSupportsTokenCostMetrics ?? true,
110
+ providerSupportsTokenCostMetrics: this.opts.providerSupportsTokenCostMetrics,
96
111
  });
97
112
  this.#controller = controller;
98
113
  this.#broker = new WorkflowGateBroker(this.opts.runId, this.opts.store ?? new MemoryGateStore(), {
@@ -0,0 +1,100 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "claude-code",
4
+ "vars": {
5
+ "bg": "#1a1a1a",
6
+ "surface": "#373737",
7
+ "surfaceSubtle": "#2a2a2a",
8
+ "separator": "#505050",
9
+ "fg": "#ffffff",
10
+ "muted": "#888888",
11
+ "dim": "#666666",
12
+ "brandTerracotta": "#d77757",
13
+ "shimmerTerracotta": "#eb9f7f",
14
+ "toolPink": "#fd5db1",
15
+ "lavender": "#b1b9f9",
16
+ "autoAcceptPurple": "#af87ff",
17
+ "successGreen": "#4eba65",
18
+ "warningAmber": "#ffc107",
19
+ "errorSoftRed": "#ff6b80",
20
+ "diffAddedBg": "#225c2b",
21
+ "diffRemovedBg": "#7a2936",
22
+ "diffRemovedFg": "#ff8fa0"
23
+ },
24
+ "colors": {
25
+ "accent": "brandTerracotta",
26
+ "border": "separator",
27
+ "borderAccent": "toolPink",
28
+ "borderMuted": "dim",
29
+ "success": "successGreen",
30
+ "error": "errorSoftRed",
31
+ "warning": "warningAmber",
32
+ "muted": "muted",
33
+ "dim": "dim",
34
+ "text": "fg",
35
+ "thinkingText": "muted",
36
+ "selectedBg": "surface",
37
+ "userMessageBg": "surface",
38
+ "userMessageText": "fg",
39
+ "customMessageBg": "surfaceSubtle",
40
+ "customMessageText": "fg",
41
+ "customMessageLabel": "brandTerracotta",
42
+ "toolPendingBg": "surfaceSubtle",
43
+ "toolSuccessBg": "diffAddedBg",
44
+ "toolErrorBg": "diffRemovedBg",
45
+ "toolTitle": "fg",
46
+ "toolOutput": "muted",
47
+ "mdHeading": "brandTerracotta",
48
+ "mdLink": "lavender",
49
+ "mdLinkUrl": "muted",
50
+ "mdCode": "shimmerTerracotta",
51
+ "mdCodeBlock": "fg",
52
+ "mdCodeBlockBorder": "toolPink",
53
+ "mdQuote": "muted",
54
+ "mdQuoteBorder": "separator",
55
+ "mdHr": "dim",
56
+ "mdListBullet": "brandTerracotta",
57
+ "toolDiffAdded": "successGreen",
58
+ "toolDiffRemoved": "diffRemovedFg",
59
+ "toolDiffContext": "muted",
60
+ "syntaxComment": "muted",
61
+ "syntaxKeyword": "lavender",
62
+ "syntaxFunction": "shimmerTerracotta",
63
+ "syntaxVariable": "fg",
64
+ "syntaxString": "successGreen",
65
+ "syntaxNumber": "warningAmber",
66
+ "syntaxType": "autoAcceptPurple",
67
+ "syntaxOperator": "toolPink",
68
+ "syntaxPunctuation": "muted",
69
+ "thinkingOff": "dim",
70
+ "thinkingMinimal": "muted",
71
+ "thinkingLow": "shimmerTerracotta",
72
+ "thinkingMedium": "brandTerracotta",
73
+ "thinkingHigh": "toolPink",
74
+ "thinkingXhigh": "errorSoftRed",
75
+ "bashMode": "toolPink",
76
+ "pythonMode": "lavender",
77
+ "statusLineBg": "bg",
78
+ "statusLineSep": "separator",
79
+ "statusLineModel": "brandTerracotta",
80
+ "statusLinePath": "lavender",
81
+ "statusLineGitClean": "successGreen",
82
+ "statusLineGitDirty": "warningAmber",
83
+ "statusLineContext": "lavender",
84
+ "statusLineSpend": "warningAmber",
85
+ "statusLineStaged": "successGreen",
86
+ "statusLineDirty": "warningAmber",
87
+ "statusLineUntracked": "errorSoftRed",
88
+ "statusLineOutput": "fg",
89
+ "statusLineCost": "shimmerTerracotta",
90
+ "statusLineSubagents": "autoAcceptPurple"
91
+ },
92
+ "export": {
93
+ "pageBg": "#1a1a1a",
94
+ "cardBg": "#2a2a2a",
95
+ "infoBg": "#373737"
96
+ },
97
+ "symbols": {
98
+ "preset": "unicode"
99
+ }
100
+ }
@@ -0,0 +1,100 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "codex",
4
+ "vars": {
5
+ "bg": "#0f1115",
6
+ "surface": "#151922",
7
+ "surfaceBright": "#1c2230",
8
+ "selection": "#24313a",
9
+ "fg": "#d7d7d7",
10
+ "muted": "#8a8f98",
11
+ "dim": "#5f6670",
12
+ "borderNeutral": "#343a46",
13
+ "ansiCyan": "#00bcd4",
14
+ "cyanSoft": "#58d5e8",
15
+ "brandMagenta": "#c678dd",
16
+ "magentaSoft": "#d7a4e8",
17
+ "successGreen": "#4caf50",
18
+ "errorRed": "#ef5350",
19
+ "warningOrange": "#f59e0b",
20
+ "diffRemovalRed": "#d84a4a",
21
+ "toolSuccessBg": "#14241a",
22
+ "toolErrorBg": "#2a1718"
23
+ },
24
+ "colors": {
25
+ "accent": "ansiCyan",
26
+ "border": "borderNeutral",
27
+ "borderAccent": "ansiCyan",
28
+ "borderMuted": "dim",
29
+ "success": "successGreen",
30
+ "error": "errorRed",
31
+ "warning": "warningOrange",
32
+ "muted": "muted",
33
+ "dim": "dim",
34
+ "text": "fg",
35
+ "thinkingText": "muted",
36
+ "selectedBg": "selection",
37
+ "userMessageBg": "surface",
38
+ "userMessageText": "fg",
39
+ "customMessageBg": "surface",
40
+ "customMessageText": "fg",
41
+ "customMessageLabel": "brandMagenta",
42
+ "toolPendingBg": "surface",
43
+ "toolSuccessBg": "toolSuccessBg",
44
+ "toolErrorBg": "toolErrorBg",
45
+ "toolTitle": "fg",
46
+ "toolOutput": "muted",
47
+ "mdHeading": "brandMagenta",
48
+ "mdLink": "ansiCyan",
49
+ "mdLinkUrl": "muted",
50
+ "mdCode": "cyanSoft",
51
+ "mdCodeBlock": "fg",
52
+ "mdCodeBlockBorder": "borderNeutral",
53
+ "mdQuote": "muted",
54
+ "mdQuoteBorder": "borderNeutral",
55
+ "mdHr": "dim",
56
+ "mdListBullet": "ansiCyan",
57
+ "toolDiffAdded": "successGreen",
58
+ "toolDiffRemoved": "diffRemovalRed",
59
+ "toolDiffContext": "muted",
60
+ "syntaxComment": "dim",
61
+ "syntaxKeyword": "brandMagenta",
62
+ "syntaxFunction": "cyanSoft",
63
+ "syntaxVariable": "fg",
64
+ "syntaxString": "successGreen",
65
+ "syntaxNumber": "magentaSoft",
66
+ "syntaxType": "ansiCyan",
67
+ "syntaxOperator": "muted",
68
+ "syntaxPunctuation": "dim",
69
+ "thinkingOff": "dim",
70
+ "thinkingMinimal": "muted",
71
+ "thinkingLow": "cyanSoft",
72
+ "thinkingMedium": "ansiCyan",
73
+ "thinkingHigh": "brandMagenta",
74
+ "thinkingXhigh": "errorRed",
75
+ "bashMode": "ansiCyan",
76
+ "pythonMode": "brandMagenta",
77
+ "statusLineBg": "bg",
78
+ "statusLineSep": "dim",
79
+ "statusLineModel": "brandMagenta",
80
+ "statusLinePath": "cyanSoft",
81
+ "statusLineGitClean": "successGreen",
82
+ "statusLineGitDirty": "warningOrange",
83
+ "statusLineContext": "ansiCyan",
84
+ "statusLineSpend": "cyanSoft",
85
+ "statusLineStaged": "successGreen",
86
+ "statusLineDirty": "warningOrange",
87
+ "statusLineUntracked": "diffRemovalRed",
88
+ "statusLineOutput": "fg",
89
+ "statusLineCost": "muted",
90
+ "statusLineSubagents": "brandMagenta"
91
+ },
92
+ "export": {
93
+ "pageBg": "#0f1115",
94
+ "cardBg": "#151922",
95
+ "infoBg": "#1c2230"
96
+ },
97
+ "symbols": {
98
+ "preset": "unicode"
99
+ }
100
+ }
@@ -1,7 +1,13 @@
1
1
  import blue_crab from "./blue-crab.json" with { type: "json" };
2
+ import claude_code from "./claude-code.json" with { type: "json" };
3
+ import codex from "./codex.json" with { type: "json" };
4
+ import opencode from "./opencode.json" with { type: "json" };
2
5
  import red_claw from "./red-claw.json" with { type: "json" };
3
6
 
4
7
  export const defaultThemes = {
5
8
  "blue-crab": blue_crab,
9
+ "claude-code": claude_code,
10
+ codex,
11
+ opencode,
6
12
  "red-claw": red_claw,
7
13
  };
@@ -0,0 +1,102 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/can1357/gajae-code/main/packages/coding-agent/theme-schema.json",
3
+ "name": "opencode",
4
+ "vars": {
5
+ "background": "#212121",
6
+ "currentLine": "#252525",
7
+ "selection": "#303030",
8
+ "backgroundDarker": "#121212",
9
+ "foreground": "#e0e0e0",
10
+ "comment": "#6a6a6a",
11
+ "primary": "#fab283",
12
+ "secondary": "#5c9cf5",
13
+ "accentPurple": "#9d7cd8",
14
+ "errorRed": "#e06c75",
15
+ "warningOrange": "#f5a742",
16
+ "successGreen": "#7fd88f",
17
+ "infoCyan": "#56b6c2",
18
+ "emphasizedYellow": "#e5c07b",
19
+ "border": "#4b4c5c",
20
+ "diffAdded": "#478247",
21
+ "diffRemoved": "#7c4444",
22
+ "diffContext": "#a0a0a0",
23
+ "addedBg": "#303a30",
24
+ "removedBg": "#3a3030"
25
+ },
26
+ "colors": {
27
+ "accent": "primary",
28
+ "border": "border",
29
+ "borderAccent": "accentPurple",
30
+ "borderMuted": "comment",
31
+ "success": "successGreen",
32
+ "error": "errorRed",
33
+ "warning": "warningOrange",
34
+ "muted": "comment",
35
+ "dim": "comment",
36
+ "text": "foreground",
37
+ "thinkingText": "diffContext",
38
+ "selectedBg": "selection",
39
+ "userMessageBg": "currentLine",
40
+ "userMessageText": "foreground",
41
+ "customMessageBg": "currentLine",
42
+ "customMessageText": "foreground",
43
+ "customMessageLabel": "primary",
44
+ "toolPendingBg": "currentLine",
45
+ "toolSuccessBg": "addedBg",
46
+ "toolErrorBg": "removedBg",
47
+ "toolTitle": "foreground",
48
+ "toolOutput": "comment",
49
+ "mdHeading": "secondary",
50
+ "mdLink": "primary",
51
+ "mdLinkUrl": "infoCyan",
52
+ "mdCode": "successGreen",
53
+ "mdCodeBlock": "foreground",
54
+ "mdCodeBlockBorder": "border",
55
+ "mdQuote": "emphasizedYellow",
56
+ "mdQuoteBorder": "emphasizedYellow",
57
+ "mdHr": "comment",
58
+ "mdListBullet": "primary",
59
+ "toolDiffAdded": "diffAdded",
60
+ "toolDiffRemoved": "diffRemoved",
61
+ "toolDiffContext": "diffContext",
62
+ "syntaxComment": "comment",
63
+ "syntaxKeyword": "secondary",
64
+ "syntaxFunction": "primary",
65
+ "syntaxVariable": "errorRed",
66
+ "syntaxString": "successGreen",
67
+ "syntaxNumber": "accentPurple",
68
+ "syntaxType": "emphasizedYellow",
69
+ "syntaxOperator": "infoCyan",
70
+ "syntaxPunctuation": "foreground",
71
+ "thinkingOff": "comment",
72
+ "thinkingMinimal": "diffContext",
73
+ "thinkingLow": "infoCyan",
74
+ "thinkingMedium": "secondary",
75
+ "thinkingHigh": "accentPurple",
76
+ "thinkingXhigh": "errorRed",
77
+ "bashMode": "infoCyan",
78
+ "pythonMode": "accentPurple",
79
+ "statusLineBg": "backgroundDarker",
80
+ "statusLineSep": "border",
81
+ "statusLineModel": "primary",
82
+ "statusLinePath": "secondary",
83
+ "statusLineGitClean": "successGreen",
84
+ "statusLineGitDirty": "warningOrange",
85
+ "statusLineContext": "infoCyan",
86
+ "statusLineSpend": "emphasizedYellow",
87
+ "statusLineStaged": "diffAdded",
88
+ "statusLineDirty": "warningOrange",
89
+ "statusLineUntracked": "errorRed",
90
+ "statusLineOutput": "foreground",
91
+ "statusLineCost": "primary",
92
+ "statusLineSubagents": "accentPurple"
93
+ },
94
+ "export": {
95
+ "pageBg": "#121212",
96
+ "cardBg": "#212121",
97
+ "infoBg": "#252525"
98
+ },
99
+ "symbols": {
100
+ "preset": "unicode"
101
+ }
102
+ }
@@ -810,7 +810,7 @@ const colorValueSchema = z.union([
810
810
 
811
811
  type ColorValue = z.infer<typeof colorValueSchema>;
812
812
 
813
- const THEME_COLOR_KEYS = [
813
+ export const THEME_COLOR_KEYS = [
814
814
  "accent",
815
815
  "border",
816
816
  "borderAccent",
@@ -1648,7 +1648,7 @@ async function loadThemeJson(name: string): Promise<ThemeJson> {
1648
1648
  errorMessage += `\nMissing required color tokens:\n`;
1649
1649
  errorMessage += missingColors.map(c => ` - ${c}`).join("\n");
1650
1650
  errorMessage += `\n\nPlease add these colors to your theme's "colors" object.`;
1651
- errorMessage += `\nSee the built-in themes (red-claw.json, blue-crab.json) for reference values.`;
1651
+ errorMessage += `\nSee the built-in themes under src/modes/theme/defaults/ for reference values.`;
1652
1652
  }
1653
1653
  if (otherErrors.length > 0) {
1654
1654
  errorMessage += `\n\nOther errors:\n${otherErrors.join("\n")}`;
@@ -109,7 +109,7 @@ export interface InteractiveModeContext {
109
109
  retryLoader: Loader | undefined;
110
110
  autoCompactionEscapeHandler?: () => void;
111
111
  retryEscapeHandler?: () => void;
112
- retryCountdownTimer?: ReturnType<typeof setInterval>;
112
+ retryCountdownTimer?: NodeJS.Timeout;
113
113
  unsubscribe?: () => void;
114
114
  onInputCallback?: (input: SubmittedUserInput) => void;
115
115
  optimisticUserMessageSignature: string | undefined;
@@ -706,13 +706,16 @@ export class UiHelpers {
706
706
 
707
707
  /** Move pending bash components from pending area to chat */
708
708
  flushPendingBashComponents(): void {
709
+ // Move (detach, not dispose) the live execution components from the pending
710
+ // area into the chat transcript — they are reused instances, so a disposing
711
+ // removeChild() would tear them down before re-adding.
709
712
  for (const component of this.ctx.pendingBashComponents) {
710
- this.ctx.pendingMessagesContainer.removeChild(component);
713
+ this.ctx.pendingMessagesContainer.detachChild(component);
711
714
  this.ctx.chatContainer.addChild(component);
712
715
  }
713
716
  this.ctx.pendingBashComponents = [];
714
717
  for (const component of this.ctx.pendingPythonComponents) {
715
- this.ctx.pendingMessagesContainer.removeChild(component);
718
+ this.ctx.pendingMessagesContainer.detachChild(component);
716
719
  this.ctx.chatContainer.addChild(component);
717
720
  }
718
721
  this.ctx.pendingPythonComponents = [];
@@ -36,10 +36,13 @@ This mode activates only when the assignment explicitly labels Executor as Ultra
36
36
 
37
37
  When active:
38
38
  - Start from the approved plan/spec/acceptance criteria, then user-facing contracts, then implementation code only as supporting evidence. Treat plan/code mismatches as blockers.
39
- - Exercise the real user-facing invocation rather than inspecting internals alone: GUI/web uses browser automation plus screenshot or image verdict; CLI uses logs or terminal transcripts; API/package uses external consumer or black-box tests through the public interface; algorithm/math uses boundary, property, adversarial, and failure-mode cases.
39
+ - Exercise the real user-facing invocation rather than inspecting internals alone. Live artifacts must be runtime-valid: GUI/web needs a real automation transcript plus non-uniform screenshot; CLI needs executed argv-only replay; native/desktop/TUI needs a real screenshot, PTY capture with control codes, or app-automation transcript. `inlineEvidence` is supplemental only and is never sole proof for live surfaces.
40
+ - For CLI evidence, emit argv-only replay JSON with `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, and `command` as a string array. Use only allowlisted deterministic executables/arguments, or mark unsafe/non-deterministic commands with audited `replayExempt` metadata plus a valid structural fallback artifact.
41
+ - Native/TUI evidence must be structural, not prose-only: screenshot, app transcript, or PTY artifact with terminal control codes.
42
+ - Do not call the `ask` tool while an Ultragoal run is active; record unresolved decisions with `gjc ultragoal record-review-blockers`.
40
43
  - Try to break the work with adversarial cases, not just happy-path confirmations.
41
44
  - Report the QA matrix with the final field names `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`.
42
- - Include artifact refs for every executed surface and adversarial case: transcript ids, log paths, screenshots, image verdicts, test outputs, or other durable evidence.
45
+ - Include artifact refs for every executed surface and adversarial case: transcript ids, log paths, screenshots, image verdicts, CLI replay records, PTY captures, test outputs, or other durable evidence.
43
46
  - Use `status: "not_applicable"` only for rows in `executorQa.contractCoverage` and `executorQa.surfaceEvidence`; each not-applicable row requires `contractRef` plus `reason`. `executorQa.adversarialCases` rows cannot be not-applicable.
44
47
  - Report blockers for any missing plan/spec/acceptance source, contract ambiguity, plan/code mismatch, untestable surface, failed adversarial case, shallow evidence, or missing artifact ref.
45
48
  </ultragoal_red_team_mode>