@gajae-code/coding-agent 0.6.4 → 0.7.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 (231) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/types/async/job-manager.d.ts +3 -1
  3. package/dist/types/cli/daemon-cli.d.ts +25 -0
  4. package/dist/types/cli/migrate-cli.d.ts +20 -0
  5. package/dist/types/cli/notify-cli.d.ts +23 -0
  6. package/dist/types/cli/setup-cli.d.ts +20 -1
  7. package/dist/types/commands/daemon.d.ts +41 -0
  8. package/dist/types/commands/migrate.d.ts +33 -0
  9. package/dist/types/commands/notify.d.ts +41 -0
  10. package/dist/types/config/keybindings.d.ts +4 -0
  11. package/dist/types/config/model-profile-activation.d.ts +12 -0
  12. package/dist/types/config/model-profiles.d.ts +2 -1
  13. package/dist/types/config/model-registry.d.ts +3 -3
  14. package/dist/types/config/models-config-schema.d.ts +5 -0
  15. package/dist/types/config/settings-schema.d.ts +38 -0
  16. package/dist/types/coordinator/contract.d.ts +1 -1
  17. package/dist/types/daemon/builtin.d.ts +20 -0
  18. package/dist/types/daemon/control-types.d.ts +57 -0
  19. package/dist/types/daemon/runtime.d.ts +25 -0
  20. package/dist/types/extensibility/extensions/types.d.ts +8 -0
  21. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
  22. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
  23. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  24. package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
  25. package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
  26. package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
  27. package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
  28. package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
  29. package/dist/types/gjc-runtime/state-writer.d.ts +38 -7
  30. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +15 -0
  31. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +21 -4
  32. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
  33. package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
  34. package/dist/types/harness-control-plane/storage.d.ts +2 -1
  35. package/dist/types/hooks/skill-state.d.ts +12 -4
  36. package/dist/types/migrate/action-planner.d.ts +11 -0
  37. package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
  38. package/dist/types/migrate/adapters/codex.d.ts +5 -0
  39. package/dist/types/migrate/adapters/index.d.ts +45 -0
  40. package/dist/types/migrate/adapters/opencode.d.ts +2 -0
  41. package/dist/types/migrate/executor.d.ts +2 -0
  42. package/dist/types/migrate/mcp-mapper.d.ts +20 -0
  43. package/dist/types/migrate/report.d.ts +18 -0
  44. package/dist/types/migrate/skill-normalizer.d.ts +27 -0
  45. package/dist/types/migrate/types.d.ts +126 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -1
  47. package/dist/types/modes/components/oauth-selector.d.ts +2 -0
  48. package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
  49. package/dist/types/modes/interactive-mode.d.ts +1 -1
  50. package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
  51. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  52. package/dist/types/modes/types.d.ts +7 -1
  53. package/dist/types/notifications/config-commands.d.ts +26 -0
  54. package/dist/types/notifications/config.d.ts +61 -0
  55. package/dist/types/notifications/helpers.d.ts +55 -0
  56. package/dist/types/notifications/html-format.d.ts +62 -0
  57. package/dist/types/notifications/index.d.ts +28 -0
  58. package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
  59. package/dist/types/notifications/telegram-cli.d.ts +19 -0
  60. package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
  61. package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
  62. package/dist/types/notifications/telegram-daemon.d.ts +276 -0
  63. package/dist/types/notifications/telegram-reference.d.ts +111 -0
  64. package/dist/types/notifications/threaded-inbound.d.ts +58 -0
  65. package/dist/types/notifications/threaded-render.d.ts +66 -0
  66. package/dist/types/notifications/topic-registry.d.ts +67 -0
  67. package/dist/types/research-plan/index.d.ts +1 -0
  68. package/dist/types/research-plan/ledger.d.ts +33 -0
  69. package/dist/types/rlm/artifacts.d.ts +1 -1
  70. package/dist/types/rlm/index.d.ts +12 -0
  71. package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
  72. package/dist/types/session/agent-session.d.ts +39 -2
  73. package/dist/types/session/auth-storage.d.ts +1 -1
  74. package/dist/types/setup/credential-auto-import.d.ts +63 -0
  75. package/dist/types/setup/credential-import.d.ts +3 -0
  76. package/dist/types/setup/host-plugin-setup.d.ts +39 -0
  77. package/dist/types/skill-state/active-state.d.ts +6 -11
  78. package/dist/types/skill-state/canonical-skills.d.ts +3 -0
  79. package/dist/types/skill-state/workflow-hud.d.ts +2 -0
  80. package/dist/types/task/spawn-gate.d.ts +1 -10
  81. package/dist/types/tools/ask-answer-registry.d.ts +13 -0
  82. package/dist/types/tools/index.d.ts +18 -0
  83. package/dist/types/tools/subagent.d.ts +3 -0
  84. package/package.json +7 -7
  85. package/scripts/build-binary.ts +3 -0
  86. package/src/async/job-manager.ts +5 -1
  87. package/src/cli/daemon-cli.ts +122 -0
  88. package/src/cli/migrate-cli.ts +106 -0
  89. package/src/cli/notify-cli.ts +274 -0
  90. package/src/cli/setup-cli.ts +173 -84
  91. package/src/cli.ts +3 -0
  92. package/src/commands/daemon.ts +47 -0
  93. package/src/commands/deep-interview.ts +2 -2
  94. package/src/commands/migrate.ts +46 -0
  95. package/src/commands/notify.ts +61 -0
  96. package/src/commands/setup.ts +11 -1
  97. package/src/commands/state.ts +2 -1
  98. package/src/commands/team.ts +7 -3
  99. package/src/config/model-profile-activation.ts +74 -5
  100. package/src/config/model-profiles.ts +7 -4
  101. package/src/config/model-registry.ts +6 -3
  102. package/src/config/models-config-schema.ts +1 -1
  103. package/src/config/settings-schema.ts +29 -0
  104. package/src/coordinator/contract.ts +3 -0
  105. package/src/coordinator-mcp/policy.ts +10 -2
  106. package/src/coordinator-mcp/server.ts +270 -1
  107. package/src/daemon/builtin.ts +46 -0
  108. package/src/daemon/control-types.ts +65 -0
  109. package/src/daemon/runtime.ts +51 -0
  110. package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
  111. package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
  112. package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
  113. package/src/defaults/gjc/skills/team/SKILL.md +51 -47
  114. package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -13
  115. package/src/extensibility/custom-commands/loader.ts +0 -7
  116. package/src/extensibility/extensions/runner.ts +4 -0
  117. package/src/extensibility/extensions/types.ts +8 -0
  118. package/src/extensibility/gjc-plugins/injection.ts +23 -4
  119. package/src/extensibility/gjc-plugins/state.ts +16 -1
  120. package/src/gjc-runtime/deep-interview-recorder.ts +51 -18
  121. package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
  122. package/src/gjc-runtime/goal-mode-request.ts +26 -11
  123. package/src/gjc-runtime/launch-tmux.ts +6 -1
  124. package/src/gjc-runtime/ralplan-runtime.ts +79 -50
  125. package/src/gjc-runtime/session-layout.ts +180 -0
  126. package/src/gjc-runtime/session-resolution.ts +217 -0
  127. package/src/gjc-runtime/state-graph.ts +1 -2
  128. package/src/gjc-runtime/state-migrations.ts +1 -0
  129. package/src/gjc-runtime/state-runtime.ts +247 -124
  130. package/src/gjc-runtime/state-schema.ts +2 -0
  131. package/src/gjc-runtime/state-writer.ts +289 -41
  132. package/src/gjc-runtime/team-runtime.ts +43 -19
  133. package/src/gjc-runtime/tmux-sessions.ts +7 -1
  134. package/src/gjc-runtime/ultragoal-guard.ts +102 -4
  135. package/src/gjc-runtime/ultragoal-runtime.ts +226 -60
  136. package/src/gjc-runtime/workflow-command-ref.ts +1 -2
  137. package/src/gjc-runtime/workflow-manifest.generated.json +27 -2
  138. package/src/gjc-runtime/workflow-manifest.ts +12 -3
  139. package/src/goals/tools/goal-tool.ts +11 -2
  140. package/src/harness-control-plane/storage.ts +14 -4
  141. package/src/hooks/native-skill-hook.ts +38 -12
  142. package/src/hooks/skill-state.ts +178 -83
  143. package/src/internal-urls/docs-index.generated.ts +9 -6
  144. package/src/main.ts +30 -0
  145. package/src/migrate/action-planner.ts +318 -0
  146. package/src/migrate/adapters/claude-code.ts +39 -0
  147. package/src/migrate/adapters/codex.ts +70 -0
  148. package/src/migrate/adapters/index.ts +277 -0
  149. package/src/migrate/adapters/opencode.ts +52 -0
  150. package/src/migrate/executor.ts +81 -0
  151. package/src/migrate/mcp-mapper.ts +152 -0
  152. package/src/migrate/report.ts +104 -0
  153. package/src/migrate/skill-normalizer.ts +80 -0
  154. package/src/migrate/types.ts +163 -0
  155. package/src/modes/acp/acp-event-mapper.ts +1 -0
  156. package/src/modes/bridge/bridge-mode.ts +2 -2
  157. package/src/modes/components/custom-editor.ts +30 -20
  158. package/src/modes/components/hook-editor.ts +7 -2
  159. package/src/modes/components/oauth-selector.ts +19 -0
  160. package/src/modes/controllers/event-controller.ts +20 -0
  161. package/src/modes/controllers/selector-controller.ts +80 -17
  162. package/src/modes/interactive-mode.ts +6 -2
  163. package/src/modes/rpc/rpc-mode.ts +2 -2
  164. package/src/modes/runtime-init.ts +1 -0
  165. package/src/modes/shared/agent-wire/event-contract.ts +1 -0
  166. package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
  167. package/src/modes/shared/agent-wire/event-observation.ts +16 -0
  168. package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
  169. package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
  170. package/src/modes/types.ts +7 -1
  171. package/src/modes/utils/ui-helpers.ts +23 -0
  172. package/src/notifications/config-commands.ts +50 -0
  173. package/src/notifications/config.ts +107 -0
  174. package/src/notifications/helpers.ts +135 -0
  175. package/src/notifications/html-format.ts +389 -0
  176. package/src/notifications/index.ts +663 -0
  177. package/src/notifications/rate-limit-pool.ts +179 -0
  178. package/src/notifications/telegram-cli.ts +194 -0
  179. package/src/notifications/telegram-daemon-cli.ts +74 -0
  180. package/src/notifications/telegram-daemon-control.ts +370 -0
  181. package/src/notifications/telegram-daemon.ts +1370 -0
  182. package/src/notifications/telegram-reference.ts +335 -0
  183. package/src/notifications/threaded-inbound.ts +80 -0
  184. package/src/notifications/threaded-render.ts +155 -0
  185. package/src/notifications/topic-registry.ts +133 -0
  186. package/src/prompts/agents/init.md +1 -1
  187. package/src/prompts/system/plan-mode-active.md +1 -1
  188. package/src/prompts/tools/ast-grep.md +1 -1
  189. package/src/prompts/tools/search.md +1 -1
  190. package/src/prompts/tools/task.md +1 -2
  191. package/src/research-plan/index.ts +1 -0
  192. package/src/research-plan/ledger.ts +177 -0
  193. package/src/rlm/artifacts.ts +12 -3
  194. package/src/rlm/index.ts +26 -0
  195. package/src/runtime-mcp/config-writer.ts +46 -0
  196. package/src/sdk.ts +16 -0
  197. package/src/session/agent-session.ts +128 -24
  198. package/src/session/auth-storage.ts +3 -0
  199. package/src/session/session-dump-format.ts +43 -2
  200. package/src/session/session-manager.ts +39 -5
  201. package/src/setup/credential-auto-import.ts +258 -0
  202. package/src/setup/credential-import.ts +17 -0
  203. package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
  204. package/src/setup/hermes-setup.ts +1 -1
  205. package/src/setup/host-plugin-setup.ts +142 -0
  206. package/src/skill-state/active-state.ts +72 -108
  207. package/src/skill-state/canonical-skills.ts +4 -0
  208. package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
  209. package/src/skill-state/workflow-hud.ts +4 -2
  210. package/src/skill-state/workflow-state-contract.ts +3 -3
  211. package/src/slash-commands/builtin-registry.ts +4 -1
  212. package/src/task/agents.ts +1 -22
  213. package/src/task/executor.ts +5 -1
  214. package/src/task/index.ts +1 -41
  215. package/src/task/spawn-gate.ts +1 -38
  216. package/src/task/types.ts +1 -1
  217. package/src/tools/ask-answer-registry.ts +25 -0
  218. package/src/tools/ask.ts +108 -16
  219. package/src/tools/computer.ts +58 -4
  220. package/src/tools/image-gen.ts +5 -8
  221. package/src/tools/index.ts +19 -0
  222. package/src/tools/inspect-image.ts +16 -11
  223. package/src/tools/subagent-render.ts +7 -0
  224. package/src/tools/subagent.ts +38 -7
  225. package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
  226. package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
  227. package/src/prompts/agents/explore.md +0 -58
  228. package/src/prompts/agents/plan.md +0 -49
  229. package/src/prompts/agents/reviewer.md +0 -141
  230. package/src/prompts/agents/task.md +0 -16
  231. package/src/prompts/review-request.md +0 -70
