@gajae-code/coding-agent 0.2.4 → 0.3.0

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 (266) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +145 -2
  4. package/dist/types/commands/harness.d.ts +37 -0
  5. package/dist/types/config/settings-schema.d.ts +13 -3
  6. package/dist/types/config/settings.d.ts +3 -1
  7. package/dist/types/deep-interview/render-middleware.d.ts +5 -0
  8. package/dist/types/discovery/helpers.d.ts +1 -0
  9. package/dist/types/exec/bash-executor.d.ts +8 -1
  10. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  11. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  12. package/dist/types/extensibility/shared-events.d.ts +1 -0
  13. package/dist/types/gjc-runtime/restricted-role-agent-bash.d.ts +2 -0
  14. package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
  15. package/dist/types/gjc-runtime/state-migrations.d.ts +24 -0
  16. package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
  17. package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
  18. package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
  19. package/dist/types/gjc-runtime/state-writer.d.ts +137 -0
  20. package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
  21. package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
  22. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  23. package/dist/types/harness-control-plane/control-endpoint.d.ts +30 -0
  24. package/dist/types/harness-control-plane/finalize.d.ts +47 -0
  25. package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
  26. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  27. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  28. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  29. package/dist/types/harness-control-plane/receipts.d.ts +88 -0
  30. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  31. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  32. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  33. package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
  34. package/dist/types/harness-control-plane/storage.d.ts +53 -0
  35. package/dist/types/harness-control-plane/types.d.ts +162 -0
  36. package/dist/types/hooks/skill-keywords.d.ts +2 -1
  37. package/dist/types/hooks/skill-state.d.ts +2 -29
  38. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  39. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  40. package/dist/types/modes/components/skill-hud/render.d.ts +1 -1
  41. package/dist/types/modes/interactive-mode.d.ts +2 -0
  42. package/dist/types/modes/theme/defaults/index.d.ts +45 -9477
  43. package/dist/types/modes/theme/theme.d.ts +1 -5
  44. package/dist/types/modes/types.d.ts +2 -0
  45. package/dist/types/sdk.d.ts +4 -0
  46. package/dist/types/session/agent-session.d.ts +8 -0
  47. package/dist/types/session/streaming-output.d.ts +11 -0
  48. package/dist/types/skill-state/active-state.d.ts +3 -0
  49. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  50. package/dist/types/skill-state/workflow-state-contract.d.ts +24 -0
  51. package/dist/types/task/executor.d.ts +3 -0
  52. package/dist/types/task/types.d.ts +56 -3
  53. package/dist/types/tools/bash-allowed-prefixes.d.ts +5 -0
  54. package/dist/types/tools/bash.d.ts +24 -0
  55. package/dist/types/tools/cron.d.ts +110 -0
  56. package/dist/types/tools/index.d.ts +4 -0
  57. package/dist/types/tools/monitor.d.ts +54 -0
  58. package/dist/types/tools/subagent.d.ts +11 -1
  59. package/dist/types/web/search/index.d.ts +1 -0
  60. package/dist/types/web/search/provider.d.ts +11 -4
  61. package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
  62. package/dist/types/web/search/types.d.ts +1 -1
  63. package/package.json +7 -7
  64. package/src/async/job-manager.ts +522 -6
  65. package/src/cli/agents-cli.ts +3 -0
  66. package/src/cli/auth-broker-cli.ts +1 -0
  67. package/src/cli/config-cli.ts +10 -2
  68. package/src/cli.ts +2 -0
  69. package/src/commands/harness.ts +592 -0
  70. package/src/commands/team.ts +36 -39
  71. package/src/config/settings-schema.ts +15 -2
  72. package/src/config/settings.ts +49 -7
  73. package/src/deep-interview/render-middleware.ts +366 -0
  74. package/src/defaults/gjc/skills/deep-interview/SKILL.md +9 -2
  75. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  76. package/src/defaults/gjc/skills/team/SKILL.md +47 -21
  77. package/src/defaults/gjc/skills/ultragoal/SKILL.md +78 -11
  78. package/src/discovery/helpers.ts +5 -0
  79. package/src/eval/js/shared/rewrite-imports.ts +1 -2
  80. package/src/exec/bash-executor.ts +20 -9
  81. package/src/extensibility/custom-tools/types.ts +1 -0
  82. package/src/extensibility/extensions/types.ts +6 -0
  83. package/src/extensibility/shared-events.ts +1 -0
  84. package/src/gjc-runtime/deep-interview-runtime.ts +40 -21
  85. package/src/gjc-runtime/goal-mode-request.ts +11 -3
  86. package/src/gjc-runtime/ralplan-runtime.ts +27 -10
  87. package/src/gjc-runtime/restricted-role-agent-bash.ts +5 -0
  88. package/src/gjc-runtime/state-graph.ts +86 -0
  89. package/src/gjc-runtime/state-migrations.ts +132 -0
  90. package/src/gjc-runtime/state-renderer.ts +345 -0
  91. package/src/gjc-runtime/state-runtime.ts +733 -21
  92. package/src/gjc-runtime/state-validation.ts +49 -0
  93. package/src/gjc-runtime/state-writer.ts +718 -0
  94. package/src/gjc-runtime/team-runtime.ts +1083 -89
  95. package/src/gjc-runtime/ultragoal-runtime.ts +348 -19
  96. package/src/gjc-runtime/workflow-manifest.generated.json +1497 -0
  97. package/src/gjc-runtime/workflow-manifest.ts +425 -0
  98. package/src/harness-control-plane/classifier.ts +128 -0
  99. package/src/harness-control-plane/control-endpoint.ts +137 -0
  100. package/src/harness-control-plane/finalize.ts +222 -0
  101. package/src/harness-control-plane/frame-mapper.ts +286 -0
  102. package/src/harness-control-plane/operate.ts +225 -0
  103. package/src/harness-control-plane/owner.ts +553 -0
  104. package/src/harness-control-plane/preserve.ts +102 -0
  105. package/src/harness-control-plane/receipts.ts +216 -0
  106. package/src/harness-control-plane/rpc-adapter.ts +276 -0
  107. package/src/harness-control-plane/seams.ts +39 -0
  108. package/src/harness-control-plane/session-lease.ts +388 -0
  109. package/src/harness-control-plane/state-machine.ts +97 -0
  110. package/src/harness-control-plane/storage.ts +257 -0
  111. package/src/harness-control-plane/types.ts +214 -0
  112. package/src/hooks/skill-keywords.ts +4 -2
  113. package/src/hooks/skill-state.ts +25 -42
  114. package/src/internal-urls/docs-index.generated.ts +6 -4
  115. package/src/lsp/render.ts +1 -1
  116. package/src/modes/acp/acp-agent.ts +1 -1
  117. package/src/modes/acp/acp-client-bridge.ts +1 -1
  118. package/src/modes/components/agent-dashboard.ts +1 -1
  119. package/src/modes/components/assistant-message.ts +5 -1
  120. package/src/modes/components/diff.ts +2 -2
  121. package/src/modes/components/hook-selector.ts +72 -2
  122. package/src/modes/components/skill-hud/render.ts +7 -2
  123. package/src/modes/controllers/event-controller.ts +71 -6
  124. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  125. package/src/modes/controllers/input-controller.ts +19 -3
  126. package/src/modes/controllers/selector-controller.ts +3 -2
  127. package/src/modes/interactive-mode.ts +21 -2
  128. package/src/modes/theme/defaults/index.ts +0 -196
  129. package/src/modes/theme/theme.ts +35 -35
  130. package/src/modes/types.ts +2 -0
  131. package/src/prompts/agents/architect.md +5 -1
  132. package/src/prompts/agents/critic.md +5 -1
  133. package/src/prompts/agents/executor.md +13 -0
  134. package/src/prompts/agents/frontmatter.md +1 -0
  135. package/src/prompts/agents/planner.md +5 -1
  136. package/src/prompts/tools/bash.md +9 -0
  137. package/src/prompts/tools/cron.md +25 -0
  138. package/src/prompts/tools/monitor.md +30 -0
  139. package/src/prompts/tools/subagent.md +33 -3
  140. package/src/runtime-mcp/oauth-flow.ts +4 -2
  141. package/src/sdk.ts +7 -0
  142. package/src/session/agent-session.ts +247 -38
  143. package/src/session/session-manager.ts +13 -1
  144. package/src/session/streaming-output.ts +21 -0
  145. package/src/skill-state/active-state.ts +222 -78
  146. package/src/skill-state/deep-interview-mutation-guard.ts +91 -13
  147. package/src/skill-state/initial-phase.ts +2 -0
  148. package/src/skill-state/workflow-state-contract.ts +26 -0
  149. package/src/task/agents.ts +1 -0
  150. package/src/task/executor.ts +51 -8
  151. package/src/task/index.ts +120 -8
  152. package/src/task/render.ts +6 -3
  153. package/src/task/types.ts +57 -3
  154. package/src/tools/ask.ts +28 -7
  155. package/src/tools/bash-allowed-prefixes.ts +169 -0
  156. package/src/tools/bash.ts +190 -29
  157. package/src/tools/browser/tab-worker.ts +1 -1
  158. package/src/tools/cron.ts +665 -0
  159. package/src/tools/index.ts +20 -2
  160. package/src/tools/monitor.ts +136 -0
  161. package/src/tools/subagent.ts +255 -64
  162. package/src/vim/engine.ts +3 -3
  163. package/src/web/search/index.ts +31 -18
  164. package/src/web/search/provider.ts +57 -12
  165. package/src/web/search/providers/duckduckgo.ts +279 -0
  166. package/src/web/search/types.ts +2 -0
  167. package/src/modes/theme/dark.json +0 -95
  168. package/src/modes/theme/defaults/alabaster.json +0 -93
  169. package/src/modes/theme/defaults/amethyst.json +0 -96
  170. package/src/modes/theme/defaults/anthracite.json +0 -93
  171. package/src/modes/theme/defaults/basalt.json +0 -91
  172. package/src/modes/theme/defaults/birch.json +0 -95
  173. package/src/modes/theme/defaults/dark-abyss.json +0 -91
  174. package/src/modes/theme/defaults/dark-arctic.json +0 -104
  175. package/src/modes/theme/defaults/dark-aurora.json +0 -95
  176. package/src/modes/theme/defaults/dark-catppuccin.json +0 -107
  177. package/src/modes/theme/defaults/dark-cavern.json +0 -91
  178. package/src/modes/theme/defaults/dark-copper.json +0 -95
  179. package/src/modes/theme/defaults/dark-cosmos.json +0 -90
  180. package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
  181. package/src/modes/theme/defaults/dark-dracula.json +0 -98
  182. package/src/modes/theme/defaults/dark-eclipse.json +0 -91
  183. package/src/modes/theme/defaults/dark-ember.json +0 -95
  184. package/src/modes/theme/defaults/dark-equinox.json +0 -90
  185. package/src/modes/theme/defaults/dark-forest.json +0 -96
  186. package/src/modes/theme/defaults/dark-github.json +0 -105
  187. package/src/modes/theme/defaults/dark-gruvbox.json +0 -112
  188. package/src/modes/theme/defaults/dark-lavender.json +0 -95
  189. package/src/modes/theme/defaults/dark-lunar.json +0 -89
  190. package/src/modes/theme/defaults/dark-midnight.json +0 -95
  191. package/src/modes/theme/defaults/dark-monochrome.json +0 -94
  192. package/src/modes/theme/defaults/dark-monokai.json +0 -98
  193. package/src/modes/theme/defaults/dark-nebula.json +0 -90
  194. package/src/modes/theme/defaults/dark-nord.json +0 -97
  195. package/src/modes/theme/defaults/dark-ocean.json +0 -101
  196. package/src/modes/theme/defaults/dark-one.json +0 -100
  197. package/src/modes/theme/defaults/dark-poimandres.json +0 -141
  198. package/src/modes/theme/defaults/dark-rainforest.json +0 -91
  199. package/src/modes/theme/defaults/dark-reef.json +0 -91
  200. package/src/modes/theme/defaults/dark-retro.json +0 -92
  201. package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
  202. package/src/modes/theme/defaults/dark-sakura.json +0 -95
  203. package/src/modes/theme/defaults/dark-slate.json +0 -95
  204. package/src/modes/theme/defaults/dark-solarized.json +0 -97
  205. package/src/modes/theme/defaults/dark-solstice.json +0 -90
  206. package/src/modes/theme/defaults/dark-starfall.json +0 -91
  207. package/src/modes/theme/defaults/dark-sunset.json +0 -99
  208. package/src/modes/theme/defaults/dark-swamp.json +0 -90
  209. package/src/modes/theme/defaults/dark-synthwave.json +0 -103
  210. package/src/modes/theme/defaults/dark-taiga.json +0 -91
  211. package/src/modes/theme/defaults/dark-terminal.json +0 -95
  212. package/src/modes/theme/defaults/dark-tokyo-night.json +0 -101
  213. package/src/modes/theme/defaults/dark-tundra.json +0 -91
  214. package/src/modes/theme/defaults/dark-twilight.json +0 -91
  215. package/src/modes/theme/defaults/dark-volcanic.json +0 -91
  216. package/src/modes/theme/defaults/graphite.json +0 -92
  217. package/src/modes/theme/defaults/light-arctic.json +0 -107
  218. package/src/modes/theme/defaults/light-aurora-day.json +0 -91
  219. package/src/modes/theme/defaults/light-canyon.json +0 -91
  220. package/src/modes/theme/defaults/light-catppuccin.json +0 -106
  221. package/src/modes/theme/defaults/light-cirrus.json +0 -90
  222. package/src/modes/theme/defaults/light-coral.json +0 -95
  223. package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
  224. package/src/modes/theme/defaults/light-dawn.json +0 -90
  225. package/src/modes/theme/defaults/light-dunes.json +0 -91
  226. package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
  227. package/src/modes/theme/defaults/light-forest.json +0 -100
  228. package/src/modes/theme/defaults/light-frost.json +0 -95
  229. package/src/modes/theme/defaults/light-github.json +0 -115
  230. package/src/modes/theme/defaults/light-glacier.json +0 -91
  231. package/src/modes/theme/defaults/light-gruvbox.json +0 -108
  232. package/src/modes/theme/defaults/light-haze.json +0 -90
  233. package/src/modes/theme/defaults/light-honeycomb.json +0 -95
  234. package/src/modes/theme/defaults/light-lagoon.json +0 -91
  235. package/src/modes/theme/defaults/light-lavender.json +0 -95
  236. package/src/modes/theme/defaults/light-meadow.json +0 -91
  237. package/src/modes/theme/defaults/light-mint.json +0 -95
  238. package/src/modes/theme/defaults/light-monochrome.json +0 -101
  239. package/src/modes/theme/defaults/light-ocean.json +0 -99
  240. package/src/modes/theme/defaults/light-one.json +0 -99
  241. package/src/modes/theme/defaults/light-opal.json +0 -91
  242. package/src/modes/theme/defaults/light-orchard.json +0 -91
  243. package/src/modes/theme/defaults/light-paper.json +0 -95
  244. package/src/modes/theme/defaults/light-poimandres.json +0 -141
  245. package/src/modes/theme/defaults/light-prism.json +0 -90
  246. package/src/modes/theme/defaults/light-retro.json +0 -98
  247. package/src/modes/theme/defaults/light-sand.json +0 -95
  248. package/src/modes/theme/defaults/light-savanna.json +0 -91
  249. package/src/modes/theme/defaults/light-solarized.json +0 -102
  250. package/src/modes/theme/defaults/light-soleil.json +0 -90
  251. package/src/modes/theme/defaults/light-sunset.json +0 -99
  252. package/src/modes/theme/defaults/light-synthwave.json +0 -98
  253. package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
  254. package/src/modes/theme/defaults/light-wetland.json +0 -91
  255. package/src/modes/theme/defaults/light-zenith.json +0 -89
  256. package/src/modes/theme/defaults/limestone.json +0 -94
  257. package/src/modes/theme/defaults/mahogany.json +0 -97
  258. package/src/modes/theme/defaults/marble.json +0 -93
  259. package/src/modes/theme/defaults/obsidian.json +0 -91
  260. package/src/modes/theme/defaults/onyx.json +0 -91
  261. package/src/modes/theme/defaults/pearl.json +0 -93
  262. package/src/modes/theme/defaults/porcelain.json +0 -91
  263. package/src/modes/theme/defaults/quartz.json +0 -96
  264. package/src/modes/theme/defaults/sandstone.json +0 -95
  265. package/src/modes/theme/defaults/titanium.json +0 -90
  266. package/src/modes/theme/light.json +0 -93
