@moretea-labs/forge 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/AGENTS.md +70 -7
  2. package/CHANGELOG.md +14 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/THIRD_PARTY_NOTICES.md +1 -1
  6. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
  8. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  9. package/adapters/chatgpt/controller-round-host.ts +15 -25
  10. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  11. package/adapters/chatgpt/provider-delivery.ts +228 -1
  12. package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
  13. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  14. package/adapters/computer/chatgpt-target-router.ts +103 -0
  15. package/adapters/computer/chrome-extension/background.js +385 -0
  16. package/adapters/computer/chrome-extension/content.js +378 -0
  17. package/adapters/computer/chrome-extension/core.js +24 -0
  18. package/adapters/computer/chrome-extension/manifest.json +30 -0
  19. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  20. package/adapters/computer/interaction-target-authority.ts +142 -18
  21. package/adapters/mcp/auth.ts +0 -1
  22. package/adapters/mcp/instructions.ts +5 -4
  23. package/adapters/mcp/multi-repository.ts +3 -19
  24. package/adapters/mcp/oauth.ts +73 -0
  25. package/adapters/mcp/paths.ts +4 -1
  26. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  27. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  28. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  29. package/adapters/mcp/runtime-gateway/context-adapter.ts +411 -73
  30. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  31. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  32. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  33. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  34. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  35. package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
  36. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  37. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
  38. package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
  39. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  40. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  41. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  42. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  43. package/adapters/mcp/runtime-gateway/router.ts +18 -504
  44. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  45. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
  46. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  47. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
  48. package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
  49. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
  50. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  51. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
  52. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  53. package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
  54. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  55. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  56. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  57. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  58. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  59. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  60. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  61. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  62. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  63. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  64. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  65. package/adapters/mcp/server.ts +67 -23
  66. package/adapters/mcp/setup.ts +1 -3
  67. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
  68. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
  69. package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
  70. package/adapters/mcp/toolset-names.ts +5 -0
  71. package/adapters/mcp/toolset.ts +6 -5
  72. package/adapters/mcp/transports/http-observation.ts +365 -0
  73. package/adapters/mcp/transports/http.ts +154 -677
  74. package/adapters/mcp/transports/oauth-http.ts +322 -0
  75. package/adapters/mcp/transports/session-registry.ts +6 -0
  76. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  77. package/assets/plugin-registry.v1.json +6 -4
  78. package/assets/reference-configs/agentic-development-flow.md +25 -0
  79. package/assets/reference-configs/global-working-rules.md +19 -0
  80. package/assets/skill-version.json +2 -2
  81. package/docs/architecture/CURRENT.md +111 -41
  82. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  83. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  84. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  85. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  86. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  87. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  88. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  89. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  90. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  91. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  92. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  93. package/docs/architecture/domains/verification.md +1 -0
  94. package/docs/architecture/domains/workflow-engine.md +6 -0
  95. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  96. package/docs/forge-plugin-management.md +2 -2
  97. package/docs/operations/controller-performance-and-502.md +17 -0
  98. package/docs/operations/controller-reliability-runbook.md +2 -2
  99. package/docs/operations/features.md +1 -1
  100. package/docs/operations/features.zh-CN.md +1 -1
  101. package/docs/operations/homebrew.md +2 -2
  102. package/docs/operations/mcp-tool-exposure.md +2 -0
  103. package/docs/operations/releasing.md +2 -0
  104. package/docs/operations/standalone-disaster-recovery.md +14 -6
  105. package/docs/wiki/Implementation.md +1 -1
  106. package/package.json +25 -14
  107. package/packages/kernel/cognition/api/index.ts +6 -0
  108. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  109. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  110. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  111. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  112. package/packages/kernel/cognition/domain/learning.ts +106 -0
  113. package/packages/kernel/cognition/domain/memory.ts +254 -0
  114. package/packages/kernel/controller/api/index.ts +1 -0
  115. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  116. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  117. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  118. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  119. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  120. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  121. package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
  122. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  123. package/packages/kernel/identity/api/index.ts +10 -0
  124. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  125. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  126. package/packages/kernel/identity/domain/grant.ts +72 -0
  127. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  128. package/packages/kernel/identity/domain/user-request.ts +93 -0
  129. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  130. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  131. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  132. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  133. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  134. package/packages/kernel/scheduler/api/index.ts +0 -2
  135. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  136. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  137. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  138. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
  139. package/packages/kernel/work/api/index.ts +3 -2
  140. package/packages/kernel/work/application/work-service.ts +17 -42
  141. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  142. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  143. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  144. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  145. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  146. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  147. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  148. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  149. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  150. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  151. package/packages/kernel/work/domain/state-machine.ts +25 -63
  152. package/packages/kernel/work/domain/types.ts +57 -51
  153. package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
  154. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  155. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  156. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  157. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  158. package/packages/plugin-runtime/computer/index.ts +2 -0
  159. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  160. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  161. package/packages/protocols/computer/contract.ts +27 -1
  162. package/scripts/activate-source-baseline.ts +7 -9
  163. package/scripts/canary-controller-semantic-wait.ts +12 -24
  164. package/scripts/check-mcp-compatibility.ts +48 -136
  165. package/scripts/check-runtime-architecture.mjs +622 -217
  166. package/scripts/forge-local-recovery-helper.mjs +93 -15
  167. package/scripts/lib/project-init-lib.sh +3 -0
  168. package/scripts/open-source-audit-allowlist.txt +1 -2
  169. package/scripts/run-bounded-command.mjs +5 -0
  170. package/scripts/run-governed-gate.ts +0 -1
  171. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  172. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  173. package/scripts/smoke-mcp-http-runtime.ts +5 -0
  174. package/scripts/smoke-runtime-recovery.ts +12 -43
  175. package/scripts/smoke-schedule-engine.ts +23 -15
  176. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  177. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  178. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  179. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  180. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  181. package/scripts/smoke-workflow-supervisor.ts +103 -0
  182. package/scripts/verify-forge-runtime.sh +2 -1
  183. package/src/cli/agent-jobs/executor-health.ts +11 -34
  184. package/src/cli/agent-jobs/integration.ts +18 -49
  185. package/src/cli/agent-jobs/job-manager.ts +7 -3
  186. package/src/cli/commands/brain-assistant.ts +27 -13
  187. package/src/cli/commands/brain.ts +13 -15
  188. package/src/cli/commands/chatgpt.ts +1 -163
  189. package/src/cli/commands/recovery.ts +135 -121
  190. package/src/cli/commands/runtime.ts +3 -3
  191. package/src/cli/commands/setup-profile.ts +8 -0
  192. package/src/cli/controller/check-runner.ts +13 -2
  193. package/src/cli/controller/completion-backlog.ts +3 -3
  194. package/src/cli/controller/completion-orchestrator.ts +4 -43
  195. package/src/cli/controller/completion-target.ts +3 -72
  196. package/src/cli/controller/context/source-materializer.ts +81 -100
  197. package/src/cli/controller/context-pack.ts +10 -8
  198. package/src/cli/controller/execution-completion.ts +4 -7
  199. package/src/cli/controller/execution-policy.ts +36 -153
  200. package/src/cli/controller/governance.ts +2 -3
  201. package/src/cli/controller/issue-store.ts +4 -99
  202. package/src/cli/controller/launch-agents.ts +59 -18
  203. package/src/cli/controller/operational-plan.ts +24 -51
  204. package/src/cli/controller/runtime-config.ts +5 -1
  205. package/src/cli/controller/stuck-state-migration.ts +2 -3
  206. package/src/cli/controller/systemd-user.ts +39 -1
  207. package/src/cli/editing/edit-session.ts +1 -7
  208. package/src/cli/local-bridge/console-automations.ts +18 -13
  209. package/src/cli/local-bridge/console-view-models.ts +3 -12
  210. package/src/cli/local-bridge/facade-api.ts +64 -191
  211. package/src/cli/local-bridge/server.ts +24 -147
  212. package/src/cli/local-bridge/types.ts +0 -1
  213. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  214. package/src/cli/repositories/command-executor.ts +29 -23
  215. package/src/cli/repositories/command-scope.ts +35 -16
  216. package/src/cli/repositories/controller-home.ts +48 -0
  217. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  218. package/src/cli/repositories/git-command-executor.ts +2 -2
  219. package/src/cli/repositories/registry.ts +77 -11
  220. package/src/cli/repositories/structured-git.ts +62 -2
  221. package/src/cli/repository/session-cache.ts +92 -6
  222. package/src/runtime/context/assistant-context.ts +20 -7
  223. package/src/runtime/context/assistant-work-context.ts +89 -27
  224. package/src/runtime/context/automatic-learning.ts +565 -0
  225. package/src/runtime/context/cognitive-attention.ts +173 -0
  226. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  227. package/src/runtime/context/context-closure.ts +46 -9
  228. package/src/runtime/context/context-pack-process.ts +103 -0
  229. package/src/runtime/context/context-plane.ts +1 -9
  230. package/src/runtime/context/direct-controller-learning.ts +298 -0
  231. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  232. package/src/runtime/context/semantic-navigation.ts +6 -34
  233. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  234. package/src/runtime/context/typescript-navigation.ts +89 -3
  235. package/src/runtime/control-plane/cleanup-artifact-retention.ts +212 -9
  236. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  237. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  238. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  239. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  240. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  241. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  242. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  243. package/src/runtime/control-plane/execution/validation.ts +6 -8
  244. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  245. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  246. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  247. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  248. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  249. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  250. package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
  251. package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
  252. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  253. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  254. package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
  255. package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
  256. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  257. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  258. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +173 -186
  259. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
  260. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  261. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
  262. package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
  263. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  264. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  265. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  266. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  267. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  268. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  269. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  270. package/src/runtime/control-plane/facade/index.ts +0 -1
  271. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  272. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  273. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  274. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  275. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  276. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  277. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  278. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  279. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  280. package/src/runtime/control-plane/facade/types.ts +49 -108
  281. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  282. package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
  283. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  284. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  285. package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
  286. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
  287. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  288. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  289. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  290. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  291. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  292. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
  293. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  294. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  295. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  296. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  297. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  298. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  299. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  300. package/src/runtime/control-plane/release-retention.ts +135 -47
  301. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  302. package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
  303. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  304. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  305. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  306. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  307. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  308. package/src/runtime/evidence/artifact-store.ts +158 -40
  309. package/src/runtime/evidence/evidence-store.ts +112 -8
  310. package/src/runtime/evidence/output-handle-index.ts +225 -0
  311. package/src/runtime/evidence/result-store.ts +158 -44
  312. package/src/runtime/execution/jobs/store.ts +3 -4
  313. package/src/runtime/execution/managed-workspace.ts +57 -0
  314. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  315. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  316. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  317. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  318. package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
  319. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  320. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  321. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  322. package/src/runtime/execution/process-runtime/index.ts +3 -0
  323. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  324. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  325. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  326. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  327. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  328. package/src/runtime/execution/process-runtime/runtime.ts +35 -2
  329. package/src/runtime/execution/process-runtime/store.ts +30 -2
  330. package/src/runtime/execution/process-runtime/types.ts +6 -1
  331. package/src/runtime/execution/workers/executor.ts +2 -6
  332. package/src/runtime/platform/service-inventory.ts +130 -0
  333. package/src/runtime/platform/service-manager.ts +8 -0
  334. package/src/runtime/plugins/action-application.ts +85 -28
  335. package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
  336. package/src/runtime/plugins/browser-adapter.ts +757 -34
  337. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  338. package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
  339. package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
  340. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  341. package/src/runtime/plugins/browser-registration.ts +16 -0
  342. package/src/runtime/plugins/browser-runtime.ts +14 -1
  343. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  344. package/src/runtime/plugins/browser-session-store.ts +46 -76
  345. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  346. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  347. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  348. package/src/runtime/plugins/computer-registration.ts +222 -11
  349. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  350. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  351. package/src/runtime/plugins/external-adapter.ts +42 -6
  352. package/src/runtime/plugins/external-registration.ts +21 -1
  353. package/src/runtime/plugins/gmail-adapter.ts +23 -2
  354. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  355. package/src/runtime/plugins/google-shared.ts +14 -7
  356. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  357. package/src/runtime/plugins/ios-adapter.ts +1 -1
  358. package/src/runtime/plugins/lightweight-action.ts +208 -2
  359. package/src/runtime/plugins/local-system-adapter.ts +12 -2
  360. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  361. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  362. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  363. package/src/runtime/plugins/store.ts +602 -428
  364. package/src/runtime/plugins/types.ts +20 -0
  365. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  366. package/src/runtime/projections/invalidation.ts +7 -3
  367. package/src/runtime/projections/materialized-view.ts +80 -36
  368. package/src/runtime/recovery/actions.ts +3 -3
  369. package/src/runtime/recovery/capability-action-application.ts +46 -45
  370. package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
  371. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  372. package/src/runtime/recovery/store.ts +29 -11
  373. package/src/runtime/release/release-coordinator.ts +296 -0
  374. package/src/runtime/release/release-session.ts +716 -0
  375. package/src/runtime/release/release-state-migration.ts +25 -0
  376. package/src/runtime/resources/leases/store.ts +56 -25
  377. package/src/runtime/root/computer-composition.ts +191 -6
  378. package/src/runtime/root/controller-progression-composition.ts +192 -0
  379. package/src/runtime/root/controller-round-composition.ts +20 -1
  380. package/src/runtime/root/deployment-topology.ts +106 -0
  381. package/src/runtime/root/entry.ts +4 -3
  382. package/src/runtime/root/known-good-recovery.ts +184 -0
  383. package/src/runtime/root/local-bridge.ts +0 -1
  384. package/src/runtime/root/mcp-transport.ts +111 -18
  385. package/src/runtime/root/ownership.ts +261 -9
  386. package/src/runtime/root/package-connector-service.ts +105 -19
  387. package/src/runtime/root/package-runtime-release.ts +117 -18
  388. package/src/runtime/root/package-runtime-service.ts +17 -11
  389. package/src/runtime/root/release-execution-canary.ts +44 -12
  390. package/src/runtime/root/release-loader.ts +90 -8
  391. package/src/runtime/root/release-manifest.ts +80 -6
  392. package/src/runtime/root/release-materialize.ts +372 -28
  393. package/src/runtime/root/release-store.ts +395 -36
  394. package/src/runtime/root/runtime-lane.ts +191 -0
  395. package/src/runtime/root/runtime.ts +159 -27
  396. package/src/runtime/root/scheduled-controller-composition.ts +28 -3
  397. package/src/runtime/root/service-runner.ts +2 -2
  398. package/src/runtime/root/service.ts +36 -33
  399. package/src/runtime/root/status.ts +48 -0
  400. package/src/runtime/root/types.ts +32 -3
  401. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  402. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  403. package/src/runtime/root/write-fence.ts +50 -2
  404. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  405. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  406. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  407. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  408. package/src/runtime/shared/process-environment.ts +20 -9
  409. package/src/runtime/shared/response-budget.ts +1 -3
  410. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  411. package/src/runtime/standalone-recovery/core.ts +3702 -342
  412. package/src/runtime/standalone-recovery/entry.ts +469 -39
  413. package/src/runtime/standalone-recovery/installer.ts +262 -148
  414. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  415. package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
  416. package/src/runtime/standalone-recovery/performance.ts +54 -16
  417. package/src/runtime/standalone-recovery/release.ts +53 -5
  418. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  419. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  420. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  421. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
  422. package/src/runtime/workflow/schedules/engine.ts +138 -172
  423. package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
  424. package/src/runtime/workflows/runtime.ts +15 -19
  425. package/src/testing/test-governance.ts +5 -1
  426. package/supervisor/chatgpt-conversation.ts +23 -0
  427. package/supervisor/client.ts +201 -0
  428. package/supervisor/control-plane.ts +868 -0
  429. package/supervisor/entry.ts +15 -0
  430. package/supervisor/forge-validators.ts +80 -0
  431. package/supervisor/index.ts +5 -0
  432. package/supervisor/native-browser-adapter.ts +786 -0
  433. package/supervisor/native-messaging/host.ts +39 -0
  434. package/supervisor/native-messaging/manifest.ts +26 -0
  435. package/supervisor/native-messaging/protocol.ts +27 -0
  436. package/supervisor/paths.ts +18 -0
  437. package/supervisor/protocol.ts +202 -0
  438. package/supervisor/server.ts +404 -0
  439. package/supervisor/store.ts +1451 -0
  440. package/supervisor/tsconfig.json +8 -0
  441. package/supervisor/types.ts +255 -0
  442. package/adapters/chatgpt/controller-host.ts +0 -119
  443. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  444. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  445. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  446. package/packages/kernel/progression/api/index.ts +0 -2
  447. package/packages/kernel/progression/application/projector.ts +0 -211
  448. package/packages/kernel/progression/domain/types.ts +0 -116
  449. package/packages/kernel/progression/index.ts +0 -1
  450. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  451. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  452. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  453. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  454. package/scripts/benchmark-background-check-overlap.ts +0 -228
  455. package/scripts/benchmark-computer-hotpath.ts +0 -255
  456. package/scripts/benchmark-controller-hotpath.ts +0 -212
  457. package/scripts/benchmark-controller-round2.ts +0 -191
  458. package/scripts/benchmark-ios-agent-device.ts +0 -511
  459. package/scripts/benchmark-mcp-host.sh +0 -117
  460. package/scripts/benchmark-mcp-transport.ts +0 -169
  461. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  462. package/scripts/benchmark-operational-shadow.ts +0 -28
  463. package/scripts/benchmark-quality-harness.ts +0 -146
  464. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  465. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  466. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  467. package/scripts/benchmark-thin-harness.ts +0 -251
  468. package/scripts/benchmark-typescript-navigation.ts +0 -186
  469. package/scripts/benchmark-work-efficiency.ts +0 -334
  470. package/scripts/build-controller-ui.ts +0 -28
  471. package/scripts/check-npm-release.sh +0 -32
  472. package/scripts/check-public-release-surface.sh +0 -19
  473. package/scripts/check-release-published.sh +0 -57
  474. package/scripts/check-release-readiness.sh +0 -37
  475. package/scripts/check-release-version.mjs +0 -74
  476. package/scripts/check-tarball-install-smoke.sh +0 -150
  477. package/scripts/package-source-archive.sh +0 -147
  478. package/scripts/public-release-files.txt +0 -39
  479. package/scripts/public-release.ts +0 -174
  480. package/scripts/publish-release-tarball.sh +0 -27
  481. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  482. package/scripts/stage-runtime-release.ts +0 -44
  483. package/src/cli/controller/progress.ts +0 -656
  484. package/src/cli/controller/work-mode.ts +0 -181
  485. package/src/cli/local-bridge/console-assets.ts +0 -17
  486. package/src/cli/local-bridge/dashboard.ts +0 -17
  487. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  488. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  489. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  490. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  491. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  492. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  493. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  494. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  495. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  496. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  497. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  498. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  499. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  500. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  501. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  502. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  503. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  504. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  505. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  506. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  507. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  508. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  509. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  510. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  511. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  512. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  513. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  514. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  515. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  516. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  517. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  518. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  519. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  520. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  521. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  522. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  523. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  524. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  525. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  526. package/src/runtime/control-plane/routing/index.ts +0 -1
  527. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  528. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -2,16 +2,27 @@ import { randomUUID } from 'crypto';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import type { ExecutionJobOrigin } from '../../src/runtime/execution/jobs/types';