@@ -12,7 +12,6 @@ import { getConfigDirs } from "../../config";
12
12
  import { execCommand } from "../../exec/exec";
13
13
  import * as typebox from "../typebox";
14
14
  import { GreenCommand } from "./bundled/ci-green";
15
- import { ReviewCommand } from "./bundled/review";
16
15
  import type {
17
16
  CustomCommand,
18
17
  CustomCommandAPI,
@@ -155,12 +154,6 @@ function loadBundledCommands(sharedApi: CustomCommandAPI): LoadedCustomCommand[]
155
154
  command: new GreenCommand(sharedApi),
156
155
  source: "bundled",
157
156
  });
158
- bundled.push({
159
- path: "bundled:review",
160
- resolvedPath: "bundled:review",
161
- command: new ReviewCommand(sharedApi),
162
- source: "bundled",
163
- });
164
157
 
165
158
  return bundled;
166
159
  }
@@ -6,6 +6,7 @@ import type { CredentialDisabledEvent, ImageContent, Model, ProviderResponseMeta
6
6
  import type { KeyId } from "@gajae-code/tui";
7
7
  import { logger } from "@gajae-code/utils";
8
8
  import type { ModelRegistry } from "../../config/model-registry";
9
+ import type { WorkflowGateEmitter } from "../../modes/shared/agent-wire/unattended-session";
9
10
  import { type Theme, theme } from "../../modes/theme/theme";
10
11
  import type { SessionManager } from "../../session/session-manager";
11
12
  import type {
@@ -180,6 +181,7 @@ export class ExtensionRunner {
180
181
  #getContextUsageFn: () => ContextUsage | undefined = () => undefined;
181
182
  #compactFn: (instructionsOrOptions?: string | CompactOptions) => Promise<void> = async () => {};
182
183
  #getSystemPromptFn: () => string[] = () => [];
184
+ #getWorkflowGateFn: () => WorkflowGateEmitter | undefined = () => undefined;
183
185
  #newSessionHandler: NewSessionHandler = async () => ({ cancelled: false });
184
186
  #branchHandler: BranchHandler = async () => ({ cancelled: false });
185
187
  #navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
@@ -234,6 +236,7 @@ export class ExtensionRunner {
234
236
  this.#hasPendingMessagesFn = contextActions.hasPendingMessages;
235
237
  this.#shutdownHandler = contextActions.shutdown;
236
238
  this.#getSystemPromptFn = contextActions.getSystemPrompt;
239
+ this.#getWorkflowGateFn = contextActions.getWorkflowGate ?? (() => undefined);
237
240
 
238
241
  // Command context actions (optional, only for interactive mode)
239
242
  if (commandContextActions) {
@@ -463,6 +466,7 @@ export class ExtensionRunner {
463
466
  shutdown: () => this.#shutdownHandler(),
464
467
  getSystemPrompt: () => this.#getSystemPromptFn(),
465
468
  hasQueuedMessages: () => this.#hasPendingMessagesFn(), // deprecated alias
469
+ workflowGate: this.#getWorkflowGateFn(),
466
470
  };
467
471
  }
468
472
 
@@ -32,6 +32,7 @@ import type { PythonResult } from "../../eval/py/executor";
32
32
  import type { BashResult } from "../../exec/bash-executor";
33
33
  import type { ExecOptions, ExecResult } from "../../exec/exec";
34
34
  import type { CustomEditor } from "../../modes/components/custom-editor";
35
+ import type { WorkflowGateEmitter } from "../../modes/shared/agent-wire/unattended-session";
35
36
  import type { Theme } from "../../modes/theme/theme";
36
37
  import type { CustomMessage } from "../../session/messages";
37
38
  import type { ReadonlySessionManager, SessionManager } from "../../session/session-manager";
@@ -310,6 +311,11 @@ export interface ExtensionContext {
310
311
  getSystemPrompt(): string[];
311
312
  /** @deprecated Use hasPendingMessages() instead */
312
313
  hasQueuedMessages(): boolean;
314
+ /**
315
+ * Unattended workflow-gate bridge. Present only when the session runs in
316
+ * unattended/RPC mode; `undefined` in interactive/TUI mode (notify-only).
317
+ */
318
+ workflowGate?: WorkflowGateEmitter;
313
319
  }
314
320
 
315
321
  /**
@@ -1234,6 +1240,8 @@ export interface ExtensionContextActions {
1234
1240
  getContextUsage: () => ContextUsage | undefined;
1235
1241
  compact: (instructionsOrOptions?: string | CompactOptions) => Promise<void>;
1236
1242
  getSystemPrompt: () => string[];
1243
+ /** Unattended workflow-gate bridge (present only in unattended/RPC mode). */
1244
+ getWorkflowGate?: () => WorkflowGateEmitter | undefined;
1237
1245
  }
1238
1246
 
1239
1247
  /** Actions for ExtensionCommandContext (ctx.* in command handlers). */
@@ -1,3 +1,16 @@
1
+ import { resolveGjcSessionForRead, SessionResolutionError } from "../../gjc-runtime/session-resolution";
2
+
3
+ async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string | undefined> {
4
+ const normalizedSessionId = sessionId?.trim();
5
+ if (normalizedSessionId) return normalizedSessionId;
6
+ try {
7
+ return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
8
+ } catch (error) {
9
+ if (error instanceof SessionResolutionError && error.code === "no_session") return undefined;
10
+ throw error;
11
+ }
12
+ }
13
+
1
14
  import { readVisibleSkillActiveState } from "../../skill-state/active-state";
2
15
  import { initialPhaseForSkill } from "../../skill-state/initial-phase";
3
16
  import { readActiveSubskillsForParent } from "./state";
@@ -35,7 +48,8 @@ export async function resolveCurrentPhaseForParent(input: {
35
48
  const explicitPhase = input.explicitPhase?.trim();
36
49
  if (explicitPhase) return explicitPhase;
37
50
 
38
- const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
51
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
52
+ const state = resolvedSessionId ? await readVisibleSkillActiveState(input.cwd, resolvedSessionId) : null;
39
53
  const persistedPhase = state?.active_skills?.find(entry => entry.skill === input.parent)?.phase?.trim();
40
54
  if (persistedPhase) return persistedPhase;
41
55
 
@@ -54,9 +68,10 @@ export async function buildSubskillInjection(input: {
54
68
  activation?: LoadedSubskillActivation;
55
69
  currentPhase?: string;
56
70
  }): Promise<{ block: string; details?: LoadedSubskillActivation } | null> {
71
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
57
72
  const resolvedPhase = await resolveCurrentPhaseForParent({
58
73
  cwd: input.cwd,
59
- sessionId: input.sessionId,
74
+ sessionId: resolvedSessionId,
60
75
  parent: input.skillName,
61
76
  explicitPhase: input.currentPhase,
62
77
  });
@@ -67,9 +82,11 @@ export async function buildSubskillInjection(input: {
67
82
  return { block: wrapSubskillBlock(directActivation, body), details: directActivation };
68
83
  }
69
84
 
85
+ if (!resolvedSessionId) return null;
86
+
70
87
  const [entry] = await readActiveSubskillsForParent({
71
88
  cwd: input.cwd,
72
- sessionId: input.sessionId,
89
+ sessionId: resolvedSessionId,
73
90
  parent: input.skillName,
74
91
  phase: resolvedPhase,
75
92
  });
@@ -96,9 +113,11 @@ export async function buildAgentSubskillInjection(input: {
96
113
  }): Promise<string> {
97
114
  if (!(GJC_SUBSKILL_PARENT_AGENTS as readonly string[]).includes(input.agentName)) return "";
98
115
 
116
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
117
+ if (!resolvedSessionId) return "";
99
118
  const entries = await readActiveSubskillsForParent({
100
119
  cwd: input.cwd,
101
- sessionId: input.sessionId,
120
+ sessionId: resolvedSessionId,
102
121
  parent: input.agentName,
103
122
  phase: "prompt",
104
123
  });
@@ -1,3 +1,16 @@
1
+ import { resolveGjcSessionForRead, SessionResolutionError } from "../../gjc-runtime/session-resolution";
2
+
3
+ async function resolveBoundarySessionId(cwd: string, sessionId?: string): Promise<string | undefined> {
4
+ const normalizedSessionId = sessionId?.trim();
5
+ if (normalizedSessionId) return normalizedSessionId;
6
+ try {
7
+ return (await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID })).gjcSessionId;
8
+ } catch (error) {
9
+ if (error instanceof SessionResolutionError && error.code === "no_session") return undefined;
10
+ throw error;
11
+ }
12
+ }
13
+
1
14
  import type { ActiveSubskillEntry } from "../../skill-state/active-state";
2
15
  import { readVisibleSkillActiveState } from "../../skill-state/active-state";
3
16
  import type { LoadedSubskillActivation } from "./types";
@@ -21,7 +34,9 @@ export async function readActiveSubskillsForParent(input: {
21
34
  parent: string;
22
35
  phase: string;
23
36
  }): Promise<ActiveSubskillEntry[]> {
24
- const state = await readVisibleSkillActiveState(input.cwd, input.sessionId);
37
+ const resolvedSessionId = await resolveBoundarySessionId(input.cwd, input.sessionId);
38
+ if (!resolvedSessionId) return [];
39
+ const state = await readVisibleSkillActiveState(input.cwd, resolvedSessionId);
25
40
  const parent = input.parent.trim();
26
41
  const phase = input.phase.trim();
27
42
  if (!state || !parent || !phase) return [];
@@ -11,7 +11,8 @@ import {
11
11
  normalizeDeepInterviewEnvelope,
12
12
  questionHash,
13
13
  } from "./deep-interview-state";
14
- import { readExistingStateForMutation, writeWorkflowEnvelopeAtomic } from "./state-writer";
14
+ import { writeSessionActivityMarker } from "./session-resolution";
15
+ import { readExistingStateForMutation, writeGuardedWorkflowEnvelopeAtomic } from "./state-writer";
15
16
 
16
17
  export * from "./deep-interview-state";
17
18
 
@@ -298,6 +299,12 @@ async function readEnvelope(statePath: string): Promise<DeepInterviewStateEnvelo
298
299
  return ensureDeepInterviewStateShape(undefined);
299
300
  }
300
301
 
302
+ function existingStateRevision(value: unknown): number | undefined {
303
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
304
+ const revision = (value as Record<string, unknown>).state_revision;
305
+ return typeof revision === "number" && Number.isFinite(revision) ? revision : 0;
306
+ }
307
+
301
308
  function interviewIdOf(envelope: DeepInterviewStateEnvelope): string | undefined {
302
309
  const inner = (envelope.state ?? {}) as Record<string, unknown>;
303
310
  return typeof inner.interview_id === "string" ? inner.interview_id : undefined;
@@ -310,6 +317,7 @@ async function persistEnvelope(
310
317
  sessionId: string | undefined,
311
318
  command: string,
312
319
  ): Promise<void> {
320
+ if (!sessionId) throw new Error("deep-interview recorder requires a session id");
313
321
  const now = new Date().toISOString();
314
322
  const payload: Record<string, unknown> = { ...normalizeDeepInterviewEnvelope(envelope), updated_at: now };
315
323
  // Guarantee RequiredOnWriteEnvelopeSchema fields for the fresh/absent fallback;
@@ -318,11 +326,22 @@ async function persistEnvelope(
318
326
  payload.version ??= WORKFLOW_STATE_VERSION;
319
327
  payload.active ??= true;
320
328
  payload.current_phase ??= "interviewing";
321
- await writeWorkflowEnvelopeAtomic(statePath, payload, {
329
+ const expectedRevision = existingStateRevision(envelope);
330
+ const writeResult = await writeGuardedWorkflowEnvelopeAtomic(statePath, payload, {
322
331
  cwd,
332
+ policy: "source",
333
+ expectedRevision,
323
334
  receipt: { cwd, skill: "deep-interview", owner: "gjc-runtime", command, sessionId, nowIso: now },
324
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
335
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview", sessionId },
325
336
  });
337
+ // Reflect the freshly written revision back onto the in-memory envelope so a
338
+ // follow-up HUD sync derives its `sourceRevision` from the persisted revision
339
+ // (not the stale pre-write value), otherwise the active-state writer treats the
340
+ // newer HUD as stale and skips it (e.g. dropping the ambiguity chip after scoring).
341
+ if (writeResult.written && typeof expectedRevision === "number") {
342
+ (envelope as Record<string, unknown>).state_revision = expectedRevision + 1;
343
+ }
344
+ await writeSessionActivityMarker(cwd, sessionId, { writer: "deep-interview-recorder", path: statePath });
326
345
  }
327
346
 
328
347
  /**
@@ -335,20 +354,17 @@ async function syncRecorderHud(
335
354
  envelope: DeepInterviewStateEnvelope,
336
355
  sessionId: string | undefined,
337
356
  ): Promise<void> {
338
- try {
339
- const phase = typeof envelope.current_phase === "string" ? envelope.current_phase : "interviewing";
340
- await syncSkillActiveState({
341
- cwd,
342
- skill: "deep-interview",
343
- active: phase !== "complete",
344
- phase,
345
- sessionId,
346
- source: "gjc-runtime-deep-interview-recorder",
347
- hud: deriveDeepInterviewHud(envelope as Record<string, unknown>, { phase }),
348
- });
349
- } catch {
350
- // HUD sync is best-effort cache maintenance and must not change record semantics.
351
- }
357
+ const phase = typeof envelope.current_phase === "string" ? envelope.current_phase : "interviewing";
358
+ await syncSkillActiveState({
359
+ cwd,
360
+ skill: "deep-interview",
361
+ active: phase !== "complete",
362
+ phase,
363
+ sessionId,
364
+ source: "gjc-runtime-deep-interview-recorder",
365
+ hud: deriveDeepInterviewHud(normalizeDeepInterviewEnvelope(envelope) as Record<string, unknown>, { phase }),
366
+ sourceRevision: (existingStateRevision(envelope) ?? 0) + 1,
367
+ });
352
368
  }
353
369
 
354
370
  /**
@@ -360,6 +376,19 @@ async function repairRecorderHudFromPersisted(
360
376
  cwd: string,
361
377
  statePath: string,
362
378
  sessionId: string | undefined,
379
+ ): Promise<void> {
380
+ try {
381
+ await syncDeepInterviewRecorderHud(cwd, statePath, sessionId);
382
+ } catch {
383
+ // HUD sync is best-effort cache maintenance and must not change record semantics.
384
+ }
385
+ }
386
+
387
+ /** Refresh the best-effort HUD cache from persisted deep-interview state. */
388
+ export async function syncDeepInterviewRecorderHud(
389
+ cwd: string,
390
+ statePath: string,
391
+ sessionId: string | undefined,
363
392
  ): Promise<void> {
364
393
  const read = await readExistingStateForMutation(statePath);
365
394
  if (read.kind !== "valid") return;
@@ -384,7 +413,11 @@ export async function appendOrMergeDeepInterviewRound(
384
413
  }
385
414
  (envelope.state as Record<string, unknown>).rounds = result.rounds;
386
415
  await persistEnvelope(cwd, statePath, envelope, options.sessionId, "gjc deep-interview record-answer");
387
- await syncRecorderHud(cwd, envelope, options.sessionId);
416
+ try {
417
+ await syncRecorderHud(cwd, envelope, options.sessionId);
418
+ } catch {
419
+ // HUD sync is best-effort cache maintenance and must not change record semantics.
420
+ }
388
421
  return { action: result.action, record: result.record };
389
422
  }
390
423
 
@@ -7,6 +7,8 @@ import { deriveDeepInterviewHud } from "../skill-state/workflow-hud";
7
7
  import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
8
8
  import { normalizeDeepInterviewEnvelope } from "./deep-interview-state";
9
9
  import { runNativeRalplanCommand } from "./ralplan-runtime";
10
+ import { modeStatePath, sessionSpecsDir } from "./session-layout";
11
+ import { resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
10
12
  import { runNativeStateCommand } from "./state-runtime";
11
13
  import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
12
14
 
@@ -76,10 +78,6 @@ function assertSafePathComponent(value: string, label: string): void {
76
78
  }
77
79
  }
78
80
 
79
- function encodeSessionSegment(value: string): string {
80
- return encodeURIComponent(value).replaceAll(".", "%2E");
81
- }
82
-
83
81
  function defaultSpecSlug(now: Date = new Date()): string {
84
82
  const yyyy = now.getUTCFullYear().toString().padStart(4, "0");
85
83
  const mm = (now.getUTCMonth() + 1).toString().padStart(2, "0");
@@ -89,14 +87,10 @@ function defaultSpecSlug(now: Date = new Date()): string {
89
87
  return `${yyyy}-${mm}-${dd}-${hh}${min}-${randomBytes(2).toString("hex")}`;
90
88
  }
91
89
 
92
- function stateDirFor(cwd: string, sessionId: string | undefined): string {
93
- return sessionId
94
- ? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(sessionId))
95
- : path.join(cwd, ".gjc", "state");
96
- }
97
-
98
- export function deepInterviewStatePath(cwd: string, sessionId: string | undefined): string {
99
- return path.join(stateDirFor(cwd, sessionId), "deep-interview-state.json");
90
+ export function deepInterviewStatePath(cwd: string, sessionId?: string): string {
91
+ const resolvedSessionId = sessionId?.trim() || process.env.GJC_SESSION_ID?.trim();
92
+ if (!resolvedSessionId) throw new Error("deep-interview state path requires a session id");
93
+ return modeStatePath(cwd, resolvedSessionId, "deep-interview");
100
94
  }
101
95
 
102
96
  async function resolveSpecContent(rawSpec: string, cwd: string): Promise<string> {
@@ -117,7 +111,7 @@ interface ResolvedDeepInterviewArgs {
117
111
  resolution: DeepInterviewResolution;
118
112
  threshold: number;
119
113
  thresholdSource: string;
120
- sessionId?: string;
114
+ sessionId: string;
121
115
  idea: string;
122
116
  language?: DeepInterviewLanguagePreference;
123
117
  json: boolean;
@@ -134,7 +128,7 @@ export interface ResolvedDeepInterviewSpecWriteArgs {
134
128
  stage: "final";
135
129
  slug: string;
136
130
  spec: string;
137
- sessionId?: string;
131
+ sessionId: string;
138
132
  json: boolean;
139
133
  deliberate: boolean;
140
134
  handoff?: "ralplan";
@@ -277,8 +271,12 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
277
271
  throw new DeepInterviewCommandError(2, "--spec is required for deep-interview --write");
278
272
  }
279
273
 
280
- const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
281
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
274
+ const session = resolveGjcSessionForWrite(cwd, {
275
+ flagValue: flagValue(args, "--session-id"),
276
+ envSessionId: process.env.GJC_SESSION_ID,
277
+ });
278
+ const sessionId = session.gjcSessionId;
279
+ assertSafePathComponent(sessionId, "session-id");
282
280
 
283
281
  const rawHandoff = flagValue(args, "--handoff")?.trim() || undefined;
284
282
  if (rawHandoff && rawHandoff !== "ralplan") {
@@ -324,8 +322,12 @@ async function resolveSpecWriteArgs(args: readonly string[], cwd: string): Promi
324
322
  }
325
323
 
326
324
  async function resolveDeepInterviewArgs(args: readonly string[], cwd: string): Promise<ResolvedDeepInterviewArgs> {
327
- const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
328
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
325
+ const session = resolveGjcSessionForWrite(cwd, {
326
+ flagValue: flagValue(args, "--session-id"),
327
+ envSessionId: process.env.GJC_SESSION_ID,
328
+ });
329
+ const sessionId = session.gjcSessionId;
330
+ assertSafePathComponent(sessionId, "session-id");
329
331
 
330
332
  const explicitResolutions = (["quick", "standard", "deep"] as const).filter(name => hasFlag(args, `--${name}`));
331
333
  if (explicitResolutions.length > 1) {
@@ -402,19 +404,34 @@ export async function persistDeepInterviewSpec(
402
404
  }
403
405
  const existing = existingRead.kind === "valid" ? existingRead.value : {};
404
406
 
405
- const specPath = path.join(cwd, ".gjc", "specs", `deep-interview-${resolved.slug}.md`);
407
+ const specPath = path.join(sessionSpecsDir(cwd, resolved.sessionId), `deep-interview-${resolved.slug}.md`);
406
408
  const content = resolved.spec.endsWith("\n") ? resolved.spec : `${resolved.spec}\n`;
407
409
  await writeArtifact(specPath, content, {
408
410
  cwd,
409
- audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
411
+ audit: {
412
+ category: "artifact",
413
+ verb: "write",
414
+ owner: "gjc-runtime",
415
+ skill: "deep-interview",
416
+ sessionId: resolved.sessionId,
417
+ },
410
418
  });
411
419
 
412
420
  const sha256 = createHash("sha256").update(content).digest("hex");
413
421
  const createdAt = new Date().toISOString();
414
422
  await appendJsonl(
415
- path.join(cwd, ".gjc", "specs", "deep-interview-index.jsonl"),
423
+ path.join(sessionSpecsDir(cwd, resolved.sessionId), "deep-interview-index.jsonl"),
416
424
  { slug: resolved.slug, stage: resolved.stage, path: specPath, created_at: createdAt, sha256 },
417
- { cwd, audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "deep-interview" } },
425
+ {
426
+ cwd,
427
+ audit: {
428
+ category: "ledger",
429
+ verb: "append",
430
+ owner: "gjc-runtime",
431
+ skill: "deep-interview",
432
+ sessionId: resolved.sessionId,
433
+ },
434
+ },
418
435
  );
419
436
 
420
437
  const payload = normalizeDeepInterviewEnvelope({
@@ -446,9 +463,11 @@ export async function persistDeepInterviewSpec(
446
463
  verb: "write",
447
464
  owner: "gjc-runtime",
448
465
  skill: "deep-interview",
466
+ sessionId: resolved.sessionId,
449
467
  forced: resolved.force,
450
468
  },
451
469
  });
470
+ await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "deep-interview-runtime", path: statePath });
452
471
  await syncDeepInterviewHud({
453
472
  cwd,
454
473
  sessionId: resolved.sessionId,
@@ -503,8 +522,15 @@ async function seedDeepInterviewState(cwd: string, resolved: ResolvedDeepIntervi
503
522
  sessionId: resolved.sessionId,
504
523
  nowIso: now,
505
524
  },
506
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "deep-interview" },
525
+ audit: {
526
+ category: "state",
527
+ verb: "write",
528
+ owner: "gjc-runtime",
529
+ skill: "deep-interview",
530
+ sessionId: resolved.sessionId,
531
+ },
507
532
  });
533
+ await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "deep-interview-runtime", path: statePath });
508
534
  await syncDeepInterviewHud({ cwd, sessionId: resolved.sessionId, payload, phase: "interviewing" });
509
535
  return statePath;
510
536
  }
@@ -8,6 +8,8 @@ import {
8
8
  type ModeChangeEntry,
9
9
  type SessionEntry,
10
10
  } from "../session/session-manager";
11
+ import { sessionStateDir, sessionUltragoalDir } from "./session-layout";
12
+ import { resolveGjcSessionForRead, resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
11
13
  import { removeFileAudited, writeJsonAtomic } from "./state-writer";
12
14
 
13
15
  export const GJC_SESSION_FILE_ENV = "GJC_SESSION_FILE";
@@ -48,12 +50,12 @@ function isEnoent(error: unknown): boolean {
48
50
  );
49
51
  }
50
52
 
51
- function requestPath(cwd: string): string {
52
- return path.join(cwd, ".gjc", "state", "goal-mode-request.json");
53
+ function requestPath(cwd: string, sessionId: string): string {
54
+ return path.join(sessionStateDir(cwd, sessionId), "goal-mode-request.json");
53
55
  }
54
56
 
55
- function ultragoalGoalsPath(cwd: string): string {
56
- return path.join(cwd, ".gjc", "ultragoal", "goals.json");
57
+ function ultragoalGoalsPath(cwd: string, sessionId: string): string {
58
+ return path.join(sessionUltragoalDir(cwd, sessionId), "goals.json");
57
59
  }
58
60
 
59
61
  function isCreateGoalsArg(value: string): boolean {
@@ -65,8 +67,14 @@ export function isUltragoalCreateGoalsInvocation(args: readonly string[]): boole
65
67
  return command !== undefined && isCreateGoalsArg(command);
66
68
  }
67
69
 
68
- export async function readUltragoalGjcObjective(cwd: string): Promise<{ objective: string; goalsPath: string }> {
69
- const goalsPath = ultragoalGoalsPath(cwd);
70
+ export async function readUltragoalGjcObjective(
71
+ cwd: string,
72
+ sessionId?: string | null,
73
+ ): Promise<{ objective: string; goalsPath: string }> {
74
+ const session = sessionId?.trim()
75
+ ? { gjcSessionId: sessionId.trim() }
76
+ : await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID });
77
+ const goalsPath = ultragoalGoalsPath(cwd, session.gjcSessionId);
70
78
  try {
71
79
  const plan = (await Bun.file(goalsPath).json()) as UltragoalPlanShape;
72
80
  const objective = typeof plan.gjcObjective === "string" ? plan.gjcObjective.trim() : "";
@@ -87,7 +95,10 @@ export async function writePendingGoalModeRequest(input: {
87
95
  }): Promise<PendingGoalModeRequest> {
88
96
  const objective = input.objective.trim();
89
97
  if (!objective) throw new Error("goal objective is required");
90
- const sessionId = input.sessionId?.trim();
98
+ const resolvedSessionId =
99
+ input.sessionId?.trim() ||
100
+ resolveGjcSessionForWrite(input.cwd, { envSessionId: process.env.GJC_SESSION_ID }).gjcSessionId;
101
+ const sessionId = resolvedSessionId;
91
102
  const request: PendingGoalModeRequest = {
92
103
  version: REQUEST_VERSION,
93
104
  kind: "goal_mode_request",
@@ -97,11 +108,12 @@ export async function writePendingGoalModeRequest(input: {
97
108
  goalsPath: input.goalsPath,
98
109
  ...(sessionId ? { sessionId } : {}),
99
110
  };
100
- const filePath = requestPath(input.cwd);
111
+ const filePath = requestPath(input.cwd, sessionId);
101
112
  await writeJsonAtomic(filePath, request, {
102
113
  cwd: input.cwd,
103
- audit: { category: "state", verb: "write", owner: "gjc-runtime" },
114
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", sessionId },
104
115
  });
116
+ await writeSessionActivityMarker(input.cwd, sessionId, { writer: "goal-mode-request", path: filePath });
105
117
  return request;
106
118
  }
107
119
 
@@ -175,7 +187,10 @@ export async function consumePendingGoalModeRequest(
175
187
  cwd: string,
176
188
  currentSessionId?: string | null,
177
189
  ): Promise<PendingGoalModeRequest | null> {
178
- const filePath = requestPath(cwd);
190
+ const session = currentSessionId?.trim()
191
+ ? { gjcSessionId: currentSessionId.trim() }
192
+ : await resolveGjcSessionForRead(cwd, { envSessionId: process.env.GJC_SESSION_ID });
193
+ const filePath = requestPath(cwd, session.gjcSessionId);
179
194
  let raw: unknown;
180
195
  try {
181
196
  raw = await Bun.file(filePath).json();
@@ -203,7 +218,7 @@ export async function consumePendingGoalModeRequest(
203
218
  }
204
219
  await removeFileAudited(filePath, {
205
220
  cwd,
206
- audit: { category: "prune", verb: "remove", owner: "gjc-runtime" },
221
+ audit: { category: "prune", verb: "remove", owner: "gjc-runtime", sessionId: session.gjcSessionId },
207
222
  }).catch(error => {
208
223
  if (!isEnoent(error)) throw error;
209
224
  });
@@ -2,6 +2,7 @@ import { Buffer } from "node:buffer";
2
2
  import * as path from "node:path";
3
3
  import { safeStderrWrite } from "@gajae-code/utils";
4
4
  import type { Args } from "../cli/args";
5
+ import { tmuxRuntimeSessionPath } from "./session-layout";
5
6
  import { GJC_COORDINATOR_SESSION_ID_ENV, GJC_COORDINATOR_SESSION_STATE_FILE_ENV } from "./session-state-sidecar";
6
7
  import {
7
8
  buildGjcTmuxProfileCommands,
@@ -360,9 +361,13 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
360
361
  const sessionName = buildGjcTmuxSessionName(env, { branch });
361
362
  const tmuxCommand = resolveGjcTmuxCommand(env);
362
363
  const sessionId = env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || sessionName;
364
+ // The session ROOT is keyed by the active GJC session (GJC_SESSION_ID), NOT the
365
+ // coordinator/tmux identity. Fall back to the coordinator id only for standalone
366
+ // tmux launches with no GJC session context.
367
+ const gjcSessionId = env.GJC_SESSION_ID?.trim() || sessionId;
363
368
  const sessionStateFile =
364
369
  env[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim() ||
365
- path.join(cwd, ".gjc", "runtime", "tmux-sessions", `${buildGjcTmuxSessionSlug(sessionName)}.json`);
370
+ tmuxRuntimeSessionPath(cwd, gjcSessionId, buildGjcTmuxSessionSlug(sessionName));
366
371
  const tmuxAvailable = context.tmuxAvailable ?? Bun.which(tmuxCommand) !== null;
367
372
  if (!tmuxAvailable) return undefined;
368
373
  const existingSessionName =