@gajae-code/coding-agent 0.2.5 → 0.3.1

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 (234) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/types/async/job-manager.d.ts +91 -2
  3. package/dist/types/cli/args.d.ts +1 -1
  4. package/dist/types/commands/deep-interview.d.ts +3 -0
  5. package/dist/types/commands/harness.d.ts +37 -0
  6. package/dist/types/config/keybindings.d.ts +5 -0
  7. package/dist/types/config/settings-schema.d.ts +10 -4
  8. package/dist/types/config/settings.d.ts +2 -0
  9. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  10. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  11. package/dist/types/deep-interview/render-middleware.d.ts +6 -0
  12. package/dist/types/eval/py/executor.d.ts +2 -0
  13. package/dist/types/eval/py/kernel.d.ts +2 -0
  14. package/dist/types/exec/bash-executor.d.ts +10 -0
  15. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  17. package/dist/types/extensibility/shared-events.d.ts +1 -0
  18. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  19. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  20. package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
  21. package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
  22. package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
  23. package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
  24. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  25. package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
  26. package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
  28. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  29. package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
  30. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  31. package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
  32. package/dist/types/harness-control-plane/finalize.d.ts +47 -0
  33. package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
  34. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  35. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  36. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  37. package/dist/types/harness-control-plane/receipts.d.ts +88 -0
  38. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  39. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  40. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  41. package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
  42. package/dist/types/harness-control-plane/storage.d.ts +53 -0
  43. package/dist/types/harness-control-plane/types.d.ts +162 -0
  44. package/dist/types/hooks/skill-keywords.d.ts +2 -1
  45. package/dist/types/hooks/skill-state.d.ts +23 -29
  46. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  47. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  48. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  49. package/dist/types/internal-urls/types.d.ts +4 -0
  50. package/dist/types/lsp/index.d.ts +10 -10
  51. package/dist/types/modes/bridge/auth.d.ts +12 -0
  52. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  53. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  54. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  55. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  56. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  57. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  58. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  59. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  60. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  61. package/dist/types/modes/components/status-line.d.ts +2 -0
  62. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  63. package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
  64. package/dist/types/modes/index.d.ts +1 -0
  65. package/dist/types/modes/interactive-mode.d.ts +2 -0
  66. package/dist/types/modes/jobs-observer.d.ts +57 -0
  67. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  68. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  69. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  70. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  71. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  72. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  73. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  74. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  75. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  76. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  77. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  78. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  79. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  80. package/dist/types/modes/types.d.ts +2 -0
  81. package/dist/types/sdk.d.ts +4 -0
  82. package/dist/types/session/agent-session.d.ts +19 -1
  83. package/dist/types/skill-state/active-state.d.ts +2 -0
  84. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  85. package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
  86. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  87. package/dist/types/task/executor.d.ts +3 -0
  88. package/dist/types/task/id.d.ts +7 -0
  89. package/dist/types/task/index.d.ts +5 -0
  90. package/dist/types/task/receipt.d.ts +85 -0
  91. package/dist/types/task/spawn-gate.d.ts +38 -0
  92. package/dist/types/task/types.d.ts +198 -14
  93. package/dist/types/tools/cron.d.ts +6 -0
  94. package/dist/types/tools/index.d.ts +2 -0
  95. package/dist/types/tools/path-utils.d.ts +1 -0
  96. package/dist/types/tools/subagent.d.ts +26 -1
  97. package/package.json +7 -7
  98. package/scripts/build-binary.ts +7 -0
  99. package/src/async/job-manager.ts +334 -6
  100. package/src/cli/args.ts +9 -2
  101. package/src/cli/auth-broker-cli.ts +1 -0
  102. package/src/cli/config-cli.ts +10 -2
  103. package/src/cli.ts +2 -0
  104. package/src/commands/deep-interview.ts +1 -0
  105. package/src/commands/harness.ts +862 -0
  106. package/src/commands/launch.ts +2 -2
  107. package/src/commands/state.ts +2 -1
  108. package/src/commands/team.ts +54 -39
  109. package/src/config/keybindings.ts +6 -0
  110. package/src/config/settings-schema.ts +13 -3
  111. package/src/config/settings.ts +5 -0
  112. package/src/dap/client.ts +17 -3
  113. package/src/debug/crash-diagnostics.ts +223 -0
  114. package/src/debug/runtime-gauges.ts +20 -0
  115. package/src/deep-interview/render-middleware.ts +372 -0
  116. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  117. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  118. package/src/defaults/gjc/skills/team/SKILL.md +47 -21
  119. package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
  120. package/src/eval/py/executor.ts +21 -1
  121. package/src/eval/py/kernel.ts +15 -0
  122. package/src/exec/bash-executor.ts +41 -0
  123. package/src/extensibility/custom-tools/types.ts +1 -0
  124. package/src/extensibility/extensions/types.ts +6 -0
  125. package/src/extensibility/shared-events.ts +1 -0
  126. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  127. package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
  128. package/src/gjc-runtime/goal-mode-request.ts +11 -3
  129. package/src/gjc-runtime/ralplan-runtime.ts +235 -43
  130. package/src/gjc-runtime/state-graph.ts +86 -0
  131. package/src/gjc-runtime/state-migrations.ts +179 -0
  132. package/src/gjc-runtime/state-renderer.ts +345 -0
  133. package/src/gjc-runtime/state-runtime.ts +1155 -46
  134. package/src/gjc-runtime/state-schema.ts +192 -0
  135. package/src/gjc-runtime/state-validation.ts +49 -0
  136. package/src/gjc-runtime/state-writer.ts +749 -0
  137. package/src/gjc-runtime/team-runtime.ts +1255 -189
  138. package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
  139. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  140. package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
  141. package/src/gjc-runtime/workflow-manifest.ts +427 -0
  142. package/src/harness-control-plane/classifier.ts +128 -0
  143. package/src/harness-control-plane/control-endpoint.ts +148 -0
  144. package/src/harness-control-plane/finalize.ts +222 -0
  145. package/src/harness-control-plane/frame-mapper.ts +286 -0
  146. package/src/harness-control-plane/operate.ts +225 -0
  147. package/src/harness-control-plane/owner.ts +600 -0
  148. package/src/harness-control-plane/preserve.ts +102 -0
  149. package/src/harness-control-plane/receipts.ts +216 -0
  150. package/src/harness-control-plane/rpc-adapter.ts +276 -0
  151. package/src/harness-control-plane/seams.ts +39 -0
  152. package/src/harness-control-plane/session-lease.ts +388 -0
  153. package/src/harness-control-plane/state-machine.ts +98 -0
  154. package/src/harness-control-plane/storage.ts +257 -0
  155. package/src/harness-control-plane/types.ts +214 -0
  156. package/src/hooks/skill-keywords.ts +4 -2
  157. package/src/hooks/skill-state.ts +197 -64
  158. package/src/internal-urls/agent-protocol.ts +68 -21
  159. package/src/internal-urls/artifact-protocol.ts +12 -17
  160. package/src/internal-urls/docs-index.generated.ts +3 -2
  161. package/src/internal-urls/registry-helpers.ts +19 -16
  162. package/src/internal-urls/types.ts +4 -0
  163. package/src/lsp/client.ts +18 -2
  164. package/src/main.ts +21 -5
  165. package/src/modes/bridge/auth.ts +41 -0
  166. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  167. package/src/modes/bridge/bridge-mode.ts +520 -0
  168. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  169. package/src/modes/bridge/event-stream.ts +70 -0
  170. package/src/modes/components/assistant-message.ts +5 -1
  171. package/src/modes/components/custom-editor.ts +101 -0
  172. package/src/modes/components/hook-selector.ts +133 -20
  173. package/src/modes/components/jobs-overlay-model.ts +109 -0
  174. package/src/modes/components/jobs-overlay.ts +172 -0
  175. package/src/modes/components/status-line/presets.ts +7 -5
  176. package/src/modes/components/status-line/segments.ts +25 -0
  177. package/src/modes/components/status-line/types.ts +2 -0
  178. package/src/modes/components/status-line.ts +9 -1
  179. package/src/modes/controllers/event-controller.ts +71 -6
  180. package/src/modes/controllers/extension-ui-controller.ts +43 -1
  181. package/src/modes/controllers/input-controller.ts +105 -9
  182. package/src/modes/controllers/selector-controller.ts +31 -1
  183. package/src/modes/index.ts +1 -0
  184. package/src/modes/interactive-mode.ts +28 -0
  185. package/src/modes/jobs-observer.ts +204 -0
  186. package/src/modes/rpc/host-tools.ts +1 -186
  187. package/src/modes/rpc/host-uris.ts +1 -235
  188. package/src/modes/rpc/rpc-client.ts +25 -10
  189. package/src/modes/rpc/rpc-mode.ts +12 -381
  190. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  191. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  192. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  193. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  194. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  195. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  196. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  197. package/src/modes/shared/agent-wire/responses.ts +17 -0
  198. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  199. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  200. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  201. package/src/modes/types.ts +2 -0
  202. package/src/prompts/agents/executor.md +13 -0
  203. package/src/prompts/tools/subagent.md +39 -4
  204. package/src/prompts/tools/task-summary.md +3 -9
  205. package/src/prompts/tools/task.md +5 -1
  206. package/src/sdk.ts +8 -0
  207. package/src/session/agent-session.ts +445 -71
  208. package/src/session/session-manager.ts +13 -1
  209. package/src/skill-state/active-state.ts +58 -65
  210. package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
  211. package/src/skill-state/initial-phase.ts +2 -0
  212. package/src/skill-state/workflow-state-contract.ts +33 -4
  213. package/src/skill-state/workflow-state-version.ts +3 -0
  214. package/src/slash-commands/builtin-registry.ts +8 -0
  215. package/src/task/executor.ts +79 -13
  216. package/src/task/id.ts +33 -0
  217. package/src/task/index.ts +376 -74
  218. package/src/task/output-manager.ts +5 -4
  219. package/src/task/receipt.ts +297 -0
  220. package/src/task/render.ts +54 -134
  221. package/src/task/spawn-gate.ts +132 -0
  222. package/src/task/types.ts +104 -10
  223. package/src/tools/ask.ts +88 -27
  224. package/src/tools/ast-edit.ts +1 -0
  225. package/src/tools/ast-grep.ts +1 -0
  226. package/src/tools/bash.ts +1 -1
  227. package/src/tools/cron.ts +48 -0
  228. package/src/tools/find.ts +4 -1
  229. package/src/tools/index.ts +2 -0
  230. package/src/tools/path-utils.ts +3 -2
  231. package/src/tools/read.ts +1 -0
  232. package/src/tools/search.ts +1 -0
  233. package/src/tools/skill.ts +6 -1
  234. package/src/tools/subagent.ts +423 -79
