@gajae-code/coding-agent 0.5.0 → 0.5.2

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 (194) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +26 -0
  4. package/dist/types/cli/args.d.ts +1 -0
  5. package/dist/types/cli/list-models.d.ts +6 -0
  6. package/dist/types/cli/setup-cli.d.ts +8 -1
  7. package/dist/types/commands/gc.d.ts +26 -0
  8. package/dist/types/commands/setup.d.ts +7 -0
  9. package/dist/types/config/file-lock-gc.d.ts +5 -0
  10. package/dist/types/config/file-lock.d.ts +29 -0
  11. package/dist/types/config/model-registry.d.ts +4 -0
  12. package/dist/types/config/models-config-schema.d.ts +5 -0
  13. package/dist/types/config/settings-schema.d.ts +62 -0
  14. package/dist/types/coordinator/contract.d.ts +1 -1
  15. package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
  16. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
  17. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
  18. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
  19. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
  21. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
  24. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
  25. package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
  26. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  27. package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
  28. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
  29. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
  30. package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
  31. package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
  32. package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
  33. package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
  34. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  35. package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
  36. package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
  37. package/dist/types/gjc-runtime/tmux-common.d.ts +11 -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/gjc-runtime/ultragoal-guard.d.ts +10 -0
  41. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  42. package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
  43. package/dist/types/harness-control-plane/owner.d.ts +7 -0
  44. package/dist/types/harness-control-plane/storage.d.ts +20 -0
  45. package/dist/types/modes/components/hook-selector.d.ts +7 -1
  46. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  48. package/dist/types/modes/interactive-mode.d.ts +1 -1
  49. package/dist/types/modes/rpc/rpc-mode.d.ts +72 -2
  50. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
  51. package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
  52. package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
  53. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  54. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  55. package/dist/types/modes/theme/theme.d.ts +1 -0
  56. package/dist/types/modes/types.d.ts +1 -1
  57. package/dist/types/session/agent-session.d.ts +1 -1
  58. package/dist/types/session/blob-store.d.ts +39 -3
  59. package/dist/types/session/history-storage.d.ts +2 -2
  60. package/dist/types/session/session-manager.d.ts +10 -1
  61. package/dist/types/setup/credential-import.d.ts +79 -0
  62. package/dist/types/skill-state/workflow-hud.d.ts +14 -0
  63. package/dist/types/task/executor.d.ts +1 -0
  64. package/dist/types/task/render.d.ts +1 -1
  65. package/dist/types/tools/ask.d.ts +15 -1
  66. package/dist/types/tools/subagent-render.d.ts +7 -1
  67. package/dist/types/tools/subagent.d.ts +27 -0
  68. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  69. package/dist/types/web/search/index.d.ts +4 -4
  70. package/dist/types/web/search/provider.d.ts +16 -20
  71. package/dist/types/web/search/providers/base.d.ts +2 -1
  72. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  73. package/dist/types/web/search/types.d.ts +14 -2
  74. package/package.json +7 -7
  75. package/scripts/build-binary.ts +7 -0
  76. package/src/async/job-manager.ts +52 -0
  77. package/src/cli/args.ts +5 -0
  78. package/src/cli/auth-broker-cli.ts +1 -0
  79. package/src/cli/fast-help.ts +2 -0
  80. package/src/cli/list-models.ts +13 -1
  81. package/src/cli/setup-cli.ts +138 -3
  82. package/src/cli.ts +1 -0
  83. package/src/commands/gc.ts +22 -0
  84. package/src/commands/harness.ts +7 -3
  85. package/src/commands/setup.ts +5 -1
  86. package/src/commands/ultragoal.ts +3 -1
  87. package/src/config/file-lock-gc.ts +193 -0
  88. package/src/config/file-lock.ts +66 -10
  89. package/src/config/model-profile-activation.ts +15 -3
  90. package/src/config/model-profiles.ts +39 -30
  91. package/src/config/model-registry.ts +21 -1
  92. package/src/config/models-config-schema.ts +1 -0
  93. package/src/config/settings-schema.ts +62 -0
  94. package/src/coordinator/contract.ts +1 -0
  95. package/src/coordinator-mcp/server.ts +459 -3
  96. package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
  97. package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
  98. package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
  99. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
  100. package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
  101. package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
  102. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
  103. package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
  104. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
  105. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
  106. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
  107. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
  108. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
  109. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
  110. package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
  111. package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
  112. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  113. package/src/defaults/gjc-defaults.ts +7 -0
  114. package/src/defaults/gjc-grok-cli.ts +22 -0
  115. package/src/extensibility/extensions/index.ts +1 -0
  116. package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
  117. package/src/gjc-runtime/deep-interview-recorder.ts +457 -0
  118. package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
  119. package/src/gjc-runtime/deep-interview-state.ts +324 -0
  120. package/src/gjc-runtime/gc-render.ts +70 -0
  121. package/src/gjc-runtime/gc-runtime.ts +403 -0
  122. package/src/gjc-runtime/launch-tmux.ts +3 -4
  123. package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
  124. package/src/gjc-runtime/ralplan-runtime.ts +232 -19
  125. package/src/gjc-runtime/state-renderer.ts +12 -3
  126. package/src/gjc-runtime/state-runtime.ts +48 -30
  127. package/src/gjc-runtime/state-writer.ts +254 -7
  128. package/src/gjc-runtime/team-gc.ts +49 -0
  129. package/src/gjc-runtime/team-runtime.ts +179 -2
  130. package/src/gjc-runtime/tmux-common.ts +14 -0
  131. package/src/gjc-runtime/tmux-gc.ts +177 -0
  132. package/src/gjc-runtime/tmux-sessions.ts +49 -1
  133. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  134. package/src/gjc-runtime/ultragoal-runtime.ts +1239 -31
  135. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  136. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  137. package/src/harness-control-plane/gc-adapter.ts +184 -0
  138. package/src/harness-control-plane/owner.ts +14 -2
  139. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  140. package/src/harness-control-plane/storage.ts +70 -0
  141. package/src/hooks/skill-state.ts +121 -2
  142. package/src/internal-urls/docs-index.generated.ts +22 -12
  143. package/src/lsp/defaults.json +1 -0
  144. package/src/main.ts +18 -3
  145. package/src/modes/acp/acp-agent.ts +4 -2
  146. package/src/modes/bridge/bridge-mode.ts +2 -1
  147. package/src/modes/components/history-search.ts +5 -2
  148. package/src/modes/components/hook-selector.ts +19 -0
  149. package/src/modes/components/model-selector.ts +51 -8
  150. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  151. package/src/modes/components/status-line/segments.ts +1 -1
  152. package/src/modes/controllers/command-controller.ts +25 -6
  153. package/src/modes/controllers/extension-ui-controller.ts +3 -0
  154. package/src/modes/controllers/selector-controller.ts +81 -1
  155. package/src/modes/interactive-mode.ts +11 -1
  156. package/src/modes/rpc/rpc-mode.ts +266 -34
  157. package/src/modes/shared/agent-wire/command-dispatch.ts +281 -261
  158. package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
  159. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  160. package/src/modes/shared/agent-wire/session-registry.ts +109 -0
  161. package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
  162. package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
  163. package/src/modes/shared/agent-wire/unattended-session.ts +32 -2
  164. package/src/modes/theme/defaults/claude-code.json +100 -0
  165. package/src/modes/theme/defaults/codex.json +100 -0
  166. package/src/modes/theme/defaults/index.ts +6 -0
  167. package/src/modes/theme/defaults/opencode.json +102 -0
  168. package/src/modes/theme/theme.ts +2 -2
  169. package/src/modes/types.ts +1 -1
  170. package/src/prompts/agents/executor.md +5 -2
  171. package/src/sdk.ts +29 -4
  172. package/src/session/agent-session.ts +99 -19
  173. package/src/session/blob-store.ts +59 -3
  174. package/src/session/history-storage.ts +32 -11
  175. package/src/session/session-manager.ts +72 -20
  176. package/src/setup/credential-import.ts +429 -0
  177. package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
  178. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  179. package/src/skill-state/workflow-hud.ts +106 -10
  180. package/src/slash-commands/builtin-registry.ts +3 -2
  181. package/src/task/executor.ts +16 -1
  182. package/src/task/render.ts +18 -7
  183. package/src/tools/ask.ts +59 -2
  184. package/src/tools/cron.ts +1 -1
  185. package/src/tools/job.ts +3 -2
  186. package/src/tools/monitor.ts +36 -1
  187. package/src/tools/subagent-render.ts +128 -29
  188. package/src/tools/subagent.ts +173 -9
  189. package/src/tools/ultragoal-ask-guard.ts +39 -0
  190. package/src/web/search/index.ts +25 -25
  191. package/src/web/search/provider.ts +178 -87
  192. package/src/web/search/providers/base.ts +2 -1
  193. package/src/web/search/providers/openai-compatible.ts +151 -0
  194. package/src/web/search/types.ts +47 -22
