@moretea-labs/forge 1.7.2 → 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 (900) hide show
  1. package/AGENTS.md +121 -62
  2. package/CHANGELOG.md +40 -0
  3. package/CLAUDE.md +57 -62
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.md +2 -2
  6. package/SKILL.md +0 -1
  7. package/THIRD_PARTY_NOTICES.md +5 -2
  8. package/adapters/browser/index.ts +2 -0
  9. package/adapters/chatgpt/browser-delivery-host.ts +77 -0
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
  11. package/adapters/chatgpt/controller-binding-store.ts +134 -0
  12. package/adapters/chatgpt/controller-round-host.ts +68 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
  14. package/adapters/chatgpt/provider-delivery.ts +340 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/chatgpt-extension-target.ts +463 -0
  18. package/adapters/computer/chatgpt-target-router.ts +103 -0
  19. package/adapters/computer/chrome-extension/background.js +385 -0
  20. package/adapters/computer/chrome-extension/content.js +378 -0
  21. package/adapters/computer/chrome-extension/core.js +24 -0
  22. package/adapters/computer/chrome-extension/manifest.json +30 -0
  23. package/adapters/computer/desktop-operator-contract.ts +7 -0
  24. package/adapters/computer/desktop-operator-discovery.ts +181 -0
  25. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  26. package/adapters/computer/desktop-operator-provider.ts +312 -0
  27. package/adapters/computer/index.ts +1 -0
  28. package/adapters/computer/interaction-target-authority.ts +1068 -0
  29. package/adapters/controller-process/binding-store.ts +34 -0
  30. package/adapters/controller-process/controller-host.ts +26 -0
  31. package/adapters/mcp/access-mode.ts +132 -0
  32. package/adapters/mcp/audit.ts +32 -0
  33. package/adapters/mcp/auth.ts +406 -0
  34. package/adapters/mcp/instructions.ts +37 -0
  35. package/adapters/mcp/legacy-context.ts +18 -0
  36. package/adapters/mcp/multi-repository.ts +421 -0
  37. package/adapters/mcp/oauth.ts +464 -0
  38. package/adapters/mcp/paths.ts +143 -0
  39. package/adapters/mcp/policy.ts +232 -0
  40. package/adapters/mcp/proxy-env.ts +62 -0
  41. package/adapters/mcp/readiness-projection.ts +24 -0
  42. package/adapters/mcp/redaction.ts +21 -0
  43. package/adapters/mcp/repo.ts +24 -0
  44. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  45. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
  46. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  47. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  48. package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
  49. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
  50. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
  51. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  52. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
  53. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  54. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  55. package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
  56. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  57. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
  58. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  59. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  60. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  61. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  62. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
  63. package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
  64. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
  65. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
  66. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  67. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  68. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
  69. package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
  70. package/adapters/mcp/runtime-gateway/router.ts +242 -0
  71. package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
  72. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
  73. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  74. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
  75. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  76. package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
  77. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
  78. package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
  79. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
  80. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
  81. package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
  82. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  83. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  84. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
  85. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  86. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  87. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  88. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  89. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  90. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
  91. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  92. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  93. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
  94. package/adapters/mcp/server.ts +1092 -0
  95. package/adapters/mcp/setup.ts +789 -0
  96. package/adapters/mcp/tool-capability-cache.ts +87 -0
  97. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  98. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
  99. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
  100. package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
  101. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  102. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  103. package/adapters/mcp/toolset-names.ts +79 -0
  104. package/adapters/mcp/toolset-selection.ts +41 -0
  105. package/adapters/mcp/toolset.ts +337 -0
  106. package/adapters/mcp/transports/http-observation.ts +365 -0
  107. package/adapters/mcp/transports/http.ts +933 -0
  108. package/adapters/mcp/transports/oauth-http.ts +322 -0
  109. package/adapters/mcp/transports/session-registry.ts +519 -0
  110. package/adapters/mcp/transports/stdio.ts +8 -0
  111. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
  112. package/adapters/mcp/types.ts +41 -0
  113. package/assets/AGENTS.md +2 -16
  114. package/assets/CLAUDE.md +2 -16
  115. package/assets/hooks/AGENTS.md +3 -14
  116. package/assets/hooks/CLAUDE.md +3 -14
  117. package/assets/hooks/hook-input.sh +9 -2
  118. package/assets/hooks/lib/workflow-state.sh +28 -10
  119. package/assets/hooks/prompt-guard.sh +63 -37
  120. package/assets/hooks/security-sentinel.sh +2 -1
  121. package/assets/hooks/session-start-context.sh +7 -5
  122. package/assets/hooks/stop-orchestrator.sh +13 -3
  123. package/assets/initializer-question-pack.v4.json +11 -7
  124. package/assets/partials/05-workflow.partial.md +18 -73
  125. package/assets/partials/07-footer.partial.md +1 -1
  126. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  127. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  128. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  129. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  130. package/assets/plan-map.json +11 -11
  131. package/assets/plugin-registry.v1.json +11 -3
  132. package/assets/plugin-trust-policy.v1.json +6 -0
  133. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  134. package/assets/recovery/forge-wsl-rescue +271 -0
  135. package/assets/reference-configs/agentic-development-flow.md +25 -0
  136. package/assets/reference-configs/development-protocol.md +11 -15
  137. package/assets/reference-configs/document-generation.md +1 -1
  138. package/assets/reference-configs/global-working-rules.md +28 -1
  139. package/assets/reference-configs/harness-overview.md +22 -112
  140. package/assets/reference-configs/hook-operations.md +1 -1
  141. package/assets/reference-configs/sprint-contracts.md +2 -2
  142. package/assets/skill-version.json +2 -2
  143. package/assets/templates/helpers/architecture-event.ts +3 -34
  144. package/assets/templates/helpers/architecture-queue.sh +61 -10
  145. package/assets/templates/helpers/archive-workflow.sh +90 -180
  146. package/assets/templates/helpers/capability-config.ts +8 -11
  147. package/assets/templates/helpers/capability-resolver.ts +37 -19
  148. package/assets/templates/helpers/capture-plan.sh +2 -2
  149. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  150. package/assets/templates/helpers/check-task-sync.sh +82 -11
  151. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  152. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  153. package/assets/templates/helpers/contract-worktree.sh +26 -24
  154. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  155. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  156. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  157. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  158. package/assets/templates/helpers/new-plan.sh +2 -2
  159. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  160. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  161. package/assets/templates/helpers/switch-plan.sh +1 -1
  162. package/assets/templates/helpers/workflow-contract.ts +1 -0
  163. package/assets/workflow-contract.v1.json +63 -171
  164. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  165. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  166. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  167. package/docs/architecture/CURRENT.md +233 -28
  168. package/docs/architecture/EVOLUTION.md +13 -0
  169. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  170. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  171. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  172. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  173. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  174. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  175. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
  176. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
  177. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  178. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  179. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  180. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
  181. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  182. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
  183. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
  184. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  185. package/docs/architecture/domains/verification.md +8 -0
  186. package/docs/architecture/domains/workflow-engine.md +7 -1
  187. package/docs/architecture/index.md +3 -3
  188. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  189. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  190. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  191. package/docs/forge-plugin-management.md +20 -4
  192. package/docs/forge-runtime-self-healing-loop.md +2 -0
  193. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  194. package/docs/operations/controller-browser-plugin.md +3 -1
  195. package/docs/operations/controller-performance-and-502.md +17 -0
  196. package/docs/operations/controller-reliability-runbook.md +2 -2
  197. package/docs/operations/features.md +1 -1
  198. package/docs/operations/features.zh-CN.md +1 -1
  199. package/docs/operations/homebrew.md +2 -2
  200. package/docs/operations/mcp-tool-exposure.md +2 -0
  201. package/docs/operations/platform-support.md +16 -8
  202. package/docs/operations/platform-support.zh-CN.md +2 -2
  203. package/docs/operations/releasing.md +2 -0
  204. package/docs/operations/standalone-disaster-recovery.md +38 -5
  205. package/docs/operations/uu-remote-rescue.md +41 -0
  206. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  207. package/docs/public-usage-guide.md +1 -1
  208. package/docs/public-usage-guide.zh-CN.md +1 -1
  209. package/docs/tutorials/01-install-and-start.md +1 -1
  210. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  211. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  212. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  213. package/docs/wiki/Implementation.md +1 -1
  214. package/package.json +46 -26
  215. package/packages/kernel/cognition/api/index.ts +6 -0
  216. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  217. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  218. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  219. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  220. package/packages/kernel/cognition/domain/learning.ts +106 -0
  221. package/packages/kernel/cognition/domain/memory.ts +254 -0
  222. package/packages/kernel/controller/api/index.ts +3 -0
  223. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  224. package/packages/kernel/controller/application/controller-round-service.ts +41 -0
  225. package/packages/kernel/controller/application/controller-service.ts +30 -0
  226. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
  227. package/packages/kernel/controller/domain/controller-round.ts +110 -0
  228. package/packages/kernel/controller/domain/execution-quality.ts +135 -0
  229. package/packages/kernel/controller/domain/types.ts +56 -0
  230. package/packages/kernel/controller/index.ts +1 -0
  231. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  232. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
  233. package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
  234. package/packages/kernel/controller/ports/controller-host.ts +18 -0
  235. package/packages/kernel/goal/api/index.ts +1 -0
  236. package/packages/kernel/goal/domain/types.ts +40 -0
  237. package/packages/kernel/goal/index.ts +1 -0
  238. package/packages/kernel/identity/api/index.ts +15 -0
  239. package/packages/kernel/identity/application/identity-service.ts +67 -0
  240. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  241. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  242. package/packages/kernel/identity/domain/grant.ts +72 -0
  243. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  244. package/packages/kernel/identity/domain/scope.ts +63 -0
  245. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  246. package/packages/kernel/identity/domain/types.ts +44 -0
  247. package/packages/kernel/identity/domain/user-request.ts +93 -0
  248. package/packages/kernel/identity/domain/workspace.ts +56 -0
  249. package/packages/kernel/identity/index.ts +1 -0
  250. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
  251. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  252. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  253. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  254. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  255. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  256. package/packages/kernel/memory/api/index.ts +5 -0
  257. package/packages/kernel/memory/application/experience-service.ts +118 -0
  258. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  259. package/packages/kernel/memory/domain/experience.ts +103 -0
  260. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  261. package/packages/kernel/memory/index.ts +1 -0
  262. package/packages/kernel/scheduler/api/index.ts +4 -0
  263. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  264. package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
  265. package/packages/kernel/scheduler/application/settlement.ts +182 -0
  266. package/packages/kernel/scheduler/domain/schedule.ts +236 -0
  267. package/packages/kernel/scheduler/index.ts +1 -0
  268. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
  269. package/packages/kernel/work/api/index.ts +16 -0
  270. package/packages/kernel/work/application/work-service.ts +123 -0
  271. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  272. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  273. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  274. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  275. package/packages/kernel/work/domain/engineering-design.ts +286 -0
  276. package/packages/kernel/work/domain/engineering-profile.ts +238 -0
  277. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  278. package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
  279. package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
  280. package/packages/kernel/work/domain/implementation-review.ts +193 -0
  281. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  282. package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
  283. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  284. package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
  285. package/packages/kernel/work/domain/types.ts +534 -0
  286. package/packages/kernel/work/index.ts +1 -0
  287. package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
  288. package/packages/kernel/work/ports/work-contract-store.ts +21 -0
  289. package/packages/plugin-runtime/browser/index.ts +3 -0
  290. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  291. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  292. package/packages/plugin-runtime/browser/session-authority.ts +62 -0
  293. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  294. package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
  295. package/packages/plugin-runtime/computer/index.ts +8 -0
  296. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  297. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  298. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  299. package/packages/plugin-runtime/computer/provider.ts +19 -0
  300. package/packages/plugin-runtime/computer/target-authority.ts +215 -0
  301. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  302. package/packages/plugin-runtime/external/index.ts +1 -0
  303. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
  304. package/packages/plugin-runtime/index.ts +3 -0
  305. package/packages/protocols/browser/index.ts +1 -0
  306. package/packages/protocols/browser/session.ts +98 -0
  307. package/packages/protocols/computer/contract.ts +225 -0
  308. package/packages/protocols/computer/index.ts +1 -0
  309. package/packages/protocols/failure/contract.ts +158 -0
  310. package/packages/protocols/failure/index.ts +1 -0
  311. package/packages/protocols/handoff/index.ts +1 -0
  312. package/packages/protocols/handoff/status.ts +22 -0
  313. package/packages/protocols/mcp/execution-context.ts +11 -0
  314. package/packages/protocols/mcp/index.ts +2 -0
  315. package/packages/protocols/mcp/tool-contract.ts +42 -0
  316. package/packages/workflow-runtime/api/index.ts +2 -0
  317. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  318. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  319. package/packages/workflow-runtime/index.ts +1 -0
  320. package/scripts/activate-source-baseline.ts +7 -9
  321. package/scripts/assemble-template.ts +1 -1
  322. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  323. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  324. package/scripts/canary-controller-semantic-wait.ts +183 -0
  325. package/scripts/canary-global-hook.sh +2 -2
  326. package/scripts/check-mcp-compatibility.ts +85 -7
  327. package/scripts/check-platform-support.mjs +28 -2
  328. package/scripts/check-public-docs.sh +1 -1
  329. package/scripts/check-repository-hygiene.mjs +92 -0
  330. package/scripts/check-runtime-architecture.mjs +1643 -127
  331. package/scripts/check-source-duplication.mjs +9 -1
  332. package/scripts/controller-ngrok-rotation.sh +5 -4
  333. package/scripts/create-project-dirs.sh +10 -74
  334. package/scripts/forge-local-recovery-helper.mjs +282 -0
  335. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  336. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  337. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  338. package/scripts/hook-shim.sh +2 -2
  339. package/scripts/init-project.sh +12 -70
  340. package/scripts/install-desktop-operator-registration.ts +4 -2
  341. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  342. package/scripts/install-standalone-recovery.ts +122 -10
  343. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  344. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  345. package/scripts/lib/controller-home-env.sh +6 -3
  346. package/scripts/lib/project-init-lib.sh +42 -582
  347. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  348. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  349. package/scripts/open-source-audit-allowlist.txt +11 -1
  350. package/scripts/portability-bindings.mjs +70 -0
  351. package/scripts/run-bounded-command.mjs +60 -0
  352. package/scripts/run-bun-test-file.ts +22 -6
  353. package/scripts/run-governed-gate.ts +4 -2
  354. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  355. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  356. package/scripts/smoke-mcp-http-runtime.ts +6 -1
  357. package/scripts/smoke-runtime-recovery.ts +13 -11
  358. package/scripts/smoke-schedule-engine.ts +24 -16
  359. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  360. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  361. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
  362. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  363. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  364. package/scripts/smoke-workflow-supervisor.ts +103 -0
  365. package/scripts/test-governance.ts +51 -2
  366. package/scripts/verify-forge-runtime.sh +2 -1
  367. package/src/cli/agent-jobs/executor-health.ts +11 -34
  368. package/src/cli/agent-jobs/integration.ts +18 -49
  369. package/src/cli/agent-jobs/job-manager.ts +17 -4
  370. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  371. package/src/cli/chatgpt-browser/binding.ts +20 -10
  372. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  373. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  374. package/src/cli/chatgpt-browser/engine.ts +10 -11
  375. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  376. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  377. package/src/cli/chatgpt-browser/types.ts +1 -0
  378. package/src/cli/commands/bootstrap-control.ts +334 -0
  379. package/src/cli/commands/brain-assistant.ts +52 -0
  380. package/src/cli/commands/brain.ts +191 -40
  381. package/src/cli/commands/chatgpt.ts +32 -67
  382. package/src/cli/commands/computer.ts +437 -0
  383. package/src/cli/commands/doctor.ts +18 -1
  384. package/src/cli/commands/init-hook.ts +71 -61
  385. package/src/cli/commands/init.ts +3 -1
  386. package/src/cli/commands/mcp.ts +16 -10
  387. package/src/cli/commands/plugin.ts +38 -12
  388. package/src/cli/commands/recovery.ts +507 -80
  389. package/src/cli/commands/runtime.ts +12 -7
  390. package/src/cli/commands/setup-profile.ts +97 -39
  391. package/src/cli/commands/status.ts +2 -1
  392. package/src/cli/controller/check-runner.ts +500 -100
  393. package/src/cli/controller/completion-backlog.ts +3 -3
  394. package/src/cli/controller/completion-orchestrator.ts +4 -43
  395. package/src/cli/controller/completion-target.ts +3 -72
  396. package/src/cli/controller/context/source-materializer.ts +81 -100
  397. package/src/cli/controller/context/types.ts +7 -1
  398. package/src/cli/controller/context-pack.ts +28 -6
  399. package/src/cli/controller/execution-completion.ts +4 -7
  400. package/src/cli/controller/execution-policy.ts +36 -153
  401. package/src/cli/controller/governance.ts +2 -3
  402. package/src/cli/controller/issue-store.ts +4 -99
  403. package/src/cli/controller/launch-agents.ts +59 -18
  404. package/src/cli/controller/operational-plan.ts +24 -51
  405. package/src/cli/controller/runtime-config.ts +5 -1
  406. package/src/cli/controller/stuck-state-migration.ts +2 -3
  407. package/src/cli/controller/systemd-user.ts +148 -0
  408. package/src/cli/controller/types.ts +10 -70
  409. package/src/cli/controller/worklog.ts +67 -13
  410. package/src/cli/editing/edit-operation-contract.ts +92 -0
  411. package/src/cli/editing/edit-session.ts +290 -20
  412. package/src/cli/github/plugin.ts +17 -12
  413. package/src/cli/hook/route-registry.ts +3 -5
  414. package/src/cli/hook/runtime.ts +49 -3
  415. package/src/cli/index.ts +3 -0
  416. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  417. package/src/cli/local-bridge/console-automations.ts +20 -15
  418. package/src/cli/local-bridge/console-view-models.ts +3 -12
  419. package/src/cli/local-bridge/facade-api.ts +105 -205
  420. package/src/cli/local-bridge/job-store.ts +1 -1
  421. package/src/cli/local-bridge/server.ts +48 -174
  422. package/src/cli/local-bridge/types.ts +0 -1
  423. package/src/cli/mcp/access-mode.ts +2 -132
  424. package/src/cli/mcp/access-tools.ts +2 -387
  425. package/src/cli/mcp/audit.ts +2 -32
  426. package/src/cli/mcp/auth.ts +2 -404
  427. package/src/cli/mcp/instructions.ts +2 -34
  428. package/src/cli/mcp/legacy-context.ts +2 -16
  429. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  430. package/src/cli/mcp/multi-repository.ts +2 -436
  431. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  432. package/src/cli/mcp/oauth.ts +2 -375
  433. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  434. package/src/cli/mcp/paths.ts +2 -140
  435. package/src/cli/mcp/policy.ts +2 -232
  436. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  437. package/src/cli/mcp/proxy-env.ts +2 -62
  438. package/src/cli/mcp/readiness-projection.ts +2 -24
  439. package/src/cli/mcp/redaction.ts +2 -21
  440. package/src/cli/mcp/repo.ts +2 -24
  441. package/src/cli/mcp/repository-tools.ts +2 -1613
  442. package/src/cli/mcp/server.ts +2 -951
  443. package/src/cli/mcp/setup.ts +2 -737
  444. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  445. package/src/cli/mcp/tool-contract.ts +2 -14
  446. package/src/cli/mcp/tools.ts +2 -9
  447. package/src/cli/mcp/toolset-names.ts +2 -70
  448. package/src/cli/mcp/toolset-selection.ts +2 -41
  449. package/src/cli/mcp/toolset.ts +2 -336
  450. package/src/cli/mcp/transports/AGENTS.md +1 -10
  451. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  452. package/src/cli/mcp/transports/http.ts +2 -1414
  453. package/src/cli/mcp/transports/session-registry.ts +2 -490
  454. package/src/cli/mcp/transports/stdio.ts +2 -8
  455. package/src/cli/mcp/types.ts +2 -41
  456. package/src/cli/mcp/umbrella-tools.ts +2 -117
  457. package/src/cli/repositories/command-executor.ts +91 -39
  458. package/src/cli/repositories/command-scope.ts +139 -4
  459. package/src/cli/repositories/controller-home.ts +167 -1
  460. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  461. package/src/cli/repositories/git-command-executor.ts +2 -2
  462. package/src/cli/repositories/identity.ts +13 -0
  463. package/src/cli/repositories/registry.ts +145 -60
  464. package/src/cli/repositories/repository-snapshot.ts +53 -7
  465. package/src/cli/repositories/runtime-storage.ts +27 -2
  466. package/src/cli/repositories/safe-patch.ts +34 -38
  467. package/src/cli/repositories/selected-path-actions.ts +22 -2
  468. package/src/cli/repositories/structured-git.ts +167 -27
  469. package/src/cli/repository/session-cache.ts +92 -6
  470. package/src/cli/runtime/helper-runner.ts +7 -0
  471. package/src/cli/runtime-invocation.ts +8 -1
  472. package/src/cli/tools/codegraph.ts +44 -25
  473. package/src/core/adoption/manifest-templates.ts +1 -1
  474. package/src/core/adoption/plan.ts +3 -26
  475. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  476. package/src/effects/process-runner.ts +8 -14
  477. package/src/runtime/context/assistant-context.ts +167 -0
  478. package/src/runtime/context/assistant-work-context.ts +209 -0
  479. package/src/runtime/context/automatic-learning.ts +565 -0
  480. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  481. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  482. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  483. package/src/runtime/context/cognitive-attention.ts +173 -0
  484. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  485. package/src/runtime/context/context-closure.ts +305 -0
  486. package/src/runtime/context/context-pack-process.ts +103 -0
  487. package/src/runtime/context/context-plane.ts +203 -0
  488. package/src/runtime/context/direct-controller-learning.ts +298 -0
  489. package/src/runtime/context/project-engineering-contract.ts +60 -0
  490. package/src/runtime/context/semantic-navigation-contract.ts +9 -0
  491. package/src/runtime/context/semantic-navigation.ts +6 -34
  492. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  493. package/src/runtime/context/typescript-navigation.ts +89 -3
  494. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  495. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  496. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  497. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  498. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  499. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  500. package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
  501. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  502. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
  503. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
  504. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
  505. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  506. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  507. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  508. package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
  509. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  510. package/src/runtime/control-plane/execution/validation.ts +34 -11
  511. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
  512. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  513. package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
  514. package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
  515. package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
  516. package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
  517. package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
  518. package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
  519. package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
  520. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
  521. package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
  522. package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
  523. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  524. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  525. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
  526. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
  527. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  528. package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
  529. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
  530. package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
  531. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  532. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  533. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  534. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  535. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
  536. package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
  537. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
  538. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
  539. package/src/runtime/control-plane/facade/index.ts +4 -3
  540. package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
  541. package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
  542. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  543. package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
  544. package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
  545. package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
  546. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
  547. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  548. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  549. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
  550. package/src/runtime/control-plane/facade/types.ts +218 -620
  551. package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
  552. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  553. package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
  554. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  555. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
  556. package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
  557. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  558. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  559. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
  560. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  561. package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
  562. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  563. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  564. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  565. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  566. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
  567. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  568. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
  569. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  570. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
  571. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
  572. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  573. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  574. package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
  575. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  576. package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
  577. package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
  578. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  579. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  580. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  581. package/src/runtime/control-plane/release-retention.ts +154 -26
  582. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  583. package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
  584. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  585. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  586. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
  587. package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
  588. package/src/runtime/diagnostics/incident-repair.ts +573 -0
  589. package/src/runtime/diagnostics/mcp-timing.ts +239 -6
  590. package/src/runtime/diagnostics/performance.ts +6 -14
  591. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  592. package/src/runtime/evidence/artifact-store.ts +216 -35
  593. package/src/runtime/evidence/evidence-store.ts +112 -8
  594. package/src/runtime/evidence/operational-shadow.ts +38 -0
  595. package/src/runtime/evidence/output-handle-index.ts +225 -0
  596. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  597. package/src/runtime/evidence/result-store.ts +158 -44
  598. package/src/runtime/evidence/sensitive-output.ts +4 -4
  599. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  600. package/src/runtime/execution/jobs/store.ts +124 -6
  601. package/src/runtime/execution/managed-workspace.ts +112 -5
  602. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  603. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  604. package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
  605. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  606. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  607. package/src/runtime/execution/process-runtime/check-result.ts +177 -2
  608. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
  609. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  610. package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
  611. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  612. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  613. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  614. package/src/runtime/execution/process-runtime/index.ts +4 -0
  615. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  616. package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
  617. package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
  618. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  619. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  620. package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
  621. package/src/runtime/execution/process-runtime/runtime.ts +167 -17
  622. package/src/runtime/execution/process-runtime/store.ts +30 -2
  623. package/src/runtime/execution/process-runtime/types.ts +27 -0
  624. package/src/runtime/execution/workers/executor.ts +18 -19
  625. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  626. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  627. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  628. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  629. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  630. package/src/runtime/gateway/mcp/router.ts +2 -1125
  631. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  632. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
  633. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  634. package/src/runtime/maintenance/cleanup.ts +38 -24
  635. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  636. package/src/runtime/platform/computer-platform.ts +14 -0
  637. package/src/runtime/platform/executable-discovery.ts +54 -0
  638. package/src/runtime/platform/fetch-http-server.ts +90 -0
  639. package/src/runtime/platform/service-inventory.ts +130 -0
  640. package/src/runtime/platform/service-manager.ts +146 -0
  641. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  642. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  643. package/src/runtime/plugins/action-application.ts +192 -0
  644. package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
  645. package/src/runtime/plugins/browser-adapter.ts +898 -135
  646. package/src/runtime/plugins/browser-automation-service.ts +33 -34
  647. package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
  648. package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
  649. package/src/runtime/plugins/browser-native-session-service.ts +64 -14
  650. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  651. package/src/runtime/plugins/browser-registration.ts +32 -0
  652. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  653. package/src/runtime/plugins/browser-runtime.ts +55 -10
  654. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  655. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  656. package/src/runtime/plugins/browser-session-store.ts +243 -80
  657. package/src/runtime/plugins/browser-session-types.ts +2 -98
  658. package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
  659. package/src/runtime/plugins/catalog-trust.ts +82 -0
  660. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
  661. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
  662. package/src/runtime/plugins/computer-registration.ts +1050 -0
  663. package/src/runtime/plugins/config-store.ts +119 -0
  664. package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
  665. package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
  666. package/src/runtime/plugins/errors.ts +8 -1
  667. package/src/runtime/plugins/external-adapter.ts +82 -323
  668. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  669. package/src/runtime/plugins/external-registration.ts +30 -2
  670. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  671. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  672. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  673. package/src/runtime/plugins/first-party-registry.ts +3 -1
  674. package/src/runtime/plugins/github-adapter.ts +1 -2
  675. package/src/runtime/plugins/gmail-adapter.ts +29 -8
  676. package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
  677. package/src/runtime/plugins/google-shared.ts +45 -53
  678. package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
  679. package/src/runtime/plugins/ios-adapter.ts +2 -3
  680. package/src/runtime/plugins/lightweight-action.ts +208 -2
  681. package/src/runtime/plugins/local-system-adapter.ts +192 -2
  682. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  683. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  684. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  685. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  686. package/src/runtime/plugins/resend-adapter.ts +15 -24
  687. package/src/runtime/plugins/store.ts +1003 -387
  688. package/src/runtime/plugins/types.ts +28 -1
  689. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  690. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  691. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  692. package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
  693. package/src/runtime/projections/controller-context.ts +12 -6
  694. package/src/runtime/projections/git-status-sampler.ts +73 -22
  695. package/src/runtime/projections/invalidation.ts +7 -3
  696. package/src/runtime/projections/materialized-view.ts +93 -43
  697. package/src/runtime/recovery/actions.ts +3 -3
  698. package/src/runtime/recovery/capability-action-application.ts +229 -0
  699. package/src/runtime/recovery/index.ts +3 -0
  700. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  701. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  702. package/src/runtime/recovery/maintenance-executor.ts +701 -168
  703. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  704. package/src/runtime/recovery/store.ts +29 -11
  705. package/src/runtime/release/identity.ts +6 -0
  706. package/src/runtime/release/release-coordinator.ts +296 -0
  707. package/src/runtime/release/release-session.ts +716 -0
  708. package/src/runtime/release/release-state-migration.ts +25 -0
  709. package/src/runtime/resources/leases/store.ts +56 -25
  710. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  711. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  712. package/src/runtime/root/browser-session-composition.ts +59 -0
  713. package/src/runtime/root/computer-composition.ts +376 -0
  714. package/src/runtime/root/computer-target-composition.ts +16 -0
  715. package/src/runtime/root/computer-target-persistence.ts +39 -0
  716. package/src/runtime/root/controller-progression-composition.ts +192 -0
  717. package/src/runtime/root/controller-round-composition.ts +126 -0
  718. package/src/runtime/root/controller-services.ts +2 -0
  719. package/src/runtime/root/deployment-topology.ts +106 -0
  720. package/src/runtime/root/entry.ts +8 -6
  721. package/src/runtime/root/gateway-adapter.ts +3 -3
  722. package/src/runtime/root/known-good-recovery.ts +184 -0
  723. package/src/runtime/root/local-bridge.ts +5 -3
  724. package/src/runtime/root/mcp-transport.ts +116 -23
  725. package/src/runtime/root/ownership.ts +261 -9
  726. package/src/runtime/root/package-connector-service.ts +212 -74
  727. package/src/runtime/root/package-runtime-release.ts +199 -33
  728. package/src/runtime/root/package-runtime-service.ts +77 -108
  729. package/src/runtime/root/release-execution-canary.ts +103 -0
  730. package/src/runtime/root/release-loader.ts +150 -0
  731. package/src/runtime/root/release-manifest.ts +185 -7
  732. package/src/runtime/root/release-materialize.ts +474 -31
  733. package/src/runtime/root/release-store.ts +425 -37
  734. package/src/runtime/root/runtime-lane.ts +191 -0
  735. package/src/runtime/root/runtime.ts +379 -32
  736. package/src/runtime/root/scheduled-controller-composition.ts +76 -0
  737. package/src/runtime/root/scheduler.ts +12 -4
  738. package/src/runtime/root/service-runner.ts +3 -3
  739. package/src/runtime/root/service.ts +73 -45
  740. package/src/runtime/root/status.ts +49 -0
  741. package/src/runtime/root/types.ts +55 -3
  742. package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
  743. package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
  744. package/src/runtime/root/write-fence.ts +50 -2
  745. package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
  746. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  747. package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
  748. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  749. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  750. package/src/runtime/shared/process-environment.ts +36 -10
  751. package/src/runtime/shared/response-budget.ts +1 -3
  752. package/src/runtime/shared/storage-capacity.ts +68 -0
  753. package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
  754. package/src/runtime/standalone-recovery/core.ts +4609 -495
  755. package/src/runtime/standalone-recovery/entry.ts +642 -81
  756. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  757. package/src/runtime/standalone-recovery/installer.ts +399 -177
  758. package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
  759. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  760. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  761. package/src/runtime/standalone-recovery/performance.ts +173 -0
  762. package/src/runtime/standalone-recovery/release.ts +58 -5
  763. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  764. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  765. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  766. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  767. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
  768. package/src/runtime/workflow/schedules/engine.ts +298 -484
  769. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  770. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  771. package/src/runtime/workflow/schedules/store.ts +2 -607
  772. package/src/runtime/workflow/schedules/types.ts +2 -127
  773. package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
  774. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  775. package/src/runtime/workflows/runtime.ts +482 -0
  776. package/src/testing/test-governance.ts +95 -30
  777. package/supervisor/chatgpt-conversation.ts +23 -0
  778. package/supervisor/client.ts +201 -0
  779. package/supervisor/control-plane.ts +868 -0
  780. package/supervisor/entry.ts +15 -0
  781. package/supervisor/forge-validators.ts +80 -0
  782. package/supervisor/index.ts +5 -0
  783. package/supervisor/native-browser-adapter.ts +786 -0
  784. package/supervisor/native-messaging/host.ts +39 -0
  785. package/supervisor/native-messaging/manifest.ts +26 -0
  786. package/supervisor/native-messaging/protocol.ts +27 -0
  787. package/supervisor/paths.ts +18 -0
  788. package/supervisor/protocol.ts +202 -0
  789. package/supervisor/server.ts +404 -0
  790. package/supervisor/store.ts +1451 -0
  791. package/supervisor/tsconfig.json +8 -0
  792. package/supervisor/types.ts +255 -0
  793. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  794. package/docs/architecture/history/README.md +0 -7
  795. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  796. package/docs/architecture/history/global-hook-runtime.md +0 -166
  797. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  798. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  799. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  800. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  801. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  802. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  803. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  804. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  805. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  806. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  807. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  808. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  809. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  810. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  811. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  812. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  813. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  814. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  815. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  816. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  817. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  818. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  819. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  820. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  821. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  822. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  823. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  824. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  825. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  826. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  827. package/docs/architecture/requests/archive/2026/root.md +0 -68
  828. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  829. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  830. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  831. package/scripts/archive-architecture-request.sh +0 -4
  832. package/scripts/benchmark-background-check-overlap.ts +0 -228
  833. package/scripts/benchmark-controller-hotpath.ts +0 -212
  834. package/scripts/benchmark-controller-round2.ts +0 -139
  835. package/scripts/benchmark-ios-agent-device.ts +0 -514
  836. package/scripts/benchmark-mcp-host.sh +0 -117
  837. package/scripts/benchmark-mcp-transport.ts +0 -170
  838. package/scripts/benchmark-quality-harness.ts +0 -146
  839. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  840. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  841. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  842. package/scripts/benchmark-thin-harness.ts +0 -251
  843. package/scripts/benchmark-typescript-navigation.ts +0 -186
  844. package/scripts/benchmark-work-efficiency.ts +0 -334
  845. package/scripts/build-controller-ui.ts +0 -28
  846. package/scripts/check-npm-release.sh +0 -32
  847. package/scripts/check-public-release-surface.sh +0 -19
  848. package/scripts/check-release-published.sh +0 -57
  849. package/scripts/check-release-readiness.sh +0 -37
  850. package/scripts/check-release-version.mjs +0 -74
  851. package/scripts/check-tarball-install-smoke.sh +0 -67
  852. package/scripts/package-source-archive.sh +0 -147
  853. package/scripts/public-release-files.txt +0 -39
  854. package/scripts/public-release.ts +0 -174
  855. package/scripts/publish-release-tarball.sh +0 -27
  856. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  857. package/scripts/run-skill-evals.ts +0 -1022
  858. package/scripts/stage-runtime-release.ts +0 -39
  859. package/src/cli/controller/progress.ts +0 -656
  860. package/src/cli/controller/work-mode.ts +0 -181
  861. package/src/cli/local-bridge/console-assets.ts +0 -17
  862. package/src/cli/local-bridge/dashboard.ts +0 -17
  863. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  864. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  865. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  866. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  867. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  868. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  869. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  870. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  871. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  872. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  873. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  874. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  875. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  876. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  877. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  878. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  879. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  880. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  881. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  882. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  883. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  884. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  885. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  886. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  887. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  888. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  889. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  890. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  891. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
  892. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
  893. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  894. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
  895. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
  896. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
  897. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  898. package/src/runtime/control-plane/routing/index.ts +0 -1
  899. package/src/runtime/control-plane/routing/route-policy.ts +0 -382
  900. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