@@ -27,6 +27,7 @@ import {
27
27
  Snowflake,
28
28
  toError,
29
29
  } from "@gajae-code/utils";
30
+ import { writeTextAtomic } from "../gjc-runtime/state-writer";
30
31
  import { ArtifactManager } from "./artifacts";
31
32
  import {
32
33
  type BlobPutResult,
@@ -56,6 +57,10 @@ import type { SessionStorage, SessionStorageWriter } from "./session-storage";
56
57
  import { FileSessionStorage, MemorySessionStorage } from "./session-storage";
57
58
 
58
59
  export const CURRENT_SESSION_VERSION = 3;
60
+ function isUnderProjectGjc(cwd: string, targetPath: string): boolean {
61
+ const relative = path.relative(path.join(path.resolve(cwd), ".gjc"), path.resolve(targetPath));
62
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
63
+ }
59
64
 
60
65
  export interface SessionHeader {
61
66
  type: "session";
@@ -384,6 +389,7 @@ const migratedSessionRoots = new Set<string>();
384
389
  * Best effort: callers decide whether migration failures should surface.
385
390
  */
386
391
  function migrateSessionDirPath(oldPath: string, newPath: string): void {
392
+ // Session-dir lifecycle migration: moves/removes whole directories, not file content writes.
387
393
  const existing = fs.statSync(newPath, { throwIfNoEntry: false });
388
394
  if (existing?.isDirectory()) {
389
395
  for (const file of fs.readdirSync(oldPath)) {
@@ -752,7 +758,13 @@ function writeTerminalBreadcrumb(cwd: string, sessionFile: string): void {
752
758
  const breadcrumbFile = path.join(breadcrumbDir, terminalId);
753
759
  const content = `${cwd}\n${sessionFile}\n`;
754
760
  // Best-effort — don't break session creation if breadcrumb fails
755
- Bun.write(breadcrumbFile, content).catch(() => {});
761
+ const write = isUnderProjectGjc(cwd, breadcrumbFile)
762
+ ? writeTextAtomic(breadcrumbFile, content, {
763
+ cwd,
764
+ audit: { category: "artifact", verb: "write", owner: "gjc-runtime" },
765
+ })
766
+ : Bun.write(breadcrumbFile, content);
767
+ write.catch(() => {});
756
768
  }
757
769
 
758
770
  /**
@@ -1,5 +1,10 @@
1
- import * as fs from "node:fs/promises";
2
1
  import * as path from "node:path";
2
+ import {
3
+ type ActiveSessionScope,
4
+ rebuildActiveSnapshot,
5
+ removeActiveEntry,
6
+ writeActiveEntry,
7
+ } from "../gjc-runtime/state-writer";
3
8
  import type { WorkflowStateReceipt } from "./workflow-state-contract";
4
9
 
5
10
  export const SKILL_ACTIVE_STATE_FILE = "skill-active-state.json";
@@ -56,6 +61,8 @@ export interface SkillActiveState {
56
61
  session_id?: string;
57
62
  thread_id?: string;
58
63
  turn_id?: string;
64
+ initialized_mode?: CanonicalGjcWorkflowSkill;
65
+ initialized_state_path?: string;
59
66
  active_skills?: SkillActiveEntry[];
60
67
  [key: string]: unknown;
61
68
  }
@@ -286,14 +293,6 @@ export function getSkillActiveStatePaths(cwd: string, sessionId?: string): Skill
286
293
  };
287
294
  }
288
295
 
289
- async function readStateFile(filePath: string): Promise<SkillActiveState | null> {
290
- try {
291
- return normalizeSkillActiveState(JSON.parse(await Bun.file(filePath).text()));
292
- } catch {
293
- return null;
294
- }
295
- }
296
-
297
296
  /**
298
297
  * Raw read for handoff mutations. Returns the *unnormalized* parsed object so
299
298
  * inactive entries remain visible to `rawActiveEntries` — `normalizeSkillActiveState`
@@ -516,15 +515,41 @@ export async function readVisibleSkillActiveState(cwd: string, sessionId?: strin
516
515
  };
517
516
  }
518
517
 
519
- async function writeStateFile(filePath: string, state: SkillActiveState): Promise<void> {
520
- await fs.mkdir(path.dirname(filePath), { recursive: true });
521
- await Bun.write(filePath, `${JSON.stringify(state, null, 2)}\n`);
518
+ function activeStateWriterAudit(verb: string) {
519
+ return { category: "state" as const, verb, owner: "gjc-runtime" as const };
520
+ }
521
+
522
+ async function persistActiveEntry(
523
+ cwd: string,
524
+ sessionScope: ActiveSessionScope | undefined,
525
+ entry: SkillActiveEntry,
526
+ ): Promise<void> {
527
+ if (entry.active === false) {
528
+ await removeActiveEntry(cwd, sessionScope, entry.skill, {
529
+ cwd,
530
+ audit: activeStateWriterAudit("remove-active-entry"),
531
+ });
532
+ } else {
533
+ await writeActiveEntry(cwd, sessionScope, entry.skill, entry, {
534
+ cwd,
535
+ audit: activeStateWriterAudit("write-active-entry"),
536
+ });
537
+ }
522
538
  }
523
539
 
524
- function upsertEntry(entries: SkillActiveEntry[], entry: SkillActiveEntry, active: boolean): SkillActiveEntry[] {
525
- const key = entryKey(entry);
526
- const retained = entries.filter(candidate => entryKey(candidate) !== key);
527
- return active ? [...retained, entry] : retained;
540
+ async function writeHandoffEntry(
541
+ cwd: string,
542
+ sessionScope: ActiveSessionScope | undefined,
543
+ entry: SkillActiveEntry,
544
+ ): Promise<void> {
545
+ await writeActiveEntry(cwd, sessionScope, entry.skill, entry, {
546
+ cwd,
547
+ audit: activeStateWriterAudit("write-active-entry"),
548
+ });
549
+ }
550
+
551
+ async function rebuildActiveState(cwd: string, sessionScope?: ActiveSessionScope): Promise<void> {
552
+ await rebuildActiveSnapshot(cwd, sessionScope, { cwd, audit: activeStateWriterAudit("rebuild-active-snapshot") });
528
553
  }
529
554
 
530
555
  export async function syncSkillActiveState(options: SyncSkillActiveStateOptions): Promise<void> {
@@ -545,36 +570,13 @@ export async function syncSkillActiveState(options: SyncSkillActiveStateOptions)
545
570
  ...(hud ? { hud } : {}),
546
571
  ...(options.receipt ? { receipt: options.receipt } : {}),
547
572
  };
548
- const { rootPath, sessionPath } = getSkillActiveStatePaths(options.cwd, options.sessionId);
549
- const rootState = (await readStateFile(rootPath)) ?? { version: 1, active_skills: [] };
550
- const rootEntries = upsertEntry(listActiveSkills(rootState), entry, options.active);
551
- const nextRoot: SkillActiveState = {
552
- ...rootState,
553
- version: 1,
554
- active: rootEntries.length > 0,
555
- skill: rootEntries[0]?.skill ?? "",
556
- phase: rootEntries[0]?.phase ?? "",
557
- updated_at: nowIso,
558
- source: options.source,
559
- active_skills: rootEntries,
560
- };
561
- await writeStateFile(rootPath, nextRoot);
573
+ await persistActiveEntry(options.cwd, undefined, entry);
574
+ await rebuildActiveState(options.cwd);
562
575
 
563
- if (!sessionPath) return;
564
- const sessionState = (await readStateFile(sessionPath)) ?? { version: 1, active_skills: [] };
565
- const sessionEntries = upsertEntry(listActiveSkills(sessionState), entry, options.active);
566
- const nextSession: SkillActiveState = {
567
- ...sessionState,
568
- version: 1,
569
- active: sessionEntries.length > 0,
570
- skill: sessionEntries[0]?.skill ?? "",
571
- phase: sessionEntries[0]?.phase ?? "",
572
- session_id: options.sessionId,
573
- updated_at: nowIso,
574
- source: options.source,
575
- active_skills: sessionEntries,
576
- };
577
- await writeStateFile(sessionPath, nextSession);
576
+ if (!options.sessionId) return;
577
+ const sessionScope = { sessionId: options.sessionId };
578
+ await persistActiveEntry(options.cwd, sessionScope, entry);
579
+ await rebuildActiveState(options.cwd, sessionScope);
578
580
  }
579
581
 
580
582
  export interface ApplyHandoffOptions {
@@ -604,6 +606,7 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
604
606
  const sessionId = options.callee.sessionId ?? options.caller.sessionId;
605
607
  const { rootPath, sessionPath } = getSkillActiveStatePaths(options.cwd, sessionId);
606
608
  const readState = (filePath: string) => readRawActiveStateForHandoff(filePath, options.strict === true);
609
+ await Promise.all([readState(rootPath), ...(sessionPath ? [readState(sessionPath)] : [])]);
607
610
 
608
611
  // A skill can hold more than one visible row in this session's scope — e.g.
609
612
  // it was seeded without a session id (rendered globally) and is now handed
@@ -637,33 +640,23 @@ export async function applyHandoffToActiveState(options: ApplyHandoffOptions): P
637
640
  : callerEntry;
638
641
  return [...kept, mergedCaller, calleeEntry];
639
642
  };
640
- const buildNextState = (
643
+ const writeEntries = async (
644
+ sessionScope: ActiveSessionScope | undefined,
641
645
  prior: SkillActiveState | null,
642
- entries: SkillActiveEntry[],
643
- scope: "session" | "root",
644
- ): SkillActiveState => {
645
- const visible = entries.filter(e => e.active !== false);
646
- return {
647
- ...(prior ?? {}),
648
- version: 1,
649
- active: visible.length > 0,
650
- skill: visible[0]?.skill ?? "",
651
- phase: visible[0]?.phase ?? "",
652
- ...(scope === "session" ? { session_id: sessionId } : {}),
653
- updated_at: nowIso,
654
- source: options.callee.source ?? options.caller.source,
655
- active_skills: entries,
656
- };
646
+ ): Promise<void> => {
647
+ const nextEntries = applyEntries(rawActiveEntries(prior));
648
+ for (const entry of nextEntries) {
649
+ await writeHandoffEntry(options.cwd, sessionScope, entry);
650
+ }
651
+ await rebuildActiveState(options.cwd, sessionScope);
657
652
  };
658
653
 
659
654
  if (sessionPath) {
660
655
  const prior = await readState(sessionPath);
661
- const next = buildNextState(prior, applyEntries(rawActiveEntries(prior)), "session");
662
- await writeStateFile(sessionPath, next);
656
+ await writeEntries({ sessionId }, prior);
663
657
  }
664
658
  const priorRoot = await readState(rootPath);
665
- const nextRoot = buildNextState(priorRoot, applyEntries(rawActiveEntries(priorRoot)), "root");
666
- await writeStateFile(rootPath, nextRoot);
659
+ await writeEntries(undefined, priorRoot);
667
660
  }
668
661
 
669
662
  function buildSyncEntry(options: SyncSkillActiveStateOptions, nowIso: string): SkillActiveEntry {
@@ -1,6 +1,7 @@
1
1
  import * as path from "node:path";
2
2
  import type { AgentTool } from "@gajae-code/agent-core";
3
3
  import { expandApplyPatchToEntries } from "../edit/modes/apply-patch";
4
+ import { ModeStateSchema } from "../gjc-runtime/state-schema";
4
5
  import { LocalProtocolHandler, resolveLocalUrlToPath } from "../internal-urls/local-protocol";
5
6
  import { resolveToCwd } from "../tools/path-utils";
6
7
  import { ToolError } from "../tools/tool-errors";
@@ -14,12 +15,16 @@ import {
14
15
  export const DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE =
15
16
  "Deep-interview phase boundary: continue gathering context/questions/risks and emit a handoff/spec before code edits. Mutation tools and patch execution are blocked while deep-interview is active; finalize specs through `gjc deep-interview --write --stage final` or hand off to an execution phase.";
16
17
  export const WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE =
17
- "Workflow state JSON is runtime-owned. Use `gjc state <skill> read|write --input '<json>'` for deep-interview, ralplan, ultragoal, and team. Planning artifacts under `.gjc/specs/` and `.gjc/plans/` remain allowed.";
18
+ ".gjc workflow state and artifacts are runtime-owned. Agent mutation tools cannot edit `.gjc/**`; use the sanctioned `gjc` CLI instead.";
18
19
 
19
- const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit"]);
20
+ const BLOCKED_TOOL_NAMES = new Set(["edit", "write", "ast_edit", "bash"]);
20
21
  const ARCHIVE_OR_SQLITE_BASE_RE = /^(.+?\.(?:tar\.gz|sqlite3|sqlite|db3|zip|tgz|tar|db))(?:$|:)/i;
21
22
  const INTERNAL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
22
23
  const VIM_FILE_SWITCH_RE = /^\s*:(?:e|e!|edit|edit!)(?:\s+([^<\r\n]+))?(?:<CR>|\r|\n|$)/i;
24
+ const BASH_TOKEN_RE = /'[^']*'|"(?:\\.|[^"\\])*"|\S+/g;
25
+ const BASH_REDIRECT_RE = /^(?:\d*)>>?$/;
26
+ const BASH_HEREDOC_RE = /^(?:\d*)<<-?$/;
27
+ const BASH_MUTATION_COMMANDS = new Set(["rm", "mv", "cp", "touch", "mkdir", "ln", "tee"]);
23
28
 
24
29
  type ToolWithEditMode = AgentTool & {
25
30
  mode?: unknown;
@@ -72,19 +77,37 @@ function modeStatePath(cwd: string, skill: string, sessionId?: string): string {
72
77
  return path.join(stateDir, fileName);
73
78
  }
74
79
 
75
- async function readJsonFile<T>(filePath: string): Promise<T | null> {
80
+ function warnInvalidModeState(filePath: string, error: string): void {
81
+ console.warn(`gjc skill-state: invalid mode-state at ${filePath}: ${error}`);
82
+ }
83
+
84
+ async function readValidatedModeState(filePath: string): Promise<ModeState | null> {
85
+ let raw: string;
76
86
  try {
77
- return JSON.parse(await Bun.file(filePath).text()) as T;
87
+ raw = await Bun.file(filePath).text();
78
88
  } catch {
79
89
  return null;
80
90
  }
91
+ let state: ModeState;
92
+ try {
93
+ state = JSON.parse(raw) as ModeState;
94
+ } catch (error) {
95
+ warnInvalidModeState(filePath, `invalid JSON: ${(error as Error).message}`);
96
+ return null;
97
+ }
98
+ const parsed = ModeStateSchema.safeParse(state);
99
+ if (!parsed.success) {
100
+ warnInvalidModeState(filePath, parsed.error.message);
101
+ return null;
102
+ }
103
+ return state;
81
104
  }
82
105
  async function readVisibleModeState(cwd: string, skill: string, sessionId?: string): Promise<ModeState | null> {
83
106
  if (sessionId) {
84
- const sessionState = await readJsonFile<ModeState>(modeStatePath(cwd, skill, sessionId));
107
+ const sessionState = await readValidatedModeState(modeStatePath(cwd, skill, sessionId));
85
108
  if (sessionState) return sessionState;
86
109
  }
87
- return await readJsonFile<ModeState>(modeStatePath(cwd, skill));
110
+ return await readValidatedModeState(modeStatePath(cwd, skill));
88
111
  }
89
112
 
90
113
  function isTerminalModeState(state: ModeState | null): boolean {
@@ -219,10 +242,75 @@ function extractEditTargets(args: unknown, tool: ToolWithEditMode): ExtractedTar
219
242
  return targets;
220
243
  }
221
244
 
245
+ function extractBashTargets(args: unknown): ExtractedTargets {
246
+ const record = getRecord(args);
247
+ const command = safeString(record?.command).trim();
248
+ const targets: ExtractedTargets = { paths: [], unknown: false };
249
+ if (!command) {
250
+ targets.unknown = true;
251
+ return targets;
252
+ }
253
+ if (/^gjc(?:\s|$)/.test(command)) return targets;
254
+
255
+ const tokens = command.match(BASH_TOKEN_RE)?.map(unquoteBashToken) ?? [];
256
+ for (let index = 0; index < tokens.length; index++) {
257
+ const token = tokens[index] ?? "";
258
+ if (BASH_REDIRECT_RE.test(token)) {
259
+ addPath(targets, tokens[index + 1]);
260
+ index++;
261
+ continue;
262
+ }
263
+ const redirectMatch = token.match(/^(?:\d*)>>?(.+)$/);
264
+ if (redirectMatch?.[1]) {
265
+ addPath(targets, redirectMatch[1]);
266
+ continue;
267
+ }
268
+ if (BASH_HEREDOC_RE.test(token)) {
269
+ addPath(targets, tokens[index + 1]);
270
+ index++;
271
+ continue;
272
+ }
273
+ const heredocMatch = token.match(/^(?:\d*)<<-?(.+)$/);
274
+ if (heredocMatch?.[1]) {
275
+ addPath(targets, heredocMatch[1]);
276
+ continue;
277
+ }
278
+ if (isMutationBashCommand(tokens, index)) {
279
+ for (let targetIndex = index + 1; targetIndex < tokens.length; targetIndex++) {
280
+ const target = tokens[targetIndex] ?? "";
281
+ if (isBashCommandBoundary(target)) break;
282
+ if (target.startsWith("-")) continue;
283
+ addPath(targets, target);
284
+ }
285
+ }
286
+ }
287
+ return targets;
288
+ }
289
+
290
+ function unquoteBashToken(token: string): string {
291
+ if (token.length < 2) return token;
292
+ const quote = token[0];
293
+ if ((quote === "'" || quote === '"') && token.at(-1) === quote) return token.slice(1, -1);
294
+ return token;
295
+ }
296
+
297
+ function isBashCommandBoundary(token: string): boolean {
298
+ return [";", "&&", "||", "|"].includes(token);
299
+ }
300
+
301
+ function isMutationBashCommand(tokens: string[], index: number): boolean {
302
+ const token = path.basename(tokens[index] ?? "");
303
+ if (BASH_MUTATION_COMMANDS.has(token)) return true;
304
+ if (token !== "sed") return false;
305
+ const next = tokens[index + 1] ?? "";
306
+ return next === "-i" || next.startsWith("-i") || next.includes("i");
307
+ }
308
+
222
309
  function extractTargets(tool: ToolWithEditMode, args: unknown): ExtractedTargets {
223
310
  if (tool.name === "write") return extractWriteTargets(args);
224
311
  if (tool.name === "ast_edit") return extractAstEditTargets(args);
225
312
  if (tool.name === "edit") return extractEditTargets(args, tool);
313
+ if (tool.name === "bash") return extractBashTargets(args);
226
314
  return { paths: [], unknown: true };
227
315
  }
228
316
 
@@ -289,6 +377,14 @@ function isAllowlistedPath(cwd: string, rawPath: string): boolean {
289
377
  if (segments?.[0] !== ".gjc") return false;
290
378
  return segments[1] === "specs" || segments[1] === "plans";
291
379
  }
380
+ function isBlockedGjcPath(cwd: string, rawPath: string): boolean {
381
+ const segments = relativeGjcSegments(cwd, rawPath);
382
+ return segments?.[0] === ".gjc";
383
+ }
384
+
385
+ function hasBlockedGjcTarget(cwd: string, targets: ExtractedTargets): boolean {
386
+ return targets.paths.some(rawPath => isBlockedGjcPath(cwd, rawPath));
387
+ }
292
388
 
293
389
  function allTargetsAllowlisted(cwd: string, targets: ExtractedTargets): boolean {
294
390
  return (
@@ -315,18 +411,16 @@ export async function getDeepInterviewMutationDecision(
315
411
  ): Promise<DeepInterviewMutationDecision> {
316
412
  if (!BLOCKED_TOOL_NAMES.has(input.tool.name)) return { blocked: false, targets: [] };
317
413
  const targets = extractTargets(input.tool, input.args);
318
- if (input.enforceWorkflowState !== false) {
414
+ if (input.enforceWorkflowState !== false && hasBlockedGjcTarget(input.cwd, targets)) {
319
415
  const stateSkill = firstBlockedWorkflowStateSkill(input.cwd, targets);
320
- if (stateSkill) {
321
- const command = sanctionedWorkflowStateCommand(stateSkill);
322
- return {
323
- blocked: true,
324
- message: `${WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE}\nUse: ${command}`,
325
- targets: targets.paths,
326
- reason: "workflow-state-target",
327
- command,
328
- };
329
- }
416
+ const command = stateSkill ? sanctionedWorkflowStateCommand(stateSkill) : "gjc <workflow-command>";
417
+ return {
418
+ blocked: true,
419
+ message: `${WORKFLOW_STATE_MUTATION_BLOCK_MESSAGE}\nUse: ${command}`,
420
+ targets: targets.paths,
421
+ reason: stateSkill ? "workflow-state-target" : "gjc-target",
422
+ command,
423
+ };
330
424
  }
331
425
  if (!(await isActiveDeepInterview(input.cwd, input.sessionId, input.threadId))) {
332
426
  return { blocked: false, targets: [] };
@@ -340,6 +434,9 @@ export async function getDeepInterviewMutationDecision(
340
434
  reason: "unknown-target",
341
435
  };
342
436
  }
437
+ if (input.tool.name === "bash") {
438
+ return { blocked: false, targets: targets.paths };
439
+ }
343
440
  return {
344
441
  blocked: true,
345
442
  message: DEEP_INTERVIEW_MUTATION_BLOCK_MESSAGE,
@@ -13,5 +13,7 @@ import type { CanonicalGjcWorkflowSkill } from "./active-state";
13
13
  export function initialPhaseForSkill(skill: CanonicalGjcWorkflowSkill | string): string {
14
14
  if (skill === "deep-interview") return "interviewing";
15
15
  if (skill === "ultragoal") return "goal-planning";
16
+ if (skill === "ralplan") return "planner";
17
+ if (skill === "team") return "starting";
16
18
  return "planning";
17
19
  }
@@ -1,14 +1,24 @@
1
1
  import * as path from "node:path";
2
2
  import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill, SKILL_ACTIVE_STATE_FILE } from "./active-state";
3
+ import { WORKFLOW_STATE_RECEIPT_FRESH_MS, WORKFLOW_STATE_RECEIPT_VERSION } from "./workflow-state-version";
3
4
 
4
- export type { CanonicalGjcWorkflowSkill };
5
-
6
- export const WORKFLOW_STATE_RECEIPT_VERSION = 1;
7
- export const WORKFLOW_STATE_RECEIPT_FRESH_MS = 30 * 60 * 1000;
5
+ export {
6
+ WORKFLOW_STATE_RECEIPT_FRESH_MS,
7
+ WORKFLOW_STATE_RECEIPT_VERSION,
8
+ WORKFLOW_STATE_VERSION,
9
+ } from "./workflow-state-version";
8
10
 
11
+ export type { CanonicalGjcWorkflowSkill };
9
12
  export type WorkflowStateMutationOwner = "gjc-state-cli" | "gjc-runtime" | "gjc-hook";
10
13
  export type WorkflowStateReceiptStatus = "fresh" | "stale";
11
14
 
15
+ export interface WorkflowStateContentChecksum {
16
+ algorithm: "sha256";
17
+ value: string;
18
+ covered_path: string;
19
+ computed_at: string;
20
+ }
21
+
12
22
  export interface WorkflowStateReceipt {
13
23
  version: 1;
14
24
  skill: CanonicalGjcWorkflowSkill;
@@ -20,6 +30,25 @@ export interface WorkflowStateReceipt {
20
30
  fresh_until: string;
21
31
  status: WorkflowStateReceiptStatus;
22
32
  mutation_id: string;
33
+ verb?: string;
34
+ from_phase?: string;
35
+ to_phase?: string;
36
+ forced?: boolean;
37
+ paths?: string[];
38
+ content_sha256?: WorkflowStateContentChecksum;
39
+ }
40
+
41
+ export interface AuditEntry {
42
+ ts: string;
43
+ skill?: string;
44
+ category: string;
45
+ verb: string;
46
+ owner: WorkflowStateMutationOwner;
47
+ mutation_id: string;
48
+ from_phase?: string;
49
+ to_phase?: string;
50
+ forced: boolean;
51
+ paths: string[];
23
52
  }
24
53
 
25
54
  function safeString(value: unknown): string {
@@ -0,0 +1,3 @@
1
+ export const WORKFLOW_STATE_RECEIPT_VERSION = 1;
2
+ export const WORKFLOW_STATE_VERSION = 2;
3
+ export const WORKFLOW_STATE_RECEIPT_FRESH_MS = 30 * 60 * 1000;
@@ -586,6 +586,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
586
586
  runtime.ctx.editor.setText("");
587
587
  },
588
588
  },
589
+ {
590
+ name: "monitors",
591
+ description: "Open the monitor/cron jobs overlay",
592
+ handleTui: (_command, runtime) => {
593
+ runtime.ctx.showJobsOverlay();
594
+ runtime.ctx.editor.setText("");
595
+ },
596
+ },
589
597
  {
590
598
  name: "tree",
591
599
  description: "Navigate session tree (switch branches)",