@moretea-labs/forge 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (900) hide show
  1. package/AGENTS.md +121 -62
  2. package/CHANGELOG.md +40 -0
  3. package/CLAUDE.md +57 -62
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.md +2 -2
  6. package/SKILL.md +0 -1
  7. package/THIRD_PARTY_NOTICES.md +5 -2
  8. package/adapters/browser/index.ts +2 -0
  9. package/adapters/chatgpt/browser-delivery-host.ts +77 -0
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
  11. package/adapters/chatgpt/controller-binding-store.ts +134 -0
  12. package/adapters/chatgpt/controller-round-host.ts +68 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
  14. package/adapters/chatgpt/provider-delivery.ts +340 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  18. package/adapters/computer/chatgpt-target-router.ts +103 -0
  19. package/adapters/computer/chrome-extension/background.js +385 -0
  20. package/adapters/computer/chrome-extension/content.js +378 -0
  21. package/adapters/computer/chrome-extension/core.js +24 -0
  22. package/adapters/computer/chrome-extension/manifest.json +30 -0
  23. package/adapters/computer/desktop-operator-contract.ts +7 -0
  24. package/adapters/computer/desktop-operator-discovery.ts +181 -0
  25. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  26. package/adapters/computer/desktop-operator-provider.ts +312 -0
  27. package/adapters/computer/index.ts +1 -0
  28. package/adapters/computer/interaction-target-authority.ts +1068 -0
  29. package/adapters/controller-process/binding-store.ts +34 -0
  30. package/adapters/controller-process/controller-host.ts +26 -0
  31. package/adapters/mcp/access-mode.ts +132 -0
  32. package/adapters/mcp/audit.ts +32 -0
  33. package/adapters/mcp/auth.ts +406 -0
  34. package/adapters/mcp/instructions.ts +37 -0
  35. package/adapters/mcp/legacy-context.ts +18 -0
  36. package/adapters/mcp/multi-repository.ts +421 -0
  37. package/adapters/mcp/oauth.ts +464 -0
  38. package/adapters/mcp/paths.ts +143 -0
  39. package/adapters/mcp/policy.ts +232 -0
  40. package/adapters/mcp/proxy-env.ts +62 -0
  41. package/adapters/mcp/readiness-projection.ts +24 -0
  42. package/adapters/mcp/redaction.ts +21 -0
  43. package/adapters/mcp/repo.ts +24 -0
  44. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  45. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  46. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  47. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  48. package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
  49. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  50. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
  51. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  52. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
  53. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  54. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  55. package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
  56. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  57. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
  58. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  59. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  60. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  61. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  62. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
  63. package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
  64. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
  65. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
  66. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  67. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  68. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
  69. package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
  70. package/adapters/mcp/runtime-gateway/router.ts +242 -0
  71. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  72. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
  73. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  74. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
  75. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  76. package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
  77. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
  78. package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
  79. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
  80. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  81. package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
  82. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  83. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  84. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  85. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  86. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  87. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  88. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  89. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  90. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  91. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  92. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  93. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  94. package/adapters/mcp/server.ts +1092 -0
  95. package/adapters/mcp/setup.ts +789 -0
  96. package/adapters/mcp/tool-capability-cache.ts +87 -0
  97. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  98. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
  99. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
  100. package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
  101. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  102. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  103. package/adapters/mcp/toolset-names.ts +79 -0
  104. package/adapters/mcp/toolset-selection.ts +41 -0
  105. package/adapters/mcp/toolset.ts +337 -0
  106. package/adapters/mcp/transports/http-observation.ts +365 -0
  107. package/adapters/mcp/transports/http.ts +933 -0
  108. package/adapters/mcp/transports/oauth-http.ts +322 -0
  109. package/adapters/mcp/transports/session-registry.ts +519 -0
  110. package/adapters/mcp/transports/stdio.ts +8 -0
  111. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
  112. package/adapters/mcp/types.ts +41 -0
  113. package/assets/AGENTS.md +2 -16
  114. package/assets/CLAUDE.md +2 -16
  115. package/assets/hooks/AGENTS.md +3 -14
  116. package/assets/hooks/CLAUDE.md +3 -14
  117. package/assets/hooks/hook-input.sh +9 -2
  118. package/assets/hooks/lib/workflow-state.sh +28 -10
  119. package/assets/hooks/prompt-guard.sh +63 -37
  120. package/assets/hooks/security-sentinel.sh +2 -1
  121. package/assets/hooks/session-start-context.sh +7 -5
  122. package/assets/hooks/stop-orchestrator.sh +13 -3
  123. package/assets/initializer-question-pack.v4.json +11 -7
  124. package/assets/partials/05-workflow.partial.md +18 -73
  125. package/assets/partials/07-footer.partial.md +1 -1
  126. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  127. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  128. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  129. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  130. package/assets/plan-map.json +11 -11
  131. package/assets/plugin-registry.v1.json +11 -3
  132. package/assets/plugin-trust-policy.v1.json +6 -0
  133. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  134. package/assets/recovery/forge-wsl-rescue +271 -0
  135. package/assets/reference-configs/agentic-development-flow.md +25 -0
  136. package/assets/reference-configs/development-protocol.md +11 -15
  137. package/assets/reference-configs/document-generation.md +1 -1
  138. package/assets/reference-configs/global-working-rules.md +28 -1
  139. package/assets/reference-configs/harness-overview.md +22 -112
  140. package/assets/reference-configs/hook-operations.md +1 -1
  141. package/assets/reference-configs/sprint-contracts.md +2 -2
  142. package/assets/skill-version.json +2 -2
  143. package/assets/templates/helpers/architecture-event.ts +3 -34
  144. package/assets/templates/helpers/architecture-queue.sh +61 -10
  145. package/assets/templates/helpers/archive-workflow.sh +90 -180
  146. package/assets/templates/helpers/capability-config.ts +8 -11
  147. package/assets/templates/helpers/capability-resolver.ts +37 -19
  148. package/assets/templates/helpers/capture-plan.sh +2 -2
  149. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  150. package/assets/templates/helpers/check-task-sync.sh +82 -11
  151. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  152. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  153. package/assets/templates/helpers/contract-worktree.sh +26 -24
  154. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  155. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  156. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  157. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  158. package/assets/templates/helpers/new-plan.sh +2 -2
  159. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  160. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  161. package/assets/templates/helpers/switch-plan.sh +1 -1
  162. package/assets/templates/helpers/workflow-contract.ts +1 -0
  163. package/assets/workflow-contract.v1.json +63 -171
  164. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  165. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  166. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  167. package/docs/architecture/CURRENT.md +233 -28
  168. package/docs/architecture/EVOLUTION.md +13 -0
  169. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  170. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  171. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  172. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  173. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  174. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  175. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
  176. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
  177. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  178. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  179. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  180. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  181. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  182. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  183. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  184. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  185. package/docs/architecture/domains/verification.md +8 -0
  186. package/docs/architecture/domains/workflow-engine.md +7 -1
  187. package/docs/architecture/index.md +3 -3
  188. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  189. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  190. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  191. package/docs/forge-plugin-management.md +20 -4
  192. package/docs/forge-runtime-self-healing-loop.md +2 -0
  193. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  194. package/docs/operations/controller-browser-plugin.md +3 -1
  195. package/docs/operations/controller-performance-and-502.md +17 -0
  196. package/docs/operations/controller-reliability-runbook.md +2 -2
  197. package/docs/operations/features.md +1 -1
  198. package/docs/operations/features.zh-CN.md +1 -1
  199. package/docs/operations/homebrew.md +2 -2
  200. package/docs/operations/mcp-tool-exposure.md +2 -0
  201. package/docs/operations/platform-support.md +16 -8
  202. package/docs/operations/platform-support.zh-CN.md +2 -2
  203. package/docs/operations/releasing.md +2 -0
  204. package/docs/operations/standalone-disaster-recovery.md +38 -5
  205. package/docs/operations/uu-remote-rescue.md +41 -0
  206. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  207. package/docs/public-usage-guide.md +1 -1
  208. package/docs/public-usage-guide.zh-CN.md +1 -1
  209. package/docs/tutorials/01-install-and-start.md +1 -1
  210. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  211. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  212. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  213. package/docs/wiki/Implementation.md +1 -1
  214. package/package.json +46 -26
  215. package/packages/kernel/cognition/api/index.ts +6 -0
  216. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  217. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  218. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  219. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  220. package/packages/kernel/cognition/domain/learning.ts +106 -0
  221. package/packages/kernel/cognition/domain/memory.ts +254 -0
  222. package/packages/kernel/controller/api/index.ts +3 -0
  223. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  224. package/packages/kernel/controller/application/controller-round-service.ts +41 -0
  225. package/packages/kernel/controller/application/controller-service.ts +30 -0
  226. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
  227. package/packages/kernel/controller/domain/controller-round.ts +110 -0
  228. package/packages/kernel/controller/domain/execution-quality.ts +135 -0
  229. package/packages/kernel/controller/domain/types.ts +56 -0
  230. package/packages/kernel/controller/index.ts +1 -0
  231. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  232. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
  233. package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
  234. package/packages/kernel/controller/ports/controller-host.ts +18 -0
  235. package/packages/kernel/goal/api/index.ts +1 -0
  236. package/packages/kernel/goal/domain/types.ts +40 -0
  237. package/packages/kernel/goal/index.ts +1 -0
  238. package/packages/kernel/identity/api/index.ts +15 -0
  239. package/packages/kernel/identity/application/identity-service.ts +67 -0
  240. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  241. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  242. package/packages/kernel/identity/domain/grant.ts +72 -0
  243. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  244. package/packages/kernel/identity/domain/scope.ts +63 -0
  245. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  246. package/packages/kernel/identity/domain/types.ts +44 -0
  247. package/packages/kernel/identity/domain/user-request.ts +93 -0
  248. package/packages/kernel/identity/domain/workspace.ts +56 -0
  249. package/packages/kernel/identity/index.ts +1 -0
  250. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  251. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  252. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  253. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  254. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  255. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  256. package/packages/kernel/memory/api/index.ts +5 -0
  257. package/packages/kernel/memory/application/experience-service.ts +118 -0
  258. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  259. package/packages/kernel/memory/domain/experience.ts +103 -0
  260. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  261. package/packages/kernel/memory/index.ts +1 -0
  262. package/packages/kernel/scheduler/api/index.ts +4 -0
  263. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  264. package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
  265. package/packages/kernel/scheduler/application/settlement.ts +182 -0
  266. package/packages/kernel/scheduler/domain/schedule.ts +236 -0
  267. package/packages/kernel/scheduler/index.ts +1 -0
  268. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
  269. package/packages/kernel/work/api/index.ts +16 -0
  270. package/packages/kernel/work/application/work-service.ts +123 -0
  271. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  272. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  273. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  274. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  275. package/packages/kernel/work/domain/engineering-design.ts +286 -0
  276. package/packages/kernel/work/domain/engineering-profile.ts +238 -0
  277. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  278. package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
  279. package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
  280. package/packages/kernel/work/domain/implementation-review.ts +193 -0
  281. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  282. package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
  283. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  284. package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
  285. package/packages/kernel/work/domain/types.ts +534 -0
  286. package/packages/kernel/work/index.ts +1 -0
  287. package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
  288. package/packages/kernel/work/ports/work-contract-store.ts +21 -0
  289. package/packages/plugin-runtime/browser/index.ts +3 -0
  290. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  291. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  292. package/packages/plugin-runtime/browser/session-authority.ts +62 -0
  293. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  294. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  295. package/packages/plugin-runtime/computer/index.ts +8 -0
  296. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  297. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  298. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  299. package/packages/plugin-runtime/computer/provider.ts +19 -0
  300. package/packages/plugin-runtime/computer/target-authority.ts +215 -0
  301. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  302. package/packages/plugin-runtime/external/index.ts +1 -0
  303. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
  304. package/packages/plugin-runtime/index.ts +3 -0
  305. package/packages/protocols/browser/index.ts +1 -0
  306. package/packages/protocols/browser/session.ts +98 -0
  307. package/packages/protocols/computer/contract.ts +225 -0
  308. package/packages/protocols/computer/index.ts +1 -0
  309. package/packages/protocols/failure/contract.ts +158 -0
  310. package/packages/protocols/failure/index.ts +1 -0
  311. package/packages/protocols/handoff/index.ts +1 -0
  312. package/packages/protocols/handoff/status.ts +22 -0
  313. package/packages/protocols/mcp/execution-context.ts +11 -0
  314. package/packages/protocols/mcp/index.ts +2 -0
  315. package/packages/protocols/mcp/tool-contract.ts +42 -0
  316. package/packages/workflow-runtime/api/index.ts +2 -0
  317. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  318. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  319. package/packages/workflow-runtime/index.ts +1 -0
  320. package/scripts/activate-source-baseline.ts +7 -9
  321. package/scripts/assemble-template.ts +1 -1
  322. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  323. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  324. package/scripts/canary-controller-semantic-wait.ts +183 -0
  325. package/scripts/canary-global-hook.sh +2 -2
  326. package/scripts/check-mcp-compatibility.ts +85 -7
  327. package/scripts/check-platform-support.mjs +28 -2
  328. package/scripts/check-public-docs.sh +1 -1
  329. package/scripts/check-repository-hygiene.mjs +92 -0
  330. package/scripts/check-runtime-architecture.mjs +1643 -127
  331. package/scripts/check-source-duplication.mjs +9 -1
  332. package/scripts/controller-ngrok-rotation.sh +5 -4
  333. package/scripts/create-project-dirs.sh +10 -74
  334. package/scripts/forge-local-recovery-helper.mjs +282 -0
  335. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  336. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  337. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  338. package/scripts/hook-shim.sh +2 -2
  339. package/scripts/init-project.sh +12 -70
  340. package/scripts/install-desktop-operator-registration.ts +4 -2
  341. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  342. package/scripts/install-standalone-recovery.ts +122 -10
  343. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  344. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  345. package/scripts/lib/controller-home-env.sh +6 -3
  346. package/scripts/lib/project-init-lib.sh +42 -582
  347. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  348. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  349. package/scripts/open-source-audit-allowlist.txt +11 -1
  350. package/scripts/portability-bindings.mjs +70 -0
  351. package/scripts/run-bounded-command.mjs +60 -0
  352. package/scripts/run-bun-test-file.ts +22 -6
  353. package/scripts/run-governed-gate.ts +4 -2
  354. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  355. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  356. package/scripts/smoke-mcp-http-runtime.ts +6 -1
  357. package/scripts/smoke-runtime-recovery.ts +13 -11
  358. package/scripts/smoke-schedule-engine.ts +24 -16
  359. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  360. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  361. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  362. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  363. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  364. package/scripts/smoke-workflow-supervisor.ts +103 -0
  365. package/scripts/test-governance.ts +51 -2
  366. package/scripts/verify-forge-runtime.sh +2 -1
  367. package/src/cli/agent-jobs/executor-health.ts +11 -34
  368. package/src/cli/agent-jobs/integration.ts +18 -49
  369. package/src/cli/agent-jobs/job-manager.ts +17 -4
  370. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  371. package/src/cli/chatgpt-browser/binding.ts +20 -10
  372. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  373. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  374. package/src/cli/chatgpt-browser/engine.ts +10 -11
  375. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  376. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  377. package/src/cli/chatgpt-browser/types.ts +1 -0
  378. package/src/cli/commands/bootstrap-control.ts +334 -0
  379. package/src/cli/commands/brain-assistant.ts +52 -0
  380. package/src/cli/commands/brain.ts +191 -40
  381. package/src/cli/commands/chatgpt.ts +32 -67
  382. package/src/cli/commands/computer.ts +437 -0
  383. package/src/cli/commands/doctor.ts +18 -1
  384. package/src/cli/commands/init-hook.ts +71 -61
  385. package/src/cli/commands/init.ts +3 -1
  386. package/src/cli/commands/mcp.ts +16 -10
  387. package/src/cli/commands/plugin.ts +38 -12
  388. package/src/cli/commands/recovery.ts +507 -80
  389. package/src/cli/commands/runtime.ts +12 -7
  390. package/src/cli/commands/setup-profile.ts +97 -39
  391. package/src/cli/commands/status.ts +2 -1
  392. package/src/cli/controller/check-runner.ts +500 -100
  393. package/src/cli/controller/completion-backlog.ts +3 -3
  394. package/src/cli/controller/completion-orchestrator.ts +4 -43
  395. package/src/cli/controller/completion-target.ts +3 -72
  396. package/src/cli/controller/context/source-materializer.ts +81 -100
  397. package/src/cli/controller/context/types.ts +7 -1
  398. package/src/cli/controller/context-pack.ts +28 -6
  399. package/src/cli/controller/execution-completion.ts +4 -7
  400. package/src/cli/controller/execution-policy.ts +36 -153
  401. package/src/cli/controller/governance.ts +2 -3
  402. package/src/cli/controller/issue-store.ts +4 -99
  403. package/src/cli/controller/launch-agents.ts +59 -18
  404. package/src/cli/controller/operational-plan.ts +24 -51
  405. package/src/cli/controller/runtime-config.ts +5 -1
  406. package/src/cli/controller/stuck-state-migration.ts +2 -3
  407. package/src/cli/controller/systemd-user.ts +148 -0
  408. package/src/cli/controller/types.ts +10 -70
  409. package/src/cli/controller/worklog.ts +67 -13
  410. package/src/cli/editing/edit-operation-contract.ts +92 -0
  411. package/src/cli/editing/edit-session.ts +290 -20
  412. package/src/cli/github/plugin.ts +17 -12
  413. package/src/cli/hook/route-registry.ts +3 -5
  414. package/src/cli/hook/runtime.ts +49 -3
  415. package/src/cli/index.ts +3 -0
  416. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  417. package/src/cli/local-bridge/console-automations.ts +20 -15
  418. package/src/cli/local-bridge/console-view-models.ts +3 -12
  419. package/src/cli/local-bridge/facade-api.ts +105 -205
  420. package/src/cli/local-bridge/job-store.ts +1 -1
  421. package/src/cli/local-bridge/server.ts +48 -174
  422. package/src/cli/local-bridge/types.ts +0 -1
  423. package/src/cli/mcp/access-mode.ts +2 -132
  424. package/src/cli/mcp/access-tools.ts +2 -387
  425. package/src/cli/mcp/audit.ts +2 -32
  426. package/src/cli/mcp/auth.ts +2 -404
  427. package/src/cli/mcp/instructions.ts +2 -34
  428. package/src/cli/mcp/legacy-context.ts +2 -16
  429. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  430. package/src/cli/mcp/multi-repository.ts +2 -436
  431. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  432. package/src/cli/mcp/oauth.ts +2 -375
  433. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  434. package/src/cli/mcp/paths.ts +2 -140
  435. package/src/cli/mcp/policy.ts +2 -232
  436. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  437. package/src/cli/mcp/proxy-env.ts +2 -62
  438. package/src/cli/mcp/readiness-projection.ts +2 -24
  439. package/src/cli/mcp/redaction.ts +2 -21
  440. package/src/cli/mcp/repo.ts +2 -24
  441. package/src/cli/mcp/repository-tools.ts +2 -1613
  442. package/src/cli/mcp/server.ts +2 -951
  443. package/src/cli/mcp/setup.ts +2 -737
  444. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  445. package/src/cli/mcp/tool-contract.ts +2 -14
  446. package/src/cli/mcp/tools.ts +2 -9
  447. package/src/cli/mcp/toolset-names.ts +2 -70
  448. package/src/cli/mcp/toolset-selection.ts +2 -41
  449. package/src/cli/mcp/toolset.ts +2 -336
  450. package/src/cli/mcp/transports/AGENTS.md +1 -10
  451. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  452. package/src/cli/mcp/transports/http.ts +2 -1414
  453. package/src/cli/mcp/transports/session-registry.ts +2 -490
  454. package/src/cli/mcp/transports/stdio.ts +2 -8
  455. package/src/cli/mcp/types.ts +2 -41
  456. package/src/cli/mcp/umbrella-tools.ts +2 -117
  457. package/src/cli/repositories/command-executor.ts +91 -39
  458. package/src/cli/repositories/command-scope.ts +139 -4
  459. package/src/cli/repositories/controller-home.ts +167 -1
  460. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  461. package/src/cli/repositories/git-command-executor.ts +2 -2
  462. package/src/cli/repositories/identity.ts +13 -0
  463. package/src/cli/repositories/registry.ts +145 -60
  464. package/src/cli/repositories/repository-snapshot.ts +53 -7
  465. package/src/cli/repositories/runtime-storage.ts +27 -2
  466. package/src/cli/repositories/safe-patch.ts +34 -38
  467. package/src/cli/repositories/selected-path-actions.ts +22 -2
  468. package/src/cli/repositories/structured-git.ts +167 -27
  469. package/src/cli/repository/session-cache.ts +92 -6
  470. package/src/cli/runtime/helper-runner.ts +7 -0
  471. package/src/cli/runtime-invocation.ts +8 -1
  472. package/src/cli/tools/codegraph.ts +44 -25
  473. package/src/core/adoption/manifest-templates.ts +1 -1
  474. package/src/core/adoption/plan.ts +3 -26
  475. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  476. package/src/effects/process-runner.ts +8 -14
  477. package/src/runtime/context/assistant-context.ts +167 -0
  478. package/src/runtime/context/assistant-work-context.ts +209 -0
  479. package/src/runtime/context/automatic-learning.ts +565 -0
  480. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  481. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  482. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  483. package/src/runtime/context/cognitive-attention.ts +173 -0
  484. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  485. package/src/runtime/context/context-closure.ts +305 -0
  486. package/src/runtime/context/context-pack-process.ts +103 -0
  487. package/src/runtime/context/context-plane.ts +203 -0
  488. package/src/runtime/context/direct-controller-learning.ts +298 -0
  489. package/src/runtime/context/project-engineering-contract.ts +60 -0
  490. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  491. package/src/runtime/context/semantic-navigation.ts +6 -34
  492. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  493. package/src/runtime/context/typescript-navigation.ts +89 -3
  494. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  495. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  496. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  497. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  498. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  499. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  500. package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
  501. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  502. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
  503. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
  504. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
  505. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  506. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  507. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  508. package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
  509. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  510. package/src/runtime/control-plane/execution/validation.ts +34 -11
  511. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
  512. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  513. package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
  514. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  515. package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
  516. package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
  517. package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
  518. package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
  519. package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
  520. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
  521. package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
  522. package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
  523. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  524. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  525. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
  526. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
  527. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  528. package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
  529. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
  530. package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
  531. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  532. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  533. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  534. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  535. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
  536. package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
  537. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
  538. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
  539. package/src/runtime/control-plane/facade/index.ts +4 -3
  540. package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
  541. package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
  542. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  543. package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
  544. package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
  545. package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
  546. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
  547. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  548. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  549. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
  550. package/src/runtime/control-plane/facade/types.ts +218 -620
  551. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  552. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  553. package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
  554. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  555. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
  556. package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
  557. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  558. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  559. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
  560. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  561. package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
  562. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  563. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  564. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  565. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  566. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
  567. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  568. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
  569. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  570. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
  571. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
  572. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  573. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  574. package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
  575. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  576. package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
  577. package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
  578. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  579. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  580. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  581. package/src/runtime/control-plane/release-retention.ts +154 -26
  582. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  583. package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
  584. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  585. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  586. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
  587. package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
  588. package/src/runtime/diagnostics/incident-repair.ts +573 -0
  589. package/src/runtime/diagnostics/mcp-timing.ts +239 -6
  590. package/src/runtime/diagnostics/performance.ts +6 -14
  591. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  592. package/src/runtime/evidence/artifact-store.ts +216 -35
  593. package/src/runtime/evidence/evidence-store.ts +112 -8
  594. package/src/runtime/evidence/operational-shadow.ts +38 -0
  595. package/src/runtime/evidence/output-handle-index.ts +225 -0
  596. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  597. package/src/runtime/evidence/result-store.ts +158 -44
  598. package/src/runtime/evidence/sensitive-output.ts +4 -4
  599. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  600. package/src/runtime/execution/jobs/store.ts +124 -6
  601. package/src/runtime/execution/managed-workspace.ts +112 -5
  602. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  603. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  604. package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
  605. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  606. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  607. package/src/runtime/execution/process-runtime/check-result.ts +177 -2
  608. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
  609. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  610. package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
  611. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  612. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  613. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  614. package/src/runtime/execution/process-runtime/index.ts +4 -0
  615. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  616. package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
  617. package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
  618. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  619. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  620. package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
  621. package/src/runtime/execution/process-runtime/runtime.ts +167 -17
  622. package/src/runtime/execution/process-runtime/store.ts +30 -2
  623. package/src/runtime/execution/process-runtime/types.ts +27 -0
  624. package/src/runtime/execution/workers/executor.ts +18 -19
  625. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  626. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  627. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  628. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  629. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  630. package/src/runtime/gateway/mcp/router.ts +2 -1125
  631. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  632. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  633. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  634. package/src/runtime/maintenance/cleanup.ts +38 -24
  635. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  636. package/src/runtime/platform/computer-platform.ts +14 -0
  637. package/src/runtime/platform/executable-discovery.ts +54 -0
  638. package/src/runtime/platform/fetch-http-server.ts +90 -0
  639. package/src/runtime/platform/service-inventory.ts +130 -0
  640. package/src/runtime/platform/service-manager.ts +146 -0
  641. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  642. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  643. package/src/runtime/plugins/action-application.ts +192 -0
  644. package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
  645. package/src/runtime/plugins/browser-adapter.ts +898 -135
  646. package/src/runtime/plugins/browser-automation-service.ts +33 -34
  647. package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
  648. package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
  649. package/src/runtime/plugins/browser-native-session-service.ts +64 -14
  650. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  651. package/src/runtime/plugins/browser-registration.ts +32 -0
  652. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  653. package/src/runtime/plugins/browser-runtime.ts +55 -10
  654. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  655. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  656. package/src/runtime/plugins/browser-session-store.ts +243 -80
  657. package/src/runtime/plugins/browser-session-types.ts +2 -98
  658. package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
  659. package/src/runtime/plugins/catalog-trust.ts +82 -0
  660. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  661. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  662. package/src/runtime/plugins/computer-registration.ts +1050 -0
  663. package/src/runtime/plugins/config-store.ts +119 -0
  664. package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
  665. package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
  666. package/src/runtime/plugins/errors.ts +8 -1
  667. package/src/runtime/plugins/external-adapter.ts +82 -323
  668. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  669. package/src/runtime/plugins/external-registration.ts +30 -2
  670. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  671. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  672. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  673. package/src/runtime/plugins/first-party-registry.ts +3 -1
  674. package/src/runtime/plugins/github-adapter.ts +1 -2
  675. package/src/runtime/plugins/gmail-adapter.ts +29 -8
  676. package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
  677. package/src/runtime/plugins/google-shared.ts +45 -53
  678. package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
  679. package/src/runtime/plugins/ios-adapter.ts +2 -3
  680. package/src/runtime/plugins/lightweight-action.ts +208 -2
  681. package/src/runtime/plugins/local-system-adapter.ts +192 -2
  682. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  683. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  684. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  685. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  686. package/src/runtime/plugins/resend-adapter.ts +15 -24
  687. package/src/runtime/plugins/store.ts +1003 -387
  688. package/src/runtime/plugins/types.ts +28 -1
  689. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  690. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  691. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  692. package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
  693. package/src/runtime/projections/controller-context.ts +12 -6
  694. package/src/runtime/projections/git-status-sampler.ts +73 -22
  695. package/src/runtime/projections/invalidation.ts +7 -3
  696. package/src/runtime/projections/materialized-view.ts +93 -43
  697. package/src/runtime/recovery/actions.ts +3 -3
  698. package/src/runtime/recovery/capability-action-application.ts +229 -0
  699. package/src/runtime/recovery/index.ts +3 -0
  700. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  701. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  702. package/src/runtime/recovery/maintenance-executor.ts +701 -168
  703. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  704. package/src/runtime/recovery/store.ts +29 -11
  705. package/src/runtime/release/identity.ts +6 -0
  706. package/src/runtime/release/release-coordinator.ts +296 -0
  707. package/src/runtime/release/release-session.ts +716 -0
  708. package/src/runtime/release/release-state-migration.ts +25 -0
  709. package/src/runtime/resources/leases/store.ts +56 -25
  710. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  711. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  712. package/src/runtime/root/browser-session-composition.ts +59 -0
  713. package/src/runtime/root/computer-composition.ts +376 -0
  714. package/src/runtime/root/computer-target-composition.ts +16 -0
  715. package/src/runtime/root/computer-target-persistence.ts +39 -0
  716. package/src/runtime/root/controller-progression-composition.ts +192 -0
  717. package/src/runtime/root/controller-round-composition.ts +126 -0
  718. package/src/runtime/root/controller-services.ts +2 -0
  719. package/src/runtime/root/deployment-topology.ts +106 -0
  720. package/src/runtime/root/entry.ts +8 -6
  721. package/src/runtime/root/gateway-adapter.ts +3 -3
  722. package/src/runtime/root/known-good-recovery.ts +184 -0
  723. package/src/runtime/root/local-bridge.ts +5 -3
  724. package/src/runtime/root/mcp-transport.ts +116 -23
  725. package/src/runtime/root/ownership.ts +261 -9
  726. package/src/runtime/root/package-connector-service.ts +212 -74
  727. package/src/runtime/root/package-runtime-release.ts +199 -33
  728. package/src/runtime/root/package-runtime-service.ts +77 -108
  729. package/src/runtime/root/release-execution-canary.ts +103 -0
  730. package/src/runtime/root/release-loader.ts +150 -0
  731. package/src/runtime/root/release-manifest.ts +185 -7
  732. package/src/runtime/root/release-materialize.ts +474 -31
  733. package/src/runtime/root/release-store.ts +425 -37
  734. package/src/runtime/root/runtime-lane.ts +191 -0
  735. package/src/runtime/root/runtime.ts +379 -32
  736. package/src/runtime/root/scheduled-controller-composition.ts +76 -0
  737. package/src/runtime/root/scheduler.ts +12 -4
  738. package/src/runtime/root/service-runner.ts +3 -3
  739. package/src/runtime/root/service.ts +73 -45
  740. package/src/runtime/root/status.ts +49 -0
  741. package/src/runtime/root/types.ts +55 -3
  742. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  743. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  744. package/src/runtime/root/write-fence.ts +50 -2
  745. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  746. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  747. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  748. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  749. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  750. package/src/runtime/shared/process-environment.ts +36 -10
  751. package/src/runtime/shared/response-budget.ts +1 -3
  752. package/src/runtime/shared/storage-capacity.ts +68 -0
  753. package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
  754. package/src/runtime/standalone-recovery/core.ts +4609 -495
  755. package/src/runtime/standalone-recovery/entry.ts +642 -81
  756. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  757. package/src/runtime/standalone-recovery/installer.ts +399 -177
  758. package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
  759. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  760. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  761. package/src/runtime/standalone-recovery/performance.ts +173 -0
  762. package/src/runtime/standalone-recovery/release.ts +58 -5
  763. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  764. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  765. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  766. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  767. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
  768. package/src/runtime/workflow/schedules/engine.ts +298 -484
  769. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  770. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  771. package/src/runtime/workflow/schedules/store.ts +2 -607
  772. package/src/runtime/workflow/schedules/types.ts +2 -127
  773. package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
  774. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  775. package/src/runtime/workflows/runtime.ts +482 -0
  776. package/src/testing/test-governance.ts +95 -30
  777. package/supervisor/chatgpt-conversation.ts +23 -0
  778. package/supervisor/client.ts +201 -0
  779. package/supervisor/control-plane.ts +868 -0
  780. package/supervisor/entry.ts +15 -0
  781. package/supervisor/forge-validators.ts +80 -0
  782. package/supervisor/index.ts +5 -0
  783. package/supervisor/native-browser-adapter.ts +786 -0
  784. package/supervisor/native-messaging/host.ts +39 -0
  785. package/supervisor/native-messaging/manifest.ts +26 -0
  786. package/supervisor/native-messaging/protocol.ts +27 -0
  787. package/supervisor/paths.ts +18 -0
  788. package/supervisor/protocol.ts +202 -0
  789. package/supervisor/server.ts +404 -0
  790. package/supervisor/store.ts +1451 -0
  791. package/supervisor/tsconfig.json +8 -0
  792. package/supervisor/types.ts +255 -0
  793. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  794. package/docs/architecture/history/README.md +0 -7
  795. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  796. package/docs/architecture/history/global-hook-runtime.md +0 -166
  797. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  798. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  799. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  800. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  801. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  802. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  803. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  804. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  805. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  806. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  807. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  808. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  809. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  810. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  811. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  812. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  813. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  814. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  815. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  816. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  817. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  818. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  819. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  820. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  821. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  822. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  823. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  824. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  825. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  826. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  827. package/docs/architecture/requests/archive/2026/root.md +0 -68
  828. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  829. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  830. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  831. package/scripts/archive-architecture-request.sh +0 -4
  832. package/scripts/benchmark-background-check-overlap.ts +0 -228
  833. package/scripts/benchmark-controller-hotpath.ts +0 -212
  834. package/scripts/benchmark-controller-round2.ts +0 -139
  835. package/scripts/benchmark-ios-agent-device.ts +0 -514
  836. package/scripts/benchmark-mcp-host.sh +0 -117
  837. package/scripts/benchmark-mcp-transport.ts +0 -170
  838. package/scripts/benchmark-quality-harness.ts +0 -146
  839. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  840. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  841. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  842. package/scripts/benchmark-thin-harness.ts +0 -251
  843. package/scripts/benchmark-typescript-navigation.ts +0 -186
  844. package/scripts/benchmark-work-efficiency.ts +0 -334
  845. package/scripts/build-controller-ui.ts +0 -28
  846. package/scripts/check-npm-release.sh +0 -32
  847. package/scripts/check-public-release-surface.sh +0 -19
  848. package/scripts/check-release-published.sh +0 -57
  849. package/scripts/check-release-readiness.sh +0 -37
  850. package/scripts/check-release-version.mjs +0 -74
  851. package/scripts/check-tarball-install-smoke.sh +0 -67
  852. package/scripts/package-source-archive.sh +0 -147
  853. package/scripts/public-release-files.txt +0 -39
  854. package/scripts/public-release.ts +0 -174
  855. package/scripts/publish-release-tarball.sh +0 -27
  856. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  857. package/scripts/run-skill-evals.ts +0 -1022
  858. package/scripts/stage-runtime-release.ts +0 -39
  859. package/src/cli/controller/progress.ts +0 -656
  860. package/src/cli/controller/work-mode.ts +0 -181
  861. package/src/cli/local-bridge/console-assets.ts +0 -17
  862. package/src/cli/local-bridge/dashboard.ts +0 -17
  863. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  864. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  865. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  866. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  867. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  868. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  869. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  870. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  871. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  872. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  873. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  874. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  875. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  876. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  877. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  878. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  879. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  880. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  881. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  882. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  883. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  884. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  885. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  886. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  887. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  888. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  889. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  890. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  891. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
  892. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
  893. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  894. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
  895. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
  896. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
  897. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  898. package/src/runtime/control-plane/routing/index.ts +0 -1
  899. package/src/runtime/control-plane/routing/route-policy.ts +0 -382
  900. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -1,4 +1,4 @@
