@moretea-labs/forge 1.8.1 → 1.8.2

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 (328) hide show
  1. package/AGENTS.md +5 -2
  2. package/CHANGELOG.md +20 -0
  3. package/README.en.md +24 -3
  4. package/README.md +10 -6
  5. package/README.zh-CN.md +5 -3
  6. package/THIRD_PARTY_NOTICES.md +1 -2
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
  8. package/adapters/chatgpt/provider-delivery.ts +1 -1
  9. package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
  10. package/adapters/computer/chatgpt-extension-target.ts +295 -50
  11. package/adapters/computer/chrome-extension/background.js +227 -71
  12. package/adapters/computer/chrome-extension/content.js +181 -157
  13. package/adapters/computer/chrome-extension/manifest.json +3 -1
  14. package/adapters/computer/desktop-operator-provider.ts +18 -6
  15. package/adapters/computer/interaction-target-authority.ts +39 -9
  16. package/adapters/mcp/access-mode.ts +18 -39
  17. package/adapters/mcp/auth.ts +2 -6
  18. package/adapters/mcp/instructions.ts +2 -2
  19. package/adapters/mcp/multi-repository.ts +7 -28
  20. package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
  21. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
  22. package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
  23. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
  24. package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
  25. package/adapters/mcp/runtime-gateway/router.ts +3 -5
  26. package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
  27. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
  28. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
  29. package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
  30. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
  31. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
  32. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
  33. package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
  34. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
  35. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
  36. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
  37. package/adapters/mcp/server.ts +24 -3
  38. package/adapters/mcp/setup.ts +4 -10
  39. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  40. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
  41. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
  42. package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
  43. package/adapters/mcp/toolset-names.ts +11 -42
  44. package/adapters/mcp/toolset.ts +16 -90
  45. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  46. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  47. package/adapters/mcp/transports/http-observation.ts +18 -6
  48. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  49. package/adapters/mcp/transports/http.ts +54 -14
  50. package/adapters/mcp/types.ts +0 -6
  51. package/assets/hooks/lib/session-state.sh +0 -38
  52. package/assets/hooks/post-bash.sh +1 -1
  53. package/assets/hooks/post-tool-observer.sh +0 -5
  54. package/assets/hooks/prompt-guard.sh +0 -55
  55. package/assets/partials/04-project-structure.partial.md +1 -1
  56. package/assets/partials/07-footer.partial.md +1 -1
  57. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  58. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  59. package/assets/reference-configs/agentic-development-flow.md +1 -1
  60. package/assets/reference-configs/external-tooling.md +0 -76
  61. package/assets/reference-configs/release-deploy.md +1 -1
  62. package/assets/templates/helpers/architecture-queue.sh +1 -1
  63. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  64. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  65. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  66. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  67. package/assets/workflow-contract.v1.json +0 -2
  68. package/docs/README.md +5 -2
  69. package/docs/architecture/CURRENT.md +114 -30
  70. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  71. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
  72. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
  73. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
  74. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  75. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  76. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  77. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  78. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  79. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  80. package/docs/architecture/domains/public-surface.md +3 -5
  81. package/docs/architecture/domains/verification.md +5 -5
  82. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
  83. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  84. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  85. package/docs/forge-plugin-management.md +14 -2
  86. package/docs/operations/controller-browser-plugin.md +23 -0
  87. package/docs/operations/features.md +6 -7
  88. package/docs/operations/features.zh-CN.md +1 -1
  89. package/docs/operations/github-repository.md +4 -4
  90. package/docs/operations/github-semantic-release-projection.md +20 -0
  91. package/docs/operations/homebrew.md +2 -2
  92. package/docs/operations/mcp-tool-exposure.md +13 -54
  93. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  94. package/docs/operations/standalone-disaster-recovery.md +1 -1
  95. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  96. package/docs/tutorials/03-first-repository-task.md +2 -0
  97. package/docs/wiki/Computer-and-Plugins.md +78 -0
  98. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  99. package/docs/wiki/Home.md +16 -2
  100. package/docs/wiki/Implementation.md +24 -12
  101. package/docs/wiki/Installation.md +28 -32
  102. package/docs/wiki/Integrations.md +4 -0
  103. package/docs/wiki/Quick-Start.md +35 -22
  104. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  105. package/docs/wiki/_Sidebar.md +5 -1
  106. package/package.json +1 -12
  107. package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
  108. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
  109. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  110. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
  111. package/packages/kernel/work/application/work-service.ts +1 -0
  112. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  113. package/packages/kernel/work/domain/state-machine.ts +9 -2
  114. package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
  115. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  116. package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
  117. package/scripts/assemble-template.ts +11 -72
  118. package/scripts/check-mcp-compatibility.ts +55 -74
  119. package/scripts/check-package-identity.mjs +0 -5
  120. package/scripts/check-runtime-architecture.mjs +305 -28
  121. package/scripts/init-project.sh +0 -46
  122. package/scripts/install-ios-hid-toolchain.sh +60 -0
  123. package/scripts/install-standalone-recovery.ts +1 -1
  124. package/scripts/lib/project-init-lib.sh +3 -3
  125. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  126. package/scripts/open-source-audit-allowlist.txt +0 -1
  127. package/scripts/run-governed-gate.ts +2 -0
  128. package/scripts/smoke-ios-physical-device.ts +38 -10
  129. package/scripts/smoke-mcp-http-runtime.ts +7 -8
  130. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  131. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
  132. package/scripts/sync-generated-authority.mjs +0 -1
  133. package/src/cli/agent-jobs/executor-health.ts +1 -1
  134. package/src/cli/agent-jobs/integration.ts +2 -2
  135. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  136. package/src/cli/commands/doctor.ts +1 -143
  137. package/src/cli/commands/global-runtime.ts +3 -196
  138. package/src/cli/commands/init-hook.ts +6 -23
  139. package/src/cli/commands/init.ts +3 -252
  140. package/src/cli/commands/mcp.ts +0 -5
  141. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  142. package/src/cli/commands/recovery.ts +2 -14
  143. package/src/cli/commands/repository.ts +17 -1
  144. package/src/cli/commands/setup-execution.ts +41 -0
  145. package/src/cli/controller/completion-orchestrator.ts +1 -1
  146. package/src/cli/controller/composite-operations.ts +1 -1
  147. package/src/cli/controller/context/instruction-context.ts +1 -1
  148. package/src/cli/controller/context/known-paths.ts +2 -2
  149. package/src/cli/controller/context/query-planning.ts +11 -4
  150. package/src/cli/controller/context/rule-resolution.ts +2 -2
  151. package/src/cli/controller/context/source-materializer.ts +2 -2
  152. package/src/cli/controller/context/structural-provider.ts +84 -0
  153. package/src/cli/controller/context/structural.ts +6 -6
  154. package/src/cli/controller/context/types.ts +8 -9
  155. package/src/cli/controller/context-pack.ts +97 -76
  156. package/src/cli/controller/operational-plan.ts +1 -1
  157. package/src/cli/controller/systemd-user.ts +4 -10
  158. package/src/cli/editing/edit-session.ts +17 -5
  159. package/src/cli/github/github.ts +4 -4
  160. package/src/cli/github/semantic-projection.ts +174 -0
  161. package/src/cli/hook/prompt-intents.ts +0 -9
  162. package/src/cli/index.ts +1 -43
  163. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  164. package/src/cli/local-bridge/console-automations.ts +1 -1
  165. package/src/cli/local-bridge/console-view-models.ts +1 -1
  166. package/src/cli/local-bridge/facade-api.ts +0 -2
  167. package/src/cli/repositories/command-classifier.ts +0 -9
  168. package/src/cli/repositories/command-scope.ts +2 -2
  169. package/src/cli/repositories/locks.ts +20 -0
  170. package/src/cli/repositories/registry.ts +2 -1
  171. package/src/cli/repositories/safe-patch.ts +2 -12
  172. package/src/cli/repositories/structured-git.ts +39 -3
  173. package/src/cli/repository/inspector.ts +185 -343
  174. package/src/effects/process-runner.ts +17 -1
  175. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  176. package/src/runtime/context/semantic-navigation-contract.ts +7 -0
  177. package/src/runtime/context/semantic-navigation.ts +1 -1
  178. package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
  179. package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
  180. package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
  181. package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
  182. package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
  183. package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
  184. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  185. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
  186. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
  187. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
  188. package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
  189. package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
  190. package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
  191. package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
  192. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
  193. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  194. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
  195. package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
  196. package/src/runtime/evidence/sensitive-output.ts +22 -2
  197. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  198. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  199. package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
  200. package/src/runtime/execution/process-runtime/runtime.ts +20 -16
  201. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  202. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  203. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  204. package/src/runtime/execution/workers/executor.ts +1 -2
  205. package/src/runtime/maintenance/cleanup.ts +41 -5
  206. package/src/runtime/plugins/action-application.ts +12 -1
  207. package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
  208. package/src/runtime/plugins/browser-adapter.ts +381 -313
  209. package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
  210. package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
  211. package/src/runtime/plugins/browser-registration.ts +2 -0
  212. package/src/runtime/plugins/browser-runtime.ts +26 -35
  213. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
  214. package/src/runtime/plugins/computer-registration.ts +76 -9
  215. package/src/runtime/plugins/github-adapter.ts +30 -3
  216. package/src/runtime/plugins/gmail-adapter.ts +166 -1
  217. package/src/runtime/plugins/google-shared.ts +10 -5
  218. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  219. package/src/runtime/plugins/ios-adapter.ts +45 -40
  220. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  221. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  222. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  223. package/src/runtime/plugins/local-system-adapter.ts +1 -99
  224. package/src/runtime/plugins/store.ts +80 -7
  225. package/src/runtime/plugins/types.ts +4 -0
  226. package/src/runtime/projections/git-status-sampler.ts +2 -19
  227. package/src/runtime/recovery/actions.ts +1 -1
  228. package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
  229. package/src/runtime/recovery/probe.ts +2 -2
  230. package/src/runtime/release/release-coordinator.ts +20 -4
  231. package/src/runtime/root/controller-progression-composition.ts +67 -5
  232. package/src/runtime/root/package-connector-service.ts +123 -11
  233. package/src/runtime/root/package-runtime-release.ts +6 -18
  234. package/src/runtime/root/package-runtime-service.ts +7 -13
  235. package/src/runtime/root/release-execution-canary.ts +18 -5
  236. package/src/runtime/root/release-manifest.ts +0 -34
  237. package/src/runtime/root/release-materialize.ts +0 -78
  238. package/src/runtime/root/runtime.ts +1 -2
  239. package/src/runtime/root/scheduled-controller-composition.ts +2 -1
  240. package/src/runtime/root/service.ts +90 -11
  241. package/src/runtime/root/types.ts +0 -6
  242. package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
  243. package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
  244. package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
  245. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  246. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  247. package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
  248. package/src/runtime/shared/atomic-text-file.ts +21 -0
  249. package/src/runtime/shared/json-files.ts +8 -3
  250. package/src/runtime/standalone-recovery/core.ts +189 -174
  251. package/src/runtime/standalone-recovery/entry.ts +62 -30
  252. package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
  253. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
  254. package/src/runtime/workflow/schedules/engine.ts +36 -14
  255. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  256. package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
  257. package/supervisor/client.ts +31 -14
  258. package/supervisor/control-plane.ts +210 -115
  259. package/supervisor/forge-validators.ts +88 -21
  260. package/supervisor/native-browser-adapter.ts +393 -28
  261. package/supervisor/native-messaging/host.ts +21 -2
  262. package/supervisor/protocol.ts +74 -26
  263. package/supervisor/server.ts +16 -8
  264. package/supervisor/store.ts +476 -57
  265. package/supervisor/types.ts +19 -13
  266. package/SKILL.md +0 -427
  267. package/adapters/computer/chatgpt-target-router.ts +0 -103
  268. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
  269. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  270. package/adapters/mcp/toolset-selection.ts +0 -41
  271. package/agents/openai.yaml +0 -4
  272. package/assets/skill-hooks.json +0 -41
  273. package/assets/skill-version.json +0 -189
  274. package/assets/skills/claude-review/SKILL.md +0 -231
  275. package/assets/skills/codex-review/SKILL.md +0 -103
  276. package/assets/templates/helpers/check-skill-version.ts +0 -193
  277. package/scripts/check-skill-version.ts +0 -5
  278. package/scripts/ensure-codegraph.sh +0 -16
  279. package/scripts/run-skill-hook.ts +0 -244
  280. package/scripts/sync-codex-installed-copies.sh +0 -182
  281. package/src/cli/commands/tools.ts +0 -167
  282. package/src/cli/mcp/access-mode.ts +0 -2
  283. package/src/cli/mcp/access-tools.ts +0 -2
  284. package/src/cli/mcp/audit.ts +0 -2
  285. package/src/cli/mcp/auth.ts +0 -2
  286. package/src/cli/mcp/instructions.ts +0 -2
  287. package/src/cli/mcp/legacy-context.ts +0 -2
  288. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  289. package/src/cli/mcp/multi-repository.ts +0 -2
  290. package/src/cli/mcp/oauth.ts +0 -2
  291. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  292. package/src/cli/mcp/paths.ts +0 -2
  293. package/src/cli/mcp/policy.ts +0 -2
  294. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  295. package/src/cli/mcp/proxy-env.ts +0 -2
  296. package/src/cli/mcp/readiness-projection.ts +0 -2
  297. package/src/cli/mcp/redaction.ts +0 -2
  298. package/src/cli/mcp/repo.ts +0 -2
  299. package/src/cli/mcp/repository-tools.ts +0 -2
  300. package/src/cli/mcp/server.ts +0 -2
  301. package/src/cli/mcp/setup.ts +0 -2
  302. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  303. package/src/cli/mcp/tool-contract.ts +0 -2
  304. package/src/cli/mcp/tools.ts +0 -2
  305. package/src/cli/mcp/toolset-names.ts +0 -2
  306. package/src/cli/mcp/toolset-selection.ts +0 -2
  307. package/src/cli/mcp/toolset.ts +0 -2
  308. package/src/cli/mcp/transports/http.ts +0 -2
  309. package/src/cli/mcp/transports/session-registry.ts +0 -2
  310. package/src/cli/mcp/transports/stdio.ts +0 -2
  311. package/src/cli/mcp/types.ts +0 -2
  312. package/src/cli/mcp/umbrella-tools.ts +0 -2
  313. package/src/cli/tools/codegraph.ts +0 -695
  314. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  315. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  316. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  317. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  318. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  319. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  320. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  321. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  322. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  323. package/src/runtime/gateway/mcp/router.ts +0 -2
  324. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  325. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  326. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  327. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
  328. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