@@ -1,11 +1,13 @@
1
1
  import { Args, Command, Flags } from "@gajae-code/utils/cli";
2
+ import { renderTeamStatusMarkdown } from "../gjc-runtime/state-renderer";
2
3
  import {
3
4
  buildTeamHudSummary,
4
5
  executeGjcTeamApiOperation,
5
6
  type GjcTeamSnapshot,
6
7
  listGjcTeams,
7
- monitorGjcTeam,
8
+ monitorGjcTeamSnapshot,
8
9
  parseTeamLaunchArgs,
10
+ persistGjcTeamModeStateSummary,
9
11
  readGjcTeamEvents,
10
12
  readGjcTeamSnapshot,
11
13
  shutdownGjcTeam,
@@ -31,6 +33,7 @@ async function syncTeamHud(snapshot: GjcTeamSnapshot): Promise<void> {
31
33
  hud: await buildTeamHudSummary(snapshot, events.at(-1)),
32
34
  source: "gjc-team",
33
35
  });
36
+ await persistGjcTeamModeStateSummary(snapshot, process.cwd());
34
37
  } catch {
35
38
  // HUD sync is best-effort and must not change command semantics.
36
39
  }
@@ -42,29 +45,6 @@ function formatTaskCounts(counts: Record<string, number>): string {
42
45
  .join(" ");
43
46
  }