1
- import { createHash, randomUUID } from 'crypto';
1
+ import { createHash } from 'crypto';
2
2
  import { execFile } from 'child_process';
3
3
  import { promisify } from 'util';
4
4
  import { getRepository } from '../../../cli/repositories/registry';
@@ -10,27 +10,35 @@ import {
10
10
  previewAutomaticRuntimeMaintenance,
11
11
  type RuntimeMaintenanceActionId,
12
12
  } from '../../recovery/maintenance-executor';
13
- import { appendWorkEvidence, controllerSessionBlocksRecovery, getControllerSession, getWorkContract, isTerminalWorkContractStatus, listHandoffItems } from '../../control-plane/facade';
14
- import { launchSuperController } from '../../control-plane/launcher/thin-launcher';
15
- import { getExternalControllerLaunchReservation } from '../../control-plane/launcher/launch-reservation-store';
16
- import { runStandaloneChatgptPrompt, runWorkChatgptContinuation } from '../../control-plane/launcher/chatgpt-work-continuation';
13
+ import { appendWorkEvidence, getWorkContract, semanticWorkState } from '../../../../packages/kernel/work/api/index';
17
14
  import {
18
- beginInitialControllerRoundDispatch,
19
- buildControllerRoundRelayPrompt,
20
- finishControllerRoundRelayDispatch,
21
- } from '../../control-plane/facade/controller-round-relay';
15
+ controllerSessionBlocksRecovery,
16
+ getControllerSession,
17
+ getControllerWorkBinding,
18
+ getRetainedControllerSession,
19
+ } from '../../../../packages/kernel/controller/api/index';
22
20
  import {
21
+ applyScheduleFailure,
22
+ applyScheduleRetryableFailure,
23
+ cronDue,
24
+ evaluateScheduleOccurrenceAdmission,
25
+ evaluateScheduleTriggerEligibility,
23
26
  getOccurrence,
24
27
  getSchedule,
25
28
  listActiveOccurrences,
26
29
  listOccurrences,
27
- recordScheduleOccurrenceHandoff,
30
+ reclaimStaleCreatedOccurrences,
28
31
  listSchedules,
29
32
  saveOccurrence,
30
33
  saveScheduleDecision,
31
34
  updateSchedule,
32
- } from './store';
33
- import { applyScheduleFailure, applyScheduleRetryableFailure } from './settlement';
35
+ } from '../../../../packages/kernel/scheduler/api/index';
36
+ export { cronDue };
37
+ import { ensureScheduledControllerBinding } from '../../root/scheduled-controller-composition';
38
+ import { reconcileControllerProgression } from '../../root/controller-progression-composition';
39
+ import { listUserRequests } from '../../../../packages/kernel/identity/api/index';
40
+ import { runStandaloneChatgptPrompt } from '../../control-plane/launcher/chatgpt-work-continuation';
41
+
34
42
  import { classifyScheduledBrowserObservation, executeScheduledBrowserProbe } from './browser-probe';