@@ -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
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Cross-process registry of running gjc RPC sessions (issue 10).
3
+ *
4
+ * Each live RPC server writes a record under `<agent-dir>/rpc-sessions/<id>.json`
5
+ * on start and removes it on shutdown, so a separate process can discover which
6
+ * sessions are alive (and, once persistence lands in issue 09, how to reach
7
+ * them). Listing reaps records whose owning process is no longer alive, so a
8
+ * crashed server never leaves a permanent phantom entry.
9
+ */
10
+ import * as fs from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ import { getAgentDir } from "@gajae-code/utils";
13
+
14
+ export type RpcSessionTransport = "stdio" | "bridge" | "socket";
15
+
16
+ export interface RpcSessionRecord {
17
+ sessionId: string;
18
+ pid: number;
19
+ transport: RpcSessionTransport;
20
+ cwd: string;
21
+ model?: string;
22
+ /** ISO-8601 start timestamp. */
23
+ startedAt: string;
24
+ /** Reachable endpoint for persistent transports (issue 09); absent for stdio. */
25
+ endpoint?: string;
26
+ }
27
+
28
+ /** Registry directory: `<agent-dir>/rpc-sessions` (honors GJC_CODING_AGENT_DIR via getAgentDir). */
29
+ function rpcSessionsDir(agentDir?: string): string {
30
+ return path.join(agentDir ?? getAgentDir(), "rpc-sessions");
31
+ }
32
+
33
+ function recordPath(sessionId: string, agentDir?: string): string {
34
+ return path.join(rpcSessionsDir(agentDir), `${sessionId}.json`);
35
+ }
36
+
37
+ /**
38
+ * Write (or replace) the registry record for a session. The record is written to
39
+ * a same-directory temp file and atomically renamed into place so a concurrent
40
+ * reader never observes (and reaps) a partially-written record.
41
+ */
42
+ export async function registerRpcSession(record: RpcSessionRecord, agentDir?: string): Promise<string> {
43
+ const file = recordPath(record.sessionId, agentDir);
44
+ // `.tmp` suffix keeps the staging file out of the `*.json` listing/reaping path.
45
+ const staging = `${file}.${process.pid}.tmp`;
46
+ await Bun.write(staging, JSON.stringify(record));
47
+ await fs.rename(staging, file);
48
+ return file;
49
+ }
50
+
51
+ /** Remove a session's registry record. Best-effort: a missing file is not an error. */
52
+ export async function unregisterRpcSession(sessionId: string, agentDir?: string): Promise<void> {
53
+ await fs.rm(recordPath(sessionId, agentDir), { force: true });
54
+ }
55
+
56
+ function isProcessAlive(pid: number): boolean {
57
+ if (!Number.isInteger(pid) || pid <= 0) return false;
58
+ try {
59
+ // Signal 0 performs error checking without delivering a signal.
60
+ process.kill(pid, 0);
61
+ return true;
62
+ } catch (err) {
63
+ // ESRCH => no such process (dead). EPERM => alive but owned by another user.
64
+ return (err as NodeJS.ErrnoException).code === "EPERM";
65
+ }
66
+ }
67
+
68
+ function parseRecord(raw: string): RpcSessionRecord | undefined {
69
+ let obj: Partial<RpcSessionRecord>;
70
+ try {
71
+ obj = JSON.parse(raw) as Partial<RpcSessionRecord>;
72
+ } catch {
73
+ return undefined;
74
+ }
75
+ if (typeof obj.sessionId !== "string" || typeof obj.pid !== "number") return undefined;
76
+ return obj as RpcSessionRecord;
77
+ }
78
+
79
+ /**
80
+ * List live RPC sessions, reaping records whose process is gone or whose file is
81
+ * unparseable. Returns records sorted by `startedAt` ascending.
82
+ */
83
+ export async function listRpcSessions(agentDir?: string): Promise<RpcSessionRecord[]> {
84
+ const dir = rpcSessionsDir(agentDir);
85
+ let entries: string[];
86
+ try {
87
+ entries = await fs.readdir(dir);
88
+ } catch {
89
+ return [];
90
+ }
91
+ const live: RpcSessionRecord[] = [];
92
+ for (const entry of entries) {
93
+ if (!entry.endsWith(".json")) continue;
94
+ const file = path.join(dir, entry);
95
+ let raw: string;
96
+ try {
97
+ raw = await fs.readFile(file, "utf8");
98
+ } catch {
99
+ continue;
100
+ }
101
+ const record = parseRecord(raw);
102
+ if (!record || !isProcessAlive(record.pid)) {
103
+ await fs.rm(file, { force: true });
104
+ continue;
105
+ }
106
+ live.push(record);
107
+ }
108
+ return live.sort((a, b) => a.startedAt.localeCompare(b.startedAt));
109
+ }
@@ -45,6 +45,30 @@ export function actionClassForScope(scope: BridgeCommandScope): RpcUnattendedAct
45
45
  }
