@gajae-code/coding-agent 0.18.7 → 0.18.9

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 (344) hide show
  1. package/CHANGELOG.md +377 -1
  2. package/dist/types/async/endpoint-id.d.ts +16 -0
  3. package/dist/types/async/job-manager.d.ts +1 -1
  4. package/dist/types/async/support.d.ts +0 -14
  5. package/dist/types/capability/mcp.d.ts +2 -0
  6. package/dist/types/capability/skill.d.ts +2 -0
  7. package/dist/types/capability/types.d.ts +2 -0
  8. package/dist/types/cli/public-command-errors.d.ts +3 -1
  9. package/dist/types/cli/setup-cli.d.ts +1 -1
  10. package/dist/types/commands/deep-interview.d.ts +3 -0
  11. package/dist/types/commands/mcp-serve.d.ts +4 -0
  12. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  13. package/dist/types/config/file-lock.d.ts +1 -0
  14. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  15. package/dist/types/config/model-profile-activation.d.ts +11 -3
  16. package/dist/types/config/model-registry.d.ts +4 -9
  17. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  18. package/dist/types/cursor-delete-path.d.ts +1 -0
  19. package/dist/types/cursor-mcp-name.d.ts +8 -0
  20. package/dist/types/customization/inventory.d.ts +2 -0
  21. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  22. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  23. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  24. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  25. package/dist/types/discovery/helpers.d.ts +6 -1
  26. package/dist/types/discovery/mcp-json.d.ts +1 -0
  27. package/dist/types/eval/py/kernel.d.ts +18 -0
  28. package/dist/types/export/html/css-color.d.ts +2 -0
  29. package/dist/types/export/html/html-export-destination.d.ts +2 -0
  30. package/dist/types/export/html/template.generated.d.ts +1 -1
  31. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
  32. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  33. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  34. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  35. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  36. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  37. package/dist/types/extensibility/skill-management.d.ts +4 -1
  38. package/dist/types/extensibility/skills.d.ts +2 -0
  39. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  40. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  41. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  42. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
  43. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  44. package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
  45. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  46. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  47. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  48. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  49. package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
  50. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  51. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  52. package/dist/types/lsp/config.d.ts +8 -5
  53. package/dist/types/lsp/workspace-path.d.ts +1 -0
  54. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  55. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  56. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  57. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  58. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  59. package/dist/types/modes/interactive-mode.d.ts +1 -0
  60. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  61. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  62. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  63. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  64. package/dist/types/progress/progress-contract.d.ts +154 -0
  65. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  66. package/dist/types/progress/project-progress.d.ts +141 -0
  67. package/dist/types/progress/render-progress.d.ts +28 -0
  68. package/dist/types/runtime-mcp/manager.d.ts +2 -1
  69. package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
  70. package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
  71. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  72. package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
  73. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  74. package/dist/types/runtime-mcp/types.d.ts +6 -0
  75. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  76. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  77. package/dist/types/sdk/broker/broker.d.ts +24 -2
  78. package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
  79. package/dist/types/sdk/broker/ensure.d.ts +14 -1
  80. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  81. package/dist/types/sdk/bus/index.d.ts +12 -1
  82. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  83. package/dist/types/sdk/client/client.d.ts +2 -0
  84. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  85. package/dist/types/sdk/index.d.ts +1 -0
  86. package/dist/types/sdk/router/session-router.d.ts +2 -0
  87. package/dist/types/sdk/session-list.d.ts +1 -1
  88. package/dist/types/sdk/session.d.ts +8 -0
  89. package/dist/types/session/agent-session.d.ts +38 -9
  90. package/dist/types/session/artifacts.d.ts +2 -2
  91. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  92. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  93. package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
  94. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  95. package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
  96. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
  97. package/dist/types/session/session-manager.d.ts +31 -5
  98. package/dist/types/session/session-retirement.d.ts +31 -7
  99. package/dist/types/session/session-storage.d.ts +69 -2
  100. package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
  101. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  102. package/dist/types/setup/credential-import.d.ts +6 -4
  103. package/dist/types/skill-state/active-state.d.ts +38 -0
  104. package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
  105. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  106. package/dist/types/task/executor.d.ts +3 -0
  107. package/dist/types/task/isolation-dir.d.ts +26 -0
  108. package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
  109. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  110. package/dist/types/tools/browser/cdp-url.d.ts +2 -0
  111. package/dist/types/tools/github-side-effect.d.ts +22 -0
  112. package/dist/types/tools/index.d.ts +3 -0
  113. package/dist/types/tools/monitor-mutation.d.ts +9 -0
  114. package/dist/types/tools/python.d.ts +9 -1
  115. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  116. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  117. package/dist/types/tools/todo-write.d.ts +6 -0
  118. package/dist/types/utils/tools-checksum.d.ts +7 -0
  119. package/package.json +7 -7
  120. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  121. package/scripts/verify-managed-empty-stop.ts +28 -0
  122. package/src/async/endpoint-id.ts +26 -0
  123. package/src/async/job-manager.ts +247 -132
  124. package/src/async/support.ts +0 -25
  125. package/src/capability/index.ts +15 -0
  126. package/src/capability/mcp.ts +2 -0
  127. package/src/capability/skill.ts +2 -0
  128. package/src/capability/types.ts +2 -0
  129. package/src/cli/auth-broker-cli.ts +1 -1
  130. package/src/cli/customize-doctor.ts +15 -2
  131. package/src/cli/doctor/repairs.ts +4 -1
  132. package/src/cli/doctor-supervisor.ts +3 -0
  133. package/src/cli/public-command-errors.ts +21 -1
  134. package/src/cli/public-command-registry.ts +28 -6
  135. package/src/cli/setup-cli.ts +4 -4
  136. package/src/cli/skills-cli.ts +5 -0
  137. package/src/commands/deep-interview.ts +47 -8
  138. package/src/commands/mcp-serve.ts +7 -0
  139. package/src/commands/sdk.ts +455 -397
  140. package/src/config/autorouting-tier-map.ts +117 -7
  141. package/src/config/file-lock.ts +316 -79
  142. package/src/config/mcp-schema.json +4 -0
  143. package/src/config/model-bindings-applier.ts +20 -0
  144. package/src/config/model-profile-activation.ts +240 -46
  145. package/src/config/model-registry.ts +47 -153
  146. package/src/config/skill-settings-defaults.ts +7 -0
  147. package/src/coordinator-mcp/policy.ts +3 -2
  148. package/src/coordinator-mcp/server.ts +2 -0
  149. package/src/cursor-delete-path.ts +10 -0
  150. package/src/cursor-mcp-name.ts +15 -0
  151. package/src/cursor.ts +13 -2
  152. package/src/customization/inventory.ts +4 -13
  153. package/src/dap/config.ts +2 -2
  154. package/src/debug/report-bundle-parent.ts +22 -0
  155. package/src/debug/report-bundle.ts +3 -0
  156. package/src/debug/system-info.ts +13 -1
  157. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
  158. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  159. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  160. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  161. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  162. package/src/defaults/gjc-defaults.test.ts +18 -1
  163. package/src/defaults/gjc-defaults.ts +3 -0
  164. package/src/defaults/gjc-skills.generated.ts +58 -36
  165. package/src/discovery/agents.ts +6 -1
  166. package/src/discovery/builtin.ts +13 -1
  167. package/src/discovery/helpers.ts +171 -29
  168. package/src/discovery/mcp-json.ts +26 -12
  169. package/src/discovery/ssh.ts +1 -1
  170. package/src/discovery/substitute-plugin-root.ts +3 -1
  171. package/src/edit/file-read-cache.ts +1 -1
  172. package/src/eval/py/executor.ts +796 -154
  173. package/src/eval/py/kernel.ts +79 -13
  174. package/src/eval/py/tool-bridge.ts +4 -0
  175. package/src/export/html/css-color.ts +9 -0
  176. package/src/export/html/html-export-destination.ts +16 -0
  177. package/src/export/html/index.ts +7 -10
  178. package/src/export/html/template.generated.ts +1 -1
  179. package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
  180. package/src/extensibility/extensions/runner.ts +15 -1
  181. package/src/extensibility/extensions/types.ts +7 -0
  182. package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
  183. package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
  184. package/src/extensibility/plugins/manager.ts +3 -2
  185. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  186. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  187. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  188. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  189. package/src/extensibility/skill-management.ts +9 -2
  190. package/src/extensibility/skills.ts +6 -1
  191. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  192. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  193. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  194. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  195. package/src/gjc-runtime/gc-runtime.ts +206 -9
  196. package/src/gjc-runtime/launch-worktree.ts +70 -9
  197. package/src/gjc-runtime/psmux-detect.ts +28 -4
  198. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  199. package/src/gjc-runtime/session-state-sidecar.ts +3 -2
  200. package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
  201. package/src/gjc-runtime/state-argv.ts +2 -0
  202. package/src/gjc-runtime/state-migrations.ts +91 -29
  203. package/src/gjc-runtime/state-runtime.ts +3716 -135
  204. package/src/gjc-runtime/state-writer.ts +105 -16
  205. package/src/gjc-runtime/tmux-common.ts +8 -5
  206. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  207. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  208. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  209. package/src/harness-control-plane/operate.ts +12 -4
  210. package/src/harness-control-plane/owner.ts +5 -1
  211. package/src/hashline/parser.ts +48 -9
  212. package/src/hashline/recovery.ts +13 -4
  213. package/src/internal-urls/docs-index.generated.ts +20 -19
  214. package/src/internal-urls/skill-path.ts +19 -0
  215. package/src/internal-urls/skill-protocol.ts +2 -0
  216. package/src/lsp/config.ts +12 -63
  217. package/src/lsp/edits.ts +6 -0
  218. package/src/lsp/workspace-path.ts +51 -0
  219. package/src/main.ts +195 -31
  220. package/src/memories/index.ts +88 -7
  221. package/src/modes/acp/acp-agent.ts +88 -4
  222. package/src/modes/components/custom-editor.ts +14 -0
  223. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  224. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  225. package/src/modes/components/extensions/state-manager.ts +15 -5
  226. package/src/modes/components/hook-editor.ts +1 -1
  227. package/src/modes/components/hook-selector.ts +1 -1
  228. package/src/modes/components/model-selector.ts +1 -4
  229. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  230. package/src/modes/components/status-line/segments.ts +4 -0
  231. package/src/modes/controllers/event-controller.ts +11 -4
  232. package/src/modes/controllers/input-controller.ts +76 -31
  233. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
  234. package/src/modes/controllers/selector-controller.ts +24 -15
  235. package/src/modes/controllers/todo-command-controller.ts +1 -1
  236. package/src/modes/interactive-mode.ts +5 -1
  237. package/src/modes/runtime-init.ts +4 -0
  238. package/src/modes/utils/abort-message.ts +3 -1
  239. package/src/modes/utils/interrupt-ownership.ts +12 -0
  240. package/src/modes/utils/keybinding-matchers.ts +4 -0
  241. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  242. package/src/progress/collect-project-progress.ts +153 -0
  243. package/src/progress/progress-contract.ts +172 -0
  244. package/src/progress/progress-snapshot.ts +140 -0
  245. package/src/progress/project-progress.ts +446 -0
  246. package/src/progress/render-progress.ts +209 -0
  247. package/src/prompts/system/master-mode.md +1 -0
  248. package/src/prompts/tools/skill-discovery.md +1 -1
  249. package/src/runtime-mcp/client.ts +4 -2
  250. package/src/runtime-mcp/config-writer.ts +10 -6
  251. package/src/runtime-mcp/config.ts +1 -0
  252. package/src/runtime-mcp/json-rpc.ts +2 -1
  253. package/src/runtime-mcp/manager.ts +29 -11
  254. package/src/runtime-mcp/mcp-redirect.ts +73 -0
  255. package/src/runtime-mcp/oauth-flow.ts +14 -2
  256. package/src/runtime-mcp/oauth-public-url.ts +14 -0
  257. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  258. package/src/runtime-mcp/pool.ts +6 -0
  259. package/src/runtime-mcp/project-host-values.ts +46 -0
  260. package/src/runtime-mcp/smithery-registry.ts +22 -3
  261. package/src/runtime-mcp/transports/http.ts +4 -3
  262. package/src/runtime-mcp/types.ts +6 -0
  263. package/src/sdk/acp/adapter.ts +95 -8
  264. package/src/sdk/broker/broker-exit.ts +52 -23
  265. package/src/sdk/broker/broker.ts +81 -6
  266. package/src/sdk/broker/doctor-restart.ts +4 -0
  267. package/src/sdk/broker/ensure.ts +31 -2
  268. package/src/sdk/broker/lifecycle.ts +711 -66
  269. package/src/sdk/broker/session-index.ts +191 -81
  270. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  271. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  272. package/src/sdk/bus/index.ts +238 -43
  273. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  274. package/src/sdk/cli/session-cli.ts +17 -3
  275. package/src/sdk/client/client.ts +28 -8
  276. package/src/sdk/host/query/handlers.ts +9 -1
  277. package/src/sdk/host/session-runtime.test.ts +2705 -303
  278. package/src/sdk/host/session-runtime.ts +490 -143
  279. package/src/sdk/host/surface-policy.ts +1 -0
  280. package/src/sdk/index.ts +8 -0
  281. package/src/sdk/lifecycle-session.ts +3 -1
  282. package/src/sdk/prompt-deadline-manager.ts +15 -0
  283. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  284. package/src/sdk/protocol/operation-registry.ts +9 -2
  285. package/src/sdk/router/session-router.ts +82 -18
  286. package/src/sdk/session-list.ts +1 -0
  287. package/src/sdk/session.ts +69 -45
  288. package/src/session/agent-session.ts +679 -238
  289. package/src/session/artifacts.ts +176 -71
  290. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  291. package/src/session/internal/managed-session-scope.ts +3779 -462
  292. package/src/session/internal/managed-session-storage.ts +501 -78
  293. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  294. package/src/session/internal/task-artifact-owner-access.ts +19 -3
  295. package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
  296. package/src/session/session-manager.ts +951 -133
  297. package/src/session/session-retirement.ts +573 -14
  298. package/src/session/session-storage.ts +447 -15
  299. package/src/session/task-artifact-owner-codec.ts +71 -8
  300. package/src/session/task-artifact-owner-retirement.ts +21 -19
  301. package/src/setup/credential-auto-import.ts +12 -3
  302. package/src/setup/credential-import.ts +176 -5
  303. package/src/setup/paseo/announce.ts +6 -1
  304. package/src/skill-state/active-state.ts +145 -2
  305. package/src/skill-state/workflow-mutation-guard.ts +102 -10
  306. package/src/slash-commands/builtin-registry.ts +39 -9
  307. package/src/slash-commands/helpers/mcp.ts +8 -4
  308. package/src/slash-commands/helpers/progress-report.ts +23 -0
  309. package/src/stt/setup.ts +3 -5
  310. package/src/task/commands.ts +3 -1
  311. package/src/task/executor.ts +4 -0
  312. package/src/task/index.ts +6 -0
  313. package/src/task/isolation-dir.ts +143 -0
  314. package/src/task/worktree.ts +16 -7
  315. package/src/tools/ask.ts +205 -6
  316. package/src/tools/ast-edit-write-path.ts +68 -0
  317. package/src/tools/ast-edit.ts +10 -1
  318. package/src/tools/bash-allowed-prefixes.ts +15 -0
  319. package/src/tools/bash-skill-urls.ts +56 -3
  320. package/src/tools/bash.ts +48 -79
  321. package/src/tools/browser/cdp-url.ts +18 -0
  322. package/src/tools/browser.ts +8 -1
  323. package/src/tools/descriptors.ts +5 -1
  324. package/src/tools/eval.ts +56 -36
  325. package/src/tools/gh.ts +10 -0
  326. package/src/tools/github-side-effect.ts +112 -0
  327. package/src/tools/image-gen.ts +1 -1
  328. package/src/tools/index.ts +3 -0
  329. package/src/tools/monitor-mutation.ts +27 -0
  330. package/src/tools/monitor.ts +7 -0
  331. package/src/tools/python.ts +188 -66
  332. package/src/tools/recipe/index.ts +9 -0
  333. package/src/tools/recipe/recipe-guard.ts +19 -0
  334. package/src/tools/restricted-cwd.ts +10 -0
  335. package/src/tools/skill-discovery.ts +5 -0
  336. package/src/tools/skill.ts +28 -1
  337. package/src/tools/todo-write.ts +10 -1
  338. package/src/tools/tool-catalog.generated.ts +1 -1
  339. package/src/tools/write.ts +1 -0
  340. package/src/utils/external-editor.ts +3 -2
  341. package/src/utils/tools-checksum.ts +34 -0
  342. package/src/utils/tools-manager.ts +24 -6
  343. package/src/web/insane/url-guard.ts +2 -1
  344. package/src/web/search/providers/codex.ts +3 -2