35
43
  import { executeScheduledGithubIssueWatch } from './github-issue-watch';
36
44
  import type {
@@ -38,7 +46,7 @@ import type {
38
46
  ScheduleDecisionType,
39
47
  ScheduleOccurrence,
40
48
  ScheduleTriggerContext,
41
- } from './types';
49
+ } from '../../../../packages/kernel/scheduler/api/index';
42
50
 
43
51
  const execFileAsync = promisify(execFile);
44
52
  const LIVE_MAINTENANCE_OPERATION = 'runtime_maintenance_apply';
@@ -65,128 +73,6 @@ function retiredScheduleReason(schedule: RepositorySchedule): string | undefined
65
73
  return undefined;
66
74
  }
67
75
 
68
- function normalizedWindow(minutes: number, at = Date.now()): string {
69
- return String(Math.floor(at / (Math.max(1, minutes) * 60_000)));
70
- }
71
-
72
- interface ZonedDateParts {
73
- year: number;
74
- month: number;
75
- day: number;
76
- hour: number;
77
- minute: number;
78
- weekday: number;
79
- }
80
-
81
- function zonedDateParts(at: number, timezone = 'UTC'): ZonedDateParts {
82
- let formatter: Intl.DateTimeFormat;
83
- try {
84
- formatter = new Intl.DateTimeFormat('en-US-u-ca-iso8601', {
85
- timeZone: timezone,
86
- year: 'numeric', month: '2-digit', day: '2-digit',
87
- hour: '2-digit', minute: '2-digit', weekday: 'short',
88
- hourCycle: 'h23',
89
- });
90
- } catch {
91
- throw new Error(`SCHEDULE_TIMEZONE_INVALID: ${timezone}`);
92
- }
93
- const values = Object.fromEntries(formatter.formatToParts(new Date(at)).map((entry) => [entry.type, entry.value]));
94
- const weekdays: Record<string, number> = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
95
- return {
96
- year: Number(values.year),
97
- month: Number(values.month),
98
- day: Number(values.day),
99
- hour: Number(values.hour),
100
- minute: Number(values.minute),
101
- weekday: weekdays[String(values.weekday)] ?? 0,
102
- };
103
- }
104
-
105
- function fixedCronTime(expression: string | undefined): { minute: number; hour: number; day: string; month: string; weekday: string } | undefined {
106
- if (!expression) return undefined;
107
- const fields = expression.trim().split(/\s+/);
108
- if (fields.length !== 5 || !/^\d+$/.test(fields[0]) || !/^\d+$/.test(fields[1])) return undefined;
109
- const minute = Number(fields[0]);
110
- const hour = Number(fields[1]);
111
- if (minute < 0 || minute > 59 || hour < 0 || hour > 23) return undefined;
112
- return { minute, hour, day: fields[2], month: fields[3], weekday: fields[4] };
113
- }
114
-
115
- function cronWindowKey(schedule: RepositorySchedule, at = Date.now()): string {
116
- const timezone = schedule.trigger.timezone ?? 'UTC';
117
- const parts = zonedDateParts(at, timezone);
118
- const fixed = fixedCronTime(schedule.trigger.cronExpression);
119
- if (fixed) {
120
- return `${parts.year}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}@${String(fixed.hour).padStart(2, '0')}:${String(fixed.minute).padStart(2, '0')}[${timezone}]`;
121
- }
122
- return `${parts.year}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}T${String(parts.hour).padStart(2, '0')}:${String(parts.minute).padStart(2, '0')}[${timezone}]`;
123
- }
124
-
125
- function triggerWindowKey(schedule: RepositorySchedule, context: ScheduleTriggerContext | undefined, at = Date.now()): string {
126
- // Explicit control-plane triggers use a separate identity namespace from timer/cron windows.
127
- // A caller-supplied event/request id is the stable retry key; otherwise each manual trigger is distinct.
128
- if (context?.source === 'manual' && schedule.trigger.type !== 'manual') {
129
- const stableManualKey = context.eventId?.trim();
130
- return `manual:${stableManualKey || randomUUID()}`;
131
- }
132
- switch (schedule.trigger.type) {
133
- case 'cron':
134
- return cronWindowKey(schedule, at);
135
- case 'calendar':
136
- return schedule.trigger.calendarAt ?? new Date(at).toISOString().slice(0, 16);
137
- case 'repository-event':
138
- return context?.eventId?.trim() || `${context?.eventName ?? schedule.trigger.eventName ?? 'event'}:${normalizedWindow(1, at)}`;
139
- case 'dependency-checkpoint':
140
- return createHash('sha256').update(JSON.stringify(schedule.trigger.dependencyJobIds ?? [])).digest('hex').slice(0, 20);
141
- case 'manual':
142
- return context?.eventId?.trim() || normalizedWindow(1, at);
143
- case 'condition':
144
- case 'interval':
145
- default:
146
- return normalizedWindow(schedule.trigger.everyMinutes ?? 60, at);
147
- }
148
- }
149
-
150
- function cronFieldMatches(value: number, field: string, min: number, max: number): boolean {
151
- return field.split(',').some((part) => {
152
- const trimmed = part.trim();
153
- if (trimmed === '*') return true;
154
- const stepMatch = trimmed.match(/^\*\/(\d+)$/);
155
- if (stepMatch) return value % Math.max(1, Number(stepMatch[1])) === 0;
156
- const rangeMatch = trimmed.match(/^(\d+)-(\d+)(?:\/(\d+))?$/);
157
- if (rangeMatch) {
158
- const start = Math.max(min, Number(rangeMatch[1]));
159
- const end = Math.min(max, Number(rangeMatch[2]));
160
- const step = Math.max(1, Number(rangeMatch[3] ?? 1));
161
- return value >= start && value <= end && (value - start) % step === 0;
162
- }
163
- const exact = Number(trimmed);
164
- return Number.isInteger(exact) && exact >= min && exact <= max && value === exact;
165
- });
166
- }
167
-
168
- export function cronDue(
169
- expression: string | undefined,
170
- at = Date.now(),
171
- timezone = 'UTC',
172
- catchUpMinutes = 0,
173
- ): boolean {
174
- if (!expression) return false;
175
- const fields = expression.trim().split(/\s+/);
176
- if (fields.length !== 5) throw new Error(`SCHEDULE_CRON_INVALID: expected five fields, received ${fields.length}`);
177
- const parts = zonedDateParts(at, timezone);
178
- const calendarMatches = cronFieldMatches(parts.day, fields[2], 1, 31)
179
- && cronFieldMatches(parts.month, fields[3], 1, 12)
180
- && cronFieldMatches(parts.weekday, fields[4], 0, 6);
181
- if (!calendarMatches) return false;
182
- if (cronFieldMatches(parts.minute, fields[0], 0, 59) && cronFieldMatches(parts.hour, fields[1], 0, 23)) return true;
183
- const fixed = fixedCronTime(expression);
184
- if (!fixed || catchUpMinutes <= 0) return false;
185
- const scheduledMinute = fixed.hour * 60 + fixed.minute;
186
- const currentMinute = parts.hour * 60 + parts.minute;
187
- return currentMinute >= scheduledMinute && currentMinute - scheduledMinute <= Math.min(24 * 60, catchUpMinutes);
188
- }
189
-
190
76
  async function workspaceDirty(controllerHome: string, repoId: string): Promise<boolean> {
191
77
  try {
192
78
  const repository = getRepository(repoId, controllerHome, { includeRemoved: true });
@@ -199,64 +85,6 @@ async function workspaceDirty(controllerHome: string, repoId: string): Promise<b
199
85
  }
200
86
  }
201
87
 
202
- async function triggerDue(
203
- controllerHome: string,
204
- schedule: RepositorySchedule,
205
- force: boolean,
206
- context?: ScheduleTriggerContext,
207
- ): Promise<{ due: boolean; reason?: string; evidence?: Record<string, unknown> }> {
208
- if (force && schedule.trigger.type !== 'repository-event') return { due: true };
209
- switch (schedule.trigger.type) {
210
- case 'manual':
211
- return { due: force, reason: force ? undefined : 'Manual Schedule requires an explicit trigger.' };
212
- case 'interval':
213
- return { due: true };
214
- case 'cron':
215
- return {
216
- due: cronDue(schedule.trigger.cronExpression, Date.now(), schedule.trigger.timezone ?? 'UTC', schedule.trigger.catchUpMinutes ?? 0),
217
- reason: `Cron expression is not due in the current ${schedule.trigger.timezone ?? 'UTC'} minute or catch-up window.`,
218
- };
219
- case 'calendar': {
220
- const at = Date.parse(schedule.trigger.calendarAt ?? '');
221
- if (!Number.isFinite(at)) throw new Error('SCHEDULE_CALENDAR_INVALID: calendarAt must be an ISO-8601 timestamp');
222
- return { due: Date.now() >= at, reason: 'Calendar trigger is not due yet.', evidence: { calendarAt: schedule.trigger.calendarAt } };
223
- }
224
- case 'repository-event': {
225
- const expected = schedule.trigger.eventName?.trim();
226
- const actual = context?.eventName?.trim();
227
- const due = context?.source === 'repository-event' && Boolean(actual) && (!expected || expected === actual);
228
- return { due, reason: due ? undefined : 'Repository event did not match this Schedule.', evidence: { expected, actual, eventId: context?.eventId } };
229
- }
230
- case 'dependency-checkpoint': {
231
- const dependencyJobIds = schedule.trigger.dependencyJobIds ?? [];
232
- if (dependencyJobIds.length === 0) throw new Error('SCHEDULE_DEPENDENCY_REQUIRED: dependency checkpoint has no Job ids');
233
- const jobs = dependencyJobIds.map((jobId) => findExecutionJob(controllerHome, jobId));
234
- const due = jobs.every((job) => job?.status === 'succeeded');
235
- return {
236
- due,
237
- reason: due ? undefined : 'Dependency checkpoint is not ready.',
238
- evidence: { dependencies: dependencyJobIds.map((jobId, index) => ({ jobId, status: jobs[index]?.status ?? 'missing' })) },
239
- };
240
- }
241
- case 'condition': {
242
- const condition = schedule.trigger.condition;
243
- if (!condition) throw new Error('SCHEDULE_CONDITION_REQUIRED');
244
- if (condition.kind === 'repository_clean') {
245
- const clean = !(await workspaceDirty(controllerHome, schedule.repoId));
246
- return { due: clean, reason: clean ? undefined : 'Repository is not clean.', evidence: { clean } };
247
- }
248
- if (condition.kind === 'job_succeeded' || condition.kind === 'job_terminal') {
249
- const job = condition.jobId ? findExecutionJob(controllerHome, condition.jobId) : undefined;
250
- const terminal = Boolean(job && ['succeeded', 'failed', 'timed_out', 'cancelled', 'orphaned', 'stale', 'human_attention_required'].includes(job.status));
251
- const due = condition.kind === 'job_succeeded' ? job?.status === 'succeeded' : terminal;
252
- return { due, reason: due ? undefined : `Condition ${condition.kind} is not met.`, evidence: { jobId: condition.jobId, status: job?.status ?? 'missing' } };
253
- }
254
- }
255
- default:
256
- return { due: false, reason: 'Unsupported Schedule trigger.' };
257
- }
258
- }
259
-
260
88
  function workBoundScheduleWorkId(schedule: RepositorySchedule): string | undefined {
261
89
  if (schedule.action.operation !== EXTERNAL_CONTROLLER_WAKE_OPERATION
262
90
  && schedule.action.operation !== BROWSER_PROBE_OPERATION) return undefined;
@@ -264,30 +92,18 @@ function workBoundScheduleWorkId(schedule: RepositorySchedule): string | undefin
264
92
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
265
93
  }