44
47
 
45
- function formatNotificationSummary(snapshot: GjcTeamSnapshot): string {
46
- const summary = snapshot.notification_summary;
47
- return `notifications: total=${summary.total} replay_eligible=${summary.replay_eligible} pending=${summary.by_state.pending} queued=${summary.by_state.queued} deferred=${summary.by_state.deferred} failed=${summary.by_state.failed}`;
48
- }
49
-
50
- function formatAwaitingIntegrationNextStep(snapshot: GjcTeamSnapshot): string[] {
51
- if (snapshot.phase !== "awaiting_integration") return [];
52
- return [
53
- "next: worker tasks are completed, but integration still needs leader attention before the team is complete",
54
- ];
55
- }
56
-
57
- function formatIntegrationSummary(snapshot: {
58
- integration_by_worker?: Record<string, { status?: string; conflict_files?: string[] }>;
59
- }): string[] {
60
- const entries = Object.entries(snapshot.integration_by_worker ?? {});
61
- if (entries.length === 0) return ["integration: no attempts recorded"];
62
- return entries.map(([worker, state]) => {
63
- const files = state.conflict_files?.length ? ` files=${state.conflict_files.join(",")}` : "";
64
- return `integration: ${worker} ${state.status ?? "unknown"}${files}`;
65
- });
66
- }
67
-
68
48
  function parseInputFlag(argv: string[]): Record<string, unknown> {
69
49
  const index = argv.indexOf("--input");
70
50
  if (index < 0) return {};
@@ -76,12 +56,13 @@ function parseInputFlag(argv: string[]): Record<string, unknown> {
76
56
  }
77
57
 
78
58
  export default class Team extends Command {
79
- static description = "Run native GJC tmux team orchestration; --dry-run writes ephemeral .gjc/state/team state only";
59
+ static description =
60
+ "Run native GJC tmux team orchestration from inside an existing tmux/GJC --tmux session; --dry-run writes ephemeral .gjc/state/team state only";
80
61
  static strict = false;
81
62
 
82
63
  static args = {
83
64
  action: Args.string({
84
- description: "start (default), status, list, shutdown, resume, or api",
65
+ description: "start (default), status, monitor, list, shutdown, resume, or api",
85
66
  required: false,
86
67
  }),
87
68
  };
@@ -96,8 +77,10 @@ export default class Team extends Command {
96
77
  };
97
78
 
98
79
  static examples = [
80
+ "gjc --tmux # start/attach the required tmux-backed leader session first",
99
81
  'gjc team 3:executor "Implement the approved plan"',
100
82
  "gjc team status <team-name> --json",
83
+ "gjc team monitor <team-name> --json",
101
84
  'gjc team api claim-task --input \'{"team_name":"demo","worker_id":"worker-1"}\' --json',
102
85
  'gjc team 2:executor --dry-run --json "Preview state only"',
103
86
  "gjc team shutdown <team-name>",
@@ -119,26 +102,36 @@ export default class Team extends Command {
119
102
  return;
120
103
  }
121
104
 
122
- if (action === "status" || action === "resume") {
105
+ if (action === "status") {
106
+ const teamName = rest.find(arg => !arg.startsWith("--"));
107
+ if (!teamName) throw new Error("missing_team_name");
108
+ const snapshot = await readGjcTeamSnapshot(teamName);
109
+ if (json) {
110
+ writeJson(snapshot);
111
+ return;
112
+ }
113
+ writeText([
114
+ renderTeamStatusMarkdown(snapshot).trimEnd(),
115
+ "- mode: read-only status; use `gjc team monitor <team>` or `gjc team resume <team>` for recovery/integration",
116
+ ]);
117
+ void formatTaskCounts(snapshot.task_counts);
118
+ return;
119
+ }
120
+
121
+ if (action === "monitor" || action === "resume") {
123
122
  const teamName = rest.find(arg => !arg.startsWith("--"));
124
123
  if (!teamName) throw new Error("missing_team_name");
125
- const snapshot = await monitorGjcTeam(teamName);
124
+ const snapshot = await monitorGjcTeamSnapshot(teamName);
126
125
  await syncTeamHud(snapshot);
127
126
  if (json) {
128
127
  writeJson(snapshot);
129
128
  return;
130
129
  }
131
130
  writeText([
132
- `team: ${snapshot.team_name}`,
133
- `phase: ${snapshot.phase}`,
134
- `tmux: ${snapshot.tmux_target || snapshot.tmux_session}`,
135
- `state: ${snapshot.state_dir}`,
136
- `tasks: ${snapshot.task_total} (${formatTaskCounts(snapshot.task_counts)})`,
137
- `workers: ${snapshot.workers.map(worker => `${worker.id}:${worker.status}`).join(" ")}`,
138
- formatNotificationSummary(snapshot),
139
- ...formatAwaitingIntegrationNextStep(snapshot),
140
- ...formatIntegrationSummary(snapshot),
131
+ renderTeamStatusMarkdown(snapshot).trimEnd(),
132
+ "- mode: mutating monitor; liveness recovery and integration may have run",
141
133
  ]);
134
+ void formatTaskCounts(snapshot.task_counts);
142
135
  return;
143
136
  }
144
137
 
@@ -162,8 +155,12 @@ export default class Team extends Command {
162
155
  "Supported operations:",
163
156
  "send-message broadcast mailbox-list mailbox-mark-delivered mailbox-mark-notified notification-list notification-read notification-replay notification-mark-pane-attempt worker-startup-ack",
164
157
  "create-task read-task list-tasks update-task claim-task transition-task-status release-task-claim",
165
- "read-config read-manifest read-worker-status read-worker-heartbeat update-worker-heartbeat write-worker-inbox write-worker-identity",
166
- "append-event read-events await-event write-shutdown-request read-shutdown-ack read-monitor-snapshot write-monitor-snapshot read-task-approval write-task-approval",
158
+ "read-config read-manifest read-worker-status update-worker-status read-worker-heartbeat recover-stale-claims update-worker-heartbeat write-worker-inbox write-worker-identity",
159
+ "append-event read-events read-traces await-event write-shutdown-request read-shutdown-ack read-monitor-snapshot write-monitor-snapshot read-task-approval write-task-approval",
160
+ "Completion example:",
161
+ 'transition-task-status --input \'{"team_name":"demo","task_id":"task-1","to":"completed","claim_token":"...","completion_evidence":{"summary":"done","items":[{"kind":"command","status":"passed","summary":"focused tests passed","command":"bun test packages/coding-agent/test/gjc-runtime/team-runtime.test.ts"}]}}\' --json',
162
+ 'Review-only completion may use {"kind":"inspection","status":"verified","summary":"review passed","location":"agent://review"}.',
163
+ 'Typed lane task example: create-task --input \'{"team_name":"demo","subject":"Verify delivery","description":"Run verification","owner":"worker-1","lane":"verification","required_role":"executor","depends_on":["task-1"]}\' --json',
167
164
  ]);
168
165
  return;
169
166
  }
@@ -147,6 +147,7 @@ interface StringDef {
147
147
  interface NumberDef {
148
148
  type: "number";
149
149
  default: number;
150
+ validate?: (value: number) => boolean;
150
151
  ui?: UiNumber;
151
152
  }
152
153
 
@@ -319,6 +320,12 @@ export const SETTINGS_SCHEMA = {
319
320
 
320
321
  cycleOrder: { type: "array", default: DEFAULT_CYCLE_ORDER },
321
322
 
323
+ "gjc.deepInterview.ambiguityThreshold": {
324
+ type: "number",
325
+ default: 0.05,
326
+ validate: (value: number) => Number.isFinite(value) && value > 0 && value <= 1,
327
+ },
328
+
322
329
  // ────────────────────────────────────────────────────────────────────────
323
330
  // Appearance
324
331
  // ────────────────────────────────────────────────────────────────────────
@@ -337,7 +344,7 @@ export const SETTINGS_SCHEMA = {
337
344
 
338
345
  "theme.light": {
339
346
  type: "string",
340
- default: "light",
347
+ default: "blue-crab",
341
348
  ui: {
342
349
  tab: "appearance",
343
350
  label: "Light Theme",
@@ -2530,6 +2537,7 @@ export const SETTINGS_SCHEMA = {
2530
2537
  type: "enum",
2531
2538
  values: [
2532
2539
  "auto",
2540
+ "duckduckgo",
2533
2541
  "exa",
2534
2542
  "brave",
2535
2543
  "jina",
@@ -2554,7 +2562,12 @@ export const SETTINGS_SCHEMA = {
2554
2562
  {
2555
2563
  value: "auto",
2556
2564
  label: "Auto",
2557
- description: "Preferred web-search provider",
2565
+ description: "Active model's native search if its creds exist, else keyless DuckDuckGo",
2566
+ },
2567
+ {
2568
+ value: "duckduckgo",
2569
+ label: "DuckDuckGo",
2570
+ description: "Keyless default — no API key or OAuth required",
2558
2571
  },
2559
2572
  { value: "exa", label: "Exa", description: "Uses Exa API when EXA_API_KEY is set" },
2560
2573
  { value: "brave", label: "Brave", description: "Requires BRAVE_API_KEY" },
@@ -5,7 +5,7 @@
5
5
  * import { settings } from "./settings";
6
6
  *
7
7
  * const enabled = settings.get("compaction.enabled"); // sync read
8
- * settings.set("theme.dark", "titanium"); // sync write, saves in background
8
+ * settings.set("theme.dark", "red-claw"); // sync write, saves in background
9
9
  *
10
10
  * For tests:
11
11
  * const isolated = Settings.isolated({ "compaction.enabled": false });
@@ -17,6 +17,7 @@ import * as path from "node:path";
17
17
  import {
18
18
  getAgentDbPath,
19
19
  getAgentDir,
20
+ getCustomThemesDir,
20
21
  getProjectDir,
21
22
  isEnoent,
22
23
  logger,
@@ -100,6 +101,14 @@ function setByPath(obj: RawSettings, segments: string[], value: unknown): void {
100
101
  }
101
102
 
102
103
  const PATH_SCOPED_ARRAY_SETTINGS = new Set<SettingPath>(["enabledModels", "disabledProviders"]);
104
+ const LEGACY_THEME_NAME_REPLACEMENTS = {
105
+ dark: "red-claw",
106
+ light: "blue-crab",
107
+ } as const;
108
+
109
+ function isLegacyThemeName(name: string): name is keyof typeof LEGACY_THEME_NAME_REPLACEMENTS {
110
+ return name === "dark" || name === "light";
111
+ }
103
112
 
104
113
  type PathScopedStringArrayEntry = {
105
114
  path?: unknown;
@@ -288,6 +297,11 @@ export class Settings {
288
297
  return getDefault(path);
289
298
  }
290
299
 
300
+ /** Check whether a setting is present in loaded settings/overrides rather than coming from schema defaults. */
301
+ has(path: SettingPath): boolean {
302
+ return getByPath(this.#merged, path.split(".")) !== undefined;
303
+ }
304
+
291
305
  /**
292
306
  * Set a setting value (sync).
293
307
  * Updates global settings and queues a background save.
@@ -587,6 +601,25 @@ export class Settings {
587
601
  }
588
602
  }
589
603
 
604
+ #hasCustomThemeFile(name: string): boolean {
605
+ try {
606
+ return fs.existsSync(path.join(getCustomThemesDir(this.#agentDir), `${name}.json`));
607
+ } catch {
608
+ return false;
609
+ }
610
+ }
611
+
612
+ #migrateLegacyBuiltInThemeName(name: string): string {
613
+ if (isLegacyThemeName(name) && !this.#hasCustomThemeFile(name)) {
614
+ return LEGACY_THEME_NAME_REPLACEMENTS[name];
615
+ }
616
+ return name;
617
+ }
618
+
619
+ #getThemeSlotForName(name: string): "dark" | "light" {
620
+ return isLightTheme(name, this.#agentDir) ? "light" : "dark";
621
+ }
622
+
590
623
  /** Apply schema migrations to raw settings */
591
624
  #migrateRawSettings(raw: RawSettings): RawSettings {
592
625
  // queueMode -> steeringMode
@@ -606,13 +639,22 @@ export class Settings {
606
639
  // Migrate old flat "theme" string to nested theme.dark/theme.light
607
640
  if (typeof raw.theme === "string") {
608
641
  const oldTheme = raw.theme;
609
- if (oldTheme === "light" || oldTheme === "dark") {
610
- // Built-in defaults just remove, let new defaults apply
611
- delete raw.theme;
642
+ const migratedTheme = this.#migrateLegacyBuiltInThemeName(oldTheme);
643
+ if (oldTheme === "dark" && migratedTheme === "red-claw") {
644
+ raw.theme = { dark: migratedTheme };
645
+ } else if (oldTheme === "light" && migratedTheme === "blue-crab") {
646
+ raw.theme = { light: migratedTheme };
612
647
  } else {
613
- // Custom theme — detect luminance to place in correct slot
614
- const slot = isLightTheme(oldTheme) ? "light" : "dark";
615
- raw.theme = { [slot]: oldTheme };
648
+ const slot = this.#getThemeSlotForName(migratedTheme);
649
+ raw.theme = { [slot]: migratedTheme };
650
+ }
651
+ } else if (raw.theme && typeof raw.theme === "object" && !Array.isArray(raw.theme)) {
652
+ const themeObj = raw.theme as Record<string, unknown>;
653
+ if (typeof themeObj.dark === "string") {
654
+ themeObj.dark = this.#migrateLegacyBuiltInThemeName(themeObj.dark);
655
+ }
656
+ if (typeof themeObj.light === "string") {
657
+ themeObj.light = this.#migrateLegacyBuiltInThemeName(themeObj.light);
616
658
  }
617
659
  }
618
660
 
@@ -0,0 +1,366 @@
1
+ import { type Component, Container, Markdown, Spacer, Text } from "@gajae-code/tui";
2
+ import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
3
+
4
+ interface RoundQuestionModel {
5
+ kind: "round-question";
6
+ round: string;
7
+ component?: string;
8
+ targeting?: string;
9
+ mode?: string;
10
+ whyNow?: string;
11
+ ambiguity?: string;
12
+ question: string;
13
+ }
14
+
15
+ interface TopologyQuestionModel {
16
+ kind: "topology-question";
17
+ context?: string;
18
+ components: Array<{ name: string; description: string }>;
19
+ question: string;
20
+ }
21
+
22
+ interface ProgressDimension {
23
+ name: string;
24
+ score: string;
25
+ weight: string;
26
+ weighted: string;
27
+ gap: string;
28
+ }
29
+
30
+ interface ProgressModel {
31
+ kind: "progress";
32
+ round: string;
33
+ dimensions: ProgressDimension[];
34
+ ambiguity?: string;
35
+ topology?: string;
36
+ ontology?: string;
37
+ nextTarget?: string;
38
+ status?: string;
39
+ extra?: string;
40
+ }
41
+
42
+ interface ThresholdModel {
43
+ kind: "threshold";
44
+ threshold: string;
45
+ source: string;
46
+ rest: string;
47
+ }
48
+
49
+ type DeepInterviewModel = RoundQuestionModel | TopologyQuestionModel | ProgressModel | ThresholdModel;
50
+
51
+ function normalizeText(text: string): string {
52
+ return text.trim().replaceAll("\r\n", "\n");
53
+ }
54
+
55
+ function stripMarkdownEmphasis(value: string): string {
56
+ return value.replace(/\*\*/g, "").replace(/^"|"$/g, "").trim();
57
+ }
58
+
59
+ function parseRoundQuestion(text: string): RoundQuestionModel | null {
60
+ const normalized = normalizeText(text);
61
+ const lines = normalized.split("\n");
62
+ const headerIndex = lines.findIndex(line => /^Round\s+\d+\s+\|/i.test(line.trim()));
63
+ if (headerIndex < 0) return null;
64
+ const headerLine = lines[headerIndex]?.trim() ?? "";
65
+ const body = lines
66
+ .slice(headerIndex + 1)
67
+ .join("\n")
68
+ .trim();
69
+ if (!body) return null;
70
+
71
+ const componentMatch =
72
+ /^Round\s+(\d+)\s+\|\s+Component:\s*(.*?)\s+\|\s+Targeting:\s*(.*?)\s+\|\s+Why now:\s*(.*?)\s+\|\s+Ambiguity:\s*(.+?)%?\s*$/i.exec(
73
+ headerLine,
74
+ );
75
+ if (componentMatch) {
76
+ return {
77
+ kind: "round-question",
78
+ round: componentMatch[1] ?? "?",
79
+ component: componentMatch[2]?.trim(),
80
+ targeting: componentMatch[3]?.trim(),
81
+ whyNow: componentMatch[4]?.trim(),
82
+ ambiguity: componentMatch[5]?.trim(),
83
+ question: body,
84
+ };
85
+ }
86
+
87
+ const targetingMatch =
88
+ /^Round\s+(\d+)\s+\|\s+Targeting:\s*(.*?)\s+\|\s+Why now:\s*(.*?)\s+\|\s+Ambiguity:\s*(.+?)%?\s*$/i.exec(
89
+ headerLine,
90
+ );
91
+ if (targetingMatch) {
92
+ return {
93
+ kind: "round-question",
94
+ round: targetingMatch[1] ?? "?",
95
+ targeting: targetingMatch[2]?.trim(),
96
+ whyNow: targetingMatch[3]?.trim(),
97
+ ambiguity: targetingMatch[4]?.trim(),
98
+ question: body,
99
+ };
100
+ }
101
+
102
+ const modeMatch = /^Round\s+(\d+)\s+\|\s+(.*?)\s+\|\s+Ambiguity:\s*(.+?)%?\s*$/i.exec(headerLine);
103
+ if (modeMatch) {
104
+ return {
105
+ kind: "round-question",
106
+ round: modeMatch[1] ?? "?",
107
+ mode: modeMatch[2]?.trim(),
108
+ ambiguity: modeMatch[3]?.trim(),
109
+ question: body,
110
+ };
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ function parseTopologyQuestion(text: string): TopologyQuestionModel | null {
117
+ const normalized = normalizeText(text);
118
+ const lines = normalized.split("\n");
119
+ const headerIndex = lines.findIndex(line =>
120
+ /^Round\s+0\s+\|\s+Topology confirmation\s+\|\s+Ambiguity:\s+not scored yet/i.test(line.trim()),
121
+ );
122
+ if (headerIndex < 0) {
123
+ return null;
124
+ }
125
+ const components: TopologyQuestionModel["components"] = [];
126
+ const contextLines: string[] = [];
127
+ const questionLines: string[] = [];
128
+ let inQuestion = false;
129
+ for (const line of lines.slice(headerIndex + 1)) {
130
+ const trimmed = line.trim();
131
+ const component = /^\s*\d+\.\s+([^:]+):\s+(.+)$/.exec(line);
132
+ if (component) {
133
+ components.push({ name: component[1]?.trim() ?? "", description: component[2]?.trim() ?? "" });
134
+ continue;
135
+ }
136
+ if (/\?$/.test(trimmed)) inQuestion = true;
137
+ if (!trimmed) continue;
138
+ if (inQuestion) questionLines.push(trimmed);
139
+ else contextLines.push(trimmed);
140
+ }
141
+ return {
142
+ kind: "topology-question",
143
+ context: contextLines.join("\n") || undefined,
144
+ components,
145
+ question: questionLines.join("\n"),
146
+ };
147
+ }
148
+
149
+ function splitMarkdownTableRow(line: string): string[] {
150
+ const trimmed = line.trim();
151
+ if (!trimmed.startsWith("|") || !trimmed.endsWith("|")) return [];
152
+ return trimmed
153
+ .slice(1, -1)
154
+ .split("|")
155
+ .map(cell => stripMarkdownEmphasis(cell));
156
+ }
157
+
158
+ function parseProgress(text: string): ProgressModel | null {
159
+ const normalized = normalizeText(text);
160
+ const roundMatch = /^Round\s+(\d+)\s+complete\./i.exec(normalized);
161
+ if (!roundMatch) return null;
162
+
163
+ const lines = normalized.split("\n");
164
+ const dimensions: ProgressDimension[] = [];
165
+ const extraLines: string[] = [];
166
+ let ambiguity: string | undefined;
167
+ let topology: string | undefined;
168
+ let ontology: string | undefined;
169
+ let nextTarget: string | undefined;
170
+ let status: string | undefined;
171
+
172
+ for (const [index, line] of lines.entries()) {
173
+ if (index === 0) continue;
174
+ const trimmed = line.trim();
175
+ const cells = splitMarkdownTableRow(line);
176
+ if (cells.length >= 5) {
177
+ const [name = "", score = "", weight = "", weighted = "", gap = ""] = cells;
178
+ if (/^-+$/.test(name) || /^Dimension$/i.test(name)) continue;
179
+ if (/^Ambiguity$/i.test(name)) {
180
+ ambiguity = weighted || score || gap;
181
+ continue;
182
+ }
183
+ dimensions.push({ name, score, weight, weighted, gap });
184
+ continue;
185
+ }
186
+ const topologyMatch = /^\*\*Topology:\*\*\s*(.+)$/.exec(trimmed);
187
+ if (topologyMatch) {
188
+ topology = topologyMatch[1]?.trim();
189
+ continue;
190
+ }
191
+ const ontologyMatch = /^\*\*Ontology:\*\*\s*(.+)$/.exec(trimmed);
192
+ if (ontologyMatch) {
193
+ ontology = ontologyMatch[1]?.trim();
194
+ continue;
195
+ }
196
+ const nextTargetMatch = /^\*\*Next target:\*\*\s*(.+)$/.exec(trimmed);
197
+ if (nextTargetMatch) {
198
+ nextTarget = nextTargetMatch[1]?.trim();
199
+ continue;
200
+ }
201
+ if (/^(Clarity threshold met!|Focusing next question on:)/i.test(trimmed)) {
202
+ status = trimmed;
203
+ continue;
204
+ }
205
+ if (trimmed) extraLines.push(trimmed);
206
+ }
207
+
208
+ if (dimensions.length === 0 && !ambiguity && !topology && !ontology && !nextTarget) return null;
209
+ return {
210
+ kind: "progress",
211
+ round: roundMatch[1] ?? "?",
212
+ dimensions,
213
+ ambiguity,
214
+ topology,
215
+ ontology,
216
+ nextTarget,
217
+ status,
218
+ extra: extraLines.join("\n") || undefined,
219
+ };
220
+ }
221
+
222
+ function parseThreshold(text: string): ThresholdModel | null {
223
+ const normalized = normalizeText(text);
224
+ const match = /^Deep Interview threshold:\s*(.*?)\s*\(source:\s*(.*?)\)\s*$/im.exec(normalized.split("\n")[0] ?? "");
225
+ if (!match) return null;
226
+ return {
227
+ kind: "threshold",
228
+ threshold: match[1]?.trim() ?? "",
229
+ source: match[2]?.trim() ?? "",
230
+ rest: normalized.split("\n").slice(1).join("\n").trim(),
231
+ };
232
+ }
233
+
234
+ function parseDeepInterview(text: string): DeepInterviewModel | null {
235
+ return parseProgress(text) ?? parseTopologyQuestion(text) ?? parseRoundQuestion(text) ?? parseThreshold(text);
236
+ }
237
+
238
+ function addLabel(container: Container, label: string, value: string | undefined, uiTheme: Theme): void {
239
+ if (!value) return;
240
+ container.addChild(new Spacer(1));
241
+ container.addChild(new Text(uiTheme.fg("accent", uiTheme.bold(label)), 0, 0));
242
+ container.addChild(
243
+ new Markdown(value, 2, 0, getMarkdownTheme(), { color: (text: string) => uiTheme.fg("toolOutput", text) }),
244
+ );
245
+ }
246
+
247
+ function renderPipeSummary(title: string, value: string | undefined): string | undefined {
248
+ if (!value) return undefined;
249
+ return (
250
+ value
251
+ .split("|")
252
+ .map(part => part.trim())
253
+ .filter(Boolean)
254
+ .map(part => `- ${part}`)
255
+ .join("\n") || title
256
+ );
257
+ }
258
+
259
+ function renderModel(model: DeepInterviewModel, uiTheme: Theme): Component {
260
+ const container = new Container();
261
+ if (model.kind === "round-question") {
262
+ const meta = [
263
+ `Round ${model.round}`,
264
+ model.ambiguity ? `Ambiguity ${model.ambiguity.replace(/%$/, "")}%` : undefined,
265
+ ]
266
+ .filter(Boolean)
267
+ .join(" · ");
268
+ container.addChild(new Text(uiTheme.fg("toolTitle", uiTheme.bold(`Deep Interview · ${meta}`)), 0, 0));
269
+ addLabel(container, "Component", model.component, uiTheme);
270
+ addLabel(container, "Mode", model.mode, uiTheme);
271
+ addLabel(container, "Target", model.targeting, uiTheme);
272
+ addLabel(container, "Why now", model.whyNow, uiTheme);
273
+ addLabel(container, "Question", model.question, uiTheme);
274
+ return container;
275
+ }
276
+
277
+ if (model.kind === "topology-question") {
278
+ container.addChild(
279
+ new Text(uiTheme.fg("toolTitle", uiTheme.bold("Deep Interview · Round 0 · Topology confirmation")), 0, 0),
280
+ );
281
+ addLabel(container, "Ambiguity", "Not scored yet", uiTheme);
282
+ addLabel(container, "Reading", model.context, uiTheme);
283
+ if (model.components.length > 0) {
284
+ const components = model.components
285
+ .map((component, index) => `${index + 1}. **${component.name}**\n ${component.description}`)
286
+ .join("\n\n");
287
+ addLabel(container, "Components", components, uiTheme);
288
+ }
289
+ addLabel(container, "Question", model.question, uiTheme);
290
+ return container;
291
+ }
292
+
293
+ if (model.kind === "progress") {
294
+ container.addChild(
295
+ new Text(uiTheme.fg("toolTitle", uiTheme.bold(`Deep Interview · Round ${model.round} complete`)), 0, 0),
296
+ );
297
+ addLabel(container, "Ambiguity", model.ambiguity, uiTheme);
298
+ if (model.dimensions.length > 0) {
299
+ container.addChild(new Spacer(1));
300
+ container.addChild(new Text(uiTheme.fg("accent", uiTheme.bold("Clarity")), 0, 0));
301
+ for (const dimension of model.dimensions) {
302
+ const body = [`Score ${dimension.score} · weight ${dimension.weight} · weighted ${dimension.weighted}`];
303
+ if (dimension.gap) body.push(/^clear$/i.test(dimension.gap) ? "Clear" : `Gap: ${dimension.gap}`);
304
+ addLabel(container, dimension.name, body.join("\n"), uiTheme);
305
+ }
306
+ }
307
+ addLabel(container, "Topology", renderPipeSummary("Topology", model.topology), uiTheme);
308
+ addLabel(container, "Ontology", renderPipeSummary("Ontology", model.ontology), uiTheme);
309
+ addLabel(container, "Next target", model.nextTarget, uiTheme);
310
+ addLabel(container, "Status", model.status, uiTheme);
311
+ addLabel(container, "Additional details", model.extra, uiTheme);
312
+ return container;
313
+ }
314
+
315
+ container.addChild(new Text(uiTheme.fg("toolTitle", uiTheme.bold("Deep Interview · Started")), 0, 0));
316
+ addLabel(container, "Threshold", `${model.threshold} · source: ${model.source}`, uiTheme);
317
+ addLabel(container, "Details", model.rest, uiTheme);
318
+ return container;
319
+ }
320
+
321
+ export function renderDeepInterviewAssistantText(text: string, uiTheme: Theme): Component | null {
322
+ const model = parseDeepInterview(text);
323
+ if (!model || model.kind === "round-question" || model.kind === "topology-question") return null;
324
+ return renderModel(model, uiTheme);
325
+ }
326
+
327
+ export function renderDeepInterviewAskQuestion(question: string, uiTheme: Theme): Component | null {
328
+ const model = parseTopologyQuestion(question) ?? parseRoundQuestion(question);
329
+ if (!model) return null;
330
+ return renderModel(model, uiTheme);
331
+ }
332
+
333
+ export function formatDeepInterviewSelectorPrompt(question: string): string | null {
334
+ const model = parseTopologyQuestion(question) ?? parseRoundQuestion(question);
335
+ if (!model) return null;
336
+ if (model.kind === "topology-question") {
337
+ const componentLines =
338
+ model.components.length > 0
339
+ ? [
340
+ "Components:",
341
+ ...model.components.map(
342
+ (component, index) => `${index + 1}. ${component.name} — ${component.description}`,
343
+ ),
344
+ ]
345
+ : [];
346
+ return [
347
+ "Deep Interview · Round 0 · Topology confirmation",
348
+ "Ambiguity: not scored yet",
349
+ model.context ? `Reading:\n${model.context}` : undefined,
350
+ ...componentLines,
351
+ model.question ? `Question:\n${model.question}` : undefined,
352
+ ]
353
+ .filter((line): line is string => Boolean(line))
354
+ .join("\n\n");
355
+ }
356
+ return [
357
+ `Deep Interview · Round ${model.round}${model.ambiguity ? ` · Ambiguity ${model.ambiguity.replace(/%$/, "")}%` : ""}`,
358
+ model.component ? `Component: ${model.component}` : undefined,
359
+ model.mode ? `Mode: ${model.mode}` : undefined,
360
+ model.targeting ? `Target: ${model.targeting}` : undefined,
361
+ model.whyNow ? `Why now: ${model.whyNow}` : undefined,
362
+ model.question,
363
+ ]
364
+ .filter((line): line is string => Boolean(line))
365
+ .join("\n");
366
+ }