4
4
  import { browserActions } from '../../src/runtime/plugins/browser-manifest-surface';
5
- import { controllerPluginRepository, executeControllerScopedPluginAction, getControllerPluginManifest, submitAssistantPluginAction } from '../../src/runtime/plugins/store';
5
+ import { executeControllerScopedPluginAction, getControllerPluginManifest, submitControllerPluginAction } from '../../src/runtime/plugins/store';
6
6
  import {
7
+ CHATGPT_AUTOMATION_RATE_LIMITED,
8
+ CHATGPT_AUTOMATION_RATE_LIMITED_AFTER_SUBMIT,
7
9
  CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN,
8
10
  ChatgptProviderDeliveryError,
11
+ chatgptProviderPageFailure,
9
12
  DEFAULT_CHATGPT_AUTOMATION_MODEL,
10
13
  type ChatgptAutomationReasoning,
11
14
  type ChatgptAutomationTabCleanupStatus,
15
+ type ChatgptProviderPageFailureCode,
12
16
  } from './provider-delivery';
13
17
 
14
- const DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION = '@forge';
18
+ // The installed/connected primary Forge plugin is `forge`, not the separate
19
+ // Plugin Creator package `forge-current-1-8-1` or the Forge Recovery app.
20
+ // Live ChatGPT selection yields an exact app:// ProseMirror Forge chip.
21
+ const CHATGPT_FORGE_PLUGIN_SLUG = 'forge';
22
+ const CHATGPT_FORGE_PLUGIN_LABEL = 'forge';
23
+ const CHATGPT_FORGE_PLUGIN_PATH = 'app://asdk_app_6ac09022b26081918f20ff868c773824';
24
+ const CHATGPT_PLUGIN_PICKER_OPTIONS = '[data-mention-section-id="plugins"] [data-mention-section-items] > button';
25
+
15
26
 
