@moretea-labs/forge 1.6.0 → 1.7.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 (469) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +4 -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/02-operating-mode.partial.md +2 -0
  17. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  18. package/assets/plugin-registry.v1.json +3 -3
  19. package/assets/reference-configs/document-generation.md +1 -1
  20. package/assets/reference-configs/harness-overview.md +1 -1
  21. package/assets/reference-configs/sprint-contracts.md +3 -3
  22. package/assets/skill-version.json +2 -2
  23. package/assets/templates/contract.template.md +2 -7
  24. package/assets/templates/helpers/capture-plan.sh +2 -2
  25. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  26. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  27. package/assets/templates/helpers/contract-worktree.sh +1 -21
  28. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  29. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  34. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/helpers/workflow-contract.ts +5 -1
  38. package/assets/templates/plan.template.md +1 -1
  39. package/assets/templates/review.template.md +9 -34
  40. package/assets/workflow-contract.v1.json +10 -19
  41. package/docs/README.md +10 -10
  42. package/docs/architecture/CURRENT.md +178 -0
  43. package/docs/architecture/EVOLUTION.md +42 -0
  44. package/docs/architecture/current/README.md +4 -156
  45. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  46. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  47. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  48. package/docs/architecture/history/README.md +7 -0
  49. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  50. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  51. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  52. package/docs/architecture/history.md +3 -34
  53. package/docs/architecture/index.md +15 -98
  54. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  55. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  56. package/docs/architecture/versions/1.6.md +38 -0
  57. package/docs/architecture/versions/1.7.md +41 -0
  58. package/docs/forge-chatgpt-browser-engine.md +2 -2
  59. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  60. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  61. package/docs/forge-github-issue-launcher.md +1 -1
  62. package/docs/forge-plugin-management.md +1 -1
  63. package/docs/forge-runtime-self-healing-loop.md +1 -1
  64. package/docs/operations/controller-browser-plugin.md +26 -6
  65. package/docs/operations/controller-performance-and-502.md +143 -0
  66. package/docs/operations/controller-reliability-runbook.md +1 -1
  67. package/docs/operations/features.md +2 -2
  68. package/docs/operations/github-repository.md +1 -1
  69. package/docs/operations/homebrew.md +2 -2
  70. package/docs/operations/mcp-tool-exposure.md +5 -3
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Integrations.md +2 -2
  74. package/docs/wiki/Work-Lifecycle.md +1 -1
  75. package/package.json +33 -11
  76. package/scripts/activate-source-baseline.ts +17 -13
  77. package/scripts/architecture-event.ts +2 -820
  78. package/scripts/architecture-queue.sh +2 -645
  79. package/scripts/archive-architecture-request.sh +2 -242
  80. package/scripts/archive-workflow.sh +1 -291
  81. package/scripts/benchmark-background-check-overlap.ts +228 -0
  82. package/scripts/benchmark-mcp-host.sh +117 -0
  83. package/scripts/benchmark-mcp-transport.ts +170 -0
  84. package/scripts/benchmark-quality-harness.ts +146 -0
  85. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  86. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  87. package/scripts/benchmark-typescript-navigation.ts +186 -0
  88. package/scripts/benchmark-work-efficiency.ts +334 -0
  89. package/scripts/capability-config.ts +2 -392
  90. package/scripts/capability-resolver.ts +2 -503
  91. package/scripts/capture-plan.sh +1 -332
  92. package/scripts/check-agent-tooling.sh +2 -1558
  93. package/scripts/check-architecture-sync.sh +2 -371
  94. package/scripts/check-brain-manifest.sh +2 -253
  95. package/scripts/check-context-files.sh +2 -47
  96. package/scripts/check-deploy-sql-order.sh +2 -115
  97. package/scripts/check-mcp-compatibility.ts +14 -0
  98. package/scripts/check-runtime-architecture.mjs +349 -79
  99. package/scripts/check-skill-version.ts +3 -191
  100. package/scripts/check-source-duplication.mjs +61 -0
  101. package/scripts/check-stable-baseline.ts +92 -0
  102. package/scripts/check-task-sync.sh +2 -63
  103. package/scripts/check-task-workflow.sh +2 -1000
  104. package/scripts/codex-handoff-resume.sh +2 -267
  105. package/scripts/context-contract-sync.sh +2 -514
  106. package/scripts/contract-run.ts +2 -463
  107. package/scripts/contract-worktree.sh +1 -722
  108. package/scripts/ensure-task-workflow.sh +1 -1225
  109. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  110. package/scripts/inspect-project-state.ts +4 -279
  111. package/scripts/install-standalone-recovery.ts +24 -0
  112. package/scripts/lib/project-init-lib.sh +22 -46
  113. package/scripts/lib/run-forge-helper.sh +19 -0
  114. package/scripts/lib/run-forge-helper.ts +26 -0
  115. package/scripts/migrate-project-template.sh +1 -1189
  116. package/scripts/migrate-workflow-docs.ts +4 -412
  117. package/scripts/new-plan.sh +1 -180
  118. package/scripts/new-spec.sh +1 -47
  119. package/scripts/new-sprint.sh +1 -46
  120. package/scripts/plan-to-todo.sh +1 -796
  121. package/scripts/prepare-codex-handoff.sh +2 -201
  122. package/scripts/prepare-handoff.sh +1 -78
  123. package/scripts/refresh-current-status.sh +2 -467
  124. package/scripts/run-governed-gate.ts +5 -1
  125. package/scripts/select-agent-context-blocks.sh +2 -64
  126. package/scripts/ship-worktrees.sh +1 -558
  127. package/scripts/smoke-ios-physical-device.ts +4 -2
  128. package/scripts/smoke-runtime-recovery.ts +1 -35
  129. package/scripts/smoke-schedule-engine.ts +1 -20
  130. package/scripts/sprint-backlog.sh +1 -834
  131. package/scripts/stage-runtime-release.ts +2 -1
  132. package/scripts/summarize-failures.sh +2 -123
  133. package/scripts/switch-plan.sh +1 -150
  134. package/scripts/sync-brain-docs.sh +2 -379
  135. package/scripts/sync-generated-authority.mjs +175 -0
  136. package/scripts/verify-contract.sh +2 -703
  137. package/scripts/verify-forge-runtime.sh +0 -1
  138. package/scripts/verify-sprint.sh +1 -677
  139. package/scripts/workflow-contract.ts +4 -205
  140. package/scripts/workstream-sync.sh +2 -261
  141. package/src/cli/agent-jobs/executor-health.ts +1 -1
  142. package/src/cli/commands/init-hook.ts +3 -3
  143. package/src/cli/commands/mcp.ts +9 -5
  144. package/src/cli/commands/plugin.ts +12 -3
  145. package/src/cli/commands/recovery.ts +25 -3
  146. package/src/cli/commands/runtime.ts +55 -3
  147. package/src/cli/commands/security.ts +12 -1
  148. package/src/cli/commands/setup-profile.ts +31 -21
  149. package/src/cli/controller/check-runner.ts +113 -5
  150. package/src/cli/controller/composite-result.ts +1 -1
  151. package/src/cli/controller/context/focus.ts +51 -0
  152. package/src/cli/controller/context/instruction-context.ts +114 -0
  153. package/src/cli/controller/context/known-paths.ts +76 -0
  154. package/src/cli/controller/context/query-planning.ts +110 -0
  155. package/src/cli/controller/context/rule-resolution.ts +532 -0
  156. package/src/cli/controller/context/source-materializer.ts +278 -0
  157. package/src/cli/controller/context/structural.ts +16 -0
  158. package/src/cli/controller/context/types.ts +286 -0
  159. package/src/cli/controller/context-pack.ts +828 -487
  160. package/src/cli/controller/launch-agents.ts +77 -2
  161. package/src/cli/controller/operational-plan.ts +2 -2
  162. package/src/cli/controller/work-mode.ts +6 -4
  163. package/src/cli/editing/edit-session.ts +60 -7
  164. package/src/cli/github/contracts.ts +16 -0
  165. package/src/cli/github/github.ts +135 -16
  166. package/src/cli/github/session.ts +2 -1
  167. package/src/cli/installer/managed-entries.ts +15 -1
  168. package/src/cli/local-bridge/console-automations.ts +54 -78
  169. package/src/cli/local-bridge/console-view-models.ts +1 -35
  170. package/src/cli/local-bridge/facade-api.ts +37 -264
  171. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  172. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  173. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  174. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  175. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  176. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  177. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  178. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  179. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  180. package/src/cli/local-bridge/job-store.ts +1 -490
  181. package/src/cli/local-bridge/server.ts +2 -515
  182. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  183. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  184. package/src/cli/mcp/auth.ts +5 -5
  185. package/src/cli/mcp/instructions.ts +3 -2
  186. package/src/cli/mcp/legacy-tool-service.ts +10 -222
  187. package/src/cli/mcp/multi-repository.ts +16 -5
  188. package/src/cli/mcp/oauth.ts +175 -26
  189. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  190. package/src/cli/mcp/repository-tools.ts +282 -146
  191. package/src/cli/mcp/server.ts +586 -116
  192. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  193. package/src/cli/mcp/tool-contract.ts +14 -0
  194. package/src/cli/mcp/toolset-selection.ts +1 -1
  195. package/src/cli/mcp/toolset.ts +44 -7
  196. package/src/cli/mcp/transports/http.ts +124 -14
  197. package/src/cli/mcp/transports/session-registry.ts +43 -5
  198. package/src/cli/mcp/types.ts +3 -3
  199. package/src/cli/repositories/command-classifier.ts +103 -21
  200. package/src/cli/repositories/command-executor.ts +78 -526
  201. package/src/cli/repositories/command-process.ts +108 -0
  202. package/src/cli/repositories/command-scope.ts +99 -41
  203. package/src/cli/repositories/controller-home.ts +7 -8
  204. package/src/cli/repositories/git-command-executor.ts +2 -0
  205. package/src/cli/repositories/locks.ts +53 -16
  206. package/src/cli/repositories/repository-snapshot.ts +124 -0
  207. package/src/cli/repositories/selected-path-actions.ts +17 -9
  208. package/src/cli/repositories/structured-git.ts +118 -6
  209. package/src/cli/repository/inspector.ts +563 -65
  210. package/src/cli/repository/session-cache.ts +58 -17
  211. package/src/cli/v81-entry.ts +1 -4
  212. package/src/effects/fs-transaction.ts +85 -17
  213. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  214. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  215. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  216. package/src/runtime/context/lsp-client.ts +283 -0
  217. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  218. package/src/runtime/context/semantic-navigation.ts +223 -0
  219. package/src/runtime/context/swift-navigation.ts +565 -0
  220. package/src/runtime/context/typescript-navigation.ts +226 -0
  221. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  222. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
  223. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  224. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  225. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  226. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  227. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  228. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  229. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  230. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  231. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  232. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  233. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  234. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  235. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  236. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  237. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  238. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  239. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  240. package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
  241. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
  242. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  243. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  244. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  245. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
  246. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  247. package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
  248. package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
  249. package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
  250. package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
  251. package/src/runtime/control-plane/facade/index.ts +3 -0
  252. package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
  253. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  254. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  255. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  256. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  257. package/src/runtime/control-plane/facade/types.ts +90 -10
  258. package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
  259. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  260. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  261. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  262. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  263. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  264. package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
  265. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  266. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  267. package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
  268. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  269. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  270. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  271. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  272. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  273. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  274. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  275. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  276. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  277. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  278. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
  279. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
  280. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
  281. package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
  282. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  283. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  284. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  285. package/src/runtime/control-plane/release-retention.ts +62 -2
  286. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  287. package/src/runtime/control-plane/routing/route-policy.ts +24 -21
  288. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  289. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  290. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  291. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  292. package/src/runtime/diagnostics/performance.ts +24 -1
  293. package/src/runtime/evidence/event-ledger.ts +1 -1
  294. package/src/runtime/evidence/process-check-execution.ts +18 -0
  295. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  296. package/src/runtime/execution/jobs/store.ts +58 -4
  297. package/src/runtime/execution/jobs/types.ts +1 -1
  298. package/src/runtime/execution/managed-workspace.ts +203 -1
  299. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  300. package/src/runtime/execution/process-runtime/check-result.ts +88 -0
  301. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  302. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  303. package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
  304. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  305. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
  306. package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
  307. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  308. package/src/runtime/execution/process-runtime/runtime.ts +53 -3
  309. package/src/runtime/execution/process-runtime/store.ts +1 -0
  310. package/src/runtime/execution/process-runtime/types.ts +4 -12
  311. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  312. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  313. package/src/runtime/execution/work-activity.ts +21 -0
  314. package/src/runtime/execution/workers/executor.ts +18 -61
  315. package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
  316. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  317. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  318. package/src/runtime/gateway/mcp/router.ts +186 -84
  319. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
  320. package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
  321. package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
  322. package/src/runtime/plugins/browser-adapter.ts +2662 -829
  323. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  324. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  325. package/src/runtime/plugins/browser-handoff.ts +110 -5
  326. package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
  327. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  328. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  329. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  330. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  331. package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
  332. package/src/runtime/plugins/browser-runtime.ts +193 -0
  333. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  334. package/src/runtime/plugins/browser-session-store.ts +121 -0
  335. package/src/runtime/plugins/browser-session-types.ts +98 -0
  336. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  337. package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
  338. package/src/runtime/plugins/external-adapter.ts +358 -3
  339. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  340. package/src/runtime/plugins/first-party-registry.ts +2 -0
  341. package/src/runtime/plugins/github-adapter.ts +125 -0
  342. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  343. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  344. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  345. package/src/runtime/plugins/ios-adapter.ts +106 -16
  346. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  347. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  348. package/src/runtime/plugins/lightweight-action.ts +153 -0
  349. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  350. package/src/runtime/plugins/macos-capability-broker.ts +47 -9
  351. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  352. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  353. package/src/runtime/plugins/resend-adapter.ts +1 -1
  354. package/src/runtime/plugins/store.ts +330 -33
  355. package/src/runtime/plugins/types.ts +38 -3
  356. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  357. package/src/runtime/projections/git-status-sampler.ts +16 -16
  358. package/src/runtime/projections/materialized-view.ts +28 -10
  359. package/src/runtime/recovery/maintenance-executor.ts +426 -25
  360. package/src/runtime/root/mcp-transport.ts +180 -10
  361. package/src/runtime/root/package-connector-service.ts +230 -10
  362. package/src/runtime/root/package-runtime-release.ts +143 -17
  363. package/src/runtime/root/package-runtime-service.ts +461 -22
  364. package/src/runtime/root/release-manifest.ts +144 -18
  365. package/src/runtime/root/release-materialize.ts +125 -32
  366. package/src/runtime/root/release-store.ts +48 -11
  367. package/src/runtime/root/runtime.ts +23 -5
  368. package/src/runtime/root/service.ts +100 -29
  369. package/src/runtime/root/types.ts +12 -0
  370. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  371. package/src/runtime/shared/trusted-executable.ts +62 -0
  372. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  373. package/src/runtime/standalone-recovery/core.ts +1079 -240
  374. package/src/runtime/standalone-recovery/entry.ts +100 -24
  375. package/src/runtime/standalone-recovery/installer.ts +20 -4
  376. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  377. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  378. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  379. package/src/runtime/workflow/schedules/engine.ts +364 -202
  380. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  381. package/src/runtime/workflow/schedules/settlement.ts +64 -17
  382. package/src/runtime/workflow/schedules/store.ts +209 -19
  383. package/src/runtime/workflow/schedules/types.ts +2 -4
  384. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  385. package/src/testing/test-governance.ts +8 -40
  386. package/assets/reference-configs/heartbeat-triage.md +0 -57
  387. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  388. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  389. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  390. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  391. package/docs/architecture/current/architecture-invariants.md +0 -331
  392. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  393. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  394. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  395. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  396. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  397. package/docs/architecture/current/entity-model.md +0 -612
  398. package/docs/architecture/current/external-plugin-governance.md +0 -396
  399. package/docs/architecture/current/failure-recovery.md +0 -447
  400. package/docs/architecture/current/governance.md +0 -213
  401. package/docs/architecture/current/human-interaction-plane.md +0 -87
  402. package/docs/architecture/current/implementation-status.md +0 -128
  403. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  404. package/docs/architecture/current/legacy-governance.md +0 -58
  405. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  406. package/docs/architecture/current/migration-roadmap.md +0 -105
  407. package/docs/architecture/current/multi-repository-execution.md +0 -371
  408. package/docs/architecture/current/operations-runbook.md +0 -45
  409. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  410. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  411. package/docs/architecture/current/repository-access-modes.md +0 -102
  412. package/docs/architecture/current/route-policy.md +0 -15
  413. package/docs/architecture/current/route-session-concurrency.md +0 -85
  414. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  415. package/docs/architecture/current/runtime-directory-map.md +0 -49
  416. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  417. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  418. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  419. package/docs/architecture/current/system-overview.md +0 -169
  420. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  421. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  422. package/docs/architecture/current/thin-harness-v1.md +0 -321
  423. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  424. package/docs/forge-autonomous-goal-loop.md +0 -9
  425. package/docs/forge-provider-configuration.md +0 -7
  426. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  427. package/docs/operations/gmail-assistant-routines.md +0 -186
  428. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  429. package/docs/operations/provider-configuration.md +0 -31
  430. package/scripts/harness-trace-grade.sh +0 -150
  431. package/scripts/heartbeat-triage.sh +0 -361
  432. package/scripts/maintenance-triage.sh +0 -167
  433. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  434. package/src/cli/repositories/goal-registry.ts +0 -239
  435. package/src/runtime/assistant/action-proposals.ts +0 -279
  436. package/src/runtime/assistant/intent.ts +0 -395
  437. package/src/runtime/assistant/model-provider.ts +0 -460
  438. package/src/runtime/assistant/openapi.ts +0 -223
  439. package/src/runtime/assistant/policy.ts +0 -89
  440. package/src/runtime/assistant/readiness.ts +0 -258
  441. package/src/runtime/assistant/routine-runtime.ts +0 -477
  442. package/src/runtime/assistant/schedule-binding.ts +0 -171
  443. package/src/runtime/assistant/standing-grants.ts +0 -375
  444. package/src/runtime/assistant/store.ts +0 -243
  445. package/src/runtime/assistant/types.ts +0 -135
  446. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  447. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  448. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  449. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  450. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  451. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  452. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  453. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  454. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  455. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  456. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  457. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  458. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  459. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  460. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  461. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  462. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  463. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  464. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  465. package/src/runtime/workflow/findings/store.ts +0 -204
  466. package/src/runtime/workflow/findings/types.ts +0 -58
  467. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  468. package/src/runtime/workflow/portfolio/store.ts +0 -185
  469. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,10 +1,13 @@
