@moretea-labs/forge 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,10 +1,12 @@
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
+ parseChatgptConversationIdentity,
9
+ rebindChatgptWorkConversation,
8
10
  type ChatgptWorkConversationBinding,
9
11
  } from './chatgpt-work-binding-store';
10
12
 
@@ -14,13 +16,17 @@ export const DEFAULT_CHATGPT_AUTOMATION_REASONING = 'high';
14
16
  export const DEFAULT_CHATGPT_AUTOMATION_TAB_POLICY = 'auto';
15
17
  export const DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION = '@forge';
16
18
  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';
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;
19
24
  const CHATGPT_CAPABILITY_SLIDER_SELECTOR = '[role="slider"]';
20
25
  const CHATGPT_CAPABILITY_MENUITEM_SELECTOR = '[role="menuitem"][aria-keyshortcuts~="ArrowLeft"][aria-keyshortcuts~="ArrowRight"]';
21
26
 
22
27
  export type ChatgptAutomationReasoning = 'medium' | 'high' | 'xhigh';
23
28
  export type ChatgptAutomationTabPolicy = 'auto' | 'reuse' | 'new';
29
+ export type ChatgptAutomationTabCleanupStatus = 'closed' | 'preserved_user_owned' | 'session_closed' | 'failed';
24
30
 
25
31
  export interface WorkChatgptContinuationInput {
26
32
  controllerHome: string;
@@ -37,33 +43,6 @@ export interface WorkChatgptContinuationInput {
37
43
  timeoutMs?: number;
38
44
  }
39
45
 
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
46
  export interface WorkChatgptContinuationResult {
68
47
  status: 'dispatched' | 'failed';
69
48
  provider: 'controller-browser';
@@ -76,15 +55,42 @@ export interface WorkChatgptContinuationResult {
76
55
  reasoning: ChatgptAutomationReasoning;
77
56
  tabPolicy: ChatgptAutomationTabPolicy;
78
57
  executionPreferenceVerified: boolean;
58
+ tabCleanupStatus?: ChatgptAutomationTabCleanupStatus;
59
+ tabCleanupError?: { code: string; message: string };
79
60
  error?: { code: string; message: string };
80
61
  }
81
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
+
82
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.`;
83
77
 
84
78
  function requestId(workId: string, actionId: string): string {
85
79
  return `chatgpt-work:${workId}:${actionId}:${randomUUID()}`;
86
80
  }
87
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
+
88
94
  async function controllerBrowserAction(
89
95
  controllerHome: string,
90
96
  workId: string,
@@ -102,7 +108,7 @@ async function controllerBrowserAction(
102
108
  pluginId: 'browser',
103
109
  actionId,
104
110
  requestId: requestId(workId, actionId),
105
- args,
111
+ args: chatgptBrowserActionArgs(actionId, args),
106
112
  timeoutMs,
107
113
  origin: { surface: 'schedule', actor: 'chatgpt-work-continuation' },
108
114
  });
@@ -112,18 +118,12 @@ async function ensureControllerChatgptBrowser(controllerHome: string, workId: st
112
118
  const repository = controllerPluginRepository(controllerHome);
113
119
  const repoRoot = repository.canonicalRoot ?? repository.localRoot;
114
120
  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
- });
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 });
127
127
  }
128
128
 
129
129
  function stringField(value: unknown): string | undefined {
@@ -149,9 +149,16 @@ export function stableChatgptWorkBrowserSessionId(repoId: string, workId: string
149
149
  return `forge-chatgpt-work-${digest}`;
150
150
  }
151
151
 
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}`;
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;
155
162
  }
156
163
 
157
164
  export function resolveChatgptWorkBrowserSessionId(input: {
@@ -189,10 +196,45 @@ function matchSelector(value: BrowserQueryMatch | undefined): string | undefined
189
196
  return typeof value?.selectorHint === 'string' && value.selectorHint.trim() ? value.selectorHint.trim() : undefined;
190
197
  }
191
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
+
192
224
  function modelLabelMatches(label: string | undefined, model: string): boolean {
193
225
  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');
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');
196
238
  }
197
239
 
198
240
  function reasoningSliderValue(reasoning: ChatgptAutomationReasoning): number {
@@ -207,13 +249,31 @@ async function findChatgptIntelligenceControl(
207
249
  browserSessionId: string,
208
250
  timeoutMs?: number,
209
251
  ): 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)));
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) };
217
277
  }
218
278
 
219
279
  async function waitForChatgptIntelligenceControl(
@@ -222,10 +282,10 @@ async function waitForChatgptIntelligenceControl(
222
282
  browserSessionId: string,
223
283
  timeoutMs?: number,
224
284
  ): Promise<BrowserQueryMatch | undefined> {
225
- const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 12_000, 1_000), 12_000);
285
+ const { waitBudgetMs, probeTimeoutMs } = chatgptAutomationControlWaitBudgets(timeoutMs);
226
286
  const deadline = Date.now() + waitBudgetMs;
227
287
  do {
228
- const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, Math.min(waitBudgetMs, 5_000));
288
+ const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, probeTimeoutMs);
229
289
  if (control) return control;
230
290
  if (Date.now() >= deadline) break;
231
291
  await new Promise((resolveWait) => setTimeout(resolveWait, 500));
@@ -233,6 +293,46 @@ async function waitForChatgptIntelligenceControl(
233
293
  return undefined;
234
294
  }
235
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
+
236
336
  async function ensureChatgptExecutionPreference(
237
337
  controllerHome: string,
238
338
  workId: string,
@@ -243,8 +343,15 @@ async function ensureChatgptExecutionPreference(
243
343
  ): Promise<boolean> {
244
344
  if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL) throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${model}`);
245
345
  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}`);
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;
248
355
  let controlSelector = matchSelector(control);