@@ -1,5 +1,6 @@
1
+ import { writeAtomicTextFile } from '../shared/atomic-text-file';
1
2
  import { createHash, randomUUID } from 'crypto';
2
- import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'fs';
3
+ import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync } from 'fs';
3
4
  import { homedir } from 'os';
4
5
  import { dirname, isAbsolute, join, relative, resolve } from 'path';
5
6
  import { resolveControllerHome } from '../../cli/repositories/controller-home';
@@ -37,13 +38,6 @@ function xml(value: string): string {
37
38
  return value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
38
39
  }
39
40
 
40
- function atomicWrite(path: string, content: string, mode = 0o600): void {
41
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
42
- const temporary = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
43
- writeFileSync(temporary, content, { encoding: 'utf8', mode });
44
- renameSync(temporary, path);
45
- }
46
-
47
41
  export function forgeRuntimeServicePaths(controllerHome: string): ForgeRuntimeServicePaths {
48
42
  const home = resolveControllerHome(controllerHome);
49
43
  const serviceRoot = join(home, 'runtime', 'service');
@@ -194,6 +188,91 @@ async function retireLegacyBrowserAutomationLaunchAgent(controllerHome: string):
194
188
  rmSync(legacy.installedPlistPath, { force: true });
195
189
  }
196
190
 