16
27
  type ChatgptBrowserActionOrigin = Pick<ExecutionJobOrigin, 'surface' | 'actor'>;
17
28
  interface ChatgptBrowserActionContext {
@@ -28,16 +39,20 @@ export function withChatgptBrowserActionOrigin<T>(
28
39
  return chatgptBrowserActionOrigin.run({ origin, authorizationGrantRefs }, operation);
29
40
  }
30
41
 
31
- function withForgePluginMention(prompt: string): string {
32
- const value = prompt.trim();
42
+ /** Strip the legacy text trigger; only a real ChatGPT app chip selects Forge. */
43
+ export function chatgptSupervisorPromptBody(prompt: string): string {
44
+ const value = prompt.trim().replace(/^@forge(?:[ \t]*\r?\n|[ \t]+|$)/i, '').trim();
33
45
  if (!value) throw new Error('CHATGPT_AUTOMATION_PROMPT_REQUIRED');
34
- if (/^@forge(?:\s|$)/i.test(value)) return value;
35
- return `${DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION} ${value}`;
46
+ return value;
36
47
  }
37
48
 
38
- const CHATGPT_PROMPT_SELECTOR = 'div#prompt-textarea[contenteditable="true"]';
39
- const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"], button[aria-label*="Send"], button[data-testid*="send"]';
40
- const CHATGPT_USER_MESSAGE_SELECTOR = '[data-message-author-role="user"]';
49
+ // ChatGPT's composer is a ProseMirror textbox; the historical #prompt-textarea id is no longer stable.
50
+ // Bind delivery to the semantic composer contract used by the live UI so readiness, fill, and Enter fallback share one selector authority.
51
+ const CHATGPT_PROMPT_SELECTOR = '[data-composer-markdown][role="textbox"][contenteditable="true"]';
52
+ const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"], button[aria-label="Send"], button[aria-label="发送"], button[data-testid*="send"]';
53
+ const CHATGPT_USER_MESSAGE_SELECTOR = '[data-message-author-role="user"], [data-chatgpt-search-unit-key$=":user"], [data-content-search-unit-key$=":user"]';
54
+ const CHATGPT_ASSISTANT_MESSAGE_SELECTOR = '[data-message-author-role="assistant"], [data-chatgpt-search-unit-key$=":assistant"], [data-content-search-unit-key$=":assistant"]';
55
+ const CHATGPT_STOP_GENERATING_SELECTOR = '[data-testid="stop-button"], button[aria-label*="Stop"]';
41
56
  const CHATGPT_INTELLIGENCE_CONTROL_SELECTORS = [
42
57
  'main button, main [role="button"]',
43
58
  'button, [role="button"]',
@@ -55,16 +70,15 @@ const CHATGPT_BROWSER_AUTHORIZATION_ACTIONS = new Set(
55
70
  .map((action) => action.actionId),
56
71
  );
57
72
 
58
- const CHATGPT_BROWSER_TRANSPORT_OVERRIDES = {
59
- browser_mode: 'attach_preferred',
60
- cdp_attach_fallback: 'fail_closed',
61
- native_attach_mode: 'auto',
62
- native_browser_candidates: ['chrome'],
63
- } as const;
64
-
65
73
  export function chatgptBrowserActionArgs(actionId: string, args: Record<string, unknown>): Record<string, unknown> {
66
- if (actionId === 'configure' || actionId === 'list_sessions' || actionId === 'reconcile_sessions') return args;
67
- return { ...args, ...CHATGPT_BROWSER_TRANSPORT_OVERRIDES };
74
+ // Browser transport policy is controller-scoped configuration. Do not copy
75
+ // it into every action envelope: older Browser action schemas legitimately
76
+ // reject these optional fields even though the persisted configuration still
77
+ // supports the same policy. Keeping the envelope action-specific also lets a
78
+ // resumed ChatGPT round cross a connector/schema rotation without failing
79
+ // before the provider can observe the conversation.
80
+ void actionId;
81
+ return args;
68
82
  }
69
83
 
70
84
  export function chatgptBrowserActionResult(envelope: Record<string, unknown>, actionId: string): Record<string, unknown> {
@@ -97,11 +111,7 @@ async function controllerBrowserAction(
97
111
  // reusable controller-scoped Browser grant. Scheduled delivery stays on the
98
112
  // low-level executor and must present explicit refs already bound to this Work.
99
113
  if (origin.surface === 'chatgpt-action' && CHATGPT_BROWSER_AUTHORIZATION_ACTIONS.has(actionId)) {
100
- const submitted = await submitAssistantPluginAction(
101
- controllerHome,
102
- controllerPluginRepository(controllerHome),
103
- actionRequest,
104
- );
114
+ const submitted = await submitControllerPluginAction(controllerHome, actionRequest);
105
115
  const grantId = submitted.authorization?.grantId?.trim();
106
116
  if (grantId) context?.authorizationGrantRefs.add(grantId);
107
117
  if (!submitted.result) throw new Error(`CHATGPT_BROWSER_ACTION_RESULT_INVALID:${actionId}`);
@@ -155,17 +165,31 @@ function normalizeChatgptOutboundText(value: string): string {
155
165
  const CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES = ['收起', 'Collapse', 'Show less'] as const;
156
166
  const MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS = 100_000;
157
167
  const MIN_TRUNCATED_CHATGPT_OUTBOUND_PREFIX_CHARS = 256;
168
+ const CHATGPT_PROVIDER_ERROR_SELECTOR = '[role="alert"], [aria-live="assertive"], [data-testid*="error"], [data-testid*="toast"], [data-sonner-toast]';
169
+ const CHATGPT_DELIVERY_FAILURE_PROBE_INTERVAL_MS = 1_000;
170
+
171
+ interface BrowserFailedRequest {
172
+ url?: string;
173
+ method?: string;
174
+ status?: number;
175
+ failure?: string;
176
+ }
177
+
178
+ type ChatgptFailedRequestBaseline = Map<string, number>;
158
179
 
159
180
  export function chatgptOutboundMessageMatchesPrompt(
160
181
  messageText: string,
161
182
  prompt: string,
162
- options: { truncated?: boolean } = {},
183
+ options: { truncated?: boolean; boundForgePlugin?: boolean } = {},
163
184
  ): boolean {
164
185
  const message = normalizeChatgptOutboundText(messageText);
165
186
  const normalizedPrompt = normalizeChatgptOutboundText(prompt);
166
187
  if (!message || !normalizedPrompt) return false;
167
- if (message === normalizedPrompt) return true;
168
- if (CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES.some((suffix) => message === `${normalizedPrompt} ${suffix}`)) return true;
188
+ const candidatePrompts = [normalizedPrompt,
189
+ ...(options.boundForgePlugin ? [`${normalizedPrompt} ${CHATGPT_FORGE_PLUGIN_LABEL}`] : []),
190
+ ];
191
+ if (candidatePrompts.some((candidate) => message === candidate)) return true;
192
+ if (candidatePrompts.some((candidate) => CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES.some((suffix) => message === `${candidate} ${suffix}`))) return true;
169
193
  // Browser text extraction is deliberately bounded. A large ControllerRound
170
194
  // prompt can exceed that bound, so requiring exact equality makes successful
171
195
  // submissions mechanically unverifiable. Accept only an explicitly reported
@@ -173,7 +197,138 @@ export function chatgptOutboundMessageMatchesPrompt(
173
197
  // remains insufficient evidence.
174
198
  return options.truncated === true
175
199
  && message.length >= MIN_TRUNCATED_CHATGPT_OUTBOUND_PREFIX_CHARS
176
- && normalizedPrompt.startsWith(message);
200
+ && candidatePrompts.some((candidate) => candidate.startsWith(message));
201
+ }
202
+
203
+ export function chatgptAutomationDeliveryFailure(
204
+ bodyText: string | undefined,
205
+ ): ChatgptProviderPageFailureCode | undefined {
206
+ return chatgptProviderPageFailure(bodyText);
207
+ }
208
+
209
+ export function chatgptSubmissionSettlementWaitBudget(timeoutMs?: number): number {
210
+ return Math.min(Math.max(timeoutMs ?? 30_000, 3_000), 30_000);
211
+ }
212
+
213
+ export function chatgptSubmissionObservationDelayMs(observationIndex: number): number {
214
+ const schedule = [500, 1_000, 2_000, 3_000, 5_000] as const;
215
+ return schedule[Math.min(Math.max(0, Math.trunc(observationIndex)), schedule.length - 1)];
216
+ }
217
+
218
+ export function chatgptSubmissionAcceptanceObserved(input: {
219
+ outboundConfirmed: boolean;
220
+ hasConversationIdentity: boolean;
221
+ assistantResponseObserved: boolean;
222
+ generationInProgress: boolean;
223
+ }): boolean {
224
+ return input.outboundConfirmed
225
+ && input.hasConversationIdentity
226
+ && (input.assistantResponseObserved || input.generationInProgress);
227
+ }
228
+
229
+ export function chatgptComposerRetainsPrompt(
230
+ composerText: string | undefined,
231
+ prompt: string,
232
+ ): boolean {
233
+ if (composerText === undefined) return false;
234
+ const composer = normalizeChatgptOutboundText(composerText);
235
+ const expected = normalizeChatgptOutboundText(prompt);
236
+ return Boolean(composer && expected && composer === expected);
237
+ }
238
+
239
+ async function currentChatgptComposerText(
240
+ controllerHome: string,
241
+ workId: string,
242
+ browserSessionId: string,
243
+ timeoutMs?: number,
244
+ ): Promise<string | undefined> {
245
+ const result = await controllerBrowserAction(controllerHome, workId, 'get_text', {
246
+ session_id: browserSessionId,
247
+ selector: CHATGPT_PROMPT_SELECTOR,
248
+ max_chars: MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS,
249
+ timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
250
+ }, timeoutMs).catch(() => undefined);
251
+ return typeof result?.text === 'string' ? result.text : undefined;
252
+ }
253
+
254
+ async function currentChatgptComposerHtml(
255
+ controllerHome: string,
256
+ workId: string,
257
+ browserSessionId: string,
258
+ timeoutMs?: number,
259
+ ): Promise<string | undefined> {
260
+ const result = await controllerBrowserAction(controllerHome, workId, 'get_html', {
261
+ session_id: browserSessionId,
262
+ selector: CHATGPT_PROMPT_SELECTOR,
263
+ max_chars: MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS,
264
+ timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
265
+ }, timeoutMs).catch(() => undefined);
266
+ return typeof result?.text === 'string' && result.truncated !== true ? result.text : undefined;
267
+ }
268
+
269
+ /** A real ChatGPT plugin mention is a non-editable ProseMirror app chip, not @ text. */
270
+ export function chatgptForgePluginMentionBound(
271
+ composerHtml: string | undefined,
272
+ composerText: string | undefined,
273
+ prompt: string,
274
+ ): boolean {
275
+ if (!composerHtml || !composerText) return false;
276
+ const chips = [...composerHtml.matchAll(/\b(?:plugin|app)-mention-name="([^"]+)"/g)].map((match) => match[1]);
277
+ if (chips.length !== 1 || chips[0] !== CHATGPT_FORGE_PLUGIN_SLUG
278
+ || !composerHtml.includes(`app-mention-path="${CHATGPT_FORGE_PLUGIN_PATH}"`)) return false;
279
+ const normalized = normalizeChatgptOutboundText(composerText);
280
+ const payload = normalizeChatgptOutboundText(prompt);
281
+ return normalized === `${payload} ${CHATGPT_FORGE_PLUGIN_LABEL}`;
282
+ }
283
+
284
+ /** Return the single exact main-Forge suggestion after normalizing UI line breaks. */
285
+ export function chatgptForgePluginPickerIndex(labels: string[]): number | undefined {
286
+ const matches = labels.flatMap((label, index) => {
287
+ const value = normalizeChatgptOutboundText(label);
288
+ return value === CHATGPT_FORGE_PLUGIN_LABEL || value.startsWith(`${CHATGPT_FORGE_PLUGIN_LABEL} `)
289
+ ? [index] : [];
290
+ });
291
+ return matches.length === 1 ? matches[0] : undefined;
292
+ }
293
+
294
+ /** Browser owns DOM actions; Supervisor remains the only effect and delivery owner. */
295
+ async function bindChatgptForgePluginMention(
296
+ controllerHome: string,
297
+ workId: string,
298
+ browserSessionId: string,
299
+ prompt: string,
300
+ targetUrl: string,
301
+ timeoutMs?: number,
302
+ ): Promise<void> {
303
+ const baseArgs = { session_id: browserSessionId, timeout_ms: timeoutMs ?? 60_000 };
304
+ // The payload and final standalone @ paragraph were inserted together.
305
+ // ProseMirror drops the leading whitespace of a later ` @` insertion.
306
+ await controllerBrowserAction(controllerHome, workId, 'type', {
307
+ ...baseArgs, selector: CHATGPT_PROMPT_SELECTOR, text: CHATGPT_FORGE_PLUGIN_LABEL,
308
+ post_action_wait_ms: 120,
309
+ }, timeoutMs);
310
+ const entries = await controllerBrowserAction(controllerHome, workId, 'query_all', {
311
+ ...baseArgs, selector: CHATGPT_PLUGIN_PICKER_OPTIONS, limit: 80,
312
+ }, timeoutMs);
313
+ const index = chatgptForgePluginPickerIndex(queryMatches(entries).map(matchText));
314
+ if (index === undefined) {
315
+ throw new ChatgptProviderDeliveryError('CHATGPT_AUTOMATION_PLUGIN_NOT_IN_PICKER',
316
+ `CHATGPT_AUTOMATION_PLUGIN_NOT_IN_PICKER:${CHATGPT_FORGE_PLUGIN_SLUG}:${targetUrl}`,
317
+ { conversationUrl: targetUrl });
318
+ }
319
+ // The option order was observed from this exact open picker. Never select
320
+ // another Forge-prefixed app as a fallback; verify the resulting app chip.
321
+ await controllerBrowserAction(controllerHome, workId, 'click', {
322
+ ...baseArgs, selector: `${CHATGPT_PLUGIN_PICKER_OPTIONS}:nth-of-type(${index + 1})`,
323
+ }, timeoutMs);
324
+ const [composerHtml, composerText] = await Promise.all([
325
+ currentChatgptComposerHtml(controllerHome, workId, browserSessionId, timeoutMs),
326
+ currentChatgptComposerText(controllerHome, workId, browserSessionId, timeoutMs),
327
+ ]);
328
+ if (!chatgptForgePluginMentionBound(composerHtml, composerText, prompt)) {
329
+ throw new ChatgptProviderDeliveryError('CHATGPT_AUTOMATION_PLUGIN_MENTION_UNVERIFIED',
330
+ `CHATGPT_AUTOMATION_PLUGIN_MENTION_UNVERIFIED:${targetUrl}`, { conversationUrl: targetUrl });
331
+ }
177
332
  }
178
333
 
179
334
  async function latestChatgptUserMessage(
@@ -213,8 +368,138 @@ async function fullChatgptMessageText(
213
368
  };
214
369
  }
215
370
 
371
+ async function latestChatgptAssistantMessage(
372
+ controllerHome: string,
373
+ workId: string,
374
+ browserSessionId: string,
375
+ timeoutMs?: number,
376
+ ): Promise<{ selector?: string; preview: string }> {
377
+ const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
378
+ session_id: browserSessionId,
379
+ selector: CHATGPT_ASSISTANT_MESSAGE_SELECTOR,
380
+ limit: 1,
381
+ from_end: true,
382
+ timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
383
+ }, timeoutMs);
384
+ const latest = queryMatches(result).at(-1);
385
+ return { selector: matchSelector(latest), preview: latest ? matchText(latest) : '' };
386
+ }
387
+
388
+ function chatgptMessageObservationChanged(
389
+ before: { selector?: string; preview: string },
390
+ latest: { selector?: string; preview: string },
391
+ ): boolean {
392
+ return Boolean(
393
+ (latest.selector && latest.selector !== before.selector)
394
+ || (!before.preview && latest.preview)
395
+ || latest.preview !== before.preview,
396
+ );
397
+ }
398
+
399
+ async function chatgptGenerationInProgress(
400
+ controllerHome: string,
401
+ workId: string,
402
+ browserSessionId: string,
403
+ timeoutMs?: number,
404
+ ): Promise<boolean> {
405
+ const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
406
+ session_id: browserSessionId,
407
+ selector: CHATGPT_STOP_GENERATING_SELECTOR,
408
+ limit: 1,
409
+ timeout_ms: Math.min(timeoutMs ?? 1_000, 1_000),
410
+ }, timeoutMs).catch(() => undefined);
411
+ return queryMatches(result).length > 0;
412
+ }
413
+
414
+ function failedRequestKey(request: BrowserFailedRequest): string {
415
+ return `${request.method?.toUpperCase() ?? ''}|${request.status ?? ''}|${request.url ?? ''}|${request.failure ?? ''}`;
416
+ }
417
+
418
+ /** Only provider requests causally inside the current send pipeline may classify that send as rate-limited. */
419
+ export function chatgptFailedRequestIsCausalRateLimit(request: Pick<BrowserFailedRequest, 'url' | 'method' | 'status'>): boolean {
420
+ if (request.status !== 429 || request.method?.trim().toUpperCase() !== 'POST') return false;
421
+ try {
422
+ const url = new URL(request.url ?? '');
423
+ if (url.protocol !== 'https:' || url.hostname !== 'chatgpt.com') return false;
424
+ const path = url.pathname.toLowerCase();
425
+ return /^\/backend-api\/(?:f\/)?conversation(?:\/|$)/.test(path)
426
+ || path === '/backend-api/sentinel/chat-requirements';
427
+ } catch {
428
+ return false;
429
+ }
430
+ }
431
+
432
+ async function chatgptFailedRequestBaseline(
433
+ controllerHome: string,
434
+ workId: string,
435
+ browserSessionId: string,
436
+ timeoutMs?: number,
437
+ ): Promise<ChatgptFailedRequestBaseline | undefined> {
438
+ const result = await controllerBrowserAction(controllerHome, workId, 'get_failed_requests', {
439
+ session_id: browserSessionId,
440
+ timeout_ms: Math.min(timeoutMs ?? 2_000, 2_000),
441
+ }, timeoutMs).catch(() => undefined);
442
+ if (!result || !Array.isArray(result.failedRequests)) return undefined;
443
+ const baseline = new Map<string, number>();
444
+ for (const request of result.failedRequests) {
445
+ if (!request || typeof request !== 'object') continue;
446
+ const key = failedRequestKey(request as BrowserFailedRequest);
447
+ baseline.set(key, (baseline.get(key) ?? 0) + 1);
448
+ }
449
+ return baseline;
450
+ }
451
+
452
+ async function chatgptProviderFailureOnErrorUi(
453
+ controllerHome: string,
454
+ workId: string,
455
+ browserSessionId: string,
456
+ timeoutMs?: number,
457
+ ): Promise<ChatgptProviderPageFailureCode | undefined> {
458
+ const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
459
+ session_id: browserSessionId,
460
+ selector: CHATGPT_PROVIDER_ERROR_SELECTOR,
461
+ limit: 20,
462
+ timeout_ms: Math.min(timeoutMs ?? 2_000, 2_000),
463
+ }, timeoutMs).catch(() => undefined);
464
+ for (const match of queryMatches(result)) {
465
+ const failure = chatgptAutomationDeliveryFailure(matchText(match));
466
+ if (failure) return failure;
467
+ }
468
+ return undefined;
469
+ }
470
+
471
+ async function chatgptDeliveryFailureOnPage(
472
+ controllerHome: string,
473
+ workId: string,
474
+ browserSessionId: string,
475
+ failedRequestBaseline: ChatgptFailedRequestBaseline | undefined,
476
+ timeoutMs?: number,
477
+ ): Promise<ChatgptProviderPageFailureCode | undefined> {
478
+ const uiFailure = await chatgptProviderFailureOnErrorUi(controllerHome, workId, browserSessionId, timeoutMs);
479
+ if (uiFailure) return uiFailure;
480
+ if (!failedRequestBaseline) return undefined;
481
+
482
+ const current = await controllerBrowserAction(controllerHome, workId, 'get_failed_requests', {
483
+ session_id: browserSessionId,
484
+ timeout_ms: Math.min(timeoutMs ?? 2_000, 2_000),
485
+ }, timeoutMs).catch(() => undefined);
486
+ if (!current || !Array.isArray(current.failedRequests)) return undefined;
487
+ const seen = new Map<string, number>();
488
+ for (const value of current.failedRequests) {
489
+ if (!value || typeof value !== 'object') continue;
490
+ const request = value as BrowserFailedRequest;
491
+ const key = failedRequestKey(request);
492
+ const occurrence = (seen.get(key) ?? 0) + 1;
493
+ seen.set(key, occurrence);
494
+ const baselineCount = failedRequestBaseline.get(key) ?? 0;
495
+ if (occurrence <= baselineCount) continue;
496
+ if (chatgptFailedRequestIsCausalRateLimit(request)) return CHATGPT_AUTOMATION_RATE_LIMITED;
497
+ }
498
+ return undefined;
499
+ }
500
+
216
501
  function chatgptSendControlUnavailable(error: unknown): boolean {
217
- return error instanceof Error && (error.message.includes('Selector') && error.message.includes('not found'));
502
+ return error instanceof Error && error.message.includes('PLUGIN_BROWSER_SELECTOR_UNAVAILABLE');
218
503
  }
219
504
 
220
505
  function normalizeExecutionControlLabel(label: string | undefined): string {
@@ -438,7 +723,7 @@ export async function ensureChatgptExecutionPreference(
438
723
  for (let index = 0; index < Math.abs(targetValue - currentValue); index += 1) {
439
724
  await controllerBrowserAction(controllerHome, workId, 'press', {
440
725
  session_id: browserSessionId,
441
- selector: CHATGPT_CAPABILITY_MENUITEM_SELECTOR,
726
+ selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
442
727
  key: direction,
443
728
  timeout_ms: timeoutMs ?? 60_000,
444
729
  post_action_wait_ms: 120,
@@ -502,6 +787,42 @@ function chatgptUrlMatchesContinuationTarget(observedUrl: string, targetUrl: str
502
787
  }
503
788
  }
504
789
 
790
+ /** The saved Browser session id is not proof of the live native tab identity. */
791
+ export function chatgptBrowserObservationMatchesTarget(
792
+ observation: Record<string, unknown>,
793
+ browserSessionId: string,
794
+ targetUrl: string,
795
+ ): boolean {
796
+ const observedSessionId = stringField(observation.sessionId) ?? resultSessionId(observation);
797
+ if (observedSessionId && observedSessionId !== browserSessionId) return false;
798
+ const connection = observation.browserConnection;
799
+ const liveTab = connection && typeof connection === 'object'
800
+ ? (connection as Record<string, unknown>).tab : undefined;
801
+ const liveTabUrl = liveTab && typeof liveTab === 'object'
802
+ ? stringField((liveTab as Record<string, unknown>).url) : undefined;
803
+ const urls = [resultUrl(observation), liveTabUrl].filter((url): url is string => Boolean(url));
804
+ return urls.length > 0 && urls.every((url) => chatgptUrlMatchesContinuationTarget(url, targetUrl));
805
+ }
806
+
807
+ async function verifyChatgptExactSubmissionTab(
808
+ controllerHome: string,
809
+ workId: string,
810
+ browserSessionId: string,
811
+ targetUrl: string,
812
+ timeoutMs?: number,
813
+ ): Promise<void> {
814
+ const observed = await controllerBrowserAction(controllerHome, workId, 'get_text', {
815
+ session_id: browserSessionId,
816
+ selector: CHATGPT_PROMPT_SELECTOR,
817
+ max_chars: 1,
818
+ timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
819
+ }, timeoutMs);
820
+ if (!chatgptBrowserObservationMatchesTarget(observed, browserSessionId, targetUrl)) {
821
+ throw new ChatgptProviderDeliveryError('CHATGPT_AUTOMATION_TARGET_IDENTITY_CHANGED',
822
+ `CHATGPT_AUTOMATION_TARGET_IDENTITY_CHANGED:${targetUrl}`, { conversationUrl: targetUrl });
823
+ }
824
+ }
825
+
505
826
  function completeChatgptBrowserInventorySessions(
506
827
  inventory: Record<string, unknown>,
507
828
  ): ChatgptBrowserSessionInventoryItem[] | undefined {
@@ -557,11 +878,19 @@ export async function closeChatgptAutomationTabAfterDispatch(
557
878
  workId: string,
558
879
  browserSessionId: string,
559
880
  timeoutMs?: number,
881
+ authorizationGrantRefs: readonly string[] = [],
560
882
  ): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
561
883
  try {
562
- const closed = await controllerBrowserAction(controllerHome, workId, 'close_page', {
884
+ const closePage = () => controllerBrowserAction(controllerHome, workId, 'close_page', {
563
885
  session_id: browserSessionId,
564
886
  }, Math.min(timeoutMs ?? 15_000, 15_000));
887
+ const closed = authorizationGrantRefs.length > 0
888
+ ? await withChatgptBrowserActionOrigin(
889
+ { surface: 'schedule', actor: 'chatgpt-work-continuation' },
890
+ closePage,
891
+ new Set(authorizationGrantRefs),
892
+ )
893
+ : await closePage();
565
894
  if (closed.preservedUserOwnedTab === true) return { status: 'preserved_user_owned' };
566
895
  if (closed.resourceClosed === true) return { status: 'closed' };
567
896
  return { status: 'session_closed' };
@@ -588,6 +917,7 @@ export async function settleWorkChatgptAutomationTab(input: {
588
917
  workId: string;
589
918
  browserSessionId: string;
590
919
  timeoutMs?: number;
920
+ authorizationGrantRefs?: readonly string[];
591
921
  }): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
592
922
  if (input.browserSessionId.startsWith('forge-chatgpt-bridge-')) return { status: 'session_closed' };
593
923
  return closeChatgptAutomationTabAfterDispatch(
@@ -595,6 +925,7 @@ export async function settleWorkChatgptAutomationTab(input: {
595
925
  input.workId,
596
926
  input.browserSessionId,
597
927
  input.timeoutMs,
928
+ input.authorizationGrantRefs,
598
929
  );
599
930
  }
600
931
 
@@ -632,6 +963,14 @@ export async function navigateWorkConversation(
632
963
  const opened = await controllerBrowserAction(controllerHome, workId, 'create_session', {
633
964
  session_id: sessionId,
634
965
  url,
966
+ // Direct Browser delivery must never silently escalate to the macOS
967
+ // Apple Events compatibility provider. Work-bound unattended delivery is
968
+ // owned by Workflow Supervisor's extension transport; this legacy direct
969
+ // lane uses a Forge-managed browser resource unless the caller explicitly
970
+ // configured a different transport elsewhere.
971
+ browser_mode: 'managed_persistent',
972
+ native_attach_mode: 'disabled',
973
+ cdp_attach_fallback: 'fail_closed',
635
974
  wait_until: 'domcontentloaded',
636
975
  timeout_ms: timeoutMs ?? 60_000,
637
976
  retries: 1,
@@ -665,6 +1004,13 @@ export async function navigateWorkConversation(
665
1004
  } catch (preflightError) {
666
1005
  if (!chatgptAutomationNavigationRequiresReplacement(preflightError)) throw preflightError;
667
1006
  const replacementSessionId = await openReplacement(browserSessionId, targetUrl);
1007
+ // create_session establishes only the session capability. Activating the
1008
+ // exact replacement page is an interaction-capability action, so an
1009
+ // interactive prepare also establishes the grant required by later
1010
+ // schedule-origin fill/click actions on this same origin.
1011
+ await controllerBrowserAction(controllerHome, workId, 'activate_page', {
1012
+ session_id: replacementSessionId,
1013
+ }, timeoutMs);
668
1014
  return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
669
1015
  }
670
1016
  await navigate(browserSessionId, targetUrl);
@@ -689,15 +1035,11 @@ export async function navigateWorkConversation(
689
1035
  const replacementSessionId = await openReplacement(browserSessionId, targetUrl);
690
1036
  return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId: replacementSessionId };
691
1037
  }
692
- if (!isChatgptConversationUrl(targetUrl)) throw error;
693
- try {
694
- await navigate(browserSessionId, 'https://chatgpt.com/');
695
- return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId };
696
- } catch (fallbackError) {
697
- if (!chatgptAutomationNavigationRequiresReplacement(fallbackError)) throw fallbackError;
698
- const replacementSessionId = await openReplacement(browserSessionId, 'https://chatgpt.com/');
699
- return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
700
- }
1038
+ // A bound /c/<id> is transport identity, not a hint. Falling back to the
1039
+ // ChatGPT root silently creates a replacement conversation and can turn one
1040
+ // transient navigation failure into duplicate provider recovery. Preserve the
1041
+ // exact conversation and let the owning continuation reconcile or retry it.
1042
+ throw error;
701
1043
  }
702
1044
  }
703
1045
 
@@ -709,17 +1051,62 @@ export async function submitChatgptPrompt(
709
1051
  targetUrl: string,
710
1052
  timeoutMs?: number,
711
1053
  ): Promise<string> {
712
- const renderedPrompt = withForgePluginMention(prompt);
713
- const before = await latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs)
714
- .catch((): { selector?: string; preview: string; url?: string } => ({ selector: undefined, preview: '', url: targetUrl }));
1054
+ // Verify the live native tab before reading/sending. The saved session may
1055
+ // have retargeted while its Browser handle remained stable.
1056
+ await verifyChatgptExactSubmissionTab(
1057
+ controllerHome, workId, browserSessionId, targetUrl, timeoutMs,
1058
+ );
1059
+ const renderedPrompt = chatgptSupervisorPromptBody(prompt);
1060
+ const [before, beforeAssistant, failedRequestBaseline, preexistingPageFailure] = await Promise.all([
1061
+ latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs)
1062
+ .catch((): { selector?: string; preview: string; url?: string } => ({ selector: undefined, preview: '', url: targetUrl })),
1063
+ latestChatgptAssistantMessage(controllerHome, workId, browserSessionId, timeoutMs)
1064
+ .catch((): { selector?: string; preview: string } => ({ selector: undefined, preview: '' })),
1065
+ chatgptFailedRequestBaseline(controllerHome, workId, browserSessionId, timeoutMs),
1066
+ chatgptProviderFailureOnErrorUi(controllerHome, workId, browserSessionId, timeoutMs),
1067
+ ]);
1068
+ if (preexistingPageFailure === CHATGPT_AUTOMATION_RATE_LIMITED) {
1069
+ throw new ChatgptProviderDeliveryError(
1070
+ CHATGPT_AUTOMATION_RATE_LIMITED,
1071
+ `${CHATGPT_AUTOMATION_RATE_LIMITED}:${targetUrl}`,
1072
+ { conversationUrl: targetUrl },
1073
+ );
1074
+ }
1075
+ const mentionTriggerDraft = `${renderedPrompt}\n@`;
715
1076
  await controllerBrowserAction(controllerHome, workId, 'fill', {
716
1077
  session_id: browserSessionId,
717
1078
  selector: CHATGPT_PROMPT_SELECTOR,
718
- text: renderedPrompt,
1079
+ text: mentionTriggerDraft,
719
1080
  timeout_ms: timeoutMs ?? 60_000,
720
1081
  post_action_wait_ms: 100,
721
1082
  }, timeoutMs);
722
1083
 
1084
+ // Browser fill can report success even when the live ProseMirror composer did
1085
+ // not retain the intended payload. Never send or trigger Enter fallback on
1086
+ // an unverified draft; the owning effect must remain unreplayed.
1087
+ const verifiedComposerText = await currentChatgptComposerText(
1088
+ controllerHome, workId, browserSessionId, timeoutMs,
1089
+ );
1090
+ if (!chatgptComposerRetainsPrompt(verifiedComposerText, mentionTriggerDraft)) {
1091
+ throw new ChatgptProviderDeliveryError(
1092
+ 'CHATGPT_AUTOMATION_COMPOSER_UNVERIFIED',
1093
+ `CHATGPT_AUTOMATION_COMPOSER_UNVERIFIED:${targetUrl}`,
1094
+ { conversationUrl: targetUrl },
1095
+ );
1096
+ }
1097
+
1098
+ // The immutable effect marker alone is not enough: choose the exact main
1099
+ // Forge app and verify its structured mention before sending the payload.
1100
+ await bindChatgptForgePluginMention(
1101
+ controllerHome, workId, browserSessionId, renderedPrompt, targetUrl, timeoutMs,
1102
+ );
1103
+
1104
+ // A popup or plugin-selection action can change the native tab. Re-attest
1105
+ // immediately before Send, never infer identity from a successful click.
1106
+ await verifyChatgptExactSubmissionTab(
1107
+ controllerHome, workId, browserSessionId, targetUrl, timeoutMs,
1108
+ );
1109
+
723
1110
  let observedUrl = targetUrl;
724
1111
  let submitOutcomeUnknown = false;
725
1112
  let observedNewOutbound = false;
@@ -733,9 +1120,46 @@ export async function submitChatgptPrompt(
733
1120
  observedUrl = resultUrl(sent) ?? observedUrl;
734
1121
  } catch (error) {
735
1122
  if (browserMutationOutcomeUnknown(error, 'click')) {
736
- // Never replay an outcome-unknown submit: semantic observation below decides
737
- // whether the original click committed, preventing duplicate user messages.
1123
+ // Never blind-replay an outcome-unknown submit. First reconcile the exact
1124
+ // provider state. If the same payload is still wholly present in the
1125
+ // composer, no new user message exists, and no conversation identity was
1126
+ // created, the original click is mechanically proven not to have committed.
1127
+ // Only then may the same provider dispatch resume Send once without
1128
+ // refilling the composer or creating a new semantic effect generation.
738
1129
  submitOutcomeUnknown = true;
1130
+ const latestAfterUnknown = await latestChatgptUserMessage(
1131
+ controllerHome,
1132
+ workId,
1133
+ browserSessionId,
1134
+ timeoutMs,
1135
+ ).catch(() => undefined);
1136
+ if (latestAfterUnknown) {
1137
+ observedUrl = latestAfterUnknown.url ?? observedUrl;
1138
+ observedNewOutbound = chatgptMessageObservationChanged(before, latestAfterUnknown);
1139
+ }
1140
+ const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
1141
+ const [composerHtml, composerText] = await Promise.all([
1142
+ currentChatgptComposerHtml(controllerHome, workId, browserSessionId, timeoutMs),
1143
+ currentChatgptComposerText(controllerHome, workId, browserSessionId, timeoutMs),
1144
+ ]);
1145
+ if (
1146
+ !observedNewOutbound
1147
+ && !hasConversationIdentity
1148
+ && chatgptForgePluginMentionBound(composerHtml, composerText, renderedPrompt)
1149
+ ) {
1150
+ try {
1151
+ const resumed = await controllerBrowserAction(controllerHome, workId, 'click', {
1152
+ session_id: browserSessionId,
1153
+ selector: CHATGPT_SEND_SELECTOR,
1154
+ timeout_ms: timeoutMs ?? 60_000,
1155
+ post_action_wait_ms: 250,
1156
+ }, timeoutMs);
1157
+ observedUrl = resultUrl(resumed) ?? observedUrl;
1158
+ submitOutcomeUnknown = false;
1159
+ } catch (resumeError) {
1160
+ if (!browserMutationOutcomeUnknown(resumeError, 'click')) throw resumeError;
1161
+ }
1162
+ }
739
1163
  } else if (chatgptSendControlUnavailable(error)) {
740
1164
  const pressed = await controllerBrowserAction(controllerHome, workId, 'press', {
741
1165
  session_id: browserSessionId,
@@ -750,28 +1174,78 @@ export async function submitChatgptPrompt(
750
1174
  }
751
1175
  }
752
1176
 
753
- const deadline = Date.now() + Math.min(Math.max(timeoutMs ?? 10_000, 3_000), 10_000);
1177
+ const deadline = Date.now() + chatgptSubmissionSettlementWaitBudget(timeoutMs);
1178
+ let nextFailureProbeAt = 0;
1179
+ let observationIndex = 0;
754
1180
  do {
755
1181
  const latest = await latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs).catch(() => undefined);
756
1182
  if (latest) {
757
1183
  observedUrl = latest.url ?? observedUrl;
758
- const isNewOutbound = Boolean(
759
- (latest.selector && latest.selector !== before.selector)
760
- || (!before.preview && latest.preview)
761
- || latest.preview !== before.preview,
762
- );
1184
+ const isNewOutbound = chatgptMessageObservationChanged(before, latest);
763
1185
  if (isNewOutbound) {
764
1186
  observedNewOutbound = true;
765
1187
  const fullText = await fullChatgptMessageText(controllerHome, workId, browserSessionId, latest, timeoutMs);
766
- if (chatgptOutboundMessageMatchesPrompt(fullText.text, renderedPrompt, { truncated: fullText.truncated }) && /\/c\/[^/?#]+/.test(observedUrl)) {
767
- return observedUrl;
1188
+ const outboundConfirmed = chatgptOutboundMessageMatchesPrompt(fullText.text, renderedPrompt, { truncated: fullText.truncated, boundForgePlugin: true });
1189
+ const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
1190
+ if (outboundConfirmed && hasConversationIdentity) {
1191
+ const [latestAssistant, generationInProgress] = await Promise.all([
1192
+ latestChatgptAssistantMessage(controllerHome, workId, browserSessionId, timeoutMs).catch(() => undefined),
1193
+ chatgptGenerationInProgress(controllerHome, workId, browserSessionId, timeoutMs),
1194
+ ]);
1195
+ const assistantResponseObserved = Boolean(
1196
+ latestAssistant?.preview
1197
+ && chatgptMessageObservationChanged(beforeAssistant, latestAssistant),
1198
+ );
1199
+ if (chatgptSubmissionAcceptanceObserved({
1200
+ outboundConfirmed,
1201
+ hasConversationIdentity,
1202
+ assistantResponseObserved,
1203
+ generationInProgress,
1204
+ })) {
1205
+ return observedUrl;
1206
+ }
1207
+ if (Date.now() >= nextFailureProbeAt) {
1208
+ const deliveryFailure = await chatgptDeliveryFailureOnPage(
1209
+ controllerHome,
1210
+ workId,
1211
+ browserSessionId,
1212
+ failedRequestBaseline,
1213
+ timeoutMs,
1214
+ );
1215
+ if (deliveryFailure) {
1216
+ const failureCode = deliveryFailure === CHATGPT_AUTOMATION_RATE_LIMITED
1217
+ ? CHATGPT_AUTOMATION_RATE_LIMITED_AFTER_SUBMIT
1218
+ : deliveryFailure;
1219
+ throw new ChatgptProviderDeliveryError(
1220
+ failureCode,
1221
+ `${failureCode}:${observedUrl}`,
1222
+ { conversationUrl: observedUrl },
1223
+ );
1224
+ }
1225
+ nextFailureProbeAt = Date.now() + CHATGPT_DELIVERY_FAILURE_PROBE_INTERVAL_MS;
1226
+ }
768
1227
  }
769
1228
  }
770
1229
  }
771
- await new Promise((resolveWait) => setTimeout(resolveWait, 150));
1230
+ const remainingMs = deadline - Date.now();
1231
+ if (remainingMs <= 0) break;
1232
+ const observationDelayMs = chatgptSubmissionObservationDelayMs(observationIndex);
1233
+ observationIndex += 1;
1234
+ await new Promise((resolveWait) => setTimeout(resolveWait, Math.min(observationDelayMs, remainingMs)));
772
1235
  } while (Date.now() < deadline);
773
1236
  const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
774
- const failureCode = submitOutcomeUnknown || (observedNewOutbound && hasConversationIdentity)
1237
+ const finalComposerText = submitOutcomeUnknown && !observedNewOutbound && !hasConversationIdentity
1238
+ ? await currentChatgptComposerText(controllerHome, workId, browserSessionId, timeoutMs)
1239
+ : undefined;
1240
+ const submitProvablyNotApplied = submitOutcomeUnknown
1241
+ && !observedNewOutbound
1242
+ && !hasConversationIdentity
1243
+ && chatgptComposerRetainsPrompt(finalComposerText, renderedPrompt);
1244
+ // A fresh /c/<id> or any changed outbound user message is provider-side
1245
+ // evidence that the send may have committed even when DOM observation lagged.
1246
+ // Keep that ambiguity fenced. Conversely, an exact unchanged composer proves
1247
+ // the send did not commit and must not be mislabeled outcome_unknown.
1248
+ const failureCode = (submitOutcomeUnknown && !submitProvablyNotApplied) || hasConversationIdentity || observedNewOutbound
775
1249
  ? CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN
776
1250
  : 'CHATGPT_AUTOMATION_SUBMISSION_NOT_CONFIRMED';
777
1251
  throw new ChatgptProviderDeliveryError(
@@ -780,4 +1254,3 @@ export async function submitChatgptPrompt(
780
1254
  { conversationUrl: observedUrl },
781
1255
  );
782
1256
  }
783
-