1
1
  import { createHash, randomUUID } from 'crypto';
2
- import { executeBrowserPluginAction, readBrowserPluginConfiguration } from '../../plugins/browser-adapter';
2
+ import { buildBrowserPluginManifest, executeBrowserPluginAction } from '../../plugins/browser-adapter';
3
3
  import { controllerPluginRepository } from '../../plugins/store';
4
4
  import { getWorkContract } from '../facade/work-contract-store';
5
5
  import {
6
6
  bindChatgptWorkConversation,
7
7
  getChatgptWorkConversationBinding,
8
+ hasChatgptConversationIdentity,
9
+ parseChatgptConversationIdentity,
10
+ rebindChatgptWorkConversation,
8
11
  type ChatgptWorkConversationBinding,
9
12
  } from './chatgpt-work-binding-store';
10
13
 
@@ -14,13 +17,17 @@ export const DEFAULT_CHATGPT_AUTOMATION_REASONING = 'high';
14
17
  export const DEFAULT_CHATGPT_AUTOMATION_TAB_POLICY = 'auto';
15
18
  export const DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION = '@forge';
16
19
  const CHATGPT_PROMPT_SELECTOR = 'div#prompt-textarea[contenteditable="true"]';
17
- const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"]';
18
- const CHATGPT_INTELLIGENCE_CONTROL_SELECTOR = 'main button';
20
+ const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"], button[aria-label*="Send"], button[data-testid*="send"]';
21
+ const CHATGPT_INTELLIGENCE_CONTROL_SELECTORS = [
22
+ 'main button, main [role="button"]',
23
+ 'button, [role="button"]',
24
+ ] as const;
19
25
  const CHATGPT_CAPABILITY_SLIDER_SELECTOR = '[role="slider"]';