@@ -0,0 +1,1256 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import type { ExecutionJobOrigin } from '../../src/runtime/execution/jobs/types';
4
+ import { browserActions } from '../../src/runtime/plugins/browser-manifest-surface';
5
+ import { executeControllerScopedPluginAction, getControllerPluginManifest, submitControllerPluginAction } from '../../src/runtime/plugins/store';
6
+ import {
7
+ CHATGPT_AUTOMATION_RATE_LIMITED,
8
+ CHATGPT_AUTOMATION_RATE_LIMITED_AFTER_SUBMIT,
9
+ CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN,
10
+ ChatgptProviderDeliveryError,
11
+ chatgptProviderPageFailure,
12
+ DEFAULT_CHATGPT_AUTOMATION_MODEL,
13
+ type ChatgptAutomationReasoning,
14
+ type ChatgptAutomationTabCleanupStatus,
15
+ type ChatgptProviderPageFailureCode,
16
+ } from './provider-delivery';
17
+
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
+
26
+
27
+ type ChatgptBrowserActionOrigin = Pick<ExecutionJobOrigin, 'surface' | 'actor'>;
28
+ interface ChatgptBrowserActionContext {
29
+ origin: ChatgptBrowserActionOrigin;
30
+ authorizationGrantRefs: Set<string>;
31
+ }
32
+ const chatgptBrowserActionOrigin = new AsyncLocalStorage<ChatgptBrowserActionContext>();
33
+
34
+ export function withChatgptBrowserActionOrigin<T>(
35
+ origin: ChatgptBrowserActionOrigin,
36
+ operation: () => Promise<T>,
37
+ authorizationGrantRefs: Set<string> = new Set(),
38
+ ): Promise<T> {
39
+ return chatgptBrowserActionOrigin.run({ origin, authorizationGrantRefs }, operation);
40
+ }
41
+
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();
45
+ if (!value) throw new Error('CHATGPT_AUTOMATION_PROMPT_REQUIRED');
46
+ return value;
47
+ }
48
+
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"]';
56
+ const CHATGPT_INTELLIGENCE_CONTROL_SELECTORS = [
57
+ 'main button, main [role="button"]',
58
+ 'button, [role="button"]',
59
+ ] as const;
60
+ const CHATGPT_CAPABILITY_SLIDER_SELECTOR = '[role="slider"]';
61
+ const CHATGPT_CAPABILITY_MENUITEM_SELECTOR = '[role="menuitem"][aria-keyshortcuts~="ArrowLeft"][aria-keyshortcuts~="ArrowRight"]';
62
+
63
+ function requestId(workId: string, actionId: string): string {
64
+ return `chatgpt-work:${workId}:${actionId}:${randomUUID()}`;
65
+ }
66
+
67
+ const CHATGPT_BROWSER_AUTHORIZATION_ACTIONS = new Set(
68
+ browserActions()
69
+ .filter((action) => !action.readOnly && action.confirmation === 'authorization')
70
+ .map((action) => action.actionId),
71
+ );
72
+
73
+ export function chatgptBrowserActionArgs(actionId: string, args: Record<string, unknown>): Record<string, unknown> {
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;
82
+ }
83
+
84
+ export function chatgptBrowserActionResult(envelope: Record<string, unknown>, actionId: string): Record<string, unknown> {
85
+ const result = envelope.result;
86
+ if (!result || typeof result !== 'object' || Array.isArray(result)) {
87
+ throw new Error(`CHATGPT_BROWSER_ACTION_RESULT_INVALID:${actionId}`);
88
+ }
89
+ return result as Record<string, unknown>;
90
+ }
91
+
92
+ async function controllerBrowserAction(
93
+ controllerHome: string,
94
+ workId: string,
95
+ actionId: string,
96
+ args: Record<string, unknown>,
97
+ timeoutMs?: number,
98
+ ): Promise<Record<string, unknown>> {
99
+ const context = chatgptBrowserActionOrigin.getStore();
100
+ const origin = context?.origin ?? { surface: 'schedule', actor: 'chatgpt-work-continuation' };
101
+ const actionRequest = {
102
+ pluginId: 'browser',
103
+ actionId,
104
+ requestId: requestId(workId, actionId),
105
+ args: chatgptBrowserActionArgs(actionId, args),
106
+ timeoutMs,
107
+ origin,
108
+ };
109
+
110
+ // Interactive ChatGPT delivery is the only place allowed to establish a
111
+ // reusable controller-scoped Browser grant. Scheduled delivery stays on the
112
+ // low-level executor and must present explicit refs already bound to this Work.
113
+ if (origin.surface === 'chatgpt-action' && CHATGPT_BROWSER_AUTHORIZATION_ACTIONS.has(actionId)) {
114
+ const submitted = await submitControllerPluginAction(controllerHome, actionRequest);
115
+ const grantId = submitted.authorization?.grantId?.trim();
116
+ if (grantId) context?.authorizationGrantRefs.add(grantId);
117
+ if (!submitted.result) throw new Error(`CHATGPT_BROWSER_ACTION_RESULT_INVALID:${actionId}`);
118
+ return chatgptBrowserActionResult(submitted.result, actionId);
119
+ }
120
+
121
+ const envelope = await executeControllerScopedPluginAction({
122
+ controllerHome,
123
+ ...actionRequest,
124
+ authorizationGrantRefs: [...(context?.authorizationGrantRefs ?? [])],
125
+ });
126
+ return chatgptBrowserActionResult(envelope, actionId);
127
+ }
128
+
129
+ export async function ensureControllerChatgptBrowser(controllerHome: string, workId: string): Promise<void> {
130
+ // Browser configure is not a read: it persists configuration and closes managed
131
+ // contexts. Scheduled continuations must not disturb an already-enabled provider
132
+ // merely to prove it is available. Read the existing controller-scoped manifest
133
+ // authority, and only enable it when that persisted authority is explicitly disabled;
134
+ // action-level transport overrides still fail closed later.
135
+ if (getControllerPluginManifest(controllerHome, 'browser').enabled) return;
136
+ await controllerBrowserAction(controllerHome, workId, 'configure', { enabled: true });
137
+ }
138
+
139
+ function stringField(value: unknown): string | undefined {
140
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
141
+ }
142
+
143
+ interface BrowserQueryMatch {
144
+ text?: string;
145
+ selectorHint?: string;
146
+ }
147
+
148
+ function queryMatches(result: Record<string, unknown> | undefined): BrowserQueryMatch[] {
149
+ if (!result || !Array.isArray(result.matches)) return [];
150
+ return result.matches.filter((value): value is BrowserQueryMatch => Boolean(value) && typeof value === 'object');
151
+ }
152
+
153
+ function matchText(value: BrowserQueryMatch): string {
154
+ return typeof value.text === 'string' ? value.text.trim() : '';
155
+ }
156
+
157
+ function matchSelector(value: BrowserQueryMatch | undefined): string | undefined {
158
+ return typeof value?.selectorHint === 'string' && value.selectorHint.trim() ? value.selectorHint.trim() : undefined;
159
+ }
160
+
161
+ function normalizeChatgptOutboundText(value: string): string {
162
+ return value.replace(/\s+/g, ' ').trim();
163
+ }
164
+
165
+ const CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES = ['收起', 'Collapse', 'Show less'] as const;
166
+ const MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS = 100_000;
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>;
179
+
180
+ export function chatgptOutboundMessageMatchesPrompt(
181
+ messageText: string,
182
+ prompt: string,
183
+ options: { truncated?: boolean; boundForgePlugin?: boolean } = {},
184
+ ): boolean {
185
+ const message = normalizeChatgptOutboundText(messageText);
186
+ const normalizedPrompt = normalizeChatgptOutboundText(prompt);
187
+ if (!message || !normalizedPrompt) return false;
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;
193
+ // Browser text extraction is deliberately bounded. A large ControllerRound
194
+ // prompt can exceed that bound, so requiring exact equality makes successful
195
+ // submissions mechanically unverifiable. Accept only an explicitly reported
196
+ // truncation of a substantial exact prefix; ordinary partial/mismatched text
197
+ // remains insufficient evidence.
198
+ return options.truncated === true
199
+ && message.length >= MIN_TRUNCATED_CHATGPT_OUTBOUND_PREFIX_CHARS
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
+ }
332
+ }
333
+
334
+ async function latestChatgptUserMessage(
335
+ controllerHome: string,
336
+ workId: string,
337
+ browserSessionId: string,
338
+ timeoutMs?: number,
339
+ ): Promise<{ selector?: string; preview: string; url?: string }> {
340
+ const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
341
+ session_id: browserSessionId,
342
+ selector: CHATGPT_USER_MESSAGE_SELECTOR,
343
+ limit: 1,
344
+ from_end: true,
345
+ timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
346
+ }, timeoutMs);
347
+ const latest = queryMatches(result).at(-1);
348
+ return { selector: matchSelector(latest), preview: latest ? matchText(latest) : '', url: resultUrl(result) };
349
+ }
350
+
351
+ async function fullChatgptMessageText(
352
+ controllerHome: string,
353
+ workId: string,
354
+ browserSessionId: string,
355
+ message: { selector?: string; preview: string },
356
+ timeoutMs?: number,
357
+ ): Promise<{ text: string; truncated: boolean }> {
358
+ if (!message.selector) return { text: message.preview, truncated: false };
359
+ const result = await controllerBrowserAction(controllerHome, workId, 'get_text', {
360
+ session_id: browserSessionId,
361
+ selector: message.selector,
362
+ max_chars: MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS,
363
+ timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
364
+ }, timeoutMs).catch(() => undefined);
365
+ return {
366
+ text: stringField(result?.text) ?? message.preview,
367
+ truncated: result?.truncated === true,
368
+ };
369
+ }
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
+
501
+ function chatgptSendControlUnavailable(error: unknown): boolean {
502
+ return error instanceof Error && error.message.includes('PLUGIN_BROWSER_SELECTOR_UNAVAILABLE');
503
+ }
504
+
505
+ function normalizeExecutionControlLabel(label: string | undefined): string {
506
+ return (label ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '');
507
+ }
508
+
509
+ export function chatgptAutomationReasoningLevelFromLabel(label: string | undefined): ChatgptAutomationReasoning | undefined {
510
+ const normalized = normalizeExecutionControlLabel(label);
511
+ if (!normalized) return undefined;
512
+
513
+ const exact = new Map<string, ChatgptAutomationReasoning>([
514
+ ['medium', 'medium'], ['中', 'medium'], ['中等', 'medium'],
515
+ ['high', 'high'], ['高', 'high'],
516
+ ['xhigh', 'xhigh'], ['extrahigh', 'xhigh'], ['超高', 'xhigh'], ['极高', 'xhigh'],
517
+ ]);
518
+ const exactLevel = exact.get(normalized);
519
+ if (exactLevel) return exactLevel;
520
+
521
+ const hasReasoningContext = ['reasoning', 'thinking', '推理', '思考']
522
+ .some((token) => normalized.includes(token));
523
+ if (!hasReasoningContext) return undefined;
524
+ if (['xhigh', 'extrahigh', '超高', '极高'].some((token) => normalized.includes(token))) return 'xhigh';
525
+ if (['medium', '中等'].some((token) => normalized.includes(token))) return 'medium';
526
+ if (['high', '高'].some((token) => normalized.includes(token))) return 'high';
527
+ return undefined;
528
+ }
529
+
530
+ function modelLabelMatches(label: string | undefined, model: string): boolean {
531
+ if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL || !label) return false;
532
+ const normalized = normalizeExecutionControlLabel(label);
533
+ return normalized.includes('5.6sol') || normalized.includes('gpt5.6sol');
534
+ }
535
+
536
+ export function chatgptAutomationModelFamilyMenuTrigger(
537
+ label: string | undefined,
538
+ ariaHasPopup: string | undefined,
539
+ ): boolean {
540
+ if (ariaHasPopup !== 'menu') return false;
541
+ const normalized = normalizeExecutionControlLabel(label);
542
+ // ChatGPT's composer can rotate model names independently of Forge releases.
543
+ // Locate the combined intelligence control by a bounded model-family prefix
544
+ // plus its menu-trigger role; do not treat arbitrary GPT prose/buttons as authority.
545
+ return /^gpt\d/.test(normalized);
546
+ }
547
+
548
+ function reasoningLabelMatches(label: string | undefined, reasoning: ChatgptAutomationReasoning): boolean {
549
+ return chatgptAutomationReasoningLevelFromLabel(label) === reasoning;
550
+ }
551
+
552
+ function isReasoningControlLabel(label: string | undefined): boolean {
553
+ return reasoningLabelMatches(label, 'medium')
554
+ || reasoningLabelMatches(label, 'high')
555
+ || reasoningLabelMatches(label, 'xhigh');
556
+ }
557
+
558
+ function reasoningSliderValue(reasoning: ChatgptAutomationReasoning): number {
559
+ if (reasoning === 'medium') return 2;
560
+ if (reasoning === 'high') return 3;
561
+ return 4;
562
+ }
563
+
564
+ async function findChatgptIntelligenceControl(
565
+ controllerHome: string,
566
+ workId: string,
567
+ browserSessionId: string,
568
+ timeoutMs?: number,
569
+ ): Promise<BrowserQueryMatch | undefined> {
570
+ for (const selector of CHATGPT_INTELLIGENCE_CONTROL_SELECTORS) {
571
+ const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
572
+ session_id: browserSessionId,
573
+ selector,
574
+ // Prefer the composer/main region so sidebar history cannot crowd the
575
+ // reasoning control out of a bounded query. Keep a larger global fallback
576
+ // for ChatGPT layouts that place the control outside <main>.
577
+ limit: chatgptAutomationControlQueryLimit(selector),
578
+ timeout_ms: timeoutMs ?? 60_000,
579
+ }, timeoutMs);
580
+ const candidates = queryMatches(result);
581
+ const exactMatch = candidates.find((candidate) => {
582
+ const label = matchText(candidate);
583
+ return modelLabelMatches(label, DEFAULT_CHATGPT_AUTOMATION_MODEL) || isReasoningControlLabel(label);
584
+ });
585
+ if (exactMatch) return exactMatch;
586
+ for (const candidate of candidates) {
587
+ const candidateSelector = matchSelector(candidate);
588
+ if (!candidateSelector || !/^gpt\d/.test(normalizeExecutionControlLabel(matchText(candidate)))) continue;
589
+ const popup = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
590
+ session_id: browserSessionId,
591
+ selector: candidateSelector,
592
+ attribute: 'aria-haspopup',
593
+ timeout_ms: timeoutMs ?? 60_000,
594
+ }, timeoutMs).catch(() => undefined);
595
+ if (chatgptAutomationModelFamilyMenuTrigger(matchText(candidate), stringField(popup?.value))) return candidate;
596
+ }
597
+ }
598
+ return undefined;
599
+ }
600
+
601
+ export function chatgptAutomationControlQueryLimit(selector: string): number {
602
+ // Long tool-heavy conversations can contribute well over 80 buttons before
603
+ // the composer controls. Keep the preferred <main> scan bounded but large
604
+ // enough that the current reasoning control is not crowded out by history.
605
+ return selector.startsWith('main ') ? 160 : 320;
606
+ }
607
+
608
+ export function chatgptAutomationControlWaitBudgets(timeoutMs?: number): { waitBudgetMs: number; probeTimeoutMs: number } {
609
+ const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 30_000, 1_000), 30_000);
610
+ // Native Chrome attachment has a few seconds of Apple Events/DOM latency on
611
+ // long conversations. Keep each probe bounded while allowing a real query to
612
+ // complete; the total readiness window remains capped at 30 seconds.
613
+ return { waitBudgetMs, probeTimeoutMs: Math.min(waitBudgetMs, 5_000) };
614
+ }
615
+
616
+ async function waitForChatgptIntelligenceControl(
617
+ controllerHome: string,
618
+ workId: string,
619
+ browserSessionId: string,
620
+ timeoutMs?: number,
621
+ ): Promise<BrowserQueryMatch | undefined> {
622
+ const { waitBudgetMs, probeTimeoutMs } = chatgptAutomationControlWaitBudgets(timeoutMs);
623
+ const deadline = Date.now() + waitBudgetMs;
624
+ do {
625
+ const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, probeTimeoutMs);
626
+ if (control) return control;
627
+ if (Date.now() >= deadline) break;
628
+ await new Promise((resolveWait) => setTimeout(resolveWait, 500));
629
+ } while (Date.now() < deadline);
630
+ return undefined;
631
+ }
632
+
633
+ export function chatgptAutomationPageFailure(
634
+ bodyText: string | undefined,
635
+ composerAvailable: boolean,
636
+ ): 'CHATGPT_AUTOMATION_LOGIN_REQUIRED' | 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE' | undefined {
637
+ if (composerAvailable) return undefined;
638
+ const normalized = (bodyText ?? '').toLowerCase();
639
+ const loginMarkers = [
640
+ 'log in', 'sign up', 'continue with google', 'continue with apple',
641
+ '登录', '登陆', '注册', '使用 google 继续', '使用 apple 继续',
642
+ ];
643
+ return loginMarkers.some((marker) => normalized.includes(marker))
644
+ ? 'CHATGPT_AUTOMATION_LOGIN_REQUIRED'
645
+ : 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE';
646
+ }
647
+
648
+ async function assertChatgptAutomationComposerReady(
649
+ controllerHome: string,
650
+ workId: string,
651
+ browserSessionId: string,
652
+ timeoutMs?: number,
653
+ ): Promise<void> {
654
+ const composerResult = await controllerBrowserAction(controllerHome, workId, 'query_all', {
655
+ session_id: browserSessionId,
656
+ selector: CHATGPT_PROMPT_SELECTOR,
657
+ limit: 1,
658
+ timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
659
+ }, timeoutMs).catch(() => undefined);
660
+ const composerAvailable = queryMatches(composerResult).length > 0;
661
+ if (composerAvailable) return;
662
+
663
+ const bodyResult = await controllerBrowserAction(controllerHome, workId, 'get_text', {
664
+ session_id: browserSessionId,
665
+ selector: 'body',
666
+ max_chars: 4_000,
667
+ timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
668
+ }, timeoutMs).catch(() => undefined);
669
+ const failure = chatgptAutomationPageFailure(stringField(bodyResult?.text), composerAvailable);
670
+ throw new Error(failure ?? 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE');
671
+ }
672
+
673
+ export async function ensureChatgptExecutionPreference(
674
+ controllerHome: string,
675
+ workId: string,
676
+ browserSessionId: string,
677
+ model: string,
678
+ reasoning: ChatgptAutomationReasoning,
679
+ timeoutMs?: number,
680
+ ): Promise<boolean> {
681
+ if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL) throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${model}`);
682
+ let control = await waitForChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs);
683
+ if (!control) {
684
+ await assertChatgptAutomationComposerReady(controllerHome, workId, browserSessionId, timeoutMs);
685
+ throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
686
+ }
687
+ // Current ChatGPT UI exposes the reasoning level (for example `高`) on the
688
+ // composer control instead of the model label. The model remains fail-closed
689
+ // through normalizeModel above; only the user-adjustable reasoning control is
690
+ // required to be observable in the page before unattended dispatch proceeds.
691
+ if (reasoningLabelMatches(matchText(control), reasoning)) return true;
692
+ let controlSelector = matchSelector(control);
693
+ if (!controlSelector) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
694
+
695
+ const expanded = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
696
+ session_id: browserSessionId,
697
+ selector: controlSelector,
698
+ attribute: 'aria-expanded',
699
+ timeout_ms: timeoutMs ?? 60_000,
700
+ }, timeoutMs).catch(() => undefined);
701
+ if (stringField(expanded?.value) !== 'true') {
702
+ await controllerBrowserAction(controllerHome, workId, 'press', {
703
+ session_id: browserSessionId,
704
+ selector: controlSelector,
705
+ key: 'ArrowDown',
706
+ timeout_ms: timeoutMs ?? 60_000,
707
+ post_action_wait_ms: 150,
708
+ }, timeoutMs);
709
+ }
710
+
711
+ const currentValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
712
+ session_id: browserSessionId,
713
+ selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
714
+ attribute: 'aria-valuenow',
715
+ timeout_ms: timeoutMs ?? 60_000,
716
+ }, timeoutMs).catch(() => undefined);
717
+ const currentValue = Number(stringField(currentValueResult?.value));
718
+ const targetValue = reasoningSliderValue(reasoning);
719
+ if (!Number.isInteger(currentValue) || currentValue < 0 || currentValue > 4) {
720
+ throw new Error(`CHATGPT_AUTOMATION_REASONING_STATE_UNAVAILABLE:${reasoning}`);
721
+ }
722
+ const direction = targetValue > currentValue ? 'ArrowRight' : 'ArrowLeft';
723
+ for (let index = 0; index < Math.abs(targetValue - currentValue); index += 1) {
724
+ await controllerBrowserAction(controllerHome, workId, 'press', {
725
+ session_id: browserSessionId,
726
+ selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
727
+ key: direction,
728
+ timeout_ms: timeoutMs ?? 60_000,
729
+ post_action_wait_ms: 120,
730
+ }, timeoutMs);
731
+ }
732
+
733
+ const verifiedValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
734
+ session_id: browserSessionId,
735
+ selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
736
+ attribute: 'aria-valuenow',
737
+ timeout_ms: timeoutMs ?? 60_000,
738
+ }, timeoutMs).catch(() => undefined);
739
+ if (Number(stringField(verifiedValueResult?.value)) !== targetValue) {
740
+ throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
741
+ }
742
+ control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs) ?? control;
743
+ if (isReasoningControlLabel(matchText(control)) && !reasoningLabelMatches(matchText(control), reasoning)) {
744
+ throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
745
+ }
746
+ controlSelector = matchSelector(control) ?? controlSelector;
747
+ await controllerBrowserAction(controllerHome, workId, 'press', {
748
+ session_id: browserSessionId,
749
+ selector: controlSelector,
750
+ key: 'Escape',
751
+ timeout_ms: timeoutMs ?? 60_000,
752
+ post_action_wait_ms: 100,
753
+ }, timeoutMs).catch(() => undefined);
754
+ return true;
755
+ }
756
+
757
+ function resultUrl(result: Record<string, unknown>): string | undefined {
758
+ return stringField(result.url)
759
+ ?? stringField((result.session as Record<string, unknown> | undefined)?.url);
760
+ }
761
+
762
+ function resultSessionId(result: Record<string, unknown>): string | undefined {
763
+ return stringField(result.session_id)
764
+ ?? stringField((result.session as Record<string, unknown> | undefined)?.sessionId);
765
+ }
766
+
767
+ interface ChatgptBrowserSessionInventoryItem {
768
+ sessionId?: unknown;
769
+ url?: unknown;
770
+ liveness?: unknown;
771
+ }
772
+
773
+ function chatgptUrlMatchesContinuationTarget(observedUrl: string, targetUrl: string): boolean {
774
+ try {
775
+ const observed = new URL(observedUrl);
776
+ const target = new URL(targetUrl);
777
+ const allowedHosts = new Set(['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com']);
778
+ if (observed.protocol !== 'https:' || target.protocol !== 'https:' || !allowedHosts.has(observed.hostname) || !allowedHosts.has(target.hostname)) return false;
779
+ const targetConversation = /\/c\/([^/?#]+)/.exec(target.pathname)?.[1];
780
+ if (targetConversation) {
781
+ const observedConversation = /\/c\/([^/?#]+)/.exec(observed.pathname)?.[1];
782
+ return observedConversation === targetConversation;
783
+ }
784
+ return observed.pathname === target.pathname;
785
+ } catch {
786
+ return false;
787
+ }
788
+ }
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
+
826
+ function completeChatgptBrowserInventorySessions(
827
+ inventory: Record<string, unknown>,
828
+ ): ChatgptBrowserSessionInventoryItem[] | undefined {
829
+ if (stringField(inventory.nextCursor)) return undefined;
830
+ return Array.isArray(inventory.sessions)
831
+ ? inventory.sessions as ChatgptBrowserSessionInventoryItem[]
832
+ : [];
833
+ }
834
+
835
+ export function reconciledNewChatgptOpenPageSessionId(
836
+ beforeInventory: Record<string, unknown>,
837
+ afterInventory: Record<string, unknown>,
838
+ targetUrl: string,
839
+ ): string | undefined {
840
+ const beforeSessions = completeChatgptBrowserInventorySessions(beforeInventory);
841
+ const afterSessions = completeChatgptBrowserInventorySessions(afterInventory);
842
+ if (!beforeSessions || !afterSessions) return undefined;
843
+ const beforeIds = new Set(beforeSessions
844
+ .map((entry) => stringField(entry.sessionId))
845
+ .filter((value): value is string => Boolean(value)));
846
+ const matches = afterSessions.flatMap((entry) => {
847
+ const sessionId = stringField(entry.sessionId);
848
+ const url = stringField(entry.url);
849
+ return sessionId
850
+ && !beforeIds.has(sessionId)
851
+ && entry.liveness === 'live'
852
+ && url
853
+ && chatgptUrlMatchesContinuationTarget(url, targetUrl)
854
+ ? [sessionId]
855
+ : [];
856
+ });
857
+ return matches.length === 1 ? matches[0] : undefined;
858
+ }
859
+
860
+ function browserMutationOutcomeUnknown(error: unknown, actionId: string): boolean {
861
+ return error instanceof Error
862
+ && error.message.includes('PLUGIN_BROWSER_MUTATION_OUTCOME_UNKNOWN')
863
+ && error.message.includes(`Browser action ${actionId}`);
864
+ }
865
+
866
+ export function chatgptAutomationNavigationRequiresReplacement(error: unknown): boolean {
867
+ if (!(error instanceof Error)) return false;
868
+ return [
869
+ 'BROWSER_AUTOMATION_BACKGROUND_NAVIGATION_REQUIRES_REPLACEMENT',
870
+ 'PLUGIN_BROWSER_SESSION_STATE_LOST',
871
+ 'PLUGIN_SESSION_NOT_FOUND',
872
+ 'PLUGIN_BROWSER_NATIVE_TAB_IDENTITY_UNPROVEN',
873
+ ].some((marker) => error.message.includes(marker));
874
+ }
875
+
876
+ export async function closeChatgptAutomationTabAfterDispatch(
877
+ controllerHome: string,
878
+ workId: string,
879
+ browserSessionId: string,
880
+ timeoutMs?: number,
881
+ authorizationGrantRefs: readonly string[] = [],
882
+ ): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
883
+ try {
884
+ const closePage = () => controllerBrowserAction(controllerHome, workId, 'close_page', {
885
+ session_id: browserSessionId,
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();
894
+ if (closed.preservedUserOwnedTab === true) return { status: 'preserved_user_owned' };
895
+ if (closed.resourceClosed === true) return { status: 'closed' };
896
+ return { status: 'session_closed' };
897
+ } catch (error) {
898
+ const message = error instanceof Error ? error.message : String(error);
899
+ return {
900
+ status: 'failed',
901
+ error: {
902
+ code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_AUTOMATION_TAB_CLEANUP_FAILED',
903
+ message,
904
+ },
905
+ };
906
+ }
907
+ }
908
+
909
+ /**
910
+ * Settles the browser resource for one completed Work-bound ChatGPT round.
911
+ * Conversation identity is durable in the Work binding and intentionally
912
+ * survives this ephemeral tab/session cleanup. Browser ownership policy keeps
913
+ * user-owned native tabs intact.
914
+ */
915
+ export async function settleWorkChatgptAutomationTab(input: {
916
+ controllerHome: string;
917
+ workId: string;
918
+ browserSessionId: string;
919
+ timeoutMs?: number;
920
+ authorizationGrantRefs?: readonly string[];
921
+ }): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
922
+ if (input.browserSessionId.startsWith('forge-chatgpt-bridge-')) return { status: 'session_closed' };
923
+ return closeChatgptAutomationTabAfterDispatch(
924
+ input.controllerHome,
925
+ input.workId,
926
+ input.browserSessionId,
927
+ input.timeoutMs,
928
+ input.authorizationGrantRefs,
929
+ );
930
+ }
931
+
932
+ export function isChatgptConversationUrl(value: string): boolean {
933
+ try {
934
+ const url = new URL(value);
935
+ return url.protocol === 'https:'
936
+ && ['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(url.hostname)
937
+ && /\/c\/[^/?#]+/.test(url.pathname);
938
+ } catch {
939
+ return false;
940
+ }
941
+ }
942
+
943
+ export async function navigateWorkConversation(
944
+ controllerHome: string,
945
+ workId: string,
946
+ browserSessionId: string,
947
+ targetUrl: string,
948
+ timeoutMs?: number,
949
+ ): Promise<{ submissionTargetUrl: string; recoveredFromStaleBinding: boolean; browserSessionId: string }> {
950
+ const navigate = async (sessionId: string, url: string) => controllerBrowserAction(controllerHome, workId, 'navigate', {
951
+ session_id: sessionId,
952
+ url,
953
+ wait_until: 'domcontentloaded',
954
+ timeout_ms: timeoutMs ?? 60_000,
955
+ retries: 1,
956
+ }, timeoutMs);
957
+ const openReplacement = async (sessionId: string, url: string): Promise<string> => {
958
+ // Fresh ControllerRound transport already owns an explicit Browser session
959
+ // identity. Use create_session with that exact id so native Browser never has
960
+ // to guess among multiple reusable Forge-owned tabs. The Browser adapter still
961
+ // owns tab identity/replacement and can recover a stale saved tab behind this id.
962
+ try {
963
+ const opened = await controllerBrowserAction(controllerHome, workId, 'create_session', {
964
+ session_id: sessionId,
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',
974
+ wait_until: 'domcontentloaded',
975
+ timeout_ms: timeoutMs ?? 60_000,
976
+ retries: 1,
977
+ }, timeoutMs);
978
+ const replacementSessionId = resultSessionId(opened);
979
+ if (replacementSessionId !== sessionId) throw new Error('CHATGPT_AUTOMATION_REPLACEMENT_SESSION_NOT_CONFIRMED');
980
+ return replacementSessionId;
981
+ } catch (error) {
982
+ if (!browserMutationOutcomeUnknown(error, 'create_session')) throw error;
983
+ try {
984
+ const verified = await controllerBrowserAction(controllerHome, workId, 'verify_state', {
985
+ session_id: sessionId,
986
+ expected_url: url,
987
+ }, timeoutMs);
988
+ if (verified.matched === true && stringField(verified.sessionId) === sessionId) return sessionId;
989
+ } catch {
990
+ // Preserve the original create_session mutation-unknown evidence.
991
+ }
992
+ throw error;
993
+ }
994
+ };
995
+ try {
996
+ // Prove the exact saved Browser resource is still attachable before dispatching
997
+ // a navigation mutation. A stale native tab must be replaced before any write;
998
+ // discovering staleness only after mutation creates an avoidable outcome-unknown window.
999
+ try {
1000
+ await controllerBrowserAction(controllerHome, workId, 'verify_state', {
1001
+ session_id: browserSessionId,
1002
+ expected_url: targetUrl,
1003
+ }, timeoutMs);
1004
+ } catch (preflightError) {
1005
+ if (!chatgptAutomationNavigationRequiresReplacement(preflightError)) throw preflightError;
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);
1014
+ return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
1015
+ }
1016
+ await navigate(browserSessionId, targetUrl);
1017
+ return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId };
1018
+ } catch (error) {
1019
+ if (browserMutationOutcomeUnknown(error, 'navigate')) {
1020
+ try {
1021
+ const verified = await controllerBrowserAction(controllerHome, workId, 'verify_state', {
1022
+ session_id: browserSessionId,
1023
+ expected_url: targetUrl,
1024
+ }, timeoutMs);
1025
+ if (verified.matched === true && stringField(verified.sessionId) === browserSessionId) {
1026
+ return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId };
1027
+ }
1028
+ } catch {
1029
+ // Preserve the original mutation-unknown evidence. Verification failure is
1030
+ // not authority to replay navigate or to invent a replacement session.
1031
+ }
1032
+ throw error;
1033
+ }
1034
+ if (chatgptAutomationNavigationRequiresReplacement(error)) {
1035
+ const replacementSessionId = await openReplacement(browserSessionId, targetUrl);
1036
+ return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId: replacementSessionId };
1037
+ }
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;
1043
+ }
1044
+ }
1045
+
1046
+ export async function submitChatgptPrompt(
1047
+ controllerHome: string,
1048
+ workId: string,
1049
+ browserSessionId: string,
1050
+ prompt: string,
1051
+ targetUrl: string,
1052
+ timeoutMs?: number,
1053
+ ): Promise<string> {
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@`;
1076
+ await controllerBrowserAction(controllerHome, workId, 'fill', {
1077
+ session_id: browserSessionId,
1078
+ selector: CHATGPT_PROMPT_SELECTOR,
1079
+ text: mentionTriggerDraft,
1080
+ timeout_ms: timeoutMs ?? 60_000,
1081
+ post_action_wait_ms: 100,
1082
+ }, timeoutMs);
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
+
1110
+ let observedUrl = targetUrl;
1111
+ let submitOutcomeUnknown = false;
1112
+ let observedNewOutbound = false;
1113
+ try {
1114
+ const sent = await controllerBrowserAction(controllerHome, workId, 'click', {
1115
+ session_id: browserSessionId,
1116
+ selector: CHATGPT_SEND_SELECTOR,
1117
+ timeout_ms: timeoutMs ?? 60_000,
1118
+ post_action_wait_ms: 250,
1119
+ }, timeoutMs);
1120
+ observedUrl = resultUrl(sent) ?? observedUrl;
1121
+ } catch (error) {
1122
+ if (browserMutationOutcomeUnknown(error, 'click')) {
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.
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
+ }
1163
+ } else if (chatgptSendControlUnavailable(error)) {
1164
+ const pressed = await controllerBrowserAction(controllerHome, workId, 'press', {
1165
+ session_id: browserSessionId,
1166
+ selector: CHATGPT_PROMPT_SELECTOR,
1167
+ key: 'Enter',
1168
+ timeout_ms: timeoutMs ?? 60_000,
1169
+ post_action_wait_ms: 250,
1170
+ }, timeoutMs);
1171
+ observedUrl = resultUrl(pressed) ?? observedUrl;
1172
+ } else {
1173
+ throw error;
1174
+ }
1175
+ }
1176
+
1177
+ const deadline = Date.now() + chatgptSubmissionSettlementWaitBudget(timeoutMs);
1178
+ let nextFailureProbeAt = 0;
1179
+ let observationIndex = 0;
1180
+ do {
1181
+ const latest = await latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs).catch(() => undefined);
1182
+ if (latest) {
1183
+ observedUrl = latest.url ?? observedUrl;
1184
+ const isNewOutbound = chatgptMessageObservationChanged(before, latest);
1185
+ if (isNewOutbound) {
1186
+ observedNewOutbound = true;
1187
+ const fullText = await fullChatgptMessageText(controllerHome, workId, browserSessionId, latest, timeoutMs);
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
+ }
1227
+ }
1228
+ }
1229
+ }
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)));
1235
+ } while (Date.now() < deadline);
1236
+ const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
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
1249
+ ? CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN
1250
+ : 'CHATGPT_AUTOMATION_SUBMISSION_NOT_CONFIRMED';
1251
+ throw new ChatgptProviderDeliveryError(
1252
+ failureCode,
1253
+ `${failureCode}:${observedUrl}`,
1254
+ { conversationUrl: observedUrl },
1255
+ );
1256
+ }