249
356
  if (!controlSelector) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
250
357
 
@@ -296,7 +403,9 @@ async function ensureChatgptExecutionPreference(
296
403
  throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
297
404
  }
298
405
  control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs) ?? control;
299
- if (!modelLabelMatches(matchText(control), model)) throw new Error(`CHATGPT_AUTOMATION_MODEL_NOT_VERIFIED:${model}`);
406
+ if (isReasoningControlLabel(matchText(control)) && !reasoningLabelMatches(matchText(control), reasoning)) {
407
+ throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
408
+ }
300
409
  controlSelector = matchSelector(control) ?? controlSelector;
301
410
  await controllerBrowserAction(controllerHome, workId, 'press', {
302
411
  session_id: browserSessionId,
@@ -313,6 +422,121 @@ function resultUrl(result: Record<string, unknown>): string | undefined {
313
422
  ?? stringField((result.session as Record<string, unknown> | undefined)?.url);
314
423
  }
315
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
+
316
540
  async function submitChatgptPrompt(
317
541
  controllerHome: string,
318
542
  workId: string,
@@ -358,75 +582,77 @@ export function withForgePluginMention(prompt: string): string {
358
582
  return `${DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION} ${value}`;
359
583
  }
360
584
 
361
- export async function runScheduledChatgptPrompt(input: ScheduledChatgptPromptInput): Promise<ScheduledChatgptPromptResult> {
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> {
362
591
  const model = normalizeModel(input.model);
363
592
  const reasoning = normalizeReasoning(input.reasoning);
364
593
  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;
594
+ const sessionId = resolveStandaloneChatgptBrowserSessionId(input);
595
+ const browserScopeId = `standalone:${input.scopeId}`;
596
+ const seedUrl = input.conversationUrl?.trim();
597
+
374
598
  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(
599
+ await ensureControllerChatgptBrowser(input.controllerHome, browserScopeId);
600
+ const targetUrl = seedUrl ?? 'https://chatgpt.com/';
601
+ const navigation = await navigateWorkConversation(
399
602
  input.controllerHome,
400
- input.automationId,
603
+ browserScopeId,
401
604
  sessionId,
605
+ targetUrl,
606
+ input.timeoutMs,
607
+ );
608
+ const executionPreferenceVerified = await ensureChatgptExecutionPreference(
609
+ input.controllerHome,
610
+ browserScopeId,
611
+ navigation.browserSessionId,
402
612
  model,
403
613
  reasoning,
404
614
  input.timeoutMs,
405
615
  );
406
- const observedUrl = await submitChatgptPrompt(input.controllerHome, input.automationId, sessionId, input.prompt, targetUrl, input.timeoutMs);
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
+ );
407
630
  return {
408
631
  status: 'dispatched',
409
632
  provider: 'controller-browser',
410
- browserSessionId: sessionId,
633
+ browserSessionId: navigation.browserSessionId,
411
634
  conversationUrl: observedUrl,
635
+ resumedFromBinding: false,
412
636
  model,
413
637
  reasoning,
414
638
  tabPolicy,
415
639
  executionPreferenceVerified,
416
- reusedSession,
640
+ tabCleanupStatus: tabCleanup.status,
641
+ ...(tabCleanup.error ? { tabCleanupError: tabCleanup.error } : {}),
417
642
  };
418
643
  } catch (error) {
419
644
  return {
420
645
  status: 'failed',
421
646
  provider: 'controller-browser',
422
647
  browserSessionId: sessionId,
648
+ conversationUrl: seedUrl,
649
+ resumedFromBinding: false,
423
650
  model,
424
651
  reasoning,
425
652
  tabPolicy,
426
653
  executionPreferenceVerified: false,
427
- reusedSession,
428
654
  error: {
429
- code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_SCHEDULED_PROMPT_FAILED',
655
+ code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_CONTROLLER_BROWSER_FAILED',
430
656
  message: error instanceof Error ? error.message : String(error),
431
657
  },
432
658
  };
@@ -468,34 +694,47 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
468
694
  });
469
695
  }
470
696
  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(
697
+ const navigation = await navigateWorkConversation(
479
698
  input.controllerHome,
480
699
  input.workId,
481
700
  sessionId,
701
+ targetUrl,
702
+ input.timeoutMs,
703
+ );
704
+ const executionPreferenceVerified = await ensureChatgptExecutionPreference(
705
+ input.controllerHome,
706
+ input.workId,
707
+ navigation.browserSessionId,
482
708
  model,
483
709
  reasoning,
484
710
  input.timeoutMs,
485
711
  );
486
- const observedUrl = await submitChatgptPrompt(input.controllerHome, input.workId, sessionId, `${workflowToolAttributionInstruction(input.workId)}\n\n${input.prompt}`, targetUrl, input.timeoutMs);
712
+ const observedUrl = await submitChatgptPrompt(input.controllerHome, input.workId, navigation.browserSessionId, `${workflowToolAttributionInstruction(input.workId)}\n\n${input.prompt}`, navigation.submissionTargetUrl, input.timeoutMs);
487
713
  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
- });
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
+ });
494
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.
495
734
  return {
496
735
  status: 'dispatched',
497
736
  provider: 'controller-browser',
498
- browserSessionId: sessionId,
737
+ browserSessionId: navigation.browserSessionId,
499
738
  conversationUrl: binding?.conversationUrl ?? observedUrl,
500
739
  conversationId: binding?.conversationId,
501
740
  localAlias: binding?.localAlias,