20
26
  const CHATGPT_CAPABILITY_MENUITEM_SELECTOR = '[role="menuitem"][aria-keyshortcuts~="ArrowLeft"][aria-keyshortcuts~="ArrowRight"]';
21
27
 
22
28
  export type ChatgptAutomationReasoning = 'medium' | 'high' | 'xhigh';
23
29
  export type ChatgptAutomationTabPolicy = 'auto' | 'reuse' | 'new';
30
+ export type ChatgptAutomationTabCleanupStatus = 'closed' | 'preserved_user_owned' | 'session_closed' | 'failed';
24
31
 
25
32
  export interface WorkChatgptContinuationInput {
26
33
  controllerHome: string;
@@ -37,33 +44,6 @@ export interface WorkChatgptContinuationInput {
37
44
  timeoutMs?: number;
38
45
  }
39
46
 
40
- export interface ScheduledChatgptPromptInput {
41
- controllerHome: string;
42
- repoId: string;
43
- automationId: string;
44
- prompt: string;
45
- title?: string;
46
- browserSessionId?: string;
47
- conversationUrl?: string;
48
- model?: string;
49
- reasoning?: ChatgptAutomationReasoning;
50
- tabPolicy?: ChatgptAutomationTabPolicy;
51
- timeoutMs?: number;
52
- }
53
-
54
- export interface ScheduledChatgptPromptResult {
55
- status: 'dispatched' | 'failed';
56
- provider: 'controller-browser';
57
- browserSessionId: string;
58
- conversationUrl?: string;
59
- model: string;
60
- reasoning: ChatgptAutomationReasoning;
61
- tabPolicy: ChatgptAutomationTabPolicy;
62
- executionPreferenceVerified: boolean;
63
- reusedSession: boolean;
64
- error?: { code: string; message: string };
65
- }
66
-
67
47
  export interface WorkChatgptContinuationResult {
68
48
  status: 'dispatched' | 'failed';
69
49
  provider: 'controller-browser';
@@ -76,15 +56,42 @@ export interface WorkChatgptContinuationResult {
76
56
  reasoning: ChatgptAutomationReasoning;
77
57
  tabPolicy: ChatgptAutomationTabPolicy;
78
58
  executionPreferenceVerified: boolean;
59
+ tabCleanupStatus?: ChatgptAutomationTabCleanupStatus;
60
+ tabCleanupError?: { code: string; message: string };
79
61
  error?: { code: string; message: string };
80
62
  }
81
63
 
64
+ export interface StandaloneChatgptPromptInput {
65
+ controllerHome: string;
66
+ repoId: string;
67
+ scopeId: string;
68
+ prompt: string;
69
+ browserSessionId?: string;
70
+ conversationUrl?: string;
71
+ model?: string;
72
+ reasoning?: ChatgptAutomationReasoning;
73
+ tabPolicy?: ChatgptAutomationTabPolicy;
74
+ timeoutMs?: number;
75
+ }
76
+
82
77
  const workflowToolAttributionInstruction = (workId: string): string => `Forge Workflow execution contract: first claim exact Work ${workId}. For every repository_command_execute and repository_safe_patch_apply call in this turn, pass work_id=${workId} explicitly. MCP transport sessions may change between tool calls; never omit this Work id.`;
83
78
 
84
79
  function requestId(workId: string, actionId: string): string {
85
80
  return `chatgpt-work:${workId}:${actionId}:${randomUUID()}`;
86
81
  }
87
82
 
83
+ const CHATGPT_BROWSER_TRANSPORT_OVERRIDES = {
84
+ browser_mode: 'attach_preferred',
85
+ cdp_attach_fallback: 'fail_closed',
86
+ native_attach_mode: 'auto',
87
+ native_browser_candidates: ['chrome'],
88
+ } as const;
89
+
90
+ export function chatgptBrowserActionArgs(actionId: string, args: Record<string, unknown>): Record<string, unknown> {
91
+ if (actionId === 'configure' || actionId === 'list_sessions' || actionId === 'reconcile_sessions') return args;
92
+ return { ...args, ...CHATGPT_BROWSER_TRANSPORT_OVERRIDES };
93
+ }
94
+
88
95
  async function controllerBrowserAction(
89
96
  controllerHome: string,
90
97
  workId: string,
@@ -102,7 +109,7 @@ async function controllerBrowserAction(
102
109
  pluginId: 'browser',
103
110
  actionId,
104
111
  requestId: requestId(workId, actionId),
105
- args,
112
+ args: chatgptBrowserActionArgs(actionId, args),
106
113
  timeoutMs,
107
114
  origin: { surface: 'schedule', actor: 'chatgpt-work-continuation' },
108
115
  });
@@ -112,18 +119,12 @@ async function ensureControllerChatgptBrowser(controllerHome: string, workId: st
112
119
  const repository = controllerPluginRepository(controllerHome);
113
120
  const repoRoot = repository.canonicalRoot ?? repository.localRoot;
114
121
  if (!repoRoot) throw new Error('CHATGPT_CONTROLLER_BROWSER_ROOT_UNAVAILABLE');
115
- const existing = readBrowserPluginConfiguration(repoRoot);
116
- await controllerBrowserAction(controllerHome, workId, 'configure', {
117
- enabled: true,
118
- ...(!existing.enabled ? {
119
- browser_mode: 'attach_preferred',
120
- profile_mode: 'repo_local',
121
- browser_channel: 'chromium',
122
- cdp_attach_fallback: 'fail_closed',
123
- native_attach_mode: 'auto',
124
- native_browser_candidates: ['chrome'],
125
- } : {}),
126
- });
122
+ // Browser configure is not a read: it persists configuration and closes managed
123
+ // contexts. Scheduled continuations must not disturb an already-enabled provider
124
+ // merely to prove it is available. Only enable it when the persisted authority is
125
+ // explicitly disabled; action-level transport overrides still fail closed later.
126
+ if (buildBrowserPluginManifest(0, undefined, repoRoot).enabled) return;
127
+ await controllerBrowserAction(controllerHome, workId, 'configure', { enabled: true });
127
128
  }
128
129
 
129
130
  function stringField(value: unknown): string | undefined {
@@ -149,9 +150,16 @@ export function stableChatgptWorkBrowserSessionId(repoId: string, workId: string
149
150
  return `forge-chatgpt-work-${digest}`;
150
151
  }
151
152
 
152
- export function stableChatgptAutomationBrowserSessionId(repoId: string, automationId: string): string {
153
- const digest = createHash('sha256').update(`${repoId}\nautomation\n${automationId}`).digest('hex').slice(0, 20);
154
- return `forge-chatgpt-automation-${digest}`;
153
+ export function stableStandaloneChatgptBrowserSessionId(repoId: string, scopeId: string): string {
154
+ const digest = createHash('sha256').update(`${repoId}\nstandalone\n${scopeId}`).digest('hex').slice(0, 20);
155
+ return `forge-chatgpt-standalone-${digest}`;
156
+ }
157
+
158
+ function resolveStandaloneChatgptBrowserSessionId(input: StandaloneChatgptPromptInput): string {
159
+ const policy = normalizeTabPolicy(input.tabPolicy);
160
+ const stable = stableStandaloneChatgptBrowserSessionId(input.repoId, input.scopeId);
161
+ if (policy === 'new') return `${stable}-${randomUUID().slice(0, 8)}`;
162
+ return input.browserSessionId?.trim() || stable;
155
163
  }
156
164
 
157
165
  export function resolveChatgptWorkBrowserSessionId(input: {
@@ -189,10 +197,45 @@ function matchSelector(value: BrowserQueryMatch | undefined): string | undefined
189
197
  return typeof value?.selectorHint === 'string' && value.selectorHint.trim() ? value.selectorHint.trim() : undefined;
190
198
  }
191
199
 
200
+ function normalizeExecutionControlLabel(label: string | undefined): string {
201
+ return (label ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '');
202
+ }
203
+
204
+ export function chatgptAutomationReasoningLevelFromLabel(label: string | undefined): ChatgptAutomationReasoning | undefined {
205
+ const normalized = normalizeExecutionControlLabel(label);
206
+ if (!normalized) return undefined;
207
+
208
+ const exact = new Map<string, ChatgptAutomationReasoning>([
209
+ ['medium', 'medium'], ['中', 'medium'], ['中等', 'medium'],
210
+ ['high', 'high'], ['高', 'high'],
211
+ ['xhigh', 'xhigh'], ['extrahigh', 'xhigh'], ['超高', 'xhigh'], ['极高', 'xhigh'],
212
+ ]);
213
+ const exactLevel = exact.get(normalized);
214
+ if (exactLevel) return exactLevel;
215
+
216
+ const hasReasoningContext = ['reasoning', 'thinking', '推理', '思考']
217
+ .some((token) => normalized.includes(token));
218
+ if (!hasReasoningContext) return undefined;
219
+ if (['xhigh', 'extrahigh', '超高', '极高'].some((token) => normalized.includes(token))) return 'xhigh';
220
+ if (['medium', '中等'].some((token) => normalized.includes(token))) return 'medium';
221
+ if (['high', '高'].some((token) => normalized.includes(token))) return 'high';
222
+ return undefined;
223
+ }
224
+
192
225
  function modelLabelMatches(label: string | undefined, model: string): boolean {
193
226
  if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL || !label) return false;
194
- const normalized = label.toLowerCase().replace(/\s+/g, '');
195
- return normalized.includes('5.6sol') || normalized.includes('gpt-5.6sol');
227
+ const normalized = normalizeExecutionControlLabel(label);
228
+ return normalized.includes('5.6sol') || normalized.includes('gpt5.6sol');
229
+ }
230
+
231
+ function reasoningLabelMatches(label: string | undefined, reasoning: ChatgptAutomationReasoning): boolean {
232
+ return chatgptAutomationReasoningLevelFromLabel(label) === reasoning;
233
+ }
234
+
235
+ function isReasoningControlLabel(label: string | undefined): boolean {
236
+ return reasoningLabelMatches(label, 'medium')
237
+ || reasoningLabelMatches(label, 'high')
238
+ || reasoningLabelMatches(label, 'xhigh');
196
239
  }
197
240
 
198
241
  function reasoningSliderValue(reasoning: ChatgptAutomationReasoning): number {
@@ -207,13 +250,38 @@ async function findChatgptIntelligenceControl(
207
250
  browserSessionId: string,
208
251
  timeoutMs?: number,
209
252
  ): Promise<BrowserQueryMatch | undefined> {
210
- const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
211
- session_id: browserSessionId,
212
- selector: CHATGPT_INTELLIGENCE_CONTROL_SELECTOR,
213
- limit: 50,
214
- timeout_ms: timeoutMs ?? 60_000,
215
- }, timeoutMs);
216
- return queryMatches(result).find((match) => /(?:gpt-)?5\.6\s*sol/i.test(matchText(match)));
253
+ for (const selector of CHATGPT_INTELLIGENCE_CONTROL_SELECTORS) {
254
+ const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
255
+ session_id: browserSessionId,
256
+ selector,
257
+ // Prefer the composer/main region so sidebar history cannot crowd the
258
+ // reasoning control out of a bounded query. Keep a larger global fallback
259
+ // for ChatGPT layouts that place the control outside <main>.
260
+ limit: chatgptAutomationControlQueryLimit(selector),
261
+ timeout_ms: timeoutMs ?? 60_000,
262
+ }, timeoutMs);
263
+ const match = queryMatches(result).find((candidate) => {
264
+ const label = matchText(candidate);
265
+ return modelLabelMatches(label, DEFAULT_CHATGPT_AUTOMATION_MODEL) || isReasoningControlLabel(label);
266
+ });
267
+ if (match) return match;
268
+ }
269
+ return undefined;
270
+ }
271
+
272
+ export function chatgptAutomationControlQueryLimit(selector: string): number {
273
+ // Long tool-heavy conversations can contribute well over 80 buttons before
274
+ // the composer controls. Keep the preferred <main> scan bounded but large
275
+ // enough that the current reasoning control is not crowded out by history.
276
+ return selector.startsWith('main ') ? 160 : 320;
277
+ }
278
+
279
+ export function chatgptAutomationControlWaitBudgets(timeoutMs?: number): { waitBudgetMs: number; probeTimeoutMs: number } {
280
+ const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 30_000, 1_000), 30_000);
281
+ // Native Chrome attachment has a few seconds of Apple Events/DOM latency on
282
+ // long conversations. Keep each probe bounded while allowing a real query to
283
+ // complete; the total readiness window remains capped at 30 seconds.
284
+ return { waitBudgetMs, probeTimeoutMs: Math.min(waitBudgetMs, 5_000) };
217
285
  }