191
+ /**
192
+ * Read the previously installed exact launchd contract only when config.json
193
+ * cannot be validated. Both canonical source and installed contracts must be
194
+ * identical and must still identify the active immutable Runtime release.
195
+ * A stale/mismatched plist never supplies a guessed listener port.
196
+ */
197
+ function verifiedRuntimeLaunchdContract(controllerHome: string): { port: number; args: string[] } | undefined {
198
+ try {
199
+ const paths = forgeRuntimeServicePaths(controllerHome);
200
+ const active = readActiveRuntimeRelease(paths.controllerHome);
201
+ if (!active || !activeRuntimeEntrypoint(paths.controllerHome)) return undefined;
202
+ if (!existsSync(paths.sourcePlistPath) || !existsSync(paths.installedPlistPath)) return undefined;
203
+ if (statSync(paths.sourcePlistPath).size > 128 * 1024 || statSync(paths.installedPlistPath).size > 128 * 1024) return undefined;
204
+ const source = readFileSync(paths.sourcePlistPath, 'utf8');
205
+ if (source !== readFileSync(paths.installedPlistPath, 'utf8')) return undefined;
206
+ const parseXmlString = (raw: string): string | undefined => {
207
+ if (/&(?!(?:amp|lt|gt|quot|apos);)/.test(raw) || /[<>]/.test(raw)) return undefined;
208
+ return raw.replace(/&(amp|lt|gt|quot|apos);/g, (_, entity: string) =>
209
+ ({ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" })[entity] ?? '');
210
+ };
211
+ const label = /<key>Label<\/key>\s*<string>([^<]*)<\/string>/.exec(source);
212
+ if (!label || parseXmlString(label[1]!) !== paths.label) return undefined;
213
+ const program = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(source);
214
+ if (!program) return undefined;
215
+ const args: string[] = [];
216
+ const text = program[1]!;
217
+ const itemPattern = /\s*<string>([^<]*)<\/string>\s*/g;
218
+ let cursor = 0;
219
+ for (let match = itemPattern.exec(text); match; match = itemPattern.exec(text)) {
220
+ if (match.index !== cursor) return undefined;
221
+ const value = parseXmlString(match[1]!);
222
+ if (value === undefined) return undefined;
223
+ args.push(value);
224
+ cursor = itemPattern.lastIndex;
225
+ }
226
+ if (cursor !== text.length || args[0] !== resolve(paths.activeEntrypointPath)) return undefined;
227
+ const exactValue = (flag: string): string | undefined => {
228
+ const indices = args.flatMap((arg, index) => arg === flag ? [index] : []);
229
+ return indices.length === 1 ? args[indices[0]! + 1] : undefined;
230
+ };
231
+ if (exactValue('--controller-home') !== paths.controllerHome
232
+ || exactValue('--release-manifest') !== resolve(active.manifestPath)) return undefined;
233
+ const portString = exactValue('--port');
234
+ if (!portString || !/^[1-9]\d{0,4}$/.test(portString)) return undefined;
235
+ const port = Number(portString);
236
+ return port <= 65_535 ? { port, args } : undefined;
237
+ } catch {
238
+ return undefined;
239
+ }
240
+ }
241
+
242
+ /** Read-only projection of the original installed launchd contract. */
243
+ export function verifiedRuntimePortFromLaunchdContract(controllerHome: string): number | undefined {
244
+ return verifiedRuntimeLaunchdContract(controllerHome)?.port;
245
+ }
246
+
247
+ /** Use the one existing config writer only after service and listener quiescence. */
248
+ export function restoreRuntimeConfigFromVerifiedLaunchdContract(controllerHome: string): boolean {
249
+ const proven = verifiedRuntimeLaunchdContract(controllerHome);
250
+ if (!proven) return false;
251
+ const arg = (flag: string): string | undefined => {
252
+ const indices = proven.args.flatMap((value, index) => value === flag ? [index] : []);
253
+ return indices.length === 1 ? proven.args[indices[0]! + 1] : undefined;
254
+ };
255
+ const host = arg('--host');
256
+ const authTokenFile = arg('--auth-token-file');
257
+ if (!host || !authTokenFile) return false;
258
+ try {
259
+ const topologyString = arg('--deployment-topology');
260
+ const topology = topologyString ? JSON.parse(topologyString) as RuntimeDeploymentTopology : undefined;
261
+ writeForgeRuntimeServiceConfig({
262
+ schemaVersion: 1,
263
+ controllerHome: forgeRuntimeServicePaths(controllerHome).controllerHome,
264
+ host,
265
+ port: proven.port,
266
+ authTokenFile,
267
+ ...(arg('--repo') ? { repositoryRoot: arg('--repo') } : {}),
268
+ ...(topology ? { topology } : {}),
269
+ });
270
+ return true;
271
+ } catch {
272
+ return false;
273
+ }
274
+ }
275
+
197
276
  export function activeRuntimeLaunchSpec(controllerHome: string): ActiveRuntimeLaunchSpec | undefined {
198
277
  const home = resolveControllerHome(controllerHome);
199
278
  const active = readActiveRuntimeRelease(home);
@@ -346,8 +425,8 @@ export function ensureForgeRuntimeLaunchAgentContract(
346
425
  ? readFileSync(desired.paths.installedPlistPath, 'utf8')
347
426
  : undefined;
348
427
  const changed = existingSource !== desired.plist || (input.installUserLaunchAgent === true && existingInstalled !== desired.plist);
349
- if (existingSource !== desired.plist) atomicWrite(desired.paths.sourcePlistPath, desired.plist);
350
- if (input.installUserLaunchAgent === true && existingInstalled !== desired.plist) atomicWrite(desired.paths.installedPlistPath, desired.plist, 0o644);
428
+ if (existingSource !== desired.plist) writeAtomicTextFile(desired.paths.sourcePlistPath, desired.plist);
429
+ if (input.installUserLaunchAgent === true && existingInstalled !== desired.plist) writeAtomicTextFile(desired.paths.installedPlistPath, desired.plist, 0o644);
351
430
  return { paths: desired.paths, mode: desired.mode, changed };
352
431
  }
353
432
 
@@ -367,7 +446,7 @@ export function writeForgeRuntimeServiceConfig(input: ForgeRuntimeServiceConfig)
367
446
  const config = validateForgeRuntimeServiceConfig(input);
368
447
  const paths = forgeRuntimeServicePaths(config.controllerHome);
369
448
  mkdirSync(join(paths.serviceRoot, 'logs'), { recursive: true, mode: 0o700 });
370
- atomicWrite(paths.configPath, `${JSON.stringify(config, null, 2)}\n`);
449
+ writeAtomicTextFile(paths.configPath, `${JSON.stringify(config, null, 2)}\n`);
371
450
  return { config, paths };
372
451
  }
373
452
 
@@ -84,12 +84,6 @@ export interface RuntimeReleaseManifest {
84
84
  browserAutomationHelperEntrypoint?: 'browser-automation-helper';
85
85
  browserAutomationHelperArtifactIdentity?: string;
86
86
  browserAutomationHelperContractIdentity?: string;
87
- codeGraphNodeEntrypoint?: 'codegraph-node';
88
- codeGraphNodeArtifactIdentity?: string;
89
- codeGraphSidecarEntrypoint?: 'codegraph-sidecar.cjs';
90
- codeGraphSidecarArtifactIdentity?: string;
91
- codeGraphLibraryRoot?: 'codegraph-lib';
92
- codeGraphLibraryArtifactIdentity?: string;
93
87
  packageRoot?: 'package';
94
88
  packageArtifactIdentity?: string;
95
89
  controllerUiRoot?: 'ui-dist';
@@ -12,11 +12,12 @@ import {
12
12
  import {
13
13
  bindChatgptWorkConversation,
14
14
  getChatgptWorkConversationBinding,
15
+ listChatgptWorkBindingsForConversation,
15
16
  rebindChatgptWorkConversation,
16
17
  type ChatgptWorkConversationBinding,
17
18
  } from '../../../adapters/chatgpt/work-conversation-binding-store';
18
19
  import { readRequirement } from '../control-plane/persistence/requirement-store';
19
- import { getWorkflowSupervisorCurrentConversation, getWorkflowSupervisorTaskByConversationId, registerWorkflowSupervisorTask, reserveWorkflowSupervisorEnrollment } from '../../../supervisor/client';
20
+ import { bindWorkflowSupervisorScheduledWake, getWorkflowSupervisorCurrentConversation, getWorkflowSupervisorTask, getWorkflowSupervisorTaskByConversationId, getWorkflowSupervisorTaskStall, registerWorkflowSupervisorTask, reserveWorkflowSupervisorEnrollment, wakeWorkflowSupervisorScheduledTask } from '../../../supervisor/client';
20
21
  import { resolveWorkflowSupervisorForgeHome, workflowSupervisorSocketPath } from '../../../supervisor/paths';
21
22
  import type { WorkflowSupervisorLifecycleHooks, WorkflowSupervisorTask } from '../../../supervisor/types';
22
23
  import { getRuntimeWriteClaim } from './write-fence';
@@ -130,15 +131,37 @@ export function inheritWorkflowSupervisorConversationBinding(
130
131
  });
131
132
  }
132
133
 
133
- function workflowSupervisorContractText(task: WorkflowSupervisorTask, key: string): string | undefined {
134
+ export function workflowSupervisorContractText(task: WorkflowSupervisorTask, key: string): string | undefined {
134
135
  const value = task.completionContract[key] ?? task.userBlockerPolicy[key];
135
136
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
136
137
  }
137
138
 
138
- function workflowSupervisorOriginWorkId(task: WorkflowSupervisorTask, repoId: string): string | undefined {
139
+ function workflowSupervisorOriginWorkId(
140
+ task: WorkflowSupervisorTask,
141
+ repoId: string,
142
+ controllerHome: string,
143
+ conversationId = task.conversationId,
144
+ ): string | undefined {
139
145
  const legacyWorkPrefix = `forge:${repoId}:work:`;
140
- return workflowSupervisorContractText(task, 'work_id')
146
+ const explicit = workflowSupervisorContractText(task, 'work_id')
141
147
  ?? (task.taskId.startsWith(legacyWorkPrefix) ? task.taskId.slice(legacyWorkPrefix.length) || undefined : undefined);
148
+ const original = explicit ? getWorkContract({ controllerHome, repoId }, explicit) : undefined;
149
+ if (explicit && (!original || semanticWorkState(original) === 'open')) return explicit;
150
+ const active = listChatgptWorkBindingsForConversation({ controllerHome, repoId }, conversationId)
151
+ .map(({ workId }) => getWorkContract({ controllerHome, repoId }, workId))
152
+ .filter((work): work is NonNullable<typeof work> => Boolean(work && semanticWorkState(work) === 'open'));
153
+ // A terminal predecessor retains its audit and conversation binding. Only
154
+ // explicit successor lineage within the same Requirement can transfer the
155
+ // active Work projection; siblings cannot inherit the predecessor's task.
156
+ if (explicit) {
157
+ const successors = active.filter((work) => work.predecessorWorkId === explicit
158
+ && original?.requirementId && work.requirementId === original.requirementId);
159
+ return successors.length === 1 && active.length === 1 ? successors[0]!.workId : explicit;
160
+ }
161
+ // Standalone enrollment predates Work binding. It may project into one
162
+ // unambiguous open Work, without creating a second conversation authority.
163
+ if (task.continuationPolicy.kind !== 'standalone_supervisor') return undefined;
164
+ return active.length === 1 ? active[0]!.workId : undefined;
142
165
  }
143
166
 
144
167
  /**
@@ -152,7 +175,7 @@ function workflowSupervisorWorkProgressFingerprint(controllerHome: string, task:
152
175
  const repoId = workflowSupervisorContractText(task, 'repo_id');
153
176
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
154
177
  if (!repoId || (taskControllerHome && taskControllerHome !== controllerHome)) return undefined;
155
- const workId = workflowSupervisorOriginWorkId(task, repoId);
178
+ const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
156
179
  if (!workId) return undefined;
157
180
  const work = getWorkContract({ controllerHome, repoId }, workId);
158
181
  if (!work) return undefined;
@@ -195,20 +218,44 @@ function createForgeWorkflowSupervisorBrowserTaskActive(controllerHome: string):
195
218
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
196
219
  if (!repoId || taskControllerHome !== controllerHome) return false;
197
220
  if (requirementId && readRequirement({ controllerHome }, requirementId)?.value.state === 'cancelled') return false;
198
- const workId = workflowSupervisorOriginWorkId(task, repoId);
221
+ const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
199
222
  if (!requirementId && !workId) return false;
200
- // An existing task may outlive the Work that originally enrolled it. Its
201
- // durable task/effect chain still owns the original Goal; absence cannot
202
- // mean DONE, NEEDS_USER, nor permit a replacement provider send.
223
+ // An independent Requirement Goal can outlive an execution Work; an exact
224
+ // Work-owned Supervisor cannot initiate another Send after that Work closes.
225
+ if (terminalWorkSupervisorStopReason(controllerHome, task)) return false;
203
226
  const binding = workId ? getChatgptWorkConversationBinding({ controllerHome, repoId }, workId) : undefined;
204
227
  return !binding || binding.conversationId === task.conversationId;
205
228
  };
206
229
  }
207
230
 
231
+ /**
232
+ * Only an exact Work-owned Supervisor task follows that Work's terminal state.
233
+ * Requirement-owned autonomous Goals and standalone tasks keep their own
234
+ * explicit lifecycle; a completed lower-layer carrier is not goal completion.
235
+ */
236
+ function terminalWorkSupervisorStopReason(controllerHome: string, task: WorkflowSupervisorTask): string | undefined {
237
+ const repoId = workflowSupervisorContractText(task, 'repo_id');
238
+ const workId = workflowSupervisorContractText(task, 'work_id');
239
+ const taskHome = workflowSupervisorContractText(task, 'controller_home');
240
+ if (!repoId || !workId || (taskHome && taskHome !== controllerHome)) return undefined;
241
+ if (task.taskId !== `forge:${repoId}:work:${workId}`
242
+ || task.continuationPolicy.kind === 'standalone_supervisor') return undefined;
243
+ const work = getWorkContract({ controllerHome, repoId }, workId);
244
+ if (!work) return undefined;
245
+ const state = semanticWorkState(work);
246
+ return state !== 'open'
247
+ ? `Exact Work ${workId} is ${state}; stop its Work-owned Supervisor continuation without changing the original Work or any successor Schedule.`
248
+ : undefined;
249
+ }
250
+
208
251
  export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): WorkflowSupervisorLifecycleHooks {
209
252
  const browserTaskActive = createForgeWorkflowSupervisorBrowserTaskActive(controllerHome);
210
253
  return {
254
+ terminalWorkStopReasonForTask: (task) => terminalWorkSupervisorStopReason(controllerHome, task),
211
255
  canonicalObjectiveForTask: (task) => {
256
+ // Standalone Supervisor owns its full objective; Work execution context
257
+ // must never silently replace the original autonomous business goal.
258
+ if (task.continuationPolicy.kind === 'standalone_supervisor') return undefined;
212
259
  const repoId = workflowSupervisorContractText(task, 'repo_id');
213
260
  const requirementId = workflowSupervisorContractText(task, 'requirement_id');
214
261
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
@@ -220,7 +267,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
220
267
  const outcome = readRequirement({ controllerHome }, requirementId)?.value.outcomeStatement?.trim();
221
268
  if (outcome) return outcome;
222
269
  }
223
- const workId = workflowSupervisorOriginWorkId(task, repoId);
270
+ const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
224
271
  if (!workId) return undefined;
225
272
  const work = getWorkContract({ controllerHome, repoId }, workId);
226
273
  // Legacy / independent Work without Requirement authority keeps the
@@ -231,9 +278,21 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
231
278
  const repoId = workflowSupervisorContractText(task, 'repo_id');
232
279
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
233
280
  if (!repoId || (taskControllerHome && taskControllerHome !== controllerHome)) return undefined;
234
- const workId = workflowSupervisorOriginWorkId(task, repoId);
281
+ const workId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
235
282
  if (!workId) return undefined;
236
- return renderCognitiveAttentionForWork({ controllerHome, repoId, workId, query: task.objective, maxItems: 6, maxBytes: 8 * 1024 });
283
+ const cognitive = renderCognitiveAttentionForWork({ controllerHome, repoId, workId, query: task.objective, maxItems: 6, maxBytes: 5 * 1024 });
284
+ if (task.continuationPolicy.kind !== 'standalone_supervisor') return cognitive;
285
+ const work = getWorkContract({ controllerHome, repoId }, workId);
286
+ if (!work) return cognitive;
287
+ // Thin evidence, not a second task/goal authority. Recent semantic refs
288
+ // carry the concrete next step even when Cognitive recall is empty.
289
+ const refs = (work.semanticResultRefs ?? []).slice(-5).map((ref) => ref.slice(0, 500));
290
+ return [
291
+ `Canonical Work: ${workId} (read-only execution context; Supervisor objective remains authoritative).`,
292
+ `Work objective: ${work.objective.slice(0, 600)}`,
293
+ ...refs.map((ref) => `Work evidence: ${ref}`),
294
+ ...(cognitive ? [cognitive] : []),
295
+ ].join('\n').slice(0, 8_000);
237
296
  },
238
297
  effectDispatchEvidence: ({ task }) => {
239
298
  const claim = getRuntimeWriteClaim();
@@ -255,7 +314,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
255
314
  if (effect.kind !== 'enrollment') return undefined;
256
315
  const repoId = workflowSupervisorContractText(task, 'repo_id');
257
316
  const requirementId = workflowSupervisorContractText(task, 'requirement_id');
258
- const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId) : undefined;
317
+ const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId, controllerHome) : undefined;
259
318
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
260
319
  if (!repoId || (!requirementId && !workId) || taskControllerHome !== controllerHome) return undefined;
261
320
  const store = { controllerHome, repoId };
@@ -308,16 +367,26 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
308
367
  browserTaskActive,
309
368
  bootstrapConversationBound: (task, context) => {
310
369
  const repoId = workflowSupervisorContractText(task, 'repo_id');
311
- const workId = workflowSupervisorContractText(task, 'work_id');
370
+ const declaredWorkId = workflowSupervisorContractText(task, 'work_id');
312
371
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
313
- if (!repoId || !workId || taskControllerHome !== controllerHome) return;
314
- const existing = getChatgptWorkConversationBinding({ controllerHome, repoId }, workId);
372
+ if (!repoId || taskControllerHome !== controllerHome) return;
373
+ // On migration, retain the sole active Work projection even for a
374
+ // standalone task without work_id or a completed predecessor. Prefer
375
+ // a binding already moved to the new conversation on duplicate callbacks.
376
+ const currentWorkId = workflowSupervisorOriginWorkId(task, repoId, controllerHome);
377
+ const bindingWorkId = context?.migratedFromConversationId
378
+ ? (currentWorkId && currentWorkId !== declaredWorkId
379
+ ? currentWorkId
380
+ : workflowSupervisorOriginWorkId(task, repoId, controllerHome, context.migratedFromConversationId) ?? declaredWorkId)
381
+ : declaredWorkId;
382
+ if (!bindingWorkId) return;
383
+ const existing = getChatgptWorkConversationBinding({ controllerHome, repoId }, bindingWorkId);
315
384
  if (existing && existing.conversationId !== task.conversationId) {
316
385
  if (!context?.migratedFromConversationId || context.migratedFromConversationId !== existing.conversationId) {
317
386
  throw new Error('WORKFLOW_SUPERVISOR_BOOTSTRAP_WORK_CONVERSATION_CONFLICT');
318
387
  }
319
388
  rebindChatgptWorkConversation({ controllerHome, repoId }, {
320
- workId,
389
+ workId: bindingWorkId,
321
390
  previousConversationId: existing.conversationId,
322
391
  conversationUrl: task.conversationUrl,
323
392
  localAlias: existing.localAlias,
@@ -326,7 +395,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
326
395
  }
327
396
  if (!existing) {
328
397
  bindChatgptWorkConversation({ controllerHome, repoId }, {
329
- workId,
398
+ workId: bindingWorkId,
330
399
  conversationUrl: task.conversationUrl,
331
400
  localAlias: 'Forge autonomous execution',
332
401
  });
@@ -336,7 +405,7 @@ export function forgeWorkflowSupervisorLifecycleHooks(controllerHome: string): W
336
405
  if (effect.kind !== 'enrollment') return;
337
406
  const repoId = workflowSupervisorContractText(task, 'repo_id');
338
407
  const requirementId = workflowSupervisorContractText(task, 'requirement_id');
339
- const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId) : undefined;
408
+ const workId = repoId ? workflowSupervisorOriginWorkId(task, repoId, controllerHome) : undefined;
340
409
  const taskControllerHome = workflowSupervisorContractText(task, 'controller_home');
341
410
  if (!repoId || (!requirementId && !workId) || taskControllerHome !== controllerHome) return;
342
411
  const store = { controllerHome, repoId };
@@ -408,9 +477,62 @@ export async function bindCurrentWorkflowSupervisorConversationForWork(
408
477
  return { status: 'bound', binding };
409
478
  }
410
479
 
480
+ export async function bindWorkflowSupervisorScheduledWakeForWork(
481
+ options: { controllerHome: string; repoId: string },
482
+ workId: string,
483
+ scheduleId: string,
484
+ ): Promise<{ status: 'bound'; taskId: string } | { status: 'not_enrolled' | 'binding_deferred' }> {
485
+ const forgeHome = resolveWorkflowSupervisorForgeHome(options.controllerHome);
486
+ if (!existsSync(workflowSupervisorSocketPath(forgeHome))) return { status: 'binding_deferred' };
487
+ const exactTaskId = taskIdForWork(options.repoId, workId);
488
+ let task = await getWorkflowSupervisorTask(forgeHome, exactTaskId);
489
+ if (!task) {
490
+ const binding = getChatgptWorkConversationBinding(options, workId);
491
+ if (binding) {
492
+ const authority = await getWorkflowSupervisorTaskByConversationId(forgeHome, binding.conversationId);
493
+ if (authority) {
494
+ const ownerRepoId = typeof authority.task.completionContract.repo_id === 'string'
495
+ ? authority.task.completionContract.repo_id : authority.task.continuationPolicy.repo_id;
496
+ if (ownerRepoId !== options.repoId || authority.task.completionContract.work_id !== workId) {
497
+ throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_WORK_IDENTITY_MISMATCH');
498
+ }
499
+ task = authority.task;
500
+ }
501
+ }
502
+ }
503
+ if (!task) return { status: 'not_enrolled' };
504
+ const bound = await bindWorkflowSupervisorScheduledWake(forgeHome, {
505
+ taskId: task.taskId,
506
+ repoId: options.repoId,
507
+ workId,
508
+ scheduleId,
509
+ });
510
+ return { status: 'bound', taskId: bound.task.taskId };
511
+ }
512
+
513
+ export async function wakeWorkflowSupervisorScheduledTaskForWork(
514
+ options: { controllerHome: string; repoId: string },
515
+ workId: string,
516
+ scheduleId: string,
517
+ occurrenceId: string,
518
+ ): Promise<{ status: 'woken' | 'not_waiting' | 'not_enrolled' | 'binding_deferred'; taskId?: string; effectId?: string }> {
519
+ const binding = await bindWorkflowSupervisorScheduledWakeForWork(options, workId, scheduleId);
520
+ if (binding.status !== 'bound') return binding;
521
+ if (!binding.taskId) throw new Error('WORKFLOW_SUPERVISOR_SCHEDULE_BINDING_TASK_ID_MISSING');
522
+ const forgeHome = resolveWorkflowSupervisorForgeHome(options.controllerHome);
523
+ const stall = await getWorkflowSupervisorTaskStall(forgeHome, binding.taskId);
524
+ if (stall.state !== 'waiting') return { status: 'not_waiting', taskId: binding.taskId };
525
+ const wake = await wakeWorkflowSupervisorScheduledTask(forgeHome, {
526
+ taskId: binding.taskId, scheduleId, occurrenceId,
527
+ });
528
+ return { status: 'woken', taskId: binding.taskId, effectId: wake.effect.effectId };
529
+ }
530
+
411
531
  export async function ensureWorkflowSupervisorEnrollmentForWork(
412
532
  options: { controllerHome: string; repoId: string },
413
533
  workId: string,
534
+ scheduledWakeScheduleId?: string,
535
+ scheduleOccurrenceId?: string,
414
536
  ): Promise<{ status: WorkflowSupervisorEnrollmentStatus; taskId?: string; effectId?: string; reason?: string }> {
415
537
  const boundary = workflowSupervisorBoundaryForWork(options, workId);
416
538
  if (boundary.status === 'not_eligible') return { status: boundary.status };
@@ -421,6 +543,55 @@ export async function ensureWorkflowSupervisorEnrollmentForWork(
421
543
  const taskId = taskIdForWork(options.repoId, workId);
422
544
  if (!existsSync(workflowSupervisorSocketPath(forgeHome))) return { status: 'daemon_unavailable', taskId };
423
545
 
546
+ // A Work-derived Supervisor id is a durable task reservation, not a
547
+ // registration template to rewrite on every Scheduler wake. In particular,
548
+ // an explicitly stopped historical task remains terminal even if its Work is
549
+ // still open, and a live bootstrap retains its original immutable objective
550
+ // and effect when the lower Work wording changes. Do not mint a replacement
551
+ // send, mutate Task identity, or use lower ControllerRound state to revive it.
552
+ const existingWorkTask = await getWorkflowSupervisorTask(forgeHome, taskId);
553
+ if (existingWorkTask) {
554
+ const existingRepoId = typeof existingWorkTask.completionContract.repo_id === 'string'
555
+ ? existingWorkTask.completionContract.repo_id
556
+ : existingWorkTask.continuationPolicy.repo_id;
557
+ const existingWorkId = existingWorkTask.completionContract.work_id;
558
+ if (existingRepoId !== options.repoId || existingWorkId !== workId) {
559
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_ID_CONFLICT');
560
+ }
561
+ let stall = await getWorkflowSupervisorTaskStall(forgeHome, taskId);
562
+ if (stall.state === 'terminal') {
563
+ return { status: 'lower_layer_not_ready', reason: 'WORKFLOW_SUPERVISOR_TASK_TERMINAL' };
564
+ }
565
+ if (scheduledWakeScheduleId) {
566
+ await bindWorkflowSupervisorScheduledWake(forgeHome, {
567
+ taskId,
568
+ repoId: options.repoId,
569
+ workId,
570
+ scheduleId: scheduledWakeScheduleId,
571
+ });
572
+ stall = await getWorkflowSupervisorTaskStall(forgeHome, taskId);
573
+ if (stall.state === 'waiting') {
574
+ if (!scheduleOccurrenceId) return { status: 'lower_layer_not_ready', taskId, reason: 'SCHEDULE_OCCURRENCE_ID_REQUIRED' };
575
+ const wake = await wakeWorkflowSupervisorScheduledTask(forgeHome, {
576
+ taskId, scheduleId: scheduledWakeScheduleId, occurrenceId: scheduleOccurrenceId,
577
+ });
578
+ return { status: 'enrolled', taskId, effectId: wake.effect.effectId };
579
+ }
580
+ }
581
+ const ownedRequirementId = typeof existingWorkTask.completionContract.requirement_id === 'string'
582
+ ? existingWorkTask.completionContract.requirement_id : undefined;
583
+ if (ownedRequirementId !== getWorkContract(options, workId)?.requirementId) {
584
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_REQUIREMENT_CONFLICT');
585
+ }
586
+ if (boundary.status === 'conversation_pending') {
587
+ if (existingWorkTask.continuationPolicy.kind !== 'forge_goal_outer_turn') {
588
+ throw new Error('WORKFLOW_SUPERVISOR_TASK_ID_CONFLICT');
589
+ }
590
+ const effect = await reserveWorkflowSupervisorEnrollment(forgeHome, taskId);
591
+ return { status: 'enrolled', taskId, effectId: effect.effectId };
592
+ }
593
+ }
594
+
424
595
  // Once an exact conversation is already owned by a Supervisor task, that
425
596
  // task is the outer-turn authority. ControllerRound is only first-admission
426
597
  // execution context and must not regain veto power over later continuation.
@@ -445,6 +616,22 @@ export async function ensureWorkflowSupervisorEnrollmentForWork(
445
616
  reason: `WORKFLOW_SUPERVISOR_TASK_TERMINAL:${existingAuthority.terminal}`,
446
617
  };
447
618
  }
619
+ if (scheduledWakeScheduleId) {
620
+ await bindWorkflowSupervisorScheduledWake(forgeHome, {
621
+ taskId: existingAuthority.task.taskId,
622
+ repoId: options.repoId,
623
+ workId,
624
+ scheduleId: scheduledWakeScheduleId,
625
+ });
626
+ const stall = await getWorkflowSupervisorTaskStall(forgeHome, existingAuthority.task.taskId);
627
+ if (stall.state === 'waiting') {
628
+ if (!scheduleOccurrenceId) return { status: 'lower_layer_not_ready', taskId: existingAuthority.task.taskId, reason: 'SCHEDULE_OCCURRENCE_ID_REQUIRED' };
629
+ const wake = await wakeWorkflowSupervisorScheduledTask(forgeHome, {
630
+ taskId: existingAuthority.task.taskId, scheduleId: scheduledWakeScheduleId, occurrenceId: scheduleOccurrenceId,
631
+ });
632
+ return { status: 'enrolled', taskId: existingAuthority.task.taskId, effectId: wake.effect.effectId };
633
+ }
634
+ }
448
635
  const effect = await reserveWorkflowSupervisorEnrollment(forgeHome, existingAuthority.task.taskId);
449
636
  return { status: 'enrolled', taskId: existingAuthority.task.taskId, effectId: effect.effectId };
450
637
  }
@@ -476,6 +663,7 @@ export async function ensureWorkflowSupervisorEnrollmentForWork(
476
663
  ...(boundary.status === 'outer_turn'
477
664
  ? { exact_conversation_id: boundary.conversationId, exact_conversation_url: boundary.conversationUrl }
478
665
  : { bootstrap: true }),
666
+ ...(scheduledWakeScheduleId ? { scheduled_wake_schedule_id: scheduledWakeScheduleId } : {}),
479
667
  outer_turn_owner: 'workflow_supervisor',
480
668
  },
481
669
  userBlockerPolicy: {