@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,6 +12,8 @@ import {
12
12
  summarizeRalplanIndex,
13
13
  } from "./ledger-event-renderer";
14
14
  import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
15
+ import { modeStatePath, sessionPlansDir } from "./session-layout";
16
+ import { resolveGjcSessionForWrite, writeSessionActivityMarker } from "./session-resolution";
15
17
  import { migrateWorkflowState } from "./state-migrations";
16
18
  import { runNativeStateCommand } from "./state-runtime";
17
19
  import {
@@ -169,18 +171,15 @@ interface ResolvedArtifactArgs {
169
171
  stageN: number;
170
172
  runId: string;
171
173
  artifact: string;
172
- sessionId: string | undefined;
174
+ sessionId: string;
173
175
  json: boolean;
174
176
  }
175
177
 
176
- function ralplanStatePath(cwd: string, sessionId: string | undefined): string {
177
- const stateDir = sessionId
178
- ? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(sessionId))
179
- : path.join(cwd, ".gjc", "state");
180
- return path.join(stateDir, "ralplan-state.json");
178
+ function ralplanStatePath(cwd: string, sessionId: string): string {
179
+ return modeStatePath(cwd, sessionId, "ralplan");
181
180
  }
182
181
 
183
- async function readActiveRunId(cwd: string, sessionId: string | undefined): Promise<string | undefined> {
182
+ async function readActiveRunId(cwd: string, sessionId: string): Promise<string | undefined> {
184
183
  const statePath = ralplanStatePath(cwd, sessionId);
185
184
  const existingRead = await readExistingStateForMutation(statePath);
186
185
  if (existingRead.kind === "absent") return undefined;
@@ -221,12 +220,7 @@ function advanceCurrentPhase(existingPhase: unknown, stage: RalplanStage): strin
221
220
  return stage;
222
221
  }
223
222
 
224
- async function persistActiveRunId(
225
- cwd: string,
226
- sessionId: string | undefined,
227
- runId: string,
228
- stage: RalplanStage,
229
- ): Promise<void> {
223
+ async function persistActiveRunId(cwd: string, sessionId: string, runId: string, stage: RalplanStage): Promise<void> {
230
224
  const statePath = ralplanStatePath(cwd, sessionId);
231
225
  const existingRead = await readExistingStateForMutation(statePath);
232
226
  if (existingRead.kind === "corrupt") {
@@ -261,7 +255,7 @@ async function persistActiveRunId(
261
255
  await writeWorkflowEnvelopeAtomic(statePath, existing, {
262
256
  cwd,
263
257
  receipt: { cwd, skill: "ralplan", owner: "gjc-runtime", command: "gjc ralplan persist-run-id", sessionId },
264
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
258
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan", sessionId },
265
259
  });
266
260
  }
267
261
 
@@ -384,11 +378,7 @@ function plannerStatePayload(update: PlannerStateUpdate): Record<string, unknown
384
378
  * audit/routing hint only — it records what the caller has already proven and is
385
379
  * NOT a durable cross-process subagent registry.
386
380
  */
387
- async function applyPlannerStateUpdate(
388
- cwd: string,
389
- sessionId: string | undefined,
390
- update: PlannerStateUpdate,
391
- ): Promise<void> {
381
+ async function applyPlannerStateUpdate(cwd: string, sessionId: string, update: PlannerStateUpdate): Promise<void> {
392
382
  const statePath = ralplanStatePath(cwd, sessionId);
393
383
  const existingRead = await readExistingStateForMutation(statePath);
394
384
  if (existingRead.kind === "corrupt") {
@@ -407,7 +397,7 @@ async function applyPlannerStateUpdate(
407
397
  await writeWorkflowEnvelopeAtomic(statePath, existing, {
408
398
  cwd,
409
399
  receipt: { cwd, skill: "ralplan", owner: "gjc-runtime", command: "gjc ralplan planner-state", sessionId },
410
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
400
+ audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan", sessionId },
411
401
  });
412
402
  }
413
403
 
@@ -423,9 +413,13 @@ async function resolveArtifactArgs(args: readonly string[], cwd: string): Promis
423
413
  throw new RalplanCommandError(2, "--artifact is required for ralplan --write");
424
414
  }
425
415
 
426
- const sessionIdRaw = flagValue(args, "--session-id")?.trim();
427
- const sessionId = sessionIdRaw || undefined;
428
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
416
+ const session = resolveGjcSessionForWrite(cwd, {
417
+ flagValue: flagValue(args, "--session-id"),
418
+ envSessionId: process.env.GJC_SESSION_ID,
419
+ });
420
+ const sessionId = session.gjcSessionId;
421
+ assertSafePathComponent(sessionId, "session-id");
422
+ const sessionIdRaw = sessionId;
429
423
 
430
424
  // Precedence for run_id:
431
425
  // 1. explicit --run-id flag
@@ -469,13 +463,19 @@ async function persistArtifact(
469
463
  content: string,
470
464
  sha256: string,
471
465
  ): Promise<PersistedArtifact> {
472
- const runDir = path.join(cwd, ".gjc", "plans", "ralplan", resolved.runId);
466
+ const runDir = path.join(sessionPlansDir(cwd, resolved.sessionId), "ralplan", resolved.runId);
473
467
 
474
468
  const fileName = `stage-${pad2(resolved.stageN)}-${resolved.stage}.md`;
475
469
  const filePath = path.join(runDir, fileName);
476
470
  await writeArtifact(filePath, content, {
477
471
  cwd,
478
- audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
472
+ audit: {
473
+ category: "artifact",
474
+ verb: "write",
475
+ owner: "gjc-runtime",
476
+ skill: "ralplan",
477
+ sessionId: resolved.sessionId,
478
+ },
479
479
  });
480
480
 
481
481
  const createdAt = new Date().toISOString();
@@ -488,7 +488,13 @@ async function persistArtifact(
488
488
  };
489
489
  await appendJsonlIdempotent(path.join(runDir, "index.jsonl"), indexEntry, {
490
490
  cwd,
491
- audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "ralplan" },
491
+ audit: {
492
+ category: "ledger",
493
+ verb: "append",
494
+ owner: "gjc-runtime",
495
+ skill: "ralplan",
496
+ sessionId: resolved.sessionId,
497
+ },
492
498
  key: ralplanIndexKey,
493
499
  });
494
500
 
@@ -497,7 +503,13 @@ async function persistArtifact(
497
503
  pendingApprovalPath = path.join(runDir, "pending-approval.md");
498
504
  await writeArtifact(pendingApprovalPath, content, {
499
505
  cwd,
500
- audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
506
+ audit: {
507
+ category: "artifact",
508
+ verb: "write",
509
+ owner: "gjc-runtime",
510
+ skill: "ralplan",
511
+ sessionId: resolved.sessionId,
512
+ },
501
513
  });
502
514
  }
503
515
 
@@ -528,11 +540,12 @@ interface ExistingStageArtifact {
528
540
  */
529
541
  async function findExistingStageArtifact(
530
542
  cwd: string,
543
+ sessionId: string,
531
544
  runId: string,
532
545
  stage: RalplanStage,
533
546
  stageN: number,
534
547
  ): Promise<ExistingStageArtifact | undefined> {
535
- const indexPath = path.join(cwd, ".gjc", "plans", "ralplan", runId, "index.jsonl");
548
+ const indexPath = path.join(sessionPlansDir(cwd, sessionId), "ralplan", runId, "index.jsonl");
536
549
  let text: string;
537
550
  try {
538
551
  text = await fs.readFile(indexPath, "utf8");
@@ -566,9 +579,9 @@ async function findExistingStageArtifact(
566
579
  * Read and parse the run's `index.jsonl` rows. Best-effort: returns [] when the
567
580
  * file is absent or unreadable so HUD sync never fails on a missing index.
568
581
  */
569
- async function readRalplanIndexRows(cwd: string, runId: string): Promise<RalplanIndexRow[]> {
582
+ async function readRalplanIndexRows(cwd: string, sessionId: string, runId: string): Promise<RalplanIndexRow[]> {
570
583
  try {
571
- const indexPath = path.join(cwd, ".gjc", "plans", "ralplan", runId, "index.jsonl");
584
+ const indexPath = path.join(sessionPlansDir(cwd, sessionId), "ralplan", runId, "index.jsonl");
572
585
  const text = await fs.readFile(indexPath, "utf8");
573
586
  const rows: RalplanIndexRow[] = [];
574
587
  for (const line of text.split(/\r?\n/)) {
@@ -583,7 +596,7 @@ async function readRalplanIndexRows(cwd: string, runId: string): Promise<Ralplan
583
596
 
584
597
  async function syncRalplanHud(options: {
585
598
  cwd: string;
586
- sessionId?: string;
599
+ sessionId: string;
587
600
  stage: string;
588
601
  pendingApproval: boolean;
589
602
  iteration?: number;
@@ -612,11 +625,12 @@ async function buildRalplanHud(options: {
612
625
  iteration?: number;
613
626
  latestSummary?: string;
614
627
  runId?: string;
628
+ sessionId?: string;
615
629
  }) {
616
630
  let iterationFromIndex: number | undefined;
617
631
  let stages: string | undefined;
618
- if (options.runId) {
619
- const rows = await readRalplanIndexRows(options.cwd, options.runId);
632
+ if (options.runId && options.sessionId) {
633
+ const rows = await readRalplanIndexRows(options.cwd, options.sessionId, options.runId);
620
634
  if (rows.length > 0) {
621
635
  const summary = summarizeRalplanIndex(rows);
622
636
  iterationFromIndex = summary.iteration;
@@ -643,7 +657,13 @@ async function handleArtifactWrite(args: readonly string[], cwd: string): Promis
643
657
  // Duplicate-write guard: a second `--write` for the same (stage, stage_n) must not
644
658
  // silently clobber the artifact or append a duplicate ledger row. Classify before any
645
659
  // state mutation so a conflict never regresses run-state phase.
646
- const existingArtifact = await findExistingStageArtifact(cwd, resolved.runId, resolved.stage, resolved.stageN);
660
+ const existingArtifact = await findExistingStageArtifact(
661
+ cwd,
662
+ resolved.sessionId,
663
+ resolved.runId,
664
+ resolved.stage,
665
+ resolved.stageN,
666
+ );
647
667
  if (existingArtifact) {
648
668
  if (existingArtifact.sha256 !== sha256) {
649
669
  throw new RalplanCommandError(
@@ -660,6 +680,7 @@ async function handleArtifactWrite(args: readonly string[], cwd: string): Promis
660
680
  if (plannerState) {
661
681
  await applyPlannerStateUpdate(cwd, resolved.sessionId, plannerState);
662
682
  }
683
+ await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "ralplan-runtime", path: persisted.path });
663
684
  await syncRalplanHud({
664
685
  cwd,
665
686
  sessionId: resolved.sessionId,
@@ -706,7 +727,12 @@ function buildDeduplicatedResult(
706
727
  deduplicated: true,
707
728
  };
708
729
  if (resolved.stage === "final") {
709
- payload.pending_approval_path = path.join(cwd, ".gjc", "plans", "ralplan", resolved.runId, "pending-approval.md");
730
+ payload.pending_approval_path = path.join(
731
+ sessionPlansDir(cwd, resolved.sessionId),
732
+ "ralplan",
733
+ resolved.runId,
734
+ "pending-approval.md",
735
+ );
710
736
  }
711
737
  const stdout = resolved.json
712
738
  ? `${JSON.stringify(payload, null, 2)}\n`
@@ -721,7 +747,7 @@ interface ConsensusHandoffArgs {
721
747
  deliberate: boolean;
722
748
  architectKind?: string;
723
749
  criticKind?: string;
724
- sessionId?: string;
750
+ sessionId: string;
725
751
  task: string;
726
752
  json: boolean;
727
753
  }
@@ -747,7 +773,7 @@ function extractPositionalTask(args: readonly string[]): string {
747
773
  return parts.join(" ").trim();
748
774
  }
749
775
 
750
- function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
776
+ function resolveConsensusArgs(args: readonly string[], cwd: string): ConsensusHandoffArgs {
751
777
  const architectKind = flagValue(args, "--architect")?.trim() || undefined;
752
778
  if (architectKind && !KNOWN_ARCHITECT_KINDS.has(architectKind)) {
753
779
  throw new RalplanCommandError(
@@ -762,8 +788,12 @@ function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
762
788
  `unknown --critic kind: ${criticKind}. Expected one of: ${[...KNOWN_CRITIC_KINDS].join(", ")}.`,
763
789
  );
764
790
  }
765
- const sessionId = flagValue(args, "--session-id")?.trim() || undefined;
766
- if (sessionId) assertSafePathComponent(sessionId, "session-id");
791
+ const session = resolveGjcSessionForWrite(cwd, {
792
+ flagValue: flagValue(args, "--session-id"),
793
+ envSessionId: process.env.GJC_SESSION_ID,
794
+ });
795
+ const sessionId = session.gjcSessionId;
796
+ assertSafePathComponent(sessionId, "session-id");
767
797
  const task = extractPositionalTask(args);
768
798
  return {
769
799
  interactive: hasFlag(args, "--interactive"),
@@ -776,19 +806,11 @@ function resolveConsensusArgs(args: readonly string[]): ConsensusHandoffArgs {
776
806
  };
777
807
  }
778
808
 
779
- function encodeSessionSegment(value: string): string {
780
- return encodeURIComponent(value).replaceAll(".", "%2E");
781
- }
782
-
783
809
  async function seedRalplanState(
784
810
  cwd: string,
785
811
  resolved: ConsensusHandoffArgs,
786
812
  ): Promise<{ statePath: string; runId: string }> {
787
- const stateDir = resolved.sessionId
788
- ? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
789
- : path.join(cwd, ".gjc", "state");
790
-
791
- const statePath = path.join(stateDir, "ralplan-state.json");
813
+ const statePath = ralplanStatePath(cwd, resolved.sessionId);
792
814
  // Reuse an existing run id when present so a re-invocation of `gjc ralplan "task"` doesn't
793
815
  // orphan in-progress artifacts under a fresh run id.
794
816
  const existingRunId = await readActiveRunId(cwd, resolved.sessionId);
@@ -818,13 +840,20 @@ async function seedRalplanState(
818
840
  command: "gjc ralplan seed",
819
841
  sessionId: resolved.sessionId,
820
842
  },
821
- audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
843
+ audit: {
844
+ category: "state",
845
+ verb: "write",
846
+ owner: "gjc-runtime",
847
+ skill: "ralplan",
848
+ sessionId: resolved.sessionId,
849
+ },
822
850
  });
851
+ await writeSessionActivityMarker(cwd, resolved.sessionId, { writer: "ralplan-runtime", path: statePath });
823
852
  return { statePath, runId };
824
853
  }
825
854
 
826
855
  async function handleConsensusHandoff(args: readonly string[], cwd: string): Promise<RalplanCommandResult> {
827
- const resolved = resolveConsensusArgs(args);
856
+ const resolved = resolveConsensusArgs(args, cwd);
828
857
  if (!resolved.task) {
829
858
  throw new RalplanCommandError(2, 'gjc ralplan requires a task description, e.g. `gjc ralplan "<task>"`.');
830
859
  }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Pure path layout for session-scoped GJC workflow state.
3
+ *
4
+ * Every generated/runtime artifact for a GJC session lives under
5
+ * `<cwd>/.gjc/_session-{encodedSessionId}/...`. The `_session-` prefix is what
6
+ * discriminates a session directory from shared, user-authored/installed config
7
+ * (settings.json, secrets.yml, agents/, gjc-plugins/, agent/, python-env/, user
8
+ * skills/commands), which always stays at the `.gjc/` root.
9
+ *
10
+ * This module is PURE and acyclic: every export is a deterministic function of
11
+ * its arguments. It never reads `process.env` and never touches the filesystem.
12
+ * Session resolution (flag/payload/env/latest-activity-marker) and any
13
+ * filesystem scanning live in `session-resolution.ts`, the boundary module.
14
+ */
15
+ import * as path from "node:path";
16
+
17
+ export const GJC_DIR = ".gjc";
18
+ export const GJC_SESSION_PREFIX = "_session-";
19
+ export const GJC_SESSION_ACTIVITY_FILE = ".session-activity.json";
20
+
21
+ /** Source that produced a resolved GJC session id, for audit/diagnostics. */
22
+ export type GjcSessionSource = "flag" | "payload" | "env" | "latest";
23
+
24
+ export interface GjcSessionContext {
25
+ gjcSessionId: string;
26
+ sessionRoot: string;
27
+ source: GjcSessionSource;
28
+ }
29
+
30
+ /**
31
+ * Encode a session id into a single safe path segment. Matches the historical
32
+ * encoding used across the runtimes so ids round-trip identically:
33
+ * `encodeURIComponent` plus dot-escaping (dots are legal in filenames but we
34
+ * avoid `.`/`..` traversal ambiguity).
35
+ */
36
+ export function encodeSessionSegment(value: string): string {
37
+ return encodeURIComponent(value).replaceAll(".", "%2E");
38
+ }
39
+
40
+ /** Inverse of {@link encodeSessionSegment}. */
41
+ export function decodeSessionSegment(segment: string): string {
42
+ return decodeURIComponent(segment.replaceAll("%2E", "."));
43
+ }
44
+
45
+ /** Throw when a session id is missing or blank; never let blank suppress callers. */
46
+ export function assertNonEmptyGjcSessionId(value: string | undefined, source: string): asserts value is string {
47
+ if (typeof value !== "string" || value.trim() === "") {
48
+ throw new Error(`a non-empty GJC session id is required (${source})`);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Assert a value is safe to use as a single path segment: non-blank and free of
54
+ * path separators or `.`/`..` traversal. Use for already-safe identifiers
55
+ * (skill modes, slugs) where we want identical filenames but fail closed on
56
+ * traversal rather than silently normalizing out of the intended directory.
57
+ */
58
+ export function assertSafePathComponent(value: string, label: string): void {
59
+ const trimmed = value.trim();
60
+ if (trimmed === "") throw new Error(`${label} is required`);
61
+ if (trimmed === "." || trimmed === ".." || /[/\\]/.test(trimmed)) {
62
+ throw new Error(`${label} must be a safe path component (no separators or traversal): ${value}`);
63
+ }
64
+ }
65
+
66
+ /** The shared `.gjc/` root (holds shared config; never session-scoped). */
67
+ export function gjcRoot(cwd: string): string {
68
+ return path.join(cwd, GJC_DIR);
69
+ }
70
+
71
+ /** The per-session root directory: `<cwd>/.gjc/_session-{encodedId}`. */
72
+ export function sessionRoot(cwd: string, gjcSessionId: string): string {
73
+ assertNonEmptyGjcSessionId(gjcSessionId, "sessionRoot");
74
+ return path.join(gjcRoot(cwd), `${GJC_SESSION_PREFIX}${encodeSessionSegment(gjcSessionId)}`);
75
+ }
76
+
77
+ /** Directory name (no path) for a session id, e.g. `_session-abc`. */
78
+ export function sessionDirName(gjcSessionId: string): string {
79
+ assertNonEmptyGjcSessionId(gjcSessionId, "sessionDirName");
80
+ return `${GJC_SESSION_PREFIX}${encodeSessionSegment(gjcSessionId)}`;
81
+ }
82
+
83
+ /** Return the decoded session id for a `_session-*` directory name, else undefined. */
84
+ export function sessionIdFromDirName(name: string): string | undefined {
85
+ if (!name.startsWith(GJC_SESSION_PREFIX)) return undefined;
86
+ const suffix = name.slice(GJC_SESSION_PREFIX.length);
87
+ if (suffix === "") return undefined;
88
+ let decoded: string;
89
+ try {
90
+ decoded = decodeSessionSegment(suffix);
91
+ } catch {
92
+ return undefined;
93
+ }
94
+ return decoded.trim() === "" ? undefined : decoded;
95
+ }
96
+
97
+ /** Authoritative per-session activity marker path. */
98
+ export function sessionActivityPath(cwd: string, gjcSessionId: string): string {
99
+ return path.join(sessionRoot(cwd, gjcSessionId), GJC_SESSION_ACTIVITY_FILE);
100
+ }
101
+
102
+ // ---- Top-level per-category subdir resolvers ----
103
+
104
+ export function sessionStateDir(cwd: string, gjcSessionId: string): string {
105
+ return path.join(sessionRoot(cwd, gjcSessionId), "state");
106
+ }
107
+ export function sessionSpecsDir(cwd: string, gjcSessionId: string): string {
108
+ return path.join(sessionRoot(cwd, gjcSessionId), "specs");
109
+ }
110
+ export function sessionPlansDir(cwd: string, gjcSessionId: string): string {
111
+ return path.join(sessionRoot(cwd, gjcSessionId), "plans");
112
+ }
113
+ export function sessionUltragoalDir(cwd: string, gjcSessionId: string): string {
114
+ return path.join(sessionRoot(cwd, gjcSessionId), "ultragoal");
115
+ }
116
+ export function sessionAuditDir(cwd: string, gjcSessionId: string): string {
117
+ return path.join(sessionRoot(cwd, gjcSessionId), "audit");
118
+ }
119
+ export function sessionReportsDir(cwd: string, gjcSessionId: string): string {
120
+ return path.join(sessionRoot(cwd, gjcSessionId), "reports");
121
+ }
122
+ export function sessionLogsDir(cwd: string, gjcSessionId: string): string {
123
+ return path.join(sessionRoot(cwd, gjcSessionId), "logs");
124
+ }
125
+ export function sessionRuntimeDir(cwd: string, gjcSessionId: string): string {
126
+ return path.join(sessionRoot(cwd, gjcSessionId), "runtime");
127
+ }
128
+ export function sessionRlmDir(cwd: string, gjcSessionId: string): string {
129
+ return path.join(sessionRoot(cwd, gjcSessionId), "rlm");
130
+ }
131
+
132
+ // ---- Nested resolvers under <sessionRoot>/state ----
133
+
134
+ export function activeStateDir(cwd: string, gjcSessionId: string): string {
135
+ return path.join(sessionStateDir(cwd, gjcSessionId), "active");
136
+ }
137
+ export function activeSnapshotPath(cwd: string, gjcSessionId: string): string {
138
+ return path.join(sessionStateDir(cwd, gjcSessionId), "skill-active-state.json");
139
+ }
140
+ export function activeEntryPath(cwd: string, gjcSessionId: string, skill: string): string {
141
+ const normalized = skill.trim();
142
+ if (normalized === "") throw new Error("skill is required");
143
+ return path.join(activeStateDir(cwd, gjcSessionId), `${encodeSessionSegment(normalized)}.json`);
144
+ }
145
+ export function modeStatePath(cwd: string, gjcSessionId: string, mode: string): string {
146
+ const normalized = mode.trim();
147
+ assertSafePathComponent(normalized, "mode");
148
+ return path.join(sessionStateDir(cwd, gjcSessionId), `${normalized}-state.json`);
149
+ }
150
+ export function auditPath(cwd: string, gjcSessionId: string): string {
151
+ return path.join(sessionStateDir(cwd, gjcSessionId), "audit.jsonl");
152
+ }
153
+ export function transactionJournalPath(cwd: string, gjcSessionId: string, mutationId: string): string {
154
+ return path.join(sessionStateDir(cwd, gjcSessionId), "transactions", `${encodeSessionSegment(mutationId)}.json`);
155
+ }
156
+ export function teamStateRoot(cwd: string, gjcSessionId: string): string {
157
+ return path.join(sessionStateDir(cwd, gjcSessionId), "team");
158
+ }
159
+ export function workflowGatePath(cwd: string, gjcSessionId: string, gateId: string): string {
160
+ return path.join(sessionStateDir(cwd, gjcSessionId), "workflow-gates", `${encodeSessionSegment(gateId)}.json`);
161
+ }
162
+ export function harnessStateRoot(cwd: string, gjcSessionId: string): string {
163
+ return path.join(sessionStateDir(cwd, gjcSessionId), "harness");
164
+ }
165
+ export function coordinatorMcpStateRoot(cwd: string, gjcSessionId: string): string {
166
+ return path.join(sessionStateDir(cwd, gjcSessionId), "coordinator-mcp");
167
+ }
168
+
169
+ // ---- Nested resolvers under other top-level categories ----
170
+
171
+ export function tmuxRuntimeSessionPath(cwd: string, gjcSessionId: string, slug: string): string {
172
+ const normalized = slug.trim();
173
+ assertSafePathComponent(normalized, "slug");
174
+ return path.join(sessionRuntimeDir(cwd, gjcSessionId), "tmux-sessions", `${normalized}.json`);
175
+ }
176
+ export function rlmArtifactRoot(cwd: string, gjcSessionId: string, rlmSessionId: string): string {
177
+ const normalized = rlmSessionId.trim();
178
+ if (normalized === "") throw new Error("rlmSessionId is required");
179
+ return path.join(sessionRlmDir(cwd, gjcSessionId), encodeSessionSegment(normalized));
180
+ }