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