46
46
  }
47
47
 
48
+ /** Runtime list of every v1 action class — membership-validation source for negotiate (#319). */
49
+ export const RPC_UNATTENDED_ACTION_CLASSES: readonly RpcUnattendedActionClass[] = [
50
+ "command.prompt",
51
+ "command.control",
52
+ "command.bash",
53
+ "command.export",
54
+ "command.session",
55
+ "command.model",
56
+ "command.message_read",
57
+ "command.host_tools",
58
+ "command.host_uri",
59
+ "command.admin",
60
+ "bash.readonly",
61
+ "bash.mutating",
62
+ "bash.destructive",
63
+ "git.force_push",
64
+ "file.delete",
65
+ "file.write",
66
+ "host_tool.invoke",
67
+ "host_uri.read",
68
+ "host_uri.write",
69
+ "auth.login",
70
+ ];
71
+
48
72
  const READONLY_COMMANDS = new Set([
49
73
  "ls",
50
74
  "cat",
@@ -24,7 +24,8 @@ import type {
24
24
  RpcUnattendedRefusalCode,
25
25
  } from "../../rpc/rpc-types";
26
26
  import type { BridgeCommandScope } from "./scopes";
27
- import { actionClassForScope, classifyBashAction } from "./unattended-action-policy";
27
+ import { BRIDGE_COMMAND_SCOPES, MANDATORY_FLOOR_COMMAND_SCOPES } from "./scopes";
28
+ import { actionClassForScope, classifyBashAction, RPC_UNATTENDED_ACTION_CLASSES } from "./unattended-action-policy";
28
29
 
29
30
  /** Coordinated abort surfaces invoked exactly once on a budget breach / abort. */
30
31
  export interface UnattendedAbortHooks {
@@ -157,8 +158,11 @@ export class UnattendedRunController {
157
158
  this.sessionId = ctx.sessionId;
158
159
  this.actor = declaration.actor;
159
160
  this.budget = budget;
160
- this.scopes = new Set(declaration.scopes);
161
- this.actionAllowlist = new Set(declaration.action_allowlist);
161
+ this.scopes = new Set([...declaration.scopes, ...MANDATORY_FLOOR_COMMAND_SCOPES]);
162
+ this.actionAllowlist = new Set([
163
+ ...declaration.action_allowlist,
164
+ ...MANDATORY_FLOOR_COMMAND_SCOPES.map(actionClassForScope),
165
+ ]);
162
166
  this.now = ctx.now ?? Date.now;
163
167
  this.audit = ctx.audit;
164
168
  this.abortHooks = ctx.abortHooks ?? {};
@@ -183,6 +187,22 @@ export class UnattendedRunController {
183
187
  "declaration.action_allowlist must be string[]",
184
188
  );
185
189
  }
190
+ const unknownScopes = d.scopes.filter(scope => !BRIDGE_COMMAND_SCOPES.includes(scope as BridgeCommandScope));
191
+ if (unknownScopes.length > 0) {
192
+ throw new UnattendedNegotiationError(
193
+ "invalid_unattended_declaration",
194
+ `declaration.scopes contains unknown scope(s): ${unknownScopes.join(", ")}`,
195
+ );
196
+ }
197
+ const unknownActions = d.action_allowlist.filter(
198
+ action => !RPC_UNATTENDED_ACTION_CLASSES.includes(action as RpcUnattendedActionClass),
199
+ );
200
+ if (unknownActions.length > 0) {
201
+ throw new UnattendedNegotiationError(
202
+ "invalid_unattended_declaration",
203
+ `declaration.action_allowlist contains unknown action class(es): ${unknownActions.join(", ")}`,
204
+ );
205
+ }
186
206
  const budget = validateBudget(d.budget);
187
207
  // Reject providers that cannot account for tokens/cost (fail-closed): require
188
208
  // an explicit positive capability signal — omitted/unknown is refused too.
@@ -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,
@@ -31,6 +32,27 @@ import {
31
32
  } from "./unattended-run-controller";
32
33
  import { type GateStore, MemoryGateStore, type OpenGateInput, WorkflowGateBroker } from "./workflow-gate-broker";
33
34
 
35
+ /**
36
+ * RPC commands that perform agent/tool work and therefore consume one unit of the
37
+ * `max_tool_calls` budget. Read-only/control/cancellation commands are wall-time-bounded
38
+ * and scope-checked but must NOT charge the tool-call budget (issue 04).
39
+ */
40
+ const CHARGED_COMMAND_TYPES = new Set<RpcCommand["type"]>(["bash", "prompt", "steer", "follow_up", "abort_and_prompt"]);
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
+
34
56
  /** Minimal surface a skill runtime / ask tool uses to emit a gate and await its answer. */
35
57
  export interface WorkflowGateEmitter {
36
58
  /** True only when unattended mode has been negotiated. */
@@ -85,7 +107,7 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
85
107
  this.#rejectAllPending(new Error(`unattended run aborted: ${reason}`));
86
108
  },
87
109
  },
88
- providerSupportsTokenCostMetrics: this.opts.providerSupportsTokenCostMetrics ?? true,
110
+ providerSupportsTokenCostMetrics: this.opts.providerSupportsTokenCostMetrics,
89
111
  });
90
112
  this.#controller = controller;
91
113
  this.#broker = new WorkflowGateBroker(this.opts.runId, this.opts.store ?? new MemoryGateStore(), {
@@ -116,7 +138,15 @@ export class UnattendedSessionControlPlane implements RpcUnattendedControlPlane,
116
138
 
117
139
  preflightCommand(command: RpcCommand): void {
118
140
  if (!this.#controller) return;
119
- this.#controller.preflightToolCall(`${command.type} preflight`);
141
+ const phase = `${command.type} preflight`;
142
+ // Always enforce wall-time; only charge the tool-call budget for commands that perform
143
+ // agent/tool work (issue 04). Read-only/control/cancellation commands must not consume
144
+ // max_tool_calls, but remain wall-time-bounded and scope/action-checked.
145
+ if (CHARGED_COMMAND_TYPES.has(command.type)) {
146
+ this.#controller.preflightToolCall(phase);
147
+ } else {
148
+ this.#controller.checkWallTime(phase);
149
+ }
120
150
  if (command.type === "bash") {
121
151
  this.#controller.authorizeBash(command.command);
122
152
  return;
@@ -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;
@@ -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>