@@ -1,4 +1,5 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
+ import type * as nodeFs from "node:fs";
2
3
  import { constants as fsConstants, type Stats } from "node:fs";
3
4
  import type { FileHandle } from "node:fs/promises";
4
5
  import * as fs from "node:fs/promises";
@@ -89,6 +90,7 @@ export interface WorkflowTransactionJournal {
89
90
  callee?: CanonicalGjcWorkflowSkill;
90
91
  paths: string[];
91
92
  steps: string[];
93
+ approval_audit_offset?: number;
92
94
  }
93
95
 
94
96
  export type StateWritePolicy = "source" | "cache";
@@ -308,19 +310,33 @@ export function stampWorkflowEnvelopeChecksum<T>(value: T, filePath: string, com
308
310
  return envelope as T;
309
311
  }
310
312
 
311
- export async function detectWorkflowEnvelopeIntegrityMismatch(
312
- filePath: string,
313
- ): Promise<WorkflowEnvelopeIntegrityMismatch | undefined> {
314
- const current = await readJsonIfPresent(filePath);
315
- if (!current || typeof current !== "object" || Array.isArray(current)) return undefined;
316
- const receipt = (current as Record<string, unknown>).receipt;
313
+ export type WorkflowEnvelopeChecksumStatus = "absent" | "match" | "mismatch";
314
+
315
+ /** Compare a parsed envelope to its writer-stamped `content_sha256`, when one is present. */
316
+ export function workflowEnvelopeChecksumStatus(value: unknown): WorkflowEnvelopeChecksumStatus {
317
+ const checksum = workflowEnvelopeChecksumPair(value);
318
+ if (!checksum) return "absent";
319
+ return checksum.actual === checksum.expected ? "match" : "mismatch";
320
+ }
321
+
322
+ function workflowEnvelopeChecksumPair(value: unknown): { expected: string; actual: string } | undefined {
323
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
324
+ const receipt = (value as Record<string, unknown>).receipt;
317
325
  if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) return undefined;
318
326
  const checksum = (receipt as Record<string, unknown>).content_sha256;
319
327
  if (!checksum || typeof checksum !== "object" || Array.isArray(checksum)) return undefined;
320
328
  const expected = (checksum as Record<string, unknown>).value;
321
329
  if (typeof expected !== "string" || !expected) return undefined;
322
- const actual = workflowEnvelopeContentSha256(current);
323
- return actual === expected ? undefined : { path: filePath, expected, actual };
330
+ return { expected, actual: workflowEnvelopeContentSha256(value) };
331
+ }
332
+
333
+ export async function detectWorkflowEnvelopeIntegrityMismatch(
334
+ filePath: string,
335
+ ): Promise<WorkflowEnvelopeIntegrityMismatch | undefined> {
336
+ const current = await readJsonIfPresent(filePath);
337
+ const checksum = workflowEnvelopeChecksumPair(current);
338
+ if (!checksum || checksum.actual === checksum.expected) return undefined;
339
+ return { path: filePath, expected: checksum.expected, actual: checksum.actual };
324
340
  }