218
286
 
219
287
  async function waitForChatgptIntelligenceControl(
@@ -222,10 +290,10 @@ async function waitForChatgptIntelligenceControl(
222
290
  browserSessionId: string,
223
291
  timeoutMs?: number,
224
292
  ): Promise<BrowserQueryMatch | undefined> {
225
- const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 12_000, 1_000), 12_000);
293
+ const { waitBudgetMs, probeTimeoutMs } = chatgptAutomationControlWaitBudgets(timeoutMs);
226
294
  const deadline = Date.now() + waitBudgetMs;
227
295
  do {
228
- const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, Math.min(waitBudgetMs, 5_000));
296
+ const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, probeTimeoutMs);
229
297
  if (control) return control;
230
298
  if (Date.now() >= deadline) break;
231
299
  await new Promise((resolveWait) => setTimeout(resolveWait, 500));
@@ -233,6 +301,46 @@ async function waitForChatgptIntelligenceControl(
233
301
  return undefined;
234
302
  }
235
303
 
304
+ export function chatgptAutomationPageFailure(
305
+ bodyText: string | undefined,
306
+ composerAvailable: boolean,
307
+ ): 'CHATGPT_AUTOMATION_LOGIN_REQUIRED' | 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE' | undefined {
308
+ if (composerAvailable) return undefined;
309
+ const normalized = (bodyText ?? '').toLowerCase();
310
+ const loginMarkers = [
311
+ 'log in', 'sign up', 'continue with google', 'continue with apple',
312
+ '登录', '登陆', '注册', '使用 google 继续', '使用 apple 继续',
313
+ ];
314
+ return loginMarkers.some((marker) => normalized.includes(marker))
315
+ ? 'CHATGPT_AUTOMATION_LOGIN_REQUIRED'
316
+ : 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE';
317
+ }
318
+
319
+ async function assertChatgptAutomationComposerReady(
320
+ controllerHome: string,
321
+ workId: string,
322
+ browserSessionId: string,
323
+ timeoutMs?: number,
324
+ ): Promise<void> {
325
+ const composerResult = await controllerBrowserAction(controllerHome, workId, 'query_all', {
326
+ session_id: browserSessionId,
327
+ selector: CHATGPT_PROMPT_SELECTOR,
328
+ limit: 1,
329
+ timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
330
+ }, timeoutMs).catch(() => undefined);
331
+ const composerAvailable = queryMatches(composerResult).length > 0;
332
+ if (composerAvailable) return;
333
+
334
+ const bodyResult = await controllerBrowserAction(controllerHome, workId, 'get_text', {
335
+ session_id: browserSessionId,
336
+ selector: 'body',
337
+ max_chars: 4_000,
338
+ timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
339
+ }, timeoutMs).catch(() => undefined);
340
+ const failure = chatgptAutomationPageFailure(stringField(bodyResult?.text), composerAvailable);
341
+ throw new Error(failure ?? 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE');
342
+ }
343
+
236
344
  async function ensureChatgptExecutionPreference(
237
345
  controllerHome: string,
238
346
  workId: string,
@@ -243,8 +351,15 @@ async function ensureChatgptExecutionPreference(
243
351
  ): Promise<boolean> {
244
352
  if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL) throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${model}`);
245
353
  let control = await waitForChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs);
246
- if (!control) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
247
- if (!modelLabelMatches(matchText(control), model)) throw new Error(`CHATGPT_AUTOMATION_MODEL_NOT_VERIFIED:${model}`);
354
+ if (!control) {
355
+ await assertChatgptAutomationComposerReady(controllerHome, workId, browserSessionId, timeoutMs);
356
+ throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
357
+ }
358
+ // Current ChatGPT UI exposes the reasoning level (for example `高`) on the
359
+ // composer control instead of the model label. The model remains fail-closed
360
+ // through normalizeModel above; only the user-adjustable reasoning control is
361
+ // required to be observable in the page before unattended dispatch proceeds.
362
+ if (reasoningLabelMatches(matchText(control), reasoning)) return true;
248
363
  let controlSelector = matchSelector(control);
249
364
  if (!controlSelector) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
250
365
 
@@ -296,7 +411,9 @@ async function ensureChatgptExecutionPreference(
296
411
  throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
297
412
  }
298
413
  control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs) ?? control;
299
- if (!modelLabelMatches(matchText(control), model)) throw new Error(`CHATGPT_AUTOMATION_MODEL_NOT_VERIFIED:${model}`);
414
+ if (isReasoningControlLabel(matchText(control)) && !reasoningLabelMatches(matchText(control), reasoning)) {
415
+ throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
416
+ }
300
417
  controlSelector = matchSelector(control) ?? controlSelector;
301
418
  await controllerBrowserAction(controllerHome, workId, 'press', {
302
419
  session_id: browserSessionId,
@@ -313,6 +430,122 @@ function resultUrl(result: Record<string, unknown>): string | undefined {
313
430
  ?? stringField((result.session as Record<string, unknown> | undefined)?.url);
314
431
  }
315
432
 
433
+ function resultSessionId(result: Record<string, unknown>): string | undefined {
434
+ return stringField(result.session_id)
435
+ ?? stringField((result.session as Record<string, unknown> | undefined)?.sessionId);
436
+ }
437
+
438
+ export function chatgptAutomationNavigationRequiresReplacement(error: unknown): boolean {
439
+ if (!(error instanceof Error)) return false;
440
+ return [
441
+ 'BROWSER_AUTOMATION_BACKGROUND_NAVIGATION_REQUIRES_REPLACEMENT',
442
+ 'PLUGIN_BROWSER_SESSION_STATE_LOST',
443
+ 'PLUGIN_SESSION_NOT_FOUND',
444
+ 'PLUGIN_BROWSER_NATIVE_TAB_IDENTITY_UNPROVEN',
445
+ ].some((marker) => error.message.includes(marker));
446
+ }
447
+
448
+ async function closeChatgptAutomationTabAfterDispatch(
449
+ controllerHome: string,
450
+ workId: string,
451
+ browserSessionId: string,
452
+ timeoutMs?: number,
453
+ ): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
454
+ try {
455
+ const closed = await controllerBrowserAction(controllerHome, workId, 'close_page', {
456
+ session_id: browserSessionId,
457
+ }, Math.min(timeoutMs ?? 15_000, 15_000));
458
+ if (closed.preservedUserOwnedTab === true) return { status: 'preserved_user_owned' };
459
+ if (closed.resourceClosed === true) return { status: 'closed' };
460
+ return { status: 'session_closed' };
461
+ } catch (error) {
462
+ const message = error instanceof Error ? error.message : String(error);
463
+ return {
464
+ status: 'failed',
465
+ error: {
466
+ code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_AUTOMATION_TAB_CLEANUP_FAILED',
467
+ message,
468
+ },
469
+ };
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Settles the browser resource for one completed Work-bound ChatGPT round.
475
+ * Conversation identity is durable in the Work binding and intentionally
476
+ * survives this ephemeral tab/session cleanup. Browser ownership policy keeps
477
+ * user-owned native tabs intact.
478
+ */
479
+ export async function settleWorkChatgptAutomationTab(input: {
480
+ controllerHome: string;
481
+ workId: string;
482
+ browserSessionId: string;
483
+ timeoutMs?: number;
484
+ }): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
485
+ return closeChatgptAutomationTabAfterDispatch(
486
+ input.controllerHome,
487
+ input.workId,
488
+ input.browserSessionId,
489
+ input.timeoutMs,
490
+ );
491
+ }
492
+
493
+ export function isChatgptConversationUrl(value: string): boolean {
494
+ try {
495
+ const url = new URL(value);
496
+ return url.protocol === 'https:'
497
+ && ['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(url.hostname)
498
+ && /\/c\/[^/?#]+/.test(url.pathname);
499
+ } catch {
500
+ return false;
501
+ }
502
+ }
503
+
504
+ async function navigateWorkConversation(
505
+ controllerHome: string,
506
+ workId: string,
507
+ browserSessionId: string,
508
+ targetUrl: string,
509
+ timeoutMs?: number,
510
+ ): Promise<{ submissionTargetUrl: string; recoveredFromStaleBinding: boolean; browserSessionId: string }> {
511
+ const navigate = async (sessionId: string, url: string) => controllerBrowserAction(controllerHome, workId, 'navigate', {
512
+ session_id: sessionId,
513
+ url,
514
+ wait_until: 'domcontentloaded',
515
+ timeout_ms: timeoutMs ?? 60_000,
516
+ retries: 1,
517
+ }, timeoutMs);
518
+ const openReplacement = async (url: string): Promise<string> => {
519
+ const opened = await controllerBrowserAction(controllerHome, workId, 'open_page', {
520
+ url,
521
+ wait_until: 'domcontentloaded',
522
+ timeout_ms: timeoutMs ?? 60_000,
523
+ retries: 1,
524
+ }, timeoutMs);
525
+ const replacementSessionId = resultSessionId(opened);
526
+ if (!replacementSessionId) throw new Error('CHATGPT_AUTOMATION_REPLACEMENT_SESSION_NOT_CONFIRMED');
527
+ return replacementSessionId;
528
+ };
529
+ try {
530
+ await navigate(browserSessionId, targetUrl);
531
+ return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId };
532
+ } catch (error) {
533
+ if (chatgptAutomationNavigationRequiresReplacement(error)) {
534
+ const replacementSessionId = await openReplacement(targetUrl);
535
+ return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId: replacementSessionId };
536
+ }
537
+ if (!isChatgptConversationUrl(targetUrl)) throw error;
538
+ try {
539
+ await navigate(browserSessionId, 'https://chatgpt.com/');
540
+ return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId };
541
+ } catch (fallbackError) {
542
+ if (!chatgptAutomationNavigationRequiresReplacement(fallbackError)) throw fallbackError;
543
+ const replacementSessionId = await openReplacement('https://chatgpt.com/');
544
+ return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
545
+ }
546
+ }
547
+ }
548
+
316
549
  async function submitChatgptPrompt(
317
550
  controllerHome: string,
318
551
  workId: string,
@@ -358,75 +591,77 @@ export function withForgePluginMention(prompt: string): string {
358
591
  return `${DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION} ${value}`;
359
592
  }
360
593
 
361
- export async function runScheduledChatgptPrompt(input: ScheduledChatgptPromptInput): Promise<ScheduledChatgptPromptResult> {
594
+ /**
595
+ * Dispatches a bounded standalone prompt through the same controller-owned
596
+ * ChatGPT browser path without creating or requiring a WorkContract. The scope
597
+ * id is only a stable browser correlation key (for example a Schedule id).
598
+ */
599
+ export async function runStandaloneChatgptPrompt(input: StandaloneChatgptPromptInput): Promise<WorkChatgptContinuationResult> {
362
600
  const model = normalizeModel(input.model);
363
601
  const reasoning = normalizeReasoning(input.reasoning);
364
602
  const tabPolicy = normalizeTabPolicy(input.tabPolicy);
365
- const stableSessionId = stableChatgptAutomationBrowserSessionId(input.repoId, input.automationId);
366
- const explicitSessionId = input.browserSessionId?.trim();
367
- const sessionId = tabPolicy === 'new'
368
- ? `${stableSessionId}-${randomUUID().slice(0, 8)}`
369
- : explicitSessionId || stableSessionId;
370
- const targetUrl = tabPolicy === 'new'
371
- ? 'https://chatgpt.com/'
372
- : input.conversationUrl?.trim() || 'https://chatgpt.com/';
373
- let reusedSession = false;
603
+ const sessionId = resolveStandaloneChatgptBrowserSessionId(input);
604
+ const browserScopeId = `standalone:${input.scopeId}`;
605
+ const seedUrl = input.conversationUrl?.trim();
606
+
374
607
  try {
375
- await ensureControllerChatgptBrowser(input.controllerHome, input.automationId);
376
- if (tabPolicy !== 'new') {
377
- try {
378
- await controllerBrowserAction(input.controllerHome, input.automationId, 'get_text', {
379
- session_id: sessionId,
380
- selector: 'body',
381
- max_chars: 1,
382
- timeout_ms: Math.min(input.timeoutMs ?? 60_000, 3_000),
383
- }, input.timeoutMs);
384
- reusedSession = true;
385
- } catch {
386
- reusedSession = false;
387
- }
388
- }
389
- if (!reusedSession) {
390
- await controllerBrowserAction(input.controllerHome, input.automationId, 'open_page', {
391
- session_id: sessionId,
392
- url: targetUrl,
393
- wait_until: 'domcontentloaded',
394
- timeout_ms: input.timeoutMs ?? 60_000,
395
- retries: 1,
396
- }, input.timeoutMs);
397
- }
398
- const executionPreferenceVerified = await ensureChatgptExecutionPreference(
608
+ await ensureControllerChatgptBrowser(input.controllerHome, browserScopeId);
609
+ const targetUrl = seedUrl ?? 'https://chatgpt.com/';
610
+ const navigation = await navigateWorkConversation(
399
611
  input.controllerHome,
400
- input.automationId,
612
+ browserScopeId,
401
613
  sessionId,
614
+ targetUrl,
615
+ input.timeoutMs,
616
+ );
617
+ const executionPreferenceVerified = await ensureChatgptExecutionPreference(
618
+ input.controllerHome,
619
+ browserScopeId,
620
+ navigation.browserSessionId,
402
621
  model,
403
622
  reasoning,
404
623
  input.timeoutMs,
405
624
  );
406
- const observedUrl = await submitChatgptPrompt(input.controllerHome, input.automationId, sessionId, input.prompt, targetUrl, input.timeoutMs);
625
+ const observedUrl = await submitChatgptPrompt(
626
+ input.controllerHome,
627
+ browserScopeId,
628
+ navigation.browserSessionId,
629
+ input.prompt,
630
+ navigation.submissionTargetUrl,
631
+ input.timeoutMs,
632
+ );
633
+ const tabCleanup = await closeChatgptAutomationTabAfterDispatch(
634
+ input.controllerHome,
635
+ browserScopeId,
636
+ navigation.browserSessionId,
637
+ input.timeoutMs,
638
+ );
407
639
  return {
408
640
  status: 'dispatched',
409
641
  provider: 'controller-browser',
410
- browserSessionId: sessionId,
642
+ browserSessionId: navigation.browserSessionId,
411
643
  conversationUrl: observedUrl,
644
+ resumedFromBinding: false,
412
645
  model,
413
646
  reasoning,
414
647
  tabPolicy,
415
648
  executionPreferenceVerified,
416
- reusedSession,
649
+ tabCleanupStatus: tabCleanup.status,
650
+ ...(tabCleanup.error ? { tabCleanupError: tabCleanup.error } : {}),
417
651
  };
418
652
  } catch (error) {
419
653
  return {
420
654
  status: 'failed',
421
655
  provider: 'controller-browser',
422
656
  browserSessionId: sessionId,
657
+ conversationUrl: seedUrl,
658
+ resumedFromBinding: false,
423
659
  model,
424
660
  reasoning,
425
661
  tabPolicy,
426
662
  executionPreferenceVerified: false,
427
- reusedSession,
428
663
  error: {
429
- code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_SCHEDULED_PROMPT_FAILED',
664
+ code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_CONTROLLER_BROWSER_FAILED',
430
665
  message: error instanceof Error ? error.message : String(error),
431
666
  },
432
667
  };
@@ -459,7 +694,7 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
459
694
  throw new Error(`CHATGPT_WORK_CONTRACT_NOT_FOUND: ${input.repoId}:${input.workId}`);
460
695
  }
461
696
  await ensureControllerChatgptBrowser(input.controllerHome, input.workId);
462
- if (seedUrl && !binding) {
697
+ if (seedUrl && !binding && hasChatgptConversationIdentity(seedUrl)) {
463
698
  binding = bindChatgptWorkConversation(store, {
464
699
  workId: input.workId,
465
700
  conversationUrl: seedUrl,
@@ -468,34 +703,47 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
468
703
  });
469
704
  }
470
705
  const targetUrl = binding?.conversationUrl ?? seedUrl ?? 'https://chatgpt.com/';
471
- await controllerBrowserAction(input.controllerHome, input.workId, 'navigate', {
472
- session_id: sessionId,
473
- url: targetUrl,
474
- wait_until: 'domcontentloaded',
475
- timeout_ms: input.timeoutMs ?? 60_000,
476
- retries: 1,
477
- }, input.timeoutMs);
478
- const executionPreferenceVerified = await ensureChatgptExecutionPreference(
706
+ const navigation = await navigateWorkConversation(
479
707
  input.controllerHome,
480
708
  input.workId,
481
709
  sessionId,
710
+ targetUrl,
711
+ input.timeoutMs,
712
+ );
713
+ const executionPreferenceVerified = await ensureChatgptExecutionPreference(
714
+ input.controllerHome,
715
+ input.workId,
716
+ navigation.browserSessionId,
482
717
  model,
483
718
  reasoning,
484
719
  input.timeoutMs,
485
720
  );
486
- const observedUrl = await submitChatgptPrompt(input.controllerHome, input.workId, sessionId, `${workflowToolAttributionInstruction(input.workId)}\n\n${input.prompt}`, targetUrl, input.timeoutMs);
721
+ const observedUrl = await submitChatgptPrompt(input.controllerHome, input.workId, navigation.browserSessionId, `${workflowToolAttributionInstruction(input.workId)}\n\n${input.prompt}`, navigation.submissionTargetUrl, input.timeoutMs);
487
722
  if (/\/c\/[^/?#]+/.test(observedUrl)) {
488
- binding = bindChatgptWorkConversation(store, {
489
- workId: input.workId,
490
- conversationUrl: observedUrl,
491
- latestBrowserSessionId: sessionId,
492
- localAlias: binding?.localAlias ?? input.title,
493
- });
723
+ const observedIdentity = parseChatgptConversationIdentity(observedUrl);
724
+ binding = binding && binding.conversationId !== observedIdentity.conversationId
725
+ ? rebindChatgptWorkConversation(store, {
726
+ workId: input.workId,
727
+ previousConversationId: binding.conversationId,
728
+ conversationUrl: observedUrl,
729
+ latestBrowserSessionId: navigation.browserSessionId,
730
+ localAlias: binding.localAlias ?? input.title,
731
+ })
732
+ : bindChatgptWorkConversation(store, {
733
+ workId: input.workId,
734
+ conversationUrl: observedUrl,
735
+ latestBrowserSessionId: navigation.browserSessionId,
736
+ localAlias: binding?.localAlias ?? input.title,
737
+ });
494
738
  }
739
+ // Work-bound dispatch keeps the Forge-owned automation tab/session alive for
740
+ // the launched semantic Controller round. The exact Controller release path
741
+ // settles it after an explicit disposition. Closing here would race the new
742
+ // ChatGPT round and make dispatch success look like round completion.
495
743
  return {
496
744
  status: 'dispatched',
497
745
  provider: 'controller-browser',
498
- browserSessionId: sessionId,
746
+ browserSessionId: navigation.browserSessionId,
499
747
  conversationUrl: binding?.conversationUrl ?? observedUrl,
500
748
  conversationId: binding?.conversationId,
501
749
  localAlias: binding?.localAlias,