266
94
 
267
- const HUMAN_ONLY_HANDOFF_REASONS = new Set([
268
- 'policy_approval_required',
269
- 'missing_authorization',
270
- 'invalid_objective',
271
- 'destructive_action_requires_confirmation',
272
- ]);
273
-
274
- function handoffRequiresHumanReview(item: ReturnType<typeof listHandoffItems>[number]): boolean {
275
- if (item.approvalAction) return true;
276
- if (!item.creationReason) return true;
277
- return HUMAN_ONLY_HANDOFF_REASONS.has(item.creationReason);
278
- }
279
-
280
95
  async function stopReason(controllerHome: string, schedule: RepositorySchedule): Promise<string | undefined> {
281
96
  const projection = readRepositoryProjection(controllerHome, schedule.repoId);
282
97
  const workId = workBoundScheduleWorkId(schedule);
283
98
  const work = workId ? getWorkContract({ controllerHome, repoId: schedule.repoId }, workId) : undefined;
284
99
  if (schedule.stopConditions.includes('human_review_required')) {
100
+ const pendingUserRequests = listUserRequests(controllerHome, 'pending');
285
101
  if (workId) {
286
- const activeHandoff = listHandoffItems({ controllerHome, repoId: schedule.repoId, status: 'active', limit: 100 })
287
- .find((item) => item.workId === workId && handoffRequiresHumanReview(item));
288
- if (activeHandoff) return `Work ${workId} has active Handoff ${activeHandoff.id} requiring human review.`;
289
- } else if (projection.currentAttention.length > 0) {
290
- return 'Repository has jobs requiring human attention.';
102
+ const request = pendingUserRequests.find((item) => item.targetScope?.workId === workId);
103
+ if (request) return `Work ${workId} has pending UserRequest ${request.requestId} requiring human action.`;
104
+ } else {
105
+ const request = pendingUserRequests.find((item) => item.targetScope?.repoId === schedule.repoId);
106
+ if (request) return `Repository ${schedule.repoId} has pending UserRequest ${request.requestId}.`;
291
107
  }
292
108
  }
293
109
  if (schedule.stopConditions.includes('release_ready') && projection.releaseFrozen) return 'Repository is in release freeze.';
@@ -430,20 +246,6 @@ export function classifyChatgptWakeFailure(reason: string): ChatgptWakeFailureCl
430
246
  return 'ordinary_failure';
431
247
  }
432
248
 
433
- function externalControllerLaunchArgs(args: Record<string, unknown>, controllerType: string): string[] {
434
- const launchArgs = Array.isArray(args.launch_args) ? args.launch_args.map(String) : [];
435
- if (controllerType !== 'chatgpt') return launchArgs;
436
- const explicit = new Set(launchArgs.filter((value) => value.startsWith('--')).map((value) => value.split('=', 1)[0]));
437
- const add = (flag: string, value: unknown) => {
438
- if (explicit.has(flag) || typeof value !== 'string' || !value.trim()) return;
439
- launchArgs.push(flag, value.trim());
440
- };
441
- add('--model', args.model);
442
- add('--reasoning', args.reasoning);
443
- add('--tab-policy', args.tab_policy);
444
- return launchArgs;
445
- }
446
-
447
249
  async function executeExternalControllerWake(
448
250
  controllerHome: string,
449
251
  schedule: RepositorySchedule,
@@ -454,167 +256,228 @@ async function executeExternalControllerWake(
454
256
  options: { transientWakeFailure?: boolean } = {},
455
257
  ): Promise<ScheduleOccurrence> {
456
258
  const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
457
- const controllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : 'chatgpt';
458
259
  if (!workId) {
459
260
  return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'EXTERNAL_CONTROLLER_WAKE_WORK_ID_REQUIRED');
460
261
  }
461
- if (!['chatgpt', 'codex', 'claude', 'grok'].includes(controllerType)) {
462
- return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'EXTERNAL_CONTROLLER_WAKE_TYPE_INVALID');
463
- }
464
262
  const workStore = { controllerHome, repoId: schedule.repoId };
465
263
  const work = getWorkContract(workStore, workId);
466
264
  if (!work) {
467
265
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ enabled: false, pausedReason: `Work ${workId} no longer exists.`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
468
266
  return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `EXTERNAL_CONTROLLER_WAKE_WORK_NOT_FOUND:${workId}`);
469
267
  }
470
- if (isTerminalWorkContractStatus(work.status)) {
471
- updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ enabled: false, pausedReason: `Work ${workId} is terminal (${work.status}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
472
- return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); automatic continuation stopped.`);
268
+ if (semanticWorkState(work) !== 'open') {
269
+ updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ enabled: false, pausedReason: `Work ${workId} is terminal (${work.semanticState}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
270
+ return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.semanticState}); automatic continuation stopped.`);
271
+ }
272
+
273
+ const retainedSession = getRetainedControllerSession(workStore, workId);
274
+ if (!retainedSession) {
275
+ return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `SCHEDULE_CONTINUATION_CONTROLLER_SESSION_REQUIRED:${workId}`);
276
+ }
277
+ const requestedControllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : '';
278
+ if (requestedControllerType && requestedControllerType !== retainedSession.controllerType) {
279
+ return decideOccurrence(
280
+ controllerHome,
281
+ schedule,
282
+ occurrence,
283
+ 'operation_blocked',
284
+ 'skipped',
285
+ `SCHEDULE_CONTINUATION_CONTROLLER_TYPE_MISMATCH:${workId}:expected=${retainedSession.controllerType}:requested=${requestedControllerType}`,
286
+ );
473
287
  }
474
- const existingOwner = getControllerSession(workStore, workId);
475
288
  const occurrenceNowMs = Date.parse(timestamp);
476
289
  const nowMs = Number.isFinite(occurrenceNowMs) ? occurrenceNowMs : Date.now();
477
290
  if (workHasActiveExecution(controllerHome, schedule.repoId, workId)) {
478
291
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
479
292
  return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} has active execution; duplicate Controller wake suppressed.`);
480
293
  }
294
+ const existingOwner = getControllerSession(workStore, workId);
481
295
  if (existingOwner && controllerSessionBlocksRecovery(workStore, workId, { nowMs })) {
482
296
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
483
297
  return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} has a recently active Controller ${existingOwner.controllerId}.`);
484
298
  }
485
- const launchReservation = getExternalControllerLaunchReservation(workStore, workId);
486
- if (launchReservation) {
487
- updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
488
- return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} already has a pending external Controller launch ${launchReservation.reservationId}.`);
299
+
300
+ const repository = getRepository(schedule.repoId, controllerHome, { includeRemoved: true });
301
+ let bindingId = typeof args.controller_binding_id === 'string' ? args.controller_binding_id.trim() : '';
302
+ let bindingRecord = getControllerWorkBinding(workStore, workId);
303
+ if (bindingRecord && bindingRecord.binding.bindingId !== bindingId) {
304
+ return decideOccurrence(
305
+ controllerHome,
306
+ schedule,
307
+ occurrence,
308
+ 'operation_blocked',
309
+ 'skipped',
310
+ `SCHEDULE_CONTINUATION_BINDING_DRIFT:${workId}:expected=${bindingId}:actual=${bindingRecord.binding.bindingId}`,
311
+ );
312
+ }
313
+
314
+ const hadLegacySessionIdentity = typeof args.controller_session_id === 'string' && args.controller_session_id.trim().length > 0;
315
+
316
+ // Explicit compatibility migration for schedules written before Kernel V2 B4.
317
+ // Provider metadata is copied once into the adapter-owned binding and the
318
+ // Schedule is rewritten to exact Work/ControllerSession/ControllerBinding ids.
319
+ if (!bindingRecord) {
320
+ const binding = ensureScheduledControllerBinding(
321
+ { controllerHome, repoId: schedule.repoId },
322
+ { workId, session: retainedSession, scheduleName: schedule.name, args },
323
+ );
324
+ bindingId = binding.bindingId;
325
+ bindingRecord = getControllerWorkBinding(workStore, workId);
326
+ if (!bindingRecord || bindingRecord.binding.bindingId !== bindingId) {
327
+ throw new Error(`SCHEDULE_CONTINUATION_BINDING_MIGRATION_FAILED:${workId}`);
328
+ }
489
329
  }
330
+
331
+ const continuationHintForMigration = typeof args.continuation_hint === 'string'
332
+ ? args.continuation_hint
333
+ : typeof args.continuation_prompt === 'string'
334
+ ? args.continuation_prompt
335
+ : undefined;
336
+ const relayScopeIdForMigration = typeof args.relay_scope_id === 'string' ? args.relay_scope_id.trim() : undefined;
337
+ if (hadLegacySessionIdentity || bindingId !== bindingRecord.binding.bindingId) {
338
+ bindingId = bindingRecord.binding.bindingId;
339
+ schedule = updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, (current) => ({
340
+ action: {
341
+ ...current.action,
342
+ arguments: {
343
+ work_id: workId,
344
+ controller_type: retainedSession.controllerType,
345
+ controller_binding_id: bindingId,
346
+ ...(relayScopeIdForMigration ? { relay_scope_id: relayScopeIdForMigration } : {}),
347
+ ...(continuationHintForMigration?.trim() ? { continuation_hint: continuationHintForMigration.trim() } : {}),
348
+ },
349
+ },
350
+ }));
351
+ }
352
+
353
+ const controllerType = retainedSession.controllerType;
354
+ const continuationHint = typeof schedule.action.arguments?.continuation_hint === 'string'
355
+ ? schedule.action.arguments.continuation_hint
356
+ : undefined;
357
+ const relayScopeId = typeof schedule.action.arguments?.relay_scope_id === 'string'
358
+ ? schedule.action.arguments.relay_scope_id
359
+ : undefined;
360
+ const controllerRoundOccurrenceId = typeof occurrence.triggerContext?.data?.controllerRoundOccurrenceId === 'string'
361
+ && occurrence.triggerContext.data.controllerRoundOccurrenceId.trim()
362
+ ? occurrence.triggerContext.data.controllerRoundOccurrenceId.trim()
363
+ : occurrence.occurrenceId;
490
364
  const wakeDecision = decideOccurrence(
491
365
  controllerHome,
492
366
  schedule,
493
367
  occurrence,
494
368
  'execute',
495
369
  'running',
496
- 'Deterministic external Controller wake is starting through Thin Launcher.',
370
+ 'Deterministic Controller continuation is starting through Kernel Scheduler and ControllerHost.',
497
371
  occurrenceDecisionEvidence({
498
372
  operation: schedule.action.operation,
499
373
  workId,
500
374
  controllerType,
501
- ...(controllerType === 'chatgpt' ? {
502
- model: typeof args.model === 'string' ? args.model : 'gpt-5.6',
503
- reasoning: typeof args.reasoning === 'string' ? args.reasoning : 'high',
504
- tabPolicy: typeof args.tab_policy === 'string' ? args.tab_policy : 'auto',
505
- } : {}),
375
+ controllerSessionId: retainedSession.sessionId,
376
+ controllerBindingId: bindingRecord.binding.bindingId,
506
377
  ...extraEvidence,
507
378
  }),
508
379
  );
380
+
509
381
  try {
510
- const repository = getRepository(schedule.repoId, controllerHome, { includeRemoved: true });
511
- const continuationPrompt = typeof args.continuation_prompt === 'string'
512
- ? args.continuation_prompt
513
- : `Scheduled continuation ${occurrence.occurrenceId} from ${schedule.scheduleId}. Read current Forge state and continue only the bounded Work objective.`;
514
- if (controllerType === 'chatgpt') {
515
- const reasoning = args.reasoning === 'medium' || args.reasoning === 'xhigh' ? args.reasoning : 'high';
516
- const tabPolicy = args.tab_policy === 'reuse' || args.tab_policy === 'new' ? args.tab_policy : 'auto';
517
- const timeoutMs = externalControllerWakeTimeoutMs(args.timeout_ms);
518
- let relay;
519
- try {
520
- relay = beginInitialControllerRoundDispatch(workStore, {
521
- workId,
522
- identity: {
523
- controllerId: `schedule:${schedule.scheduleId}`,
524
- principalId: 'forge-scheduler',
525
- controllerInstanceId: 'forge-runtime-scheduler',
526
- sessionId: occurrence.occurrenceId,
527
- },
528
- requirementId: work.requirementId,
529
- browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
530
- conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
531
- });
532
- if (relay.status === 'blocked') throw new Error(`CONTROLLER_RELAY_LAUNCH_BLOCKED: ${relay.blockedReason ?? relay.relayScopeId}`);
533
- const relayPrompt = `${buildControllerRoundRelayPrompt(workStore, relay, { exactOriginWork: true })}\n\nScheduled continuation hint: ${continuationPrompt}`;
534
- const dispatched = await awaitExternalControllerWake(runWorkChatgptContinuation({
535
- controllerHome,
536
- repoId: schedule.repoId,
537
- repoRoot: repository.canonicalRoot,
538
- workId,
539
- prompt: relayPrompt,
540
- title: schedule.name,
541
- browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
542
- conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
543
- model: typeof args.model === 'string' ? args.model : 'gpt-5.6',
544
- reasoning,
545
- tabPolicy,
546
- timeoutMs,
547
- }), timeoutMs);
548
- if (dispatched.status === 'failed') throw new Error(dispatched.error?.message ?? 'CHATGPT_WORK_CONTINUATION_FAILED');
549
- const completedRelay = finishControllerRoundRelayDispatch(workStore, {
550
- workId,
551
- ok: true,
552
- browserSessionId: dispatched.browserSessionId,
553
- conversationUrl: dispatched.conversationUrl,
554
- });
555
- updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, (current) => ({
556
- lastTriggeredAt: timestamp,
557
- lastOccurrenceId: occurrence.occurrenceId,
558
- consecutiveFailures: 0,
559
- nextEligibleAt: undefined,
560
- ...(current.enabled ? { pausedReason: undefined } : {}),
561
- }));
562
- const succeededOccurrence = saveOccurrence(controllerHome, {
563
- ...wakeDecision,
564
- status: 'succeeded',
565
- reason: `ChatGPT dispatch action succeeded via ${dispatched.browserSessionId}; relay ${completedRelay?.relayScopeId ?? relay.relayScopeId} remains dispatched until the new Controller claims Work ${workId}.`,
566
- });
567
- appendWorkEvidence(workStore, workId, {
568
- evidenceId: succeededOccurrence.occurrenceId,
569
- title: 'scheduled ChatGPT continuation dispatched',
570
- summary: `Schedule ${schedule.scheduleId} timer occurrence ${succeededOccurrence.occurrenceId} dispatched the Work-bound ChatGPT continuation successfully.`,
571
- detailLevel: 'summary',
572
- });
573
- return succeededOccurrence;
574
- } catch (error) {
575
- if (relay) {
576
- finishControllerRoundRelayDispatch(workStore, {
577
- workId,
578
- ok: false,
579
- error: error instanceof Error ? error.message : String(error),
580
- });
581
- }
582
- if (error instanceof Error && error.message.startsWith('CONTROLLER_RELAY_ROUND_ALREADY_OPEN:')) {
583
- updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
584
- return decideOccurrence(controllerHome, schedule, wakeDecision, 'nothing_to_do', 'skipped', `Work ${workId} already has an open ChatGPT controller round awaiting claim or disposition.`);
585
- }
586
- throw error;
587
- }
382
+ const progression = await reconcileControllerProgression(
383
+ { controllerHome, repoId: schedule.repoId, repoRoot: repository.canonicalRoot },
384
+ {
385
+ occurrenceId: controllerRoundOccurrenceId,
386
+ workId,
387
+ relayScopeId,
388
+ continuationHint,
389
+ allowSemanticWaitRecovery: controllerType === 'chatgpt',
390
+ scheduleName: schedule.name,
391
+ bindingArgs: args,
392
+ },
393
+ );
394
+
395
+ if (progression.status === 'terminal_or_missing') {
396
+ updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({
397
+ enabled: false,
398
+ pausedReason: progression.reason,
399
+ lastTriggeredAt: timestamp,
400
+ lastOccurrenceId: occurrence.occurrenceId,
401
+ }));
402
+ return saveOccurrence(controllerHome, {
403
+ ...wakeDecision,
404
+ status: 'skipped',
405
+ decision: 'nothing_to_do',
406
+ reason: progression.reason,
407
+ });
588
408
  }
589
- const launched = await launchSuperController({
590
- work: { controllerHome, repoId: schedule.repoId },
591
- handoff: { controllerHome, repoId: schedule.repoId },
592
- }, {
593
- controllerType: controllerType as 'codex' | 'claude' | 'grok',
594
- executable: typeof args.executable === 'string' ? args.executable : undefined,
595
- args: externalControllerLaunchArgs(args, controllerType),
596
- workId,
597
- handoffId: typeof args.handoff_id === 'string' ? args.handoff_id : undefined,
598
- browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
599
- conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
600
- launchReservationMs: typeof args.launch_reservation_ms === 'number' ? args.launch_reservation_ms : typeof args.lease_ms === 'number' ? args.lease_ms : undefined,
601
- continuationPrompt,
602
- cwd: repository.canonicalRoot,
603
- });
409
+
410
+ if (progression.status === 'semantic_wait'
411
+ || progression.status === 'wait_for_user'
412
+ || progression.status === 'retained_session_missing'
413
+ || progression.status === 'human_controller') {
414
+ updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({
415
+ lastTriggeredAt: timestamp,
416
+ lastOccurrenceId: occurrence.occurrenceId,
417
+ }));
418
+ return saveOccurrence(controllerHome, {
419
+ ...wakeDecision,
420
+ status: 'skipped',
421
+ decision: 'nothing_to_do',
422
+ reason: progression.reason ?? `Work ${workId} is not currently eligible for provider progression.`,
423
+ });
424
+ }
425
+
426
+ if (progression.status === 'rejected') {
427
+ throw new Error(progression.reason ?? 'CONTROLLER_HOST_RESUME_REJECTED');
428
+ }
429
+
430
+ if (progression.status === 'chatgpt_not_enrolled') {
431
+ updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({
432
+ lastTriggeredAt: timestamp,
433
+ lastOccurrenceId: occurrence.occurrenceId,
434
+ }));
435
+ return saveOccurrence(controllerHome, {
436
+ ...wakeDecision,
437
+ status: 'skipped',
438
+ decision: 'nothing_to_do',
439
+ reason: `Workflow Supervisor enrollment is not yet ready (${progression.supervisorEnrollment.status}); no ChatGPT provider attempt was consumed.`,
440
+ });
441
+ }
442
+
443
+ const supervisorEnrollment = progression.status === 'chatgpt_enrolled'
444
+ ? progression.supervisorEnrollment
445
+ : { status: 'not_eligible' as const };
604
446
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, (current) => ({
605
447
  lastTriggeredAt: timestamp,
606
448
  lastOccurrenceId: occurrence.occurrenceId,
607
449
  consecutiveFailures: 0,
608
450
  nextEligibleAt: undefined,
609
- ...(current.enabled ? { pausedReason: undefined } : {}),
451
+ ...(supervisorEnrollment.status === 'enrolled'
452
+ ? { enabled: false, pausedReason: 'workflow_supervisor_owns_outer_turn' }
453
+ : current.enabled ? { pausedReason: undefined } : {}),
610
454
  }));
611
- return saveOccurrence(controllerHome, {
455
+ const dispatchedOccurrence = saveOccurrence(controllerHome, {
612
456
  ...wakeDecision,
613
- status: 'succeeded',
614
- reason: `External Controller wake started ${launched.controllerType} pid=${String(launched.pid ?? 'unknown')} for Work ${workId}.`,
457
+ status: 'dispatched',
458
+ reason: progression.status === 'chatgpt_enrolled'
459
+ ? `Workflow Supervisor reserved the exact ChatGPT effect ${progression.supervisorEnrollment.effectId ?? progression.relayScopeId}; Scheduler issued no provider send.`
460
+ : progression.reused
461
+ ? `Controller continuation occurrence ${occurrence.occurrenceId} was already durably dispatched; external replay suppressed and semantic round closure is still pending.`
462
+ : `ControllerHost accepted durable binding ${bindingRecord.binding.bindingId} for Work ${workId}; dispatch ${progression.providerDispatchReceiptId ?? progression.relayScopeId}; semantic round closure is still pending.`,
463
+ });
464
+ appendWorkEvidence(workStore, workId, {
465
+ evidenceId: dispatchedOccurrence.occurrenceId,
466
+ title: progression.status === 'chatgpt_enrolled'
467
+ ? 'scheduled Workflow Supervisor effect reserved'
468
+ : 'scheduled Controller continuation dispatched',
469
+ summary: progression.status === 'chatgpt_enrolled'
470
+ ? `Schedule ${schedule.scheduleId} materialized the exact ControllerRound occurrence and Supervisor effect; Supervisor owns ChatGPT delivery.`
471
+ : `Schedule ${schedule.scheduleId} occurrence ${dispatchedOccurrence.occurrenceId} resumed the exact Work-bound ControllerSession through ControllerHost.`,
472
+ detailLevel: 'summary',
615
473
  });
474
+ return dispatchedOccurrence;
616
475
  } catch (error) {
617
476
  const reason = error instanceof Error ? error.message : String(error);
477
+ if (reason.startsWith('CONTROLLER_CONTINUATION_ROUND_ALREADY_OPEN:') || reason.startsWith('CONTROLLER_CONTINUATION_ALREADY_DISPATCHING:')) {
478
+ updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
479
+ return decideOccurrence(controllerHome, schedule, wakeDecision, 'nothing_to_do', 'skipped', `Work ${workId} already has an open Controller round awaiting claim or disposition.`);
480
+ }
618
481
  const failureClass = controllerType === 'chatgpt' ? classifyChatgptWakeFailure(reason) : 'ordinary_failure';
619
482
  if (options.transientWakeFailure || failureClass === 'retryable_readiness' || failureClass === 'semantic_wait') {
620
483
  const semanticWait = failureClass === 'semantic_wait';
@@ -642,18 +505,20 @@ async function executeExternalControllerWake(
642
505
  decision: 'execute',
643
506
  reason,
644
507
  ...(failureClass === 'user_action_required' ? { pauseReason: reason } : {}),
645
- handoff: {
646
- title: `External Controller wake ${occurrence.occurrenceId} failed`,
647
- summary: 'Forge recorded the schedule trigger but could not wake the configured external Controller.',
648
- reason,
649
- creationReason: 'repeated_infrastructure_failure',
650
- blockingDecision: 'Repair the external Controller/browser launch path or update the saved session reference.',
651
- recommendedDecision: 'Inspect launcher/browser readiness, then retrigger this bounded continuation.',
652
- recommendedPrompt: `Resume Work ${workId} manually, inspect failed wake occurrence ${occurrence.occurrenceId}, and repair the configured external Controller launch path before unattended continuation resumes.`,
653
- statusSummary: 'Scheduled external Controller wake failed.',
654
- blockedBy: ['external_controller_wake_failed'],
655
- attemptedActions: [`schedule:${schedule.scheduleId}`, `work:${workId}`, `controller:${controllerType}`],
656
- },
508
+ ...(failureClass === 'user_action_required' ? {
509
+ handoff: {
510
+ title: `External Controller wake ${occurrence.occurrenceId} requires user authorization`,
511
+ summary: 'Forge cannot resume the configured ControllerSession until the required user authorization is restored.',
512
+ reason,
513
+ creationReason: 'missing_authorization' as const,
514
+ blockingDecision: 'Restore the required authenticated/authorized controller capability, then retry the same scheduled Work.',
515
+ recommendedDecision: 'Restore authorization only; do not create replacement Work or replay an unknown external effect.',
516
+ recommendedPrompt: `Restore authorization for Work ${workId} and retry scheduled continuation ${occurrence.occurrenceId}.`,
517
+ statusSummary: 'Scheduled Controller continuation is waiting on user authorization.',
518
+ blockedBy: ['controller_authorization_required'],
519
+ attemptedActions: [`schedule:${schedule.scheduleId}`, `work:${workId}`, `controller:${controllerType}`, `session:${retainedSession.sessionId}`, `binding:${bindingRecord.binding.bindingId}`],
520
+ },
521
+ } : {}),
657
522
  });
658
523
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId }));
659
524
  return failed.occurrence ?? saveOccurrence(controllerHome, { ...wakeDecision, status: 'failed', reason });
@@ -673,11 +538,23 @@ export async function evaluateSchedule(
673
538
  // Preserve retired schedule records as evidence, but do not create a new
674
539
  // occurrence or resume a controller/external action from their old payload.
675
540
  if (retiredScheduleReason(schedule)) return undefined;
676
- if (!schedule.enabled && !force) return undefined;
677
- const due = await triggerDue(controllerHome, schedule, force, triggerContext);
678
- if (!due.due) return undefined;
541
+ const eligibilityNowMs = Date.now();
542
+ const triggerJobIds = new Set<string>(schedule.trigger.dependencyJobIds ?? []);
543
+ if (schedule.trigger.condition?.jobId) triggerJobIds.add(schedule.trigger.condition.jobId);
544
+ const jobStatuses = Object.fromEntries([...triggerJobIds].map((jobId) => [jobId, findExecutionJob(controllerHome, jobId)?.status]));
545
+ const repositoryClean = schedule.trigger.condition?.kind === 'repository_clean'
546
+ ? !(await workspaceDirty(controllerHome, schedule.repoId))
547
+ : undefined;
548
+ const due = evaluateScheduleTriggerEligibility(schedule, {
549
+ nowMs: eligibilityNowMs,
550
+ force,
551
+ context: triggerContext,
552
+ repositoryClean,
553
+ jobStatuses,
554
+ });
555
+ if (!due.due || !due.windowKey) return undefined;
679
556
 
680
- const key = triggerWindowKey(schedule, triggerContext);
557
+ const key = due.windowKey;
681
558
  const occurrenceId = `OCC-${schedule.scheduleId}-${createHash('sha256').update(key).digest('hex').slice(0, 20)}`;
682
559
  const existing = getOccurrence(controllerHome, schedule.repoId, occurrenceId);
683
560
  if (existing) return existing;
@@ -696,41 +573,36 @@ export async function evaluateSchedule(
696
573
  updatedAt: timestamp,
697
574
  });
698
575
 
699
- // Semantic / model-backed schedules only record a trigger + Handoff.
700
- // Deterministic allowlisted operations continue into the local engine path.
576
+ // Semantic/model-owned schedule actions record the trigger only. A trigger is
577
+ // continuation input for the external Controller/Supervisor, not a human
578
+ // blocker and therefore must not manufacture Handoff/UserRequest state.
701
579
  if (!isDeterministicSchedule(schedule)) {
702
- const externalControllerHandoff = recordScheduleOccurrenceHandoff(
580
+ const recorded = decideOccurrence(
703
581
  controllerHome,
704
582
  schedule,
705
- decideOccurrence(
706
- controllerHome,
707
- schedule,
708
- occurrence,
709
- 'operation_blocked',
710
- 'skipped',
711
- 'Schedule execution is external-controller-owned; no ExecutionJob was created.',
712
- occurrenceDecisionEvidence({ operation: schedule.action.operation, trigger: occurrence.triggerContext?.source }),
713
- ),
714
- {
715
- title: `Schedule ${schedule.name} requires an external Controller`,
716
- summary: 'The scheduled trigger was recorded without dispatching a Kernel Job.',
717
- reason: 'Schedule execution is external-controller-owned.',
718
- creationReason: 'ambiguous_outcome',
719
- blockingDecision: 'Claim or create the related Work before executing the scheduled operation.',
720
- recommendedDecision: 'Review the trigger evidence and continue it through an explicitly claimed external Controller session.',
721
- recommendedPrompt: `Review schedule ${schedule.scheduleId} occurrence ${occurrence.occurrenceId}; create or claim Work for ${schedule.action.operation} and continue through Process Runtime or Thin Launcher.`,
722
- statusSummary: 'Schedule trigger is waiting for external Controller ownership.',
723
- blockedBy: ['external_controller_required'],
724
- attemptedActions: [`operation:${schedule.action.operation}`],
725
- },
583
+ occurrence,
584
+ 'operation_blocked',
585
+ 'skipped',
586
+ 'Schedule trigger recorded for external Controller continuation; no Kernel ExecutionJob or human Handoff was created.',
587
+ occurrenceDecisionEvidence({ operation: schedule.action.operation, trigger: occurrence.triggerContext?.source }),
726
588
  );
727
589
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId }));
728
- return externalControllerHandoff;
590
+ return recorded;
729
591
  }
730
592
 
731
593
  const recent = listOccurrences(controllerHome, schedule.repoId, schedule.scheduleId, 1000);
732
- const stop = await stopReason(controllerHome, schedule);
733
- if (stop) {
594
+ reclaimStaleCreatedOccurrences(controllerHome, schedule.repoId, schedule.scheduleId);
595
+ const active = listActiveOccurrences(controllerHome, schedule.repoId, schedule.scheduleId)
596
+ .filter((entry) => entry.occurrenceId !== occurrence.occurrenceId);
597
+ const admission = evaluateScheduleOccurrenceAdmission(schedule, {
598
+ nowMs: Date.now(),
599
+ force,
600
+ stopReason: await stopReason(controllerHome, schedule),
601
+ activeOccurrenceCount: active.length,
602
+ dailyRuntimeMinutes: dailyRuntimeMinutes(controllerHome, recent),
603
+ });
604
+ if (admission.kind === 'stop_condition') {
605
+ const stop = admission.reason ?? 'Schedule stop condition is active.';
734
606
  const stopped = decideOccurrence(controllerHome, schedule, occurrence, 'stopped', 'skipped', stop);
735
607
  if (!isLiveMaintenanceSchedule(schedule)) return stopped;
736
608
  const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
@@ -739,42 +611,25 @@ export async function evaluateSchedule(
739
611
  reason: stop,
740
612
  countFailure: false,
741
613
  pauseReason: stop,
742
- handoff: {
743
- title: `Scheduled maintenance occurrence ${occurrence.occurrenceId} stopped`,
744
- summary: 'A live maintenance occurrence was blocked by an explicit schedule stop condition and requires review before automation resumes.',
745
- reason: stop,
746
- creationReason: 'ambiguous_outcome',
747
- blockingDecision: 'Review the stop condition and decide whether automatic maintenance should stay paused.',
748
- recommendedDecision: 'Resolve the stop condition, then explicitly re-enable or retrigger the maintenance schedule.',
749
- recommendedPrompt: `Review maintenance schedule ${schedule.scheduleId} for repo ${schedule.repoId}, inspect stop condition ${stop}, and decide whether to resume the schedule.`,
750
- statusSummary: 'Scheduled maintenance occurrence stopped before dispatch.',
751
- blockedBy: ['schedule_stop_condition'],
752
- attemptedActions: [`schedule:${schedule.scheduleId}`, `operation:${schedule.action.operation}`],
753
- },
614
+
754
615
  });
755
616
  return failed.occurrence ?? stopped;
756
617
  }
757
-
758
- const active = listActiveOccurrences(controllerHome, schedule.repoId, schedule.scheduleId)
759
- .filter((entry) => entry.occurrenceId !== occurrence.occurrenceId);
760
- if (active.length >= schedule.policy.maxActiveOccurrences) {
761
- return decideOccurrence(controllerHome, schedule, occurrence, 'active_occurrence', 'skipped', 'Maximum active occurrences reached.');
618
+ if (admission.kind === 'active_occurrence') {
619
+ return decideOccurrence(controllerHome, schedule, occurrence, 'active_occurrence', 'skipped', admission.reason);
762
620
  }
763
- if (schedule.consecutiveFailures >= schedule.policy.maxFailures) {
621
+ if (admission.kind === 'failure_limit') {
764
622
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, (current) => ({
765
623
  enabled: false,
766
624
  pausedReason: current.enabled ? 'Maximum consecutive failures reached.' : current.pausedReason,
767
625
  }));
768
- return decideOccurrence(controllerHome, schedule, occurrence, 'stopped', 'skipped', 'Schedule paused after repeated failures.');
626
+ return decideOccurrence(controllerHome, schedule, occurrence, 'stopped', 'skipped', admission.reason);
769
627
  }
770
- if (schedule.nextEligibleAt && Date.parse(schedule.nextEligibleAt) > Date.now() && !force) {
771
- return decideOccurrence(controllerHome, schedule, occurrence, 'cooldown', 'skipped', `Schedule backoff remains active until ${schedule.nextEligibleAt}.`);
628
+ if (admission.kind === 'cooldown') {
629
+ return decideOccurrence(controllerHome, schedule, occurrence, 'cooldown', 'skipped', admission.reason);
772
630
  }
773
- if (schedule.lastTriggeredAt && Date.now() - Date.parse(schedule.lastTriggeredAt) < schedule.policy.cooldownMinutes * 60_000 && !force) {
774
- return decideOccurrence(controllerHome, schedule, occurrence, 'cooldown', 'skipped', 'Schedule is cooling down.');
775
- }
776
- if (dailyRuntimeMinutes(controllerHome, recent) >= schedule.policy.dailyBudgetMinutes) {
777
- return decideOccurrence(controllerHome, schedule, occurrence, 'budget_exhausted', 'skipped', 'Daily schedule budget exhausted.');
631
+ if (admission.kind === 'budget_exhausted') {
632
+ return decideOccurrence(controllerHome, schedule, occurrence, 'budget_exhausted', 'skipped', admission.reason);
778
633
  }
779
634
  if (schedule.policy.shadowMode) {
780
635
  if (isLiveMaintenanceSchedule(schedule)) {
@@ -793,22 +648,20 @@ export async function evaluateSchedule(
793
648
  reason: preview.blockedReason ?? 'Automatic maintenance preview blocked the occurrence.',
794
649
  countFailure: !preview.blockedPermanently,
795
650
  pauseReason: preview.blockedPermanently ? preview.blockedReason : undefined,
796
- handoff: {
797
- title: `Scheduled maintenance occurrence ${occurrence.occurrenceId} blocked`,
798
- summary: 'A bounded live maintenance occurrence was blocked before dispatch and requires a human decision.',
799
- reason: preview.blockedReason ?? 'Automatic maintenance preview blocked the occurrence.',
800
- creationReason: preview.blockedPermanently ? 'missing_authorization' : 'ambiguous_outcome',
801
- blockingDecision: preview.blockedPermanently
802
- ? 'Fix the schedule operation or authorization before automatic maintenance can resume.'
803
- : 'Inspect runtime maintenance readiness before allowing another unattended attempt.',
804
- recommendedDecision: preview.blockedPermanently
805
- ? 'Correct the automatic maintenance configuration and re-enable the schedule deliberately.'
806
- : 'Review the maintenance preview, resolve the blocker, and then retrigger the schedule intentionally.',
807
- recommendedPrompt: `Review blocked maintenance occurrence ${occurrence.occurrenceId} for schedule ${schedule.scheduleId}. Determine whether the schedule configuration or runtime maintenance readiness should be corrected before retrying.`,
808
- statusSummary: 'Scheduled maintenance occurrence blocked before dispatch.',
809
- blockedBy: [decision],
810
- attemptedActions: [`schedule:${schedule.scheduleId}`, `operation:${schedule.action.operation}`],
811
- },
651
+ ...(preview.blockedPermanently ? {
652
+ handoff: {
653
+ title: `Scheduled maintenance occurrence ${occurrence.occurrenceId} needs a user decision`,
654
+ summary: 'Automatic maintenance is permanently blocked and requires an explicit user decision before the schedule can resume.',
655
+ reason: preview.blockedReason ?? 'Automatic maintenance preview blocked the occurrence.',
656
+ creationReason: 'ambiguous_outcome' as const,
657
+ blockingDecision: 'Correct the configuration/authorization or intentionally keep this schedule paused.',
658
+ recommendedDecision: 'Review the permanent blocker and explicitly decide whether to repair configuration and resume.',
659
+ recommendedPrompt: `Review permanently blocked maintenance occurrence ${occurrence.occurrenceId} for schedule ${schedule.scheduleId} and decide whether to repair and resume it.`,
660
+ statusSummary: 'Scheduled maintenance is permanently blocked pending a user decision.',
661
+ blockedBy: [decision],
662
+ attemptedActions: [`schedule:${schedule.scheduleId}`, `operation:${schedule.action.operation}`],
663
+ },
664
+ } : {}),
812
665
  });
813
666
  return failed.occurrence ?? blocked;
814
667
  }
@@ -845,22 +698,20 @@ export async function evaluateSchedule(
845
698
  reason: preview.blockedReason ?? 'Automatic maintenance preview blocked the occurrence.',
846
699
  countFailure: !preview.blockedPermanently,
847
700
  pauseReason: preview.blockedPermanently ? preview.blockedReason : undefined,
848
- handoff: {
849
- title: `Scheduled maintenance occurrence ${occurrence.occurrenceId} blocked`,
850
- summary: 'A bounded live maintenance occurrence was blocked before dispatch and requires a human decision.',
851
- reason: preview.blockedReason ?? 'Automatic maintenance preview blocked the occurrence.',
852
- creationReason: preview.blockedPermanently ? 'missing_authorization' : 'ambiguous_outcome',
853
- blockingDecision: preview.blockedPermanently
854
- ? 'Fix the schedule operation or authorization before automatic maintenance can resume.'
855
- : 'Inspect runtime maintenance readiness before allowing another unattended attempt.',
856
- recommendedDecision: preview.blockedPermanently
857
- ? 'Correct the automatic maintenance configuration and re-enable the schedule deliberately.'
858
- : 'Review the maintenance preview, resolve the blocker, and then retrigger the schedule intentionally.',
859
- recommendedPrompt: `Review blocked maintenance occurrence ${occurrence.occurrenceId} for schedule ${schedule.scheduleId}. Determine whether the schedule configuration or runtime maintenance readiness should be corrected before retrying.`,
860
- statusSummary: 'Scheduled maintenance occurrence blocked before dispatch.',
861
- blockedBy: [decision],
862
- attemptedActions: [`schedule:${schedule.scheduleId}`, `operation:${schedule.action.operation}`],
863
- },
701
+ ...(preview.blockedPermanently ? {
702
+ handoff: {
703
+ title: `Scheduled maintenance occurrence ${occurrence.occurrenceId} needs a user decision`,
704
+ summary: 'Automatic maintenance is permanently blocked and requires an explicit user decision before the schedule can resume.',
705
+ reason: preview.blockedReason ?? 'Automatic maintenance preview blocked the occurrence.',
706
+ creationReason: 'ambiguous_outcome' as const,
707
+ blockingDecision: 'Correct the configuration/authorization or intentionally keep this schedule paused.',
708
+ recommendedDecision: 'Review the permanent blocker and explicitly decide whether to repair configuration and resume.',
709
+ recommendedPrompt: `Review permanently blocked maintenance occurrence ${occurrence.occurrenceId} for schedule ${schedule.scheduleId} and decide whether to repair and resume it.`,
710
+ statusSummary: 'Scheduled maintenance is permanently blocked pending a user decision.',
711
+ blockedBy: [decision],
712
+ attemptedActions: [`schedule:${schedule.scheduleId}`, `operation:${schedule.action.operation}`],
713
+ },
714
+ } : {}),
864
715
  });
865
716
  return failed.occurrence ?? blocked;
866
717
  }
@@ -930,18 +781,7 @@ export async function evaluateSchedule(
930
781
  outcome: 'failed',
931
782
  decision: 'execute',
932
783
  reason,
933
- handoff: {
934
- title: `GitHub issue watcher occurrence ${occurrence.occurrenceId} failed`,
935
- summary: 'The local GitHub issue poll failed before Forge could compare issue state.',
936
- reason,
937
- creationReason: 'repeated_infrastructure_failure',
938
- blockingDecision: 'Repair GitHub CLI authentication/connectivity before unattended issue monitoring resumes.',
939
- recommendedDecision: 'Restore gh read access, then retrigger the watcher.',
940
- recommendedPrompt: `Inspect GitHub issue watcher occurrence ${occurrence.occurrenceId} and restore the local read-only gh issue polling path.`,
941
- statusSummary: 'GitHub issue watcher poll failed.',
942
- blockedBy: ['github_issue_watch_failed'],
943
- attemptedActions: [`schedule:${schedule.scheduleId}`, 'gh:api:issues'],
944
- },
784
+
945
785
  });
946
786
  return failed.occurrence ?? occurrence;
947
787
  }
@@ -960,9 +800,9 @@ export async function evaluateSchedule(
960
800
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ enabled: false, pausedReason: `Work ${workId} no longer exists.`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId }));
961
801
  return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `SCHEDULE_BROWSER_PROBE_WORK_NOT_FOUND:${workId}`);
962
802
  }
963
- if (isTerminalWorkContractStatus(work.status)) {
964
- updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ enabled: false, pausedReason: `Work ${workId} is terminal (${work.status}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId }));
965
- return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); browser watcher stopped before probing.`);
803
+ if (semanticWorkState(work) !== 'open') {
804
+ updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ enabled: false, pausedReason: `Work ${workId} is terminal (${work.semanticState}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId }));
805
+ return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.semanticState}); browser watcher stopped before probing.`);
966
806
  }
967
807
  }
968
808
 
@@ -1084,18 +924,7 @@ export async function evaluateSchedule(
1084
924
  outcome: 'failed',
1085
925
  decision: 'execute',
1086
926
  reason,
1087
- handoff: {
1088
- title: `Browser watcher occurrence ${occurrence.occurrenceId} failed`,
1089
- summary: 'A bounded browser probe failed before Forge could compare the external observation.',
1090
- reason,
1091
- creationReason: 'repeated_infrastructure_failure',
1092
- blockingDecision: 'Repair browser/session readiness or update the watcher target before unattended probing resumes.',
1093
- recommendedDecision: 'Inspect the browser plugin/session and retrigger the watcher after the target is readable.',
1094
- recommendedPrompt: `Inspect browser watcher schedule ${schedule.scheduleId} for Work ${workId}, repair the failed browser probe, then retrigger one bounded occurrence.`,
1095
- statusSummary: 'Scheduled browser watcher failed.',
1096
- blockedBy: ['browser_probe_failed'],
1097
- attemptedActions: [`schedule:${schedule.scheduleId}`, `work:${workId}`, 'operation:browser_probe'],
1098
- },
927
+
1099
928
  });
1100
929
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({ lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId }));
1101
930
  return failed.occurrence ?? saveOccurrence(controllerHome, { ...occurrence, status: 'failed', decision: 'execute', reason });
@@ -1160,22 +989,7 @@ export async function evaluateSchedule(
1160
989
  outcome: 'failed',
1161
990
  decision: 'execute',
1162
991
  reason,
1163
- handoff: {
1164
- title: `Scheduled maintenance occurrence ${occurrence.occurrenceId} failed`,
1165
- summary: 'A bounded live maintenance occurrence failed during deterministic apply and requires review before the schedule continues unattended.',
1166
- reason,
1167
- creationReason: 'repeated_infrastructure_failure',
1168
- blockingDecision: 'Review the failed maintenance occurrence and decide whether the schedule should continue automatically.',
1169
- recommendedDecision: 'Inspect the failed occurrence, fix the runtime blocker, then re-enable or retrigger the schedule intentionally.',
1170
- recommendedPrompt: `Review schedule occurrence ${occurrence.occurrenceId} for ${schedule.scheduleId}, inspect the failed runtime maintenance action, and decide whether to resume automatic maintenance.`,
1171
- statusSummary: 'Scheduled maintenance execution failed.',
1172
- blockedBy: ['scheduled_execution_failed'],
1173
- attemptedActions: [
1174
- `schedule:${schedule.scheduleId}`,
1175
- `operation:${schedule.action.operation}`,
1176
- actionIdRaw ? `action:${actionIdRaw}` : 'action:unknown',
1177
- ],
1178
- },
992
+
1179
993
  });
1180
994
  updateSchedule(controllerHome, schedule.repoId, schedule.scheduleId, () => ({
1181
995
  lastTriggeredAt: timestamp,