325
341
 
326
342
  function safeString(value: unknown): string {
@@ -1368,9 +1384,16 @@ export async function removeActiveEntry(
1368
1384
  );
1369
1385
  }
1370
1386
 
1371
- export async function readActiveEntries(
1387
+ /**
1388
+ * Read every per-skill active entry. Unreadable JSON propagates; a parsed record
1389
+ * that is not a skill entry (not an object, or without a `skill`) is skipped, or
1390
+ * rejected when `strict` is set so status-bearing readers can tell an existing
1391
+ * but unusable authoritative entry apart from a deleted one.
1392
+ */
1393
+ async function readActiveEntriesFromDir(
1372
1394
  cwd: string,
1373
- sessionScope?: string | ActiveSessionScope,
1395
+ sessionScope: string | ActiveSessionScope | undefined,
1396
+ strict: boolean,
1374
1397
  ): Promise<SkillActiveEntry[]> {
1375
1398
  const dir = activeStateDir(path.resolve(cwd), sessionScope);
1376
1399
  let names: string[];
@@ -1383,15 +1406,38 @@ export async function readActiveEntries(
1383
1406
  const entries: SkillActiveEntry[] = [];
1384
1407
  for (const name of names.sort()) {
1385
1408
  if (!name.endsWith(".json")) continue;
1386
- const raw = await readJsonIfPresent(path.join(dir, name));
1387
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
1388
- const skill = safeString((raw as SkillActiveEntry).skill).trim();
1389
- if (!skill) continue;
1409
+ const filePath = path.join(dir, name);
1410
+ const raw = await readJsonIfPresent(filePath);
1411
+ if (raw === undefined) continue;
1412
+ const skill = isPlainObject(raw) ? safeString(raw.skill).trim() : "";
1413
+ if (!skill) {
1414
+ if (strict) throw new Error(`active entry at ${filePath} is not a skill entry record`);
1415
+ continue;
1416
+ }
1390
1417
  entries.push(raw as SkillActiveEntry);
1391
1418
  }
1392
1419
  return entries;
1393
1420
  }
1394
1421
 
1422
+ export async function readActiveEntries(
1423
+ cwd: string,
1424
+ sessionScope?: string | ActiveSessionScope,
1425
+ ): Promise<SkillActiveEntry[]> {
1426
+ return readActiveEntriesFromDir(cwd, sessionScope, false);
1427
+ }
1428
+
1429
+ /**
1430
+ * Failure-observable counterpart of `readActiveEntries`: an entry file that exists
1431
+ * but holds no usable skill entry throws instead of being skipped, so its absence
1432
+ * from the result always means the entry does not exist.
1433
+ */
1434
+ export async function readActiveEntriesStrict(
1435
+ cwd: string,
1436
+ sessionScope?: string | ActiveSessionScope,
1437
+ ): Promise<SkillActiveEntry[]> {
1438
+ return readActiveEntriesFromDir(cwd, sessionScope, true);
1439
+ }
1440
+
1395
1441
  export async function rebuildActiveSnapshot(
1396
1442
  cwd: string,
1397
1443
  sessionScope?: string | ActiveSessionScope,
@@ -1559,6 +1605,7 @@ export async function appendAuditEntry(
1559
1605
  cwd: string,
1560
1606
  sessionIdOrEntry: string | AuditEntry,
1561
1607
  maybeEntry?: AuditEntry,
1608
+ options: { lockHeld?: boolean; beforeAppend?: (offset: number) => Promise<unknown> } = {},
1562
1609
  ): Promise<string> {
1563
1610
  const sessionId =
1564
1611
  typeof sessionIdOrEntry === "string"
@@ -1568,8 +1615,50 @@ export async function appendAuditEntry(
1568
1615
  const entry = typeof sessionIdOrEntry === "string" ? maybeEntry : sessionIdOrEntry;
1569
1616
  if (!entry) throw new Error("audit entry is required");
1570
1617
  const filePath = resolveGjcTarget(layoutAuditPath(cwd, sessionId), cwd);
1571
- await fs.mkdir(path.dirname(filePath), { recursive: true });
1572
- await fs.appendFile(filePath, `${JSON.stringify(entry)}\n`, "utf-8");
1618
+ const append = async () => {
1619
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
1620
+ let initialStat: nodeFs.BigIntStats | undefined;
1621
+ try {
1622
+ initialStat = await fs.lstat(filePath, { bigint: true });
1623
+ if (initialStat.isSymbolicLink() || !initialStat.isFile()) throw new Error("audit path is not a regular file");
1624
+ } catch (error) {
1625
+ if (!isErrno(error, "ENOENT")) throw error;
1626
+ }
1627
+ const flags = initialStat
1628
+ ? fsConstants.O_WRONLY |
1629
+ fsConstants.O_APPEND |
1630
+ (process.platform === "win32" ? 0 : (fsConstants.O_NOFOLLOW ?? 0))
1631
+ : fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | fsConstants.O_EXCL;
1632
+ let handle: fs.FileHandle | undefined;
1633
+ try {
1634
+ handle = await fs.open(filePath, flags, 0o600);
1635
+ const openedStat = await handle.stat({ bigint: true });
1636
+ const pathStat = await fs.lstat(filePath, { bigint: true });
1637
+ const sameObject = (left: nodeFs.BigIntStats, right: nodeFs.BigIntStats) =>
1638
+ left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink;
1639
+ if (
1640
+ openedStat.isSymbolicLink() ||
1641
+ !openedStat.isFile() ||
1642
+ pathStat.isSymbolicLink() ||
1643
+ !pathStat.isFile() ||
1644
+ (initialStat !== undefined && !sameObject(initialStat, openedStat)) ||
1645
+ !sameObject(openedStat, pathStat)
1646
+ )
1647
+ throw new Error("audit path identity changed before append");
1648
+ if (openedStat.size > BigInt(Number.MAX_SAFE_INTEGER))
1649
+ throw new Error("audit path is too large to append safely");
1650
+ await options.beforeAppend?.(Number(openedStat.size));
1651
+ await handle.writeFile(`${JSON.stringify(entry)}\n`, "utf-8");
1652
+ await handle.sync();
1653
+ const afterPathStat = await fs.lstat(filePath, { bigint: true });
1654
+ if (afterPathStat.isSymbolicLink() || !sameObject(openedStat, afterPathStat))
1655
+ throw new Error("audit path identity changed during append");
1656
+ } finally {
1657
+ await handle?.close();
1658
+ }
1659
+ };
1660
+ if (options.lockHeld) await append();
1661
+ else await withWorkflowStateLock(filePath, append, { cwd });
1573
1662
  return filePath;
1574
1663
  }
1575
1664
 
@@ -63,11 +63,14 @@ export function envDisabled(value: string | undefined): boolean {
63
63
  * failure mode where `gjc --tmux` creates a psmux-backed session and then
64
64
  * `gjc session status` fails because it queries literal `tmux` is closed off.
65
65
  *
66
- * Explicit `GJC_TMUX_COMMAND` overrides are honored on
67
- * every platform. On native Windows without an override the resolver walks
68
- * `psmux`, then `pmux`, then `tmux` and uses the first binary present on PATH.
69
- * On POSIX the resolver returns `tmux` (the historical default) and only
70
- * falls through to the platform-aware walker if the caller opts in.
66
+ * An operator `GJC_TMUX_COMMAND` is honored on every platform. A value that
67
+ * matches the project dotenv snapshot, including a dynamic declaration, is
68
+ * ignored so a repository cannot choose the binary, and the resolver uses the
69
+ * same default as when the variable is unset. On native Windows without an
70
+ * override the resolver walks `psmux`, then `pmux`, then `tmux` and uses the
71
+ * first binary present on PATH. On POSIX the resolver returns `tmux` (the
72
+ * historical default) and only falls through to the platform-aware walker if
73
+ * the caller opts in.
71
74
  */
72
75
  export function resolveGjcTmuxCommand(
73
76
  env: NodeJS.ProcessEnv = process.env,
@@ -1275,13 +1275,25 @@ export async function createUltragoalPlan(input: {
1275
1275
  return plan;
1276
1276
  }
1277
1277
 
1278
- function chooseNextGoal(plan: UltragoalPlan, retryFailed: boolean): UltragoalGoal | undefined {
1278
+ /**
1279
+ * Scheduler order shared with read-only projections: active, then pending, then failed
1280
+ * only when the caller explicitly opts into retrying failed goals. Blocked,
1281
+ * review_blocked, complete, and superseded goals are never schedulable.
1282
+ */
1283
+ export function selectNextSchedulableGoal<T extends { status: UltragoalGoalStatus }>(
1284
+ goals: readonly T[],
1285
+ retryFailed: boolean,
1286
+ ): T | undefined {
1279
1287
  return (
1280
- plan.goals.find(goal => goal.status === "active") ??
1281
- plan.goals.find(goal => goal.status === "pending") ??
1282
- (retryFailed ? plan.goals.find(goal => goal.status === "failed") : undefined)
1288
+ goals.find(goal => goal.status === "active") ??
1289
+ goals.find(goal => goal.status === "pending") ??
1290
+ (retryFailed ? goals.find(goal => goal.status === "failed") : undefined)
1283
1291
  );
1284
1292
  }
1293
+
1294
+ function chooseNextGoal(plan: UltragoalPlan, retryFailed: boolean): UltragoalGoal | undefined {
1295
+ return selectNextSchedulableGoal(plan.goals, retryFailed);
1296
+ }
1285
1297
  export interface UltragoalRunCompletionState {
1286
1298
  requiredGoals: UltragoalGoal[];
1287
1299
  incompleteGoals: UltragoalGoal[];
@@ -174,16 +174,7 @@
174
174
  "clear",
175
175
  "contract",
176
176
  "handoff",
177
- "doctor",
178
- "kickoff",
179
- "write-spec",
180
- "write-artifact",
181
- "stage",
182
- "check",
183
- "apply",
184
- "discard",
185
- "read",
186
- "write"
177
+ "doctor"
187
178
  ],
188
179
  "name": "session-id",
189
180
  "type": "string"
@@ -414,6 +405,11 @@
414
405
  "to": "handoff",
415
406
  "verb": "write-spec"
416
407
  },
408
+ {
409
+ "from": "interviewing",
410
+ "to": "handoff",
411
+ "verb": "crystallize"
412
+ },
417
413
  {
418
414
  "from": "handoff",
419
415
  "to": "complete",
@@ -426,14 +422,6 @@
426
422
  }
427
423
  ],
428
424
  "typedArgs": [
429
- {
430
- "appliesToVerbs": [
431
- "write",
432
- "api"
433
- ],
434
- "name": "input",
435
- "type": "string"
436
- },
437
425
  {
438
426
  "appliesToVerbs": [
439
427
  "read",
@@ -462,13 +450,10 @@
462
450
  "doctor",
463
451
  "kickoff",
464
452
  "write-spec",
465
- "write-artifact",
466
453
  "stage",
467
454
  "check",
468
455
  "apply",
469
- "discard",
470
- "read",
471
- "write"
456
+ "discard"
472
457
  ],
473
458
  "name": "session-id",
474
459
  "type": "string"
@@ -556,6 +541,13 @@
556
541
  "name": "deep",
557
542
  "type": "boolean"
558
543
  },
544
+ {
545
+ "appliesToVerbs": [
546
+ "kickoff"
547
+ ],
548
+ "name": "trace",
549
+ "type": "boolean"
550
+ },
559
551
  {
560
552
  "appliesToVerbs": [
561
553
  "kickoff"
@@ -587,6 +579,14 @@
587
579
  "name": "slug",
588
580
  "type": "string"
589
581
  },
582
+ {
583
+ "appliesToVerbs": [
584
+ "crystallize"
585
+ ],
586
+ "name": "slug",
587
+ "required": true,
588
+ "type": "string"
589
+ },
590
590
  {
591
591
  "appliesToVerbs": [
592
592
  "write-spec"
@@ -614,7 +614,16 @@
614
614
  },
615
615
  {
616
616
  "appliesToVerbs": [
617
+ "write-spec"
618
+ ],
619
+ "name": "force",
620
+ "type": "boolean"
621
+ },
622
+ {
623
+ "appliesToVerbs": [
624
+ "kickoff",
617
625
  "write-spec",
626
+ "crystallize",
618
627
  "stage",
619
628
  "check",
620
629
  "apply",
@@ -622,20 +631,47 @@
622
631
  "read",
623
632
  "write",
624
633
  "clear",
625
- "handoff"
634
+ "handoff",
635
+ "approve-execution"
626
636
  ],
627
637
  "name": "json",
628
638
  "type": "boolean"
629
639
  },
630
640
  {
631
641
  "appliesToVerbs": [
642
+ "api"
643
+ ],
644
+ "name": "input",
645
+ "type": "string"
646
+ },
647
+ {
648
+ "appliesToVerbs": [
649
+ "write",
632
650
  "stage",
633
- "write"
651
+ "crystallize"
634
652
  ],
635
653
  "name": "input",
636
654
  "required": true,
637
655
  "type": "string"
638
656
  },
657
+ {
658
+ "appliesToVerbs": [
659
+ "approve-execution"
660
+ ],
661
+ "enumValues": [
662
+ "deep-interview"
663
+ ],
664
+ "name": "mode",
665
+ "type": "enum"
666
+ },
667
+ {
668
+ "appliesToVerbs": [
669
+ "crystallize",
670
+ "approve-execution"
671
+ ],
672
+ "name": "session-id",
673
+ "type": "string"
674
+ },
639
675
  {
640
676
  "appliesToVerbs": [
641
677
  "write"
@@ -701,6 +737,10 @@
701
737
  "name": "write-spec",
702
738
  "surface": "command-flag"
703
739
  },
740
+ {
741
+ "name": "crystallize",
742
+ "surface": "command-flag"
743
+ },
704
744
  {
705
745
  "name": "stage",
706
746
  "surface": "command-positional"
@@ -733,6 +773,10 @@
733
773
  "name": "handoff",
734
774
  "surface": "command-positional"
735
775
  },
776
+ {
777
+ "name": "approve-execution",
778
+ "surface": "command-positional"
779
+ },
736
780
  {
737
781
  "name": "graph",
738
782
  "planned": true,
@@ -1017,14 +1061,7 @@
1017
1061
  "handoff",
1018
1062
  "doctor",
1019
1063
  "kickoff",
1020
- "write-spec",
1021
- "write-artifact",
1022
- "stage",
1023
- "check",
1024
- "apply",
1025
- "discard",
1026
- "read",
1027
- "write"
1064
+ "write-artifact"
1028
1065
  ],
1029
1066
  "name": "session-id",
1030
1067
  "type": "string"
@@ -1403,16 +1440,7 @@
1403
1440
  "clear",
1404
1441
  "contract",
1405
1442
  "handoff",
1406
- "doctor",
1407
- "kickoff",
1408
- "write-spec",
1409
- "write-artifact",
1410
- "stage",
1411
- "check",
1412
- "apply",
1413
- "discard",
1414
- "read",
1415
- "write"
1443
+ "doctor"
1416
1444
  ],
1417
1445
  "name": "session-id",
1418
1446
  "type": "string"
@@ -70,22 +70,16 @@ const PLANNED_ADMIN_VERBS = ["graph", "prune", "migrate", "force-overwrite"] as
70
70
 
71
71
  const COMMON_TYPED_ARGS: TypedArgSpec[] = [
72
72
  { name: "input", type: "string", appliesToVerbs: ["write", "api"] },
73
- { name: "mode", type: "enum", enumValues: [...CANONICAL_GJC_WORKFLOW_SKILLS], appliesToVerbs: [...STATE_VERBS] },
73
+ {
74
+ name: "mode",
75
+ type: "enum",
76
+ enumValues: [...CANONICAL_GJC_WORKFLOW_SKILLS],
77
+ appliesToVerbs: [...STATE_VERBS],
78
+ },
74
79
  {
75
80
  name: "session-id",
76
81
  type: "string",
77
- appliesToVerbs: [
78
- ...STATE_VERBS,
79
- "kickoff",
80
- "write-spec",
81
- "write-artifact",
82
- "stage",
83
- "check",
84
- "apply",
85
- "discard",
86
- "read",
87
- "write",
88
- ],
82
+ appliesToVerbs: [...STATE_VERBS, "kickoff", "write-spec", "write-artifact", "stage", "check", "apply", "discard"],
89
83
  },
90
84
  { name: "thread-id", type: "string", appliesToVerbs: ["write", "clear", "handoff"] },
91
85
  { name: "turn-id", type: "string", appliesToVerbs: ["write", "clear", "handoff"] },
@@ -137,9 +131,18 @@ function manifest(input: {
137
131
  phaseLock?: readonly string[];
138
132
  canonicalOverrides?: readonly string[];
139
133
  initialState?: string;
134
+ commonTypedArgs?: TypedArgSpec[];
140
135
  }): SkillManifest {
141
136
  const staleInitialState = initialPhaseForSkill(input.skill);
142
137
  const initialState = input.initialState ?? staleInitialState;
138
+ const supportedVerbs = new Set([...input.verbs.map(item => item.name), "api"]);
139
+ const typedArgs = [...(input.commonTypedArgs ?? COMMON_TYPED_ARGS), ...(input.typedArgs ?? [])]
140
+ .map(argument =>
141
+ argument.appliesToVerbs === undefined
142
+ ? argument
143
+ : { ...argument, appliesToVerbs: argument.appliesToVerbs.filter(verb => supportedVerbs.has(verb)) },
144
+ )
145
+ .filter(argument => argument.appliesToVerbs === undefined || argument.appliesToVerbs.length > 0);
143
146
  return {
144
147
  skill: input.skill,
145
148
  states: input.states.map(item => state(item, initialState, input.terminalStates)),
@@ -147,7 +150,7 @@ function manifest(input: {
147
150
  terminalStates: input.terminalStates,
148
151
  transitions: input.transitions,
149
152
  verbs: input.verbs,
150
- typedArgs: [...COMMON_TYPED_ARGS, ...(input.typedArgs ?? [])],
153
+ typedArgs,
151
154
  retention: input.retention,
152
155
  hudFields: input.hudFields,
153
156
  graphLabel: input.graphLabel,
@@ -171,32 +174,63 @@ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest>
171
174
  terminalStates: ["handoff", "complete"],
172
175
  transitions: [
173
176
  { from: "interviewing", to: "handoff", verb: "write-spec" },
177
+ { from: "interviewing", to: "handoff", verb: "crystallize" },
174
178
  { from: "handoff", to: "complete", verb: "clear" },
175
179
  { from: "interviewing", to: "complete", verb: "clear" },
176
180
  ],
177
181
  verbs: [
178
182
  ...stateVerbs(),
179
- ...flagVerbs(["kickoff", "write-spec"]),
180
- ...positionalVerbs(["stage", "check", "apply", "discard", "read", "write", "clear", "handoff"]),
183
+ ...flagVerbs(["kickoff", "write-spec", "crystallize"]),
184
+ ...positionalVerbs([
185
+ "stage",
186
+ "check",
187
+ "apply",
188
+ "discard",
189
+ "read",
190
+ "write",
191
+ "clear",
192
+ "handoff",
193
+ "approve-execution",
194
+ ]),
181
195
  ...plannedVerbs(PLANNED_ADMIN_VERBS),
182
196
  ],
197
+ commonTypedArgs: COMMON_TYPED_ARGS.filter(argument => argument.name !== "input"),
183
198
  typedArgs: [
184
199
  { name: "quick", type: "boolean", appliesToVerbs: ["kickoff"] },
185
200
  { name: "standard", type: "boolean", appliesToVerbs: ["kickoff"] },
186
201
  { name: "deep", type: "boolean", appliesToVerbs: ["kickoff"] },
202
+ { name: "trace", type: "boolean", appliesToVerbs: ["kickoff"] },
187
203
  { name: "threshold", type: "number", appliesToVerbs: ["kickoff"] },
188
204
  { name: "threshold-source", type: "string", appliesToVerbs: ["kickoff"] },
189
205
  { name: "stage", type: "enum", enumValues: ["final"], appliesToVerbs: ["write-spec"] },
190
206
  { name: "slug", type: "string", appliesToVerbs: ["write-spec"] },
207
+ { name: "slug", type: "string", required: true, appliesToVerbs: ["crystallize"] },
191
208
  { name: "spec", type: "string", required: true, appliesToVerbs: ["write-spec"] },
192
209
  { name: "handoff", type: "enum", enumValues: ["ralplan"], appliesToVerbs: ["write-spec"] },
193
210
  { name: "deliberate", type: "boolean", appliesToVerbs: ["write-spec"] },
211
+ { name: "force", type: "boolean", appliesToVerbs: ["write-spec"] },
194
212
  {
195
213
  name: "json",
196
214
  type: "boolean",
197
- appliesToVerbs: ["write-spec", "stage", "check", "apply", "discard", "read", "write", "clear", "handoff"],
215
+ appliesToVerbs: [
216
+ "kickoff",
217
+ "write-spec",
218
+ "crystallize",
219
+ "stage",
220
+ "check",
221
+ "apply",
222
+ "discard",
223
+ "read",
224
+ "write",
225
+ "clear",
226
+ "handoff",
227
+ "approve-execution",
228
+ ],
198
229
  },
199
- { name: "input", type: "string", required: true, appliesToVerbs: ["stage", "write"] },
230
+ { name: "input", type: "string", appliesToVerbs: ["api"] },
231
+ { name: "input", type: "string", required: true, appliesToVerbs: ["write", "stage", "crystallize"] },
232
+ { name: "mode", type: "enum", enumValues: ["deep-interview"], appliesToVerbs: ["approve-execution"] },
233
+ { name: "session-id", type: "string", appliesToVerbs: ["crystallize", "approve-execution"] },
200
234
  { name: "reset", type: "boolean", appliesToVerbs: ["write"] },
201
235
  {
202
236
  name: "for",
@@ -523,9 +557,8 @@ export function listVerbs(skill: CanonicalGjcWorkflowSkill): string[] {
523
557
  }
524
558
 
525
559
  export function typedArgsFor(skill: CanonicalGjcWorkflowSkill, verb: string): TypedArgSpec[] {
526
- return WORKFLOW_MANIFEST[skill].typedArgs.filter(
527
- arg => arg.appliesToVerbs === undefined || arg.appliesToVerbs.includes(verb),
528
- );
560
+ const manifest = WORKFLOW_MANIFEST[skill];
561
+ return manifest.typedArgs.filter(arg => arg.appliesToVerbs === undefined || arg.appliesToVerbs.includes(verb));
529
562
  }
530
563
 
531
564
  function stableSort(value: unknown): unknown {
@@ -142,6 +142,7 @@ export async function operate(goal: string, opts: OperateOptions): Promise<Opera
142
142
  lifecycle = "finalizing";
143
143
  break;
144
144
  }
145
+ lifecycle = accepted ? "observing" : "submitted";
145
146
  continue;
146
147
  }
147
148
 
@@ -182,11 +183,18 @@ export async function operate(goal: string, opts: OperateOptions): Promise<Opera
182
183
  return { completed: false, lifecycle, iterations, classifications, vanishReceiptIds, blockers };
183
184
  }
184
185
 
185
- // B3: never finalize on loop-exhaustion — require an explicit observed completion.
186
+ // An observation budget bounds this invocation, not the work itself. Keep active
187
+ // sessions nonterminal so a caller can resume observation without treating a slow
188
+ // but healthy owner as a failure. Completion still requires an explicit signal.
186
189
  if (lifecycle !== "finalizing") {
187
- blockers.push("no-observed-completion");
188
- await emit("critical", "operate_blocked", { blockers });
189
- return { completed: false, lifecycle: "blocked", iterations, classifications, vanishReceiptIds, blockers };
190
+ await emit("info", "operate_observation_window_ended", {
191
+ lifecycle,
192
+ iterations,
193
+ lastClassification: classifications.at(-1) ?? null,
194
+ completionObserved: false,
195
+ resumeWith: "observe",
196
+ });
197
+ return { completed: false, lifecycle, iterations, classifications, vanishReceiptIds, blockers };
190
198
  }
191
199
 
192
200
  const finalize = await runFinalize({
@@ -809,7 +809,11 @@ export class RuntimeOwner {
809
809
  evidence: Record<string, unknown>,
810
810
  ): Promise<void> => {
811
811
  if (signal.aborted) return;
812
- if (kind === "operate_blocked" || kind === "operate_finalized") {
812
+ if (kind === "operate_observation_window_ended") {
813
+ const lifecycle = evidence.lifecycle === "submitted" ? "submitted" : "observing";
814
+ const persisted = await this.#persistValidationLifecycle(signal, lifecycle, []);
815
+ if (!persisted.persisted) return;
816
+ } else if (kind === "operate_blocked" || kind === "operate_finalized") {
813
817
  const lifecycle = kind === "operate_finalized" && evidence.completed === true ? "completed" : "blocked";
814
818
  const blockers = Array.isArray(evidence.blockers)
815
819
  ? evidence.blockers.filter((blocker): blocker is string => typeof blocker === "string")