@moretea-labs/forge 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -9,11 +9,15 @@ import {
9
9
  previewAutomaticRuntimeMaintenance,
10
10
  type RuntimeMaintenanceActionId,
11
11
  } from '../../recovery/maintenance-executor';
12
- import { listCandidateFindings } from '../findings/store';
13
- import { getControllerSession, getWorkContract, isTerminalWorkContractStatus, listHandoffItems } from '../../control-plane/facade';
12
+ import { appendWorkEvidence, controllerSessionBlocksRecovery, getControllerSession, getWorkContract, isTerminalWorkContractStatus, listHandoffItems } from '../../control-plane/facade';
14
13
  import { launchSuperController } from '../../control-plane/launcher/thin-launcher';
15
14
  import { getExternalControllerLaunchReservation } from '../../control-plane/launcher/launch-reservation-store';
16
- import { runScheduledChatgptPrompt, runWorkChatgptContinuation } from '../../control-plane/launcher/chatgpt-work-continuation';
15
+ import { runStandaloneChatgptPrompt, runWorkChatgptContinuation } from '../../control-plane/launcher/chatgpt-work-continuation';
16
+ import {
17
+ beginInitialControllerRoundDispatch,
18
+ buildControllerRoundRelayPrompt,
19
+ finishControllerRoundRelayDispatch,
20
+ } from '../../control-plane/facade/controller-round-relay';
17
21
  import {
18
22
  getOccurrence,
19
23
  getSchedule,
@@ -25,8 +29,9 @@ import {
25
29
  saveSchedule,
26
30
  saveScheduleDecision,
27
31
  } from './store';
28
- import { applyScheduleFailure } from './settlement';
32
+ import { applyScheduleFailure, applyScheduleRetryableFailure } from './settlement';
29
33
  import { classifyScheduledBrowserObservation, executeScheduledBrowserProbe } from './browser-probe';
34
+ import { executeScheduledGithubIssueWatch } from './github-issue-watch';
30
35
  import type {
31
36
  RepositorySchedule,
32
37
  ScheduleDecisionType,
@@ -37,16 +42,24 @@ import type {
37
42
  const execFileAsync = promisify(execFile);
38
43
  const LIVE_MAINTENANCE_OPERATION = 'runtime_maintenance_apply';
39
44
  const EXTERNAL_CONTROLLER_WAKE_OPERATION = 'external_controller_wake';
40
- const CHATGPT_BROWSER_PROMPT_OPERATION = 'chatgpt_browser_prompt';
41
45
  const BROWSER_PROBE_OPERATION = 'browser_probe';
46
+ const GITHUB_ISSUE_WATCH_OPERATION = 'github_issue_watch';
42
47
 
43
48
  function isDeterministicSchedule(schedule: RepositorySchedule): boolean {
44
- // Kernel may execute only allowlisted, deterministic dispatch operations.
45
- // ChatGPT owns semantic work; the Scheduler only dispatches the configured prompt.
49
+ // Kernel executes only typed, bounded dispatch operations.
46
50
  return schedule.action.operation === LIVE_MAINTENANCE_OPERATION
47
51
  || schedule.action.operation === EXTERNAL_CONTROLLER_WAKE_OPERATION
48
- || schedule.action.operation === CHATGPT_BROWSER_PROMPT_OPERATION
49
- || schedule.action.operation === BROWSER_PROBE_OPERATION;
52
+ || schedule.action.operation === BROWSER_PROBE_OPERATION
53
+ || schedule.action.operation === GITHUB_ISSUE_WATCH_OPERATION;
54
+ }
55
+
56
+ function retiredScheduleReason(schedule: RepositorySchedule): string | undefined {
57
+ if (schedule.action.operation === 'chatgpt_browser_prompt') return 'RETIRED_WORK_FREE_PROMPT_SCHEDULE';
58
+ if (schedule.action.operation === 'assistant_routine_execute') return 'RETIRED_PERSONAL_ASSISTANT_SCHEDULE';
59
+ if ((schedule.trigger.condition as { kind?: string } | undefined)?.kind === 'candidate_observation_threshold') {
60
+ return 'RETIRED_CANDIDATE_FINDING_SCHEDULE';
61
+ }
62
+ return undefined;
50
63
  }
51
64
 
52
65
  function normalizedWindow(minutes: number, at = Date.now()): string {
@@ -107,6 +120,12 @@ function cronWindowKey(schedule: RepositorySchedule, at = Date.now()): string {
107
120
  }
108
121
 
109
122
  function triggerWindowKey(schedule: RepositorySchedule, context: ScheduleTriggerContext | undefined, at = Date.now()): string {
123
+ // Explicit control-plane triggers use a separate identity namespace from timer/cron windows.
124
+ // A caller-supplied event/request id is the stable retry key; otherwise each manual trigger is distinct.
125
+ if (context?.source === 'manual' && schedule.trigger.type !== 'manual') {
126
+ const stableManualKey = context.eventId?.trim();
127
+ return `manual:${stableManualKey || randomUUID()}`;
128
+ }
110
129
  switch (schedule.trigger.type) {
111
130
  case 'cron':
112
131
  return cronWindowKey(schedule, at);
@@ -229,11 +248,6 @@ async function triggerDue(
229
248
  const due = condition.kind === 'job_succeeded' ? job?.status === 'succeeded' : terminal;
230
249
  return { due, reason: due ? undefined : `Condition ${condition.kind} is not met.`, evidence: { jobId: condition.jobId, status: job?.status ?? 'missing' } };
231
250
  }
232
- const threshold = Math.max(1, condition.observationThreshold ?? 2);
233
- const finding = listCandidateFindings(controllerHome, schedule.repoId, { includeTerminal: true, limit: 1000 })
234
- .find((entry) => entry.semanticKey === condition.semanticKey);
235
- const due = Boolean(finding && finding.observationCount >= threshold);
236
- return { due, reason: due ? undefined : 'Candidate observation threshold is not met.', evidence: { semanticKey: condition.semanticKey, observations: finding?.observationCount ?? 0, threshold } };
237
251
  }
238
252
  default:
239
253
  return { due: false, reason: 'Unsupported Schedule trigger.' };
@@ -351,6 +365,41 @@ function decideOccurrence(
351
365
  return saveOccurrence(controllerHome, { ...occurrence, decision, decisionId, status, reason });
352
366
  }
353
367
 
368
+ export type ChatgptWakeFailureClass = 'retryable_readiness' | 'semantic_wait' | 'user_action_required' | 'ordinary_failure';
369
+
370
+ export function classifyChatgptWakeFailure(reason: string): ChatgptWakeFailureClass {
371
+ const normalized = reason.toUpperCase();
372
+ if (
373
+ normalized.includes('CHATGPT_AUTOMATION_LOGIN_REQUIRED')
374
+ || normalized.includes('PLUGIN_BROWSER_JAVASCRIPT_PERMISSION_REQUIRED')
375
+ || normalized.includes('AUTHENTICATION_REQUIRED')
376
+ || normalized.includes('CONSENT_REQUIRED')
377
+ ) return 'user_action_required';
378
+ if (normalized.includes('REPEATED_STATE:')) return 'semantic_wait';
379
+ const retryableMarkers = [
380
+ 'PLUGIN_BROWSER_ATTACH_UNAVAILABLE',
381
+ 'PLUGIN_BROWSER_DEPENDENCY_UNAVAILABLE',
382
+ 'PLUGIN_BROWSER_NODE_HOST_UNAVAILABLE',
383
+ 'PLUGIN_BROWSER_NODE_UNAVAILABLE',
384
+ 'PLUGIN_BROWSER_ACTIVE_TAB_UNAVAILABLE',
385
+ 'PLUGIN_BROWSER_SESSION_STATE_LOST',
386
+ 'PLUGIN_SESSION_NOT_FOUND',
387
+ 'PLUGIN_BROWSER_NATIVE_FOREGROUND_ACTIVATOR_UNAVAILABLE',
388
+ 'CHATGPT_CONTROLLER_BROWSER_ROOT_UNAVAILABLE',
389
+ 'CHATGPT_CONTROLLER_BROWSER_FAILED',
390
+ 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE',
391
+ 'CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE',
392
+ 'CHATGPT_AUTOMATION_REPLACEMENT_SESSION_NOT_CONFIRMED',
393
+ 'HTTP 502',
394
+ 'ECONNREFUSED',
395
+ 'ECONNRESET',
396
+ 'ETIMEDOUT',
397
+ 'UND_ERR_CONNECT_TIMEOUT',
398
+ ];
399
+ if (retryableMarkers.some((marker) => normalized.includes(marker))) return 'retryable_readiness';
400
+ return 'ordinary_failure';
401
+ }
402
+
354
403
  function externalControllerLaunchArgs(args: Record<string, unknown>, controllerType: string): string[] {
355
404
  const launchArgs = Array.isArray(args.launch_args) ? args.launch_args.map(String) : [];
356
405
  if (controllerType !== 'chatgpt') return launchArgs;
@@ -365,90 +414,6 @@ function externalControllerLaunchArgs(args: Record<string, unknown>, controllerT
365
414
  return launchArgs;
366
415
  }
367
416
 
368
- async function executeChatgptBrowserPrompt(
369
- controllerHome: string,
370
- schedule: RepositorySchedule,
371
- occurrence: ScheduleOccurrence,
372
- timestamp: string,
373
- args: Record<string, unknown>,
374
- ): Promise<ScheduleOccurrence> {
375
- const prompt = typeof args.prompt === 'string' ? args.prompt.trim() : '';
376
- if (!prompt) {
377
- return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'CHATGPT_BROWSER_PROMPT_REQUIRED');
378
- }
379
- const model = typeof args.model === 'string' ? args.model : 'gpt-5.6';
380
- const reasoning = args.reasoning === 'medium' || args.reasoning === 'xhigh' ? args.reasoning : 'high';
381
- const tabPolicy = args.tab_policy === 'reuse' || args.tab_policy === 'new' ? args.tab_policy : 'auto';
382
- const dispatching = decideOccurrence(
383
- controllerHome,
384
- schedule,
385
- occurrence,
386
- 'execute',
387
- 'running',
388
- 'Forge Scheduler is dispatching the configured prompt through the controller-owned ChatGPT browser session.',
389
- occurrenceDecisionEvidence({ operation: CHATGPT_BROWSER_PROMPT_OPERATION, model, reasoning, tabPolicy }),
390
- );
391
- const result = await runScheduledChatgptPrompt({
392
- controllerHome,
393
- repoId: schedule.repoId,
394
- automationId: schedule.scheduleId,
395
- prompt,
396
- title: schedule.name,
397
- browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
398
- conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
399
- model,
400
- reasoning,
401
- tabPolicy,
402
- timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
403
- });
404
- if (result.status === 'failed') {
405
- const reason = result.error?.message ?? 'ChatGPT browser prompt dispatch failed.';
406
- const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
407
- outcome: 'failed',
408
- decision: 'execute',
409
- reason,
410
- handoff: {
411
- title: `ChatGPT scheduled prompt ${occurrence.occurrenceId} failed`,
412
- summary: 'Forge recorded the trigger but could not dispatch the configured ChatGPT prompt.',
413
- reason,
414
- creationReason: 'repeated_infrastructure_failure',
415
- blockingDecision: 'Repair ChatGPT/browser readiness before unattended prompt dispatch resumes.',
416
- recommendedDecision: 'Inspect browser attachment and ChatGPT execution preference verification, then retrigger this automation.',
417
- recommendedPrompt: `Inspect failed ChatGPT prompt occurrence ${occurrence.occurrenceId} for schedule ${schedule.scheduleId}.`,
418
- statusSummary: 'Scheduled ChatGPT prompt dispatch failed.',
419
- blockedBy: ['chatgpt_browser_prompt_failed'],
420
- attemptedActions: [`schedule:${schedule.scheduleId}`, `browser-session:${result.browserSessionId}`],
421
- },
422
- });
423
- const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
424
- saveSchedule(controllerHome, { ...latest, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
425
- return failed.occurrence ?? saveOccurrence(controllerHome, { ...dispatching, status: 'failed', reason });
426
- }
427
- const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
428
- const durableConversationUrl = result.conversationUrl && /\/c\/[^/?#]+/.test(result.conversationUrl)
429
- ? result.conversationUrl
430
- : undefined;
431
- saveSchedule(controllerHome, {
432
- ...latest,
433
- action: durableConversationUrl && tabPolicy !== 'new'
434
- ? {
435
- ...latest.action,
436
- arguments: { ...latest.action.arguments, conversation_url: durableConversationUrl },
437
- }
438
- : latest.action,
439
- lastTriggeredAt: timestamp,
440
- lastOccurrenceId: occurrence.occurrenceId,
441
- consecutiveFailures: 0,
442
- nextEligibleAt: undefined,
443
- pausedReason: undefined,
444
- });
445
- return saveOccurrence(controllerHome, {
446
- ...dispatching,
447
- status: 'succeeded',
448
- reason: `ChatGPT prompt dispatched via ${result.browserSessionId}${result.reusedSession ? ' (reused)' : ' (new session)'}; execution preference ${result.executionPreferenceVerified ? 'verified' : 'not verified'}.`,
449
- });
450
- }
451
-
452
417
  async function executeExternalControllerWake(
453
418
  controllerHome: string,
454
419
  schedule: RepositorySchedule,
@@ -456,6 +421,7 @@ async function executeExternalControllerWake(
456
421
  timestamp: string,
457
422
  args: Record<string, unknown>,
458
423
  extraEvidence: Record<string, unknown> = {},
424
+ options: { transientWakeFailure?: boolean } = {},
459
425
  ): Promise<ScheduleOccurrence> {
460
426
  const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
461
427
  const controllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : 'chatgpt';
@@ -476,7 +442,10 @@ async function executeExternalControllerWake(
476
442
  return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); automatic continuation stopped.`);
477
443
  }
478
444
  const existingOwner = getControllerSession(workStore, workId);
479
- if (existingOwner) {
445
+ const occurrenceNowMs = Date.parse(timestamp);
446
+ if (existingOwner && controllerSessionBlocksRecovery(workStore, workId, {
447
+ nowMs: Number.isFinite(occurrenceNowMs) ? occurrenceNowMs : Date.now(),
448
+ })) {
480
449
  saveSchedule(controllerHome, { ...schedule, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
481
450
  return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} already has an active Controller ${existingOwner.controllerId}.`);
482
451
  }
@@ -512,37 +481,81 @@ async function executeExternalControllerWake(
512
481
  if (controllerType === 'chatgpt') {
513
482
  const reasoning = args.reasoning === 'medium' || args.reasoning === 'xhigh' ? args.reasoning : 'high';
514
483
  const tabPolicy = args.tab_policy === 'reuse' || args.tab_policy === 'new' ? args.tab_policy : 'auto';
515
- const dispatched = await runWorkChatgptContinuation({
516
- controllerHome,
517
- repoId: schedule.repoId,
518
- repoRoot: repository.canonicalRoot,
519
- workId,
520
- prompt: continuationPrompt,
521
- title: schedule.name,
522
- browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
523
- conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
524
- model: typeof args.model === 'string' ? args.model : 'gpt-5.6',
525
- reasoning,
526
- tabPolicy,
527
- timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
528
- });
529
- if (dispatched.status === 'failed') throw new Error(dispatched.error?.message ?? 'CHATGPT_WORK_CONTINUATION_FAILED');
530
- const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
531
- saveSchedule(controllerHome, {
532
- ...latest,
533
- lastTriggeredAt: timestamp,
534
- lastOccurrenceId: occurrence.occurrenceId,
535
- consecutiveFailures: 0,
536
- nextEligibleAt: undefined,
537
- pausedReason: undefined,
538
- });
539
- return saveOccurrence(controllerHome, {
540
- ...wakeDecision,
541
- status: 'succeeded',
542
- reason: `ChatGPT Work continuation dispatched via ${dispatched.browserSessionId}; execution preference verified.`,
543
- });
484
+ let relay;
485
+ try {
486
+ relay = beginInitialControllerRoundDispatch(workStore, {
487
+ workId,
488
+ identity: {
489
+ controllerId: `schedule:${schedule.scheduleId}`,
490
+ principalId: 'forge-scheduler',
491
+ controllerInstanceId: 'forge-runtime-scheduler',
492
+ sessionId: occurrence.occurrenceId,
493
+ },
494
+ requirementId: work.requirementId,
495
+ browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
496
+ conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
497
+ });
498
+ if (relay.status === 'blocked') throw new Error(`CONTROLLER_RELAY_LAUNCH_BLOCKED: ${relay.blockedReason ?? relay.relayScopeId}`);
499
+ const relayPrompt = `${buildControllerRoundRelayPrompt(workStore, relay)}\n\nScheduled continuation hint: ${continuationPrompt}`;
500
+ const dispatched = await runWorkChatgptContinuation({
501
+ controllerHome,
502
+ repoId: schedule.repoId,
503
+ repoRoot: repository.canonicalRoot,
504
+ workId,
505
+ prompt: relayPrompt,
506
+ title: schedule.name,
507
+ browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
508
+ conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
509
+ model: typeof args.model === 'string' ? args.model : 'gpt-5.6',
510
+ reasoning,
511
+ tabPolicy,
512
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
513
+ });
514
+ if (dispatched.status === 'failed') throw new Error(dispatched.error?.message ?? 'CHATGPT_WORK_CONTINUATION_FAILED');
515
+ const completedRelay = finishControllerRoundRelayDispatch(workStore, {
516
+ workId,
517
+ ok: true,
518
+ browserSessionId: dispatched.browserSessionId,
519
+ conversationUrl: dispatched.conversationUrl,
520
+ });
521
+ const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
522
+ saveSchedule(controllerHome, {
523
+ ...latest,
524
+ lastTriggeredAt: timestamp,
525
+ lastOccurrenceId: occurrence.occurrenceId,
526
+ consecutiveFailures: 0,
527
+ nextEligibleAt: undefined,
528
+ pausedReason: undefined,
529
+ });
530
+ const succeededOccurrence = saveOccurrence(controllerHome, {
531
+ ...wakeDecision,
532
+ status: 'succeeded',
533
+ reason: `ChatGPT dispatch action succeeded via ${dispatched.browserSessionId}; relay ${completedRelay?.relayScopeId ?? relay.relayScopeId} remains dispatched until the new Controller claims Work ${workId}.`,
534
+ });
535
+ appendWorkEvidence(workStore, workId, {
536
+ evidenceId: succeededOccurrence.occurrenceId,
537
+ title: 'scheduled ChatGPT continuation dispatched',
538
+ summary: `Schedule ${schedule.scheduleId} timer occurrence ${succeededOccurrence.occurrenceId} dispatched the Work-bound ChatGPT continuation successfully.`,
539
+ detailLevel: 'summary',
540
+ });
541
+ return succeededOccurrence;
542
+ } catch (error) {
543
+ if (relay) {
544
+ finishControllerRoundRelayDispatch(workStore, {
545
+ workId,
546
+ ok: false,
547
+ error: error instanceof Error ? error.message : String(error),
548
+ });
549
+ }
550
+ if (error instanceof Error && error.message.startsWith('CONTROLLER_RELAY_ROUND_ALREADY_OPEN:')) {
551
+ const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
552
+ saveSchedule(controllerHome, { ...latest, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
553
+ return decideOccurrence(controllerHome, schedule, wakeDecision, 'nothing_to_do', 'skipped', `Work ${workId} already has an open ChatGPT controller round awaiting claim or disposition.`);
554
+ }
555
+ throw error;
556
+ }
544
557
  }
545
- const launched = launchSuperController({
558
+ const launched = await launchSuperController({
546
559
  work: { controllerHome, repoId: schedule.repoId },
547
560
  handoff: { controllerHome, repoId: schedule.repoId },
548
561
  }, {
@@ -573,10 +586,35 @@ async function executeExternalControllerWake(
573
586
  });
574
587
  } catch (error) {
575
588
  const reason = error instanceof Error ? error.message : String(error);
589
+ const failureClass = controllerType === 'chatgpt' ? classifyChatgptWakeFailure(reason) : 'ordinary_failure';
590
+ if (options.transientWakeFailure || failureClass === 'retryable_readiness' || failureClass === 'semantic_wait') {
591
+ const semanticWait = failureClass === 'semantic_wait';
592
+ const deferred = applyScheduleRetryableFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
593
+ outcome: semanticWait ? 'skipped' : 'failed',
594
+ decision: semanticWait ? 'nothing_to_do' : 'execute',
595
+ reason: semanticWait
596
+ ? `Controller wake deferred until durable state changes; schedule remains active: ${reason}`
597
+ : `Controller wake deferred by transient readiness; schedule remains active with bounded backoff: ${reason}`,
598
+ countFailure: !semanticWait,
599
+ });
600
+ const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
601
+ saveSchedule(controllerHome, {
602
+ ...latest,
603
+ lastTriggeredAt: timestamp,
604
+ lastOccurrenceId: occurrence.occurrenceId,
605
+ });
606
+ return deferred.occurrence ?? saveOccurrence(controllerHome, {
607
+ ...wakeDecision,
608
+ status: semanticWait ? 'skipped' : 'failed',
609
+ decision: semanticWait ? 'nothing_to_do' : 'execute',
610
+ reason,
611
+ });
612
+ }
576
613
  const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
577
614
  outcome: 'failed',
578
615
  decision: 'execute',
579
616
  reason,
617
+ ...(failureClass === 'user_action_required' ? { pauseReason: reason } : {}),
580
618
  handoff: {
581
619
  title: `External Controller wake ${occurrence.occurrenceId} failed`,
582
620
  summary: 'Forge recorded the schedule trigger but could not wake the configured external Controller.',
@@ -602,6 +640,9 @@ export async function evaluateSchedule(
602
640
  force = false,
603
641
  triggerContext?: ScheduleTriggerContext,
604
642
  ): Promise<ScheduleOccurrence | undefined> {
643
+ // Preserve retired schedule records as evidence, but do not create a new
644
+ // occurrence or resume a controller/external action from their old payload.
645
+ if (retiredScheduleReason(schedule)) return undefined;
605
646
  if (!schedule.enabled && !force) return undefined;
606
647
  const due = await triggerDue(controllerHome, schedule, force, triggerContext);
607
648
  if (!due.due) return undefined;
@@ -803,24 +844,92 @@ export async function evaluateSchedule(
803
844
  return executeExternalControllerWake(controllerHome, schedule, occurrence, timestamp, args);
804
845
  }
805
846
 
806
- if (schedule.action.operation === CHATGPT_BROWSER_PROMPT_OPERATION) {
807
- return executeChatgptBrowserPrompt(controllerHome, schedule, occurrence, timestamp, args);
847
+ if (schedule.action.operation === GITHUB_ISSUE_WATCH_OPERATION) {
848
+ try {
849
+ const repository = getRepository(schedule.repoId, controllerHome, { includeRemoved: true });
850
+ const observation = await executeScheduledGithubIssueWatch({ repoRoot: repository.canonicalRoot, args, observedAt: timestamp });
851
+ const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
852
+ const evidence = occurrenceDecisionEvidence({
853
+ githubRepository: typeof args.github_repository === 'string' ? args.github_repository : undefined,
854
+ observationStatus: observation.status,
855
+ observedIssueCount: observation.issues.length,
856
+ changedIssueNumbers: observation.changedOpenIssues.map((issue) => issue.number),
857
+ });
858
+ const persistObservation = () => saveSchedule(controllerHome, {
859
+ ...getSchedule(controllerHome, schedule.repoId, schedule.scheduleId),
860
+ action: {
861
+ ...latest.action,
862
+ arguments: { ...(latest.action.arguments ?? {}), issue_watch_state: observation.nextState, issue_watch_since: undefined },
863
+ },
864
+ lastTriggeredAt: timestamp,
865
+ lastOccurrenceId: occurrenceId,
866
+ lastObservationAt: timestamp,
867
+ lastObservationStatus: observation.status,
868
+ lastObservationChangedAt: observation.shouldWake ? timestamp : latest.lastObservationChangedAt,
869
+ consecutiveFailures: 0,
870
+ consecutiveNoops: observation.shouldWake ? 0 : (latest.consecutiveNoops ?? 0) + 1,
871
+ nextEligibleAt: undefined,
872
+ pausedReason: undefined,
873
+ });
874
+ if (!observation.shouldWake) {
875
+ const observedSchedule = persistObservation();
876
+ const reason = observation.status === 'baseline'
877
+ ? 'GitHub issue watcher baseline recorded; no Controller wake was emitted.'
878
+ : 'GitHub issue watcher found no new, reopened, or updated open issue.';
879
+ return decideOccurrence(controllerHome, observedSchedule, occurrence, 'nothing_to_do', 'skipped', reason, evidence);
880
+ }
881
+ const issueSummary = observation.changedOpenIssues.slice(0, 10)
882
+ .map((issue) => `#${issue.number} ${issue.title}`)
883
+ .join('; ');
884
+ const wakeArgs = {
885
+ ...args,
886
+ continuation_prompt: typeof args.continuation_prompt === 'string'
887
+ ? `${args.continuation_prompt}\n\nGitHub issue watcher detected: ${issueSummary}`
888
+ : `GitHub issue watcher detected actionable issue changes: ${issueSummary}. Read current Work/Plan/evidence and process these issues through the bounded repair workflow.`,
889
+ };
890
+ const wake = await executeExternalControllerWake(controllerHome, schedule, occurrence, timestamp, wakeArgs, evidence, { transientWakeFailure: true });
891
+ if (wake.status !== 'failed') persistObservation();
892
+ return wake;
893
+ } catch (error) {
894
+ const reason = error instanceof Error ? error.message : String(error);
895
+ const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
896
+ outcome: 'failed',
897
+ decision: 'execute',
898
+ reason,
899
+ handoff: {
900
+ title: `GitHub issue watcher occurrence ${occurrence.occurrenceId} failed`,
901
+ summary: 'The local GitHub issue poll failed before Forge could compare issue state.',
902
+ reason,
903
+ creationReason: 'repeated_infrastructure_failure',
904
+ blockingDecision: 'Repair GitHub CLI authentication/connectivity before unattended issue monitoring resumes.',
905
+ recommendedDecision: 'Restore gh read access, then retrigger the watcher.',
906
+ recommendedPrompt: `Inspect GitHub issue watcher occurrence ${occurrence.occurrenceId} and restore the local read-only gh issue polling path.`,
907
+ statusSummary: 'GitHub issue watcher poll failed.',
908
+ blockedBy: ['github_issue_watch_failed'],
909
+ attemptedActions: [`schedule:${schedule.scheduleId}`, 'gh:api:issues'],
910
+ },
911
+ });
912
+ return failed.occurrence ?? occurrence;
913
+ }
808
914
  }
809
915
 
810
916
  if (schedule.action.operation === BROWSER_PROBE_OPERATION) {
811
917
  const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
812
- if (!workId) {
918
+ const keepaliveOnly = args.keepalive_only === true;
919
+ if (!workId && !keepaliveOnly) {
813
920
  return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'SCHEDULE_BROWSER_PROBE_WORK_ID_REQUIRED');
814
921
  }
815
- const workStore = { controllerHome, repoId: schedule.repoId };
816
- const work = getWorkContract(workStore, workId);
817
- if (!work) {
818
- saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} no longer exists.`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
819
- return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `SCHEDULE_BROWSER_PROBE_WORK_NOT_FOUND:${workId}`);
820
- }
821
- if (isTerminalWorkContractStatus(work.status)) {
822
- saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} is terminal (${work.status}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
823
- return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); browser watcher stopped before probing.`);
922
+ if (workId) {
923
+ const workStore = { controllerHome, repoId: schedule.repoId };
924
+ const work = getWorkContract(workStore, workId);
925
+ if (!work) {
926
+ saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} no longer exists.`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
927
+ return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `SCHEDULE_BROWSER_PROBE_WORK_NOT_FOUND:${workId}`);
928
+ }
929
+ if (isTerminalWorkContractStatus(work.status)) {
930
+ saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} is terminal (${work.status}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
931
+ return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); browser watcher stopped before probing.`);
932
+ }
824
933
  }
825
934
 
826
935
  try {
@@ -847,17 +956,44 @@ export async function evaluateSchedule(
847
956
  pausedReason: undefined,
848
957
  });
849
958
  if (args.wake_on_auth_required !== false) {
850
- const wakeArgs = {
851
- ...args,
852
- continuation_prompt: typeof args.auth_required_prompt === 'string'
853
- ? args.auth_required_prompt
854
- : typeof args.continuation_prompt === 'string'
855
- ? args.continuation_prompt
856
- : `Scheduled browser watcher ${schedule.scheduleId} detected that authentication is required (${probe.authReason ?? 'login marker'}). Inspect the bound external dependency and request user login only if it cannot be restored safely.`,
857
- };
858
- return executeExternalControllerWake(controllerHome, observedSchedule, occurrence, timestamp, wakeArgs, { ...observationEvidence, authReason: probe.authReason });
959
+ const authPrompt = typeof args.auth_required_prompt === 'string'
960
+ ? args.auth_required_prompt
961
+ : typeof args.continuation_prompt === 'string'
962
+ ? args.continuation_prompt
963
+ : `Scheduled browser watcher ${schedule.scheduleId} detected that authentication is required (${probe.authReason ?? 'login marker'}). Inspect the external dependency and request user login only if it cannot be restored safely.`;
964
+ if (workId) {
965
+ const wakeArgs = { ...args, continuation_prompt: authPrompt };
966
+ return executeExternalControllerWake(controllerHome, observedSchedule, occurrence, timestamp, wakeArgs, { ...observationEvidence, authReason: probe.authReason });
967
+ }
968
+ const controllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : 'chatgpt';
969
+ if (controllerType !== 'chatgpt') {
970
+ return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', 'STANDALONE_BROWSER_KEEPALIVE_CHATGPT_REQUIRED', observationEvidence);
971
+ }
972
+ const dispatched = await runStandaloneChatgptPrompt({
973
+ controllerHome,
974
+ repoId: schedule.repoId,
975
+ scopeId: `schedule:${schedule.scheduleId}`,
976
+ prompt: authPrompt,
977
+ browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
978
+ conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
979
+ });
980
+ if (dispatched.status === 'failed') throw new Error(dispatched.error?.message ?? 'CHATGPT_STANDALONE_PROMPT_FAILED');
981
+ return decideOccurrence(
982
+ controllerHome,
983
+ observedSchedule,
984
+ occurrence,
985
+ 'execute',
986
+ 'succeeded',
987
+ 'Standalone browser keepalive auth-required prompt dispatched to ChatGPT.',
988
+ {
989
+ ...observationEvidence,
990
+ authReason: probe.authReason,
991
+ browserSessionId: dispatched.browserSessionId,
992
+ conversationUrl: dispatched.conversationUrl,
993
+ },
994
+ );
859
995
  }
860
- return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', `Browser watcher requires authentication: ${probe.authReason ?? 'login marker matched'}`, observationEvidence);
996
+ return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', `Browser ${workId ? 'watcher' : 'keepalive'} requires authentication: ${probe.authReason ?? 'login marker matched'}`, observationEvidence);
861
997
  }
862
998
 
863
999
  if (probe.status === 'keepalive') {