@moretea-labs/forge 1.8.0 → 1.8.2

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 (695) hide show
  1. package/AGENTS.md +73 -7
  2. package/CHANGELOG.md +34 -0
  3. package/CLAUDE.md +17 -2
  4. package/CONTRIBUTING.md +3 -2
  5. package/README.en.md +24 -3
  6. package/README.md +10 -6
  7. package/README.zh-CN.md +5 -3
  8. package/THIRD_PARTY_NOTICES.md +1 -2
  9. package/adapters/chatgpt/browser-delivery-host.ts +7 -1
  10. package/adapters/chatgpt/browser-delivery-runtime.ts +531 -56
  11. package/adapters/chatgpt/controller-binding-store.ts +101 -22
  12. package/adapters/chatgpt/controller-round-host.ts +15 -25
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
  14. package/adapters/chatgpt/provider-delivery.ts +229 -2
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +99 -54
  16. package/adapters/computer/chatgpt-extension-target.ts +708 -0
  17. package/adapters/computer/chrome-extension/background.js +541 -0
  18. package/adapters/computer/chrome-extension/content.js +402 -0
  19. package/adapters/computer/chrome-extension/core.js +24 -0
  20. package/adapters/computer/chrome-extension/manifest.json +32 -0
  21. package/adapters/computer/desktop-operator-discovery.ts +11 -31
  22. package/adapters/computer/desktop-operator-provider.ts +18 -6
  23. package/adapters/computer/interaction-target-authority.ts +178 -24
  24. package/adapters/mcp/access-mode.ts +18 -39
  25. package/adapters/mcp/auth.ts +2 -7
  26. package/adapters/mcp/instructions.ts +6 -5
  27. package/adapters/mcp/multi-repository.ts +10 -47
  28. package/adapters/mcp/oauth.ts +73 -0
  29. package/adapters/mcp/paths.ts +4 -1
  30. package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
  31. package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
  32. package/adapters/mcp/runtime-gateway/context-adapter.ts +490 -113
  33. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +83 -0
  34. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
  35. package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
  36. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
  37. package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
  38. package/adapters/mcp/runtime-gateway/execution-tools.ts +14 -54
  39. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
  40. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +132 -47
  41. package/adapters/mcp/runtime-gateway/process-tools.ts +173 -23
  42. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
  43. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
  44. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
  45. package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
  46. package/adapters/mcp/runtime-gateway/router.ts +19 -507
  47. package/adapters/mcp/runtime-gateway/routing-policy.ts +506 -0
  48. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +15 -66
  49. package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
  50. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +123 -178
  51. package/adapters/mcp/runtime-gateway/runtime-tools.ts +11 -5
  52. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +37 -1
  53. package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
  54. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +416 -270
  55. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +140 -0
  56. package/adapters/mcp/runtime-gateway/work-adapter.ts +262 -3123
  57. package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
  58. package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
  59. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +229 -0
  60. package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
  61. package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
  62. package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
  63. package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
  64. package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
  65. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +320 -0
  66. package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
  67. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +180 -0
  68. package/adapters/mcp/server.ts +91 -26
  69. package/adapters/mcp/setup.ts +5 -13
  70. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  71. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +703 -3092
  72. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +5 -10
  73. package/adapters/mcp/tool-mapping/repository-tools.ts +170 -213
  74. package/adapters/mcp/toolset-names.ts +11 -37
  75. package/adapters/mcp/toolset.ts +19 -92
  76. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  77. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  78. package/adapters/mcp/transports/http-observation.ts +377 -0
  79. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  80. package/adapters/mcp/transports/http.ts +200 -683
  81. package/adapters/mcp/transports/oauth-http.ts +322 -0
  82. package/adapters/mcp/transports/session-registry.ts +6 -0
  83. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
  84. package/adapters/mcp/types.ts +0 -6
  85. package/assets/hooks/lib/session-state.sh +0 -38
  86. package/assets/hooks/post-bash.sh +1 -1
  87. package/assets/hooks/post-tool-observer.sh +0 -5
  88. package/assets/hooks/prompt-guard.sh +0 -55
  89. package/assets/partials/04-project-structure.partial.md +1 -1
  90. package/assets/partials/07-footer.partial.md +1 -1
  91. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  92. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  93. package/assets/plugin-registry.v1.json +6 -4
  94. package/assets/reference-configs/agentic-development-flow.md +26 -1
  95. package/assets/reference-configs/external-tooling.md +0 -76
  96. package/assets/reference-configs/global-working-rules.md +19 -0
  97. package/assets/reference-configs/release-deploy.md +1 -1
  98. package/assets/templates/helpers/architecture-queue.sh +1 -1
  99. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  100. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  101. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  102. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  103. package/assets/workflow-contract.v1.json +0 -2
  104. package/docs/README.md +5 -2
  105. package/docs/architecture/CURRENT.md +205 -51
  106. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
  107. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
  108. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  109. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
  110. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
  111. package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
  112. package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
  113. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +234 -0
  114. package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
  115. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +74 -0
  116. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +342 -0
  117. package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
  118. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  119. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  120. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  121. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  122. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  123. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  124. package/docs/architecture/domains/public-surface.md +3 -5
  125. package/docs/architecture/domains/verification.md +6 -5
  126. package/docs/architecture/domains/workflow-engine.md +6 -0
  127. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +3 -3
  128. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  129. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  130. package/docs/forge-plugin-management.md +16 -4
  131. package/docs/operations/controller-browser-plugin.md +23 -0
  132. package/docs/operations/controller-performance-and-502.md +17 -0
  133. package/docs/operations/controller-reliability-runbook.md +2 -2
  134. package/docs/operations/features.md +6 -7
  135. package/docs/operations/features.zh-CN.md +1 -1
  136. package/docs/operations/github-repository.md +4 -4
  137. package/docs/operations/github-semantic-release-projection.md +20 -0
  138. package/docs/operations/homebrew.md +2 -2
  139. package/docs/operations/mcp-tool-exposure.md +13 -52
  140. package/docs/operations/releasing.md +2 -0
  141. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  142. package/docs/operations/standalone-disaster-recovery.md +15 -7
  143. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  144. package/docs/tutorials/03-first-repository-task.md +2 -0
  145. package/docs/wiki/Computer-and-Plugins.md +78 -0
  146. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  147. package/docs/wiki/Home.md +16 -2
  148. package/docs/wiki/Implementation.md +25 -13
  149. package/docs/wiki/Installation.md +28 -32
  150. package/docs/wiki/Integrations.md +4 -0
  151. package/docs/wiki/Quick-Start.md +35 -22
  152. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  153. package/docs/wiki/_Sidebar.md +5 -1
  154. package/package.json +25 -25
  155. package/packages/kernel/cognition/api/index.ts +6 -0
  156. package/packages/kernel/cognition/application/activation-service.ts +451 -0
  157. package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
  158. package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
  159. package/packages/kernel/cognition/application/memory-service.ts +64 -0
  160. package/packages/kernel/cognition/domain/learning.ts +106 -0
  161. package/packages/kernel/cognition/domain/memory.ts +254 -0
  162. package/packages/kernel/controller/api/index.ts +1 -0
  163. package/packages/kernel/controller/application/continuation-service.ts +281 -0
  164. package/packages/kernel/controller/application/controller-round-service.ts +12 -0
  165. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
  166. package/packages/kernel/controller/domain/controller-round.ts +4 -1
  167. package/packages/kernel/controller/domain/execution-quality.ts +3 -1
  168. package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
  169. package/packages/kernel/controller/infrastructure/controller-session-store.ts +348 -97
  170. package/packages/kernel/controller/ports/controller-host.ts +2 -0
  171. package/packages/kernel/identity/api/index.ts +10 -0
  172. package/packages/kernel/identity/domain/activity-projection.ts +43 -0
  173. package/packages/kernel/identity/domain/capability-broker.ts +60 -0
  174. package/packages/kernel/identity/domain/grant.ts +72 -0
  175. package/packages/kernel/identity/domain/owned-resource.ts +116 -0
  176. package/packages/kernel/identity/domain/user-request.ts +93 -0
  177. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +87 -0
  178. package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
  179. package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
  180. package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
  181. package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
  182. package/packages/kernel/scheduler/api/index.ts +0 -2
  183. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  184. package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
  185. package/packages/kernel/scheduler/application/settlement.ts +0 -15
  186. package/packages/kernel/scheduler/domain/schedule.ts +87 -0
  187. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +166 -143
  188. package/packages/kernel/work/api/index.ts +3 -2
  189. package/packages/kernel/work/application/work-service.ts +18 -42
  190. package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
  191. package/packages/kernel/work/domain/engineering-design.ts +10 -5
  192. package/packages/kernel/work/domain/engineering-profile.ts +8 -12
  193. package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
  194. package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
  195. package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
  196. package/packages/kernel/work/domain/implementation-review.ts +14 -331
  197. package/packages/kernel/work/domain/objective-graph.ts +119 -0
  198. package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
  199. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  200. package/packages/kernel/work/domain/state-machine.ts +34 -65
  201. package/packages/kernel/work/domain/types.ts +57 -51
  202. package/packages/kernel/work/infrastructure/work-contract-store.ts +847 -729
  203. package/packages/kernel/work/ports/work-contract-store.ts +5 -2
  204. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  205. package/packages/plugin-runtime/browser/session-authority.ts +2 -1
  206. package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
  207. package/packages/plugin-runtime/computer/conversation-target.ts +153 -0
  208. package/packages/plugin-runtime/computer/index.ts +2 -0
  209. package/packages/plugin-runtime/computer/target-authority.ts +26 -1
  210. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
  211. package/packages/protocols/computer/contract.ts +27 -1
  212. package/scripts/activate-source-baseline.ts +7 -9
  213. package/scripts/assemble-template.ts +11 -72
  214. package/scripts/canary-controller-semantic-wait.ts +12 -24
  215. package/scripts/check-mcp-compatibility.ts +94 -201
  216. package/scripts/check-package-identity.mjs +0 -5
  217. package/scripts/check-runtime-architecture.mjs +921 -239
  218. package/scripts/forge-local-recovery-helper.mjs +93 -15
  219. package/scripts/init-project.sh +0 -46
  220. package/scripts/install-ios-hid-toolchain.sh +60 -0
  221. package/scripts/install-standalone-recovery.ts +1 -1
  222. package/scripts/lib/project-init-lib.sh +6 -3
  223. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  224. package/scripts/open-source-audit-allowlist.txt +1 -3
  225. package/scripts/run-bounded-command.mjs +5 -0
  226. package/scripts/run-governed-gate.ts +2 -1
  227. package/scripts/run-mobile-monitor-bridge.ts +0 -1
  228. package/scripts/smoke-control-plane-failure-domain.ts +94 -0
  229. package/scripts/smoke-ios-physical-device.ts +38 -10
  230. package/scripts/smoke-mcp-http-runtime.ts +12 -8
  231. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  232. package/scripts/smoke-runtime-recovery.ts +12 -43
  233. package/scripts/smoke-schedule-engine.ts +23 -15
  234. package/scripts/smoke-unattended-transport-recovery.ts +223 -0
  235. package/scripts/smoke-user-request-continuation-restart.ts +232 -0
  236. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +95 -0
  237. package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
  238. package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
  239. package/scripts/smoke-workflow-supervisor.ts +103 -0
  240. package/scripts/sync-generated-authority.mjs +0 -1
  241. package/scripts/verify-forge-runtime.sh +2 -1
  242. package/src/cli/agent-jobs/executor-health.ts +12 -35
  243. package/src/cli/agent-jobs/integration.ts +20 -51
  244. package/src/cli/agent-jobs/job-manager.ts +7 -3
  245. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  246. package/src/cli/commands/brain-assistant.ts +27 -13
  247. package/src/cli/commands/brain.ts +13 -15
  248. package/src/cli/commands/chatgpt.ts +1 -163
  249. package/src/cli/commands/doctor.ts +1 -143
  250. package/src/cli/commands/global-runtime.ts +3 -196
  251. package/src/cli/commands/init-hook.ts +6 -23
  252. package/src/cli/commands/init.ts +3 -252
  253. package/src/cli/commands/mcp.ts +0 -5
  254. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  255. package/src/cli/commands/recovery.ts +137 -135
  256. package/src/cli/commands/repository.ts +17 -1
  257. package/src/cli/commands/runtime.ts +3 -3
  258. package/src/cli/commands/setup-execution.ts +41 -0
  259. package/src/cli/commands/setup-profile.ts +8 -0
  260. package/src/cli/controller/check-runner.ts +13 -2
  261. package/src/cli/controller/completion-backlog.ts +3 -3
  262. package/src/cli/controller/completion-orchestrator.ts +5 -44
  263. package/src/cli/controller/completion-target.ts +3 -72
  264. package/src/cli/controller/composite-operations.ts +1 -1
  265. package/src/cli/controller/context/instruction-context.ts +1 -1
  266. package/src/cli/controller/context/known-paths.ts +2 -2
  267. package/src/cli/controller/context/query-planning.ts +11 -4
  268. package/src/cli/controller/context/rule-resolution.ts +2 -2
  269. package/src/cli/controller/context/source-materializer.ts +83 -102
  270. package/src/cli/controller/context/structural-provider.ts +84 -0
  271. package/src/cli/controller/context/structural.ts +6 -6
  272. package/src/cli/controller/context/types.ts +8 -9
  273. package/src/cli/controller/context-pack.ts +107 -84
  274. package/src/cli/controller/execution-completion.ts +4 -7
  275. package/src/cli/controller/execution-policy.ts +36 -153
  276. package/src/cli/controller/governance.ts +2 -3
  277. package/src/cli/controller/issue-store.ts +4 -99
  278. package/src/cli/controller/launch-agents.ts +59 -18
  279. package/src/cli/controller/operational-plan.ts +25 -52
  280. package/src/cli/controller/runtime-config.ts +5 -1
  281. package/src/cli/controller/stuck-state-migration.ts +2 -3
  282. package/src/cli/controller/systemd-user.ts +42 -10
  283. package/src/cli/editing/edit-session.ts +18 -12
  284. package/src/cli/github/github.ts +4 -4
  285. package/src/cli/github/semantic-projection.ts +174 -0
  286. package/src/cli/hook/prompt-intents.ts +0 -9
  287. package/src/cli/index.ts +1 -43
  288. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  289. package/src/cli/local-bridge/console-automations.ts +19 -14
  290. package/src/cli/local-bridge/console-view-models.ts +4 -13
  291. package/src/cli/local-bridge/facade-api.ts +64 -193
  292. package/src/cli/local-bridge/server.ts +24 -147
  293. package/src/cli/local-bridge/types.ts +0 -1
  294. package/src/cli/mcp/oauth-client-retirement.ts +74 -0
  295. package/src/cli/repositories/command-classifier.ts +0 -9
  296. package/src/cli/repositories/command-executor.ts +29 -23
  297. package/src/cli/repositories/command-scope.ts +37 -18
  298. package/src/cli/repositories/controller-home.ts +48 -0
  299. package/src/cli/repositories/ephemeral-workspace.ts +20 -30
  300. package/src/cli/repositories/git-command-executor.ts +2 -2
  301. package/src/cli/repositories/locks.ts +20 -0
  302. package/src/cli/repositories/registry.ts +79 -12
  303. package/src/cli/repositories/safe-patch.ts +2 -12
  304. package/src/cli/repositories/structured-git.ts +101 -5
  305. package/src/cli/repository/inspector.ts +185 -343
  306. package/src/cli/repository/session-cache.ts +92 -6
  307. package/src/effects/process-runner.ts +17 -1
  308. package/src/runtime/context/assistant-context.ts +20 -7
  309. package/src/runtime/context/assistant-work-context.ts +89 -27
  310. package/src/runtime/context/automatic-learning.ts +565 -0
  311. package/src/runtime/context/cognitive-attention.ts +173 -0
  312. package/src/runtime/context/cognitive-skill-canary.ts +158 -0
  313. package/src/runtime/context/context-closure.ts +46 -9
  314. package/src/runtime/context/context-pack-process.ts +103 -0
  315. package/src/runtime/context/context-plane.ts +1 -9
  316. package/src/runtime/context/direct-controller-learning.ts +298 -0
  317. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  318. package/src/runtime/context/semantic-navigation-contract.ts +16 -0
  319. package/src/runtime/context/semantic-navigation.ts +7 -35
  320. package/src/runtime/context/typescript-navigation-process.ts +145 -0
  321. package/src/runtime/context/typescript-navigation.ts +89 -3
  322. package/src/runtime/control-plane/cleanup-artifact-retention.ts +215 -12
  323. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
  324. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
  325. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
  326. package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
  327. package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
  328. package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
  329. package/src/runtime/control-plane/execution/session-store.ts +31 -1
  330. package/src/runtime/control-plane/execution/validation.ts +6 -8
  331. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
  332. package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
  333. package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
  334. package/src/runtime/control-plane/execution/work-delivery-service.ts +616 -0
  335. package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
  336. package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
  337. package/src/runtime/control-plane/execution/work-handle-authority.ts +197 -110
  338. package/src/runtime/control-plane/execution/work-handle-store.ts +72 -44
  339. package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
  340. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
  341. package/src/runtime/control-plane/execution/work-operation-service.ts +60 -34
  342. package/src/runtime/control-plane/execution/work-preparation-service.ts +231 -267
  343. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  344. package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
  345. package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
  346. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +201 -199
  347. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +33 -85
  348. package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
  349. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +6 -2
  350. package/src/runtime/control-plane/facade/capability-registry.ts +364 -37
  351. package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
  352. package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
  353. package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
  354. package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
  355. package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
  356. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
  357. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
  358. package/src/runtime/control-plane/facade/index.ts +0 -1
  359. package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
  360. package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
  361. package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
  362. package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
  363. package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
  364. package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
  365. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
  366. package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
  367. package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
  368. package/src/runtime/control-plane/facade/types.ts +49 -108
  369. package/src/runtime/control-plane/facade/work-continuation.ts +9 -9
  370. package/src/runtime/control-plane/generated-cache-retention.ts +206 -59
  371. package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
  372. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
  373. package/src/runtime/control-plane/global-scheduler/maintenance.ts +256 -117
  374. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -12
  375. package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
  376. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
  377. package/src/runtime/control-plane/governance/access-policy.ts +19 -2
  378. package/src/runtime/control-plane/governance/authorization.ts +20 -3
  379. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  380. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
  381. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +29 -300
  382. package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
  383. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
  384. package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
  385. package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
  386. package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
  387. package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
  388. package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
  389. package/src/runtime/control-plane/release-retention.ts +135 -47
  390. package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
  391. package/src/runtime/control-plane/runtime-cleanup.ts +295 -107
  392. package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
  393. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
  394. package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
  395. package/src/runtime/diagnostics/incident-repair.ts +313 -146
  396. package/src/runtime/diagnostics/mcp-timing.ts +114 -0
  397. package/src/runtime/evidence/artifact-store.ts +158 -40
  398. package/src/runtime/evidence/evidence-store.ts +112 -8
  399. package/src/runtime/evidence/output-handle-index.ts +225 -0
  400. package/src/runtime/evidence/result-store.ts +158 -44
  401. package/src/runtime/evidence/sensitive-output.ts +22 -2
  402. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  403. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  404. package/src/runtime/execution/jobs/store.ts +3 -4
  405. package/src/runtime/execution/managed-workspace.ts +57 -0
  406. package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
  407. package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
  408. package/src/runtime/execution/process-runtime/check-result.ts +105 -1
  409. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
  410. package/src/runtime/execution/process-runtime/command-facade.ts +82 -30
  411. package/src/runtime/execution/process-runtime/gc.ts +15 -2
  412. package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
  413. package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
  414. package/src/runtime/execution/process-runtime/index.ts +3 -0
  415. package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
  416. package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
  417. package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
  418. package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
  419. package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
  420. package/src/runtime/execution/process-runtime/runtime.ts +55 -18
  421. package/src/runtime/execution/process-runtime/store.ts +30 -2
  422. package/src/runtime/execution/process-runtime/types.ts +6 -1
  423. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  424. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  425. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  426. package/src/runtime/execution/workers/executor.ts +3 -8
  427. package/src/runtime/maintenance/cleanup.ts +41 -5
  428. package/src/runtime/platform/service-inventory.ts +130 -0
  429. package/src/runtime/platform/service-manager.ts +8 -0
  430. package/src/runtime/plugins/action-application.ts +97 -29
  431. package/src/runtime/plugins/app-store-connect-adapter.ts +290 -18
  432. package/src/runtime/plugins/browser-adapter.ts +1124 -333
  433. package/src/runtime/plugins/browser-automation-service.ts +23 -3
  434. package/src/runtime/plugins/browser-macos-bridge.ts +431 -137
  435. package/src/runtime/plugins/browser-manifest-surface.ts +47 -12
  436. package/src/runtime/plugins/browser-native-session-service.ts +63 -13
  437. package/src/runtime/plugins/browser-registration.ts +18 -0
  438. package/src/runtime/plugins/browser-runtime.ts +40 -36
  439. package/src/runtime/plugins/browser-session-authority.ts +2 -2
  440. package/src/runtime/plugins/browser-session-store.ts +46 -76
  441. package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
  442. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +228 -0
  443. package/src/runtime/plugins/computer-registration.ts +298 -20
  444. package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
  445. package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
  446. package/src/runtime/plugins/external-adapter.ts +42 -6
  447. package/src/runtime/plugins/external-registration.ts +21 -1
  448. package/src/runtime/plugins/github-adapter.ts +30 -3
  449. package/src/runtime/plugins/gmail-adapter.ts +189 -3
  450. package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
  451. package/src/runtime/plugins/google-shared.ts +24 -12
  452. package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
  453. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  454. package/src/runtime/plugins/ios-adapter.ts +46 -41
  455. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  456. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  457. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  458. package/src/runtime/plugins/lightweight-action.ts +208 -2
  459. package/src/runtime/plugins/local-system-adapter.ts +12 -100
  460. package/src/runtime/plugins/managed-process-adapter.ts +12 -5
  461. package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
  462. package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
  463. package/src/runtime/plugins/store.ts +682 -435
  464. package/src/runtime/plugins/types.ts +24 -0
  465. package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
  466. package/src/runtime/projections/git-status-sampler.ts +2 -19
  467. package/src/runtime/projections/invalidation.ts +7 -3
  468. package/src/runtime/projections/materialized-view.ts +80 -36
  469. package/src/runtime/recovery/actions.ts +4 -4
  470. package/src/runtime/recovery/capability-action-application.ts +46 -45
  471. package/src/runtime/recovery/lifecycle-inventory.ts +30 -40
  472. package/src/runtime/recovery/maintenance-executor.ts +374 -123
  473. package/src/runtime/recovery/probe.ts +2 -2
  474. package/src/runtime/recovery/store.ts +29 -11
  475. package/src/runtime/release/release-coordinator.ts +312 -0
  476. package/src/runtime/release/release-session.ts +716 -0
  477. package/src/runtime/release/release-state-migration.ts +25 -0
  478. package/src/runtime/resources/leases/store.ts +56 -25
  479. package/src/runtime/root/computer-composition.ts +191 -6
  480. package/src/runtime/root/controller-progression-composition.ts +254 -0
  481. package/src/runtime/root/controller-round-composition.ts +20 -1
  482. package/src/runtime/root/deployment-topology.ts +106 -0
  483. package/src/runtime/root/entry.ts +4 -3
  484. package/src/runtime/root/known-good-recovery.ts +184 -0
  485. package/src/runtime/root/local-bridge.ts +0 -1
  486. package/src/runtime/root/mcp-transport.ts +111 -18
  487. package/src/runtime/root/ownership.ts +261 -9
  488. package/src/runtime/root/package-connector-service.ts +227 -29
  489. package/src/runtime/root/package-runtime-release.ts +117 -30
  490. package/src/runtime/root/package-runtime-service.ts +24 -24
  491. package/src/runtime/root/release-execution-canary.ts +57 -12
  492. package/src/runtime/root/release-loader.ts +90 -8
  493. package/src/runtime/root/release-manifest.ts +80 -40
  494. package/src/runtime/root/release-materialize.ts +372 -106
  495. package/src/runtime/root/release-store.ts +395 -36
  496. package/src/runtime/root/runtime-lane.ts +191 -0
  497. package/src/runtime/root/runtime.ts +160 -29
  498. package/src/runtime/root/scheduled-controller-composition.ts +29 -3
  499. package/src/runtime/root/service-runner.ts +2 -2
  500. package/src/runtime/root/service.ts +126 -44
  501. package/src/runtime/root/status.ts +48 -0
  502. package/src/runtime/root/types.ts +32 -9
  503. package/src/runtime/root/workflow-supervisor-composition.ts +681 -0
  504. package/src/runtime/root/workflow-supervisor-runtime.ts +564 -0
  505. package/src/runtime/root/write-fence.ts +50 -2
  506. package/src/runtime/safe-tooling/google-credential-store.ts +67 -22
  507. package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
  508. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  509. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  510. package/src/runtime/safe-tooling/workspace-auth.ts +5 -2
  511. package/src/runtime/shared/atomic-text-file.ts +21 -0
  512. package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
  513. package/src/runtime/shared/json-files.ts +8 -3
  514. package/src/runtime/shared/process-environment.ts +20 -9
  515. package/src/runtime/shared/response-budget.ts +1 -3
  516. package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
  517. package/src/runtime/standalone-recovery/core.ts +3792 -417
  518. package/src/runtime/standalone-recovery/entry.ts +507 -45
  519. package/src/runtime/standalone-recovery/installer.ts +262 -148
  520. package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
  521. package/src/runtime/standalone-recovery/operation-lock.ts +12 -2
  522. package/src/runtime/standalone-recovery/performance.ts +54 -16
  523. package/src/runtime/standalone-recovery/release.ts +53 -5
  524. package/src/runtime/standalone-recovery/service-labels.ts +5 -2
  525. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
  526. package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
  527. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -3
  528. package/src/runtime/workflow/schedules/engine.ts +166 -178
  529. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  530. package/src/runtime/workflow/schedules/work-continuation.ts +95 -11
  531. package/src/runtime/workflows/runtime.ts +15 -19
  532. package/src/testing/test-governance.ts +5 -1
  533. package/supervisor/chatgpt-conversation.ts +23 -0
  534. package/supervisor/client.ts +218 -0
  535. package/supervisor/control-plane.ts +963 -0
  536. package/supervisor/entry.ts +15 -0
  537. package/supervisor/forge-validators.ts +147 -0
  538. package/supervisor/index.ts +5 -0
  539. package/supervisor/native-browser-adapter.ts +1151 -0
  540. package/supervisor/native-messaging/host.ts +58 -0
  541. package/supervisor/native-messaging/manifest.ts +26 -0
  542. package/supervisor/native-messaging/protocol.ts +27 -0
  543. package/supervisor/paths.ts +18 -0
  544. package/supervisor/protocol.ts +250 -0
  545. package/supervisor/server.ts +412 -0
  546. package/supervisor/store.ts +1870 -0
  547. package/supervisor/tsconfig.json +8 -0
  548. package/supervisor/types.ts +261 -0
  549. package/SKILL.md +0 -427
  550. package/adapters/chatgpt/controller-host.ts +0 -119
  551. package/adapters/mcp/controller-round-compatibility.ts +0 -184
  552. package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
  553. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
  554. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  555. package/adapters/mcp/toolset-selection.ts +0 -41
  556. package/agents/openai.yaml +0 -4
  557. package/assets/skill-hooks.json +0 -41
  558. package/assets/skill-version.json +0 -189
  559. package/assets/skills/claude-review/SKILL.md +0 -231
  560. package/assets/skills/codex-review/SKILL.md +0 -103
  561. package/assets/templates/helpers/check-skill-version.ts +0 -193
  562. package/packages/kernel/progression/api/index.ts +0 -2
  563. package/packages/kernel/progression/application/projector.ts +0 -211
  564. package/packages/kernel/progression/domain/types.ts +0 -116
  565. package/packages/kernel/progression/index.ts +0 -1
  566. package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
  567. package/packages/kernel/scheduler/domain/continuation.ts +0 -38
  568. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
  569. package/packages/kernel/work/domain/admission-policy.ts +0 -34
  570. package/scripts/benchmark-background-check-overlap.ts +0 -228
  571. package/scripts/benchmark-computer-hotpath.ts +0 -255
  572. package/scripts/benchmark-controller-hotpath.ts +0 -212
  573. package/scripts/benchmark-controller-round2.ts +0 -191
  574. package/scripts/benchmark-ios-agent-device.ts +0 -511
  575. package/scripts/benchmark-mcp-host.sh +0 -117
  576. package/scripts/benchmark-mcp-transport.ts +0 -169
  577. package/scripts/benchmark-operational-memory-activation.ts +0 -235
  578. package/scripts/benchmark-operational-shadow.ts +0 -28
  579. package/scripts/benchmark-quality-harness.ts +0 -146
  580. package/scripts/benchmark-route-session-concurrency.ts +0 -915
  581. package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
  582. package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
  583. package/scripts/benchmark-thin-harness.ts +0 -251
  584. package/scripts/benchmark-typescript-navigation.ts +0 -186
  585. package/scripts/benchmark-work-efficiency.ts +0 -334
  586. package/scripts/build-controller-ui.ts +0 -28
  587. package/scripts/check-npm-release.sh +0 -32
  588. package/scripts/check-public-release-surface.sh +0 -19
  589. package/scripts/check-release-published.sh +0 -57
  590. package/scripts/check-release-readiness.sh +0 -37
  591. package/scripts/check-release-version.mjs +0 -74
  592. package/scripts/check-skill-version.ts +0 -5
  593. package/scripts/check-tarball-install-smoke.sh +0 -150
  594. package/scripts/ensure-codegraph.sh +0 -16
  595. package/scripts/package-source-archive.sh +0 -147
  596. package/scripts/public-release-files.txt +0 -39
  597. package/scripts/public-release.ts +0 -174
  598. package/scripts/publish-release-tarball.sh +0 -27
  599. package/scripts/route-nl-vs-ts-eval.ts +0 -679
  600. package/scripts/run-skill-hook.ts +0 -244
  601. package/scripts/stage-runtime-release.ts +0 -44
  602. package/scripts/sync-codex-installed-copies.sh +0 -182
  603. package/src/cli/commands/tools.ts +0 -167
  604. package/src/cli/controller/progress.ts +0 -656
  605. package/src/cli/controller/work-mode.ts +0 -181
  606. package/src/cli/local-bridge/console-assets.ts +0 -17
  607. package/src/cli/local-bridge/dashboard.ts +0 -17
  608. package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
  609. package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
  610. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
  611. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
  612. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
  613. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
  614. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
  615. package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
  616. package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
  617. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
  618. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
  619. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
  620. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
  621. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
  622. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
  623. package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
  624. package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
  625. package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
  626. package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
  627. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
  628. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
  629. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
  630. package/src/cli/local-bridge/frontend/src/styles.css +0 -28
  631. package/src/cli/local-bridge/frontend/src/types.ts +0 -15
  632. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
  633. package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
  634. package/src/cli/local-bridge/ui-dist/app.css +0 -1
  635. package/src/cli/local-bridge/ui-dist/app.js +0 -11228
  636. package/src/cli/mcp/access-mode.ts +0 -2
  637. package/src/cli/mcp/access-tools.ts +0 -2
  638. package/src/cli/mcp/audit.ts +0 -2
  639. package/src/cli/mcp/auth.ts +0 -2
  640. package/src/cli/mcp/instructions.ts +0 -2
  641. package/src/cli/mcp/legacy-context.ts +0 -2
  642. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  643. package/src/cli/mcp/multi-repository.ts +0 -2
  644. package/src/cli/mcp/oauth.ts +0 -2
  645. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  646. package/src/cli/mcp/paths.ts +0 -2
  647. package/src/cli/mcp/policy.ts +0 -2
  648. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  649. package/src/cli/mcp/proxy-env.ts +0 -2
  650. package/src/cli/mcp/readiness-projection.ts +0 -2
  651. package/src/cli/mcp/redaction.ts +0 -2
  652. package/src/cli/mcp/repo.ts +0 -2
  653. package/src/cli/mcp/repository-tools.ts +0 -2
  654. package/src/cli/mcp/server.ts +0 -2
  655. package/src/cli/mcp/setup.ts +0 -2
  656. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  657. package/src/cli/mcp/tool-contract.ts +0 -2
  658. package/src/cli/mcp/tools.ts +0 -2
  659. package/src/cli/mcp/toolset-names.ts +0 -2
  660. package/src/cli/mcp/toolset-selection.ts +0 -2
  661. package/src/cli/mcp/toolset.ts +0 -2
  662. package/src/cli/mcp/transports/http.ts +0 -2
  663. package/src/cli/mcp/transports/session-registry.ts +0 -2
  664. package/src/cli/mcp/transports/stdio.ts +0 -2
  665. package/src/cli/mcp/types.ts +0 -2
  666. package/src/cli/mcp/umbrella-tools.ts +0 -2
  667. package/src/cli/tools/codegraph.ts +0 -695
  668. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  669. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  670. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  671. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  672. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  673. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
  674. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
  675. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
  676. package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
  677. package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
  678. package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
  679. package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
  680. package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
  681. package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
  682. package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
  683. package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
  684. package/src/runtime/control-plane/routing/index.ts +0 -1
  685. package/src/runtime/control-plane/routing/route-policy.ts +0 -401
  686. package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
  687. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  688. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  689. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  690. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  691. package/src/runtime/gateway/mcp/router.ts +0 -2
  692. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  693. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  694. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  695. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
@@ -1,12 +1,14 @@
1
1
  import { execFile } from 'child_process';
2
- import { existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
3
3
  import { homedir } from 'os';
4
4
  import { basename, extname } from 'path';
5
5
  import {
6
6
  callBrowserAutomationBroker,
7
7
  captureBrowserAutomationRegion,
8
8
  type BrowserAutomationBrokerAction,
9
+ type BrowserAutomationSemanticTargetProof,
9
10
  type BrowserAutomationTrustedInput,
11
+ type BrowserAutomationTrustedInputRequest,
10
12
  } from './browser-automation-service';
11
13
  import { AssistantPluginError } from './errors';
12
14
  import type { BrowserNativeAttachAttempt, BrowserNativeProduct } from '../../../packages/protocols/browser/index';
@@ -14,6 +16,7 @@ import type { BrowserNativeAttachAttempt, BrowserNativeProduct } from '../../../
14
16
  const RECORD_SEPARATOR = String.fromCharCode(30);
15
17
  const DEFAULT_NATIVE_TIMEOUT_MS = 5_000;
16
18
  const MAX_NATIVE_OUTPUT_BYTES = 1_048_576;
19
+ const MAX_NATIVE_BROWSER_FILE_ATTACH_BYTES = 16 * 1024 * 1024;
17
20
 
18
21
  export type MacOsBrowserProduct = BrowserNativeProduct;
19
22
 
@@ -64,6 +67,8 @@ export interface MacOsBrowserTabInventoryEntry {
64
67
  url: string;
65
68
  title: string;
66
69
  active: boolean;
70
+ /** True when this tab is active in the frontmost browser window. */
71
+ frontmost?: boolean;
67
72
  }
68
73
 
69
74
  export interface MacOsBrowserTabInventory {
@@ -119,7 +124,7 @@ function execFileText(file: string, args: string[], timeoutMs: number): Promise<
119
124
  }, (error, stdout, stderr) => {
120
125
  if (error) {
121
126
  const diagnostic = String(stderr || error.message).trim().slice(-2_000);
122
- reject(new Error(diagnostic || error.message));
127
+ reject(new Error(diagnostic || error.message, { cause: error }));
123
128
  return;
124
129
  }
125
130
  resolve(String(stdout).trim());
@@ -132,10 +137,16 @@ const defaultRuntimeHooks: MacOsBrowserRuntimeHooks = {
132
137
  appExists: (path) => existsSync(path),
133
138
  processRunning: async (processName, timeoutMs) => {
134
139
  try {
140
+ // Process existence only: the canonical Computer provider owns exact
141
+ // GUI PID selection and rejects ambiguous or unverified targets.
135
142
  await execFileText('/usr/bin/pgrep', ['-x', processName], timeoutMs);
136
143
  return true;
137
- } catch {
138
- return false;
144
+ } catch (error) {
145
+ // pgrep's no-match exit is absence; timeout/permission/transport failure
146
+ // cannot prove that an application's tabs are absent.
147
+ const cause = error instanceof Error ? error.cause : undefined;
148
+ if (cause && typeof cause === 'object' && 'code' in cause && cause.code === 1) return false;
149
+ throw error;
139
150
  }
140
151
  },
141
152
  };
@@ -214,6 +225,7 @@ async function runCreateTabAutomation(
214
225
  testArgs: string[],
215
226
  timeoutMs: number,
216
227
  ): Promise<MacOsBrowserCreateTabEvidence> {
228
+ await assertMacOsBrowserApplicationRunning(request.product, timeoutMs);
217
229
  if (runtimeHooks.runAppleScript) {
218
230
  const value = await runtimeHooks.runAppleScript(testScript, testArgs, timeoutMs);
219
231
  return parseMacOsBrowserCreateTabBrokerResult({ value });
@@ -227,6 +239,12 @@ async function runBrowserAutomationText(
227
239
  testArgs: string[],
228
240
  timeoutMs: number,
229
241
  ): Promise<string> {
242
+ // AppleScript `tell application` starts an application that is not already
243
+ // running while it compiles the script. Every browser entry point therefore
244
+ // fails closed unless the exact browser process is already live, so routine
245
+ // inventory/reconcile work can never launch the user's browser or hand Forge
246
+ // an unauthenticated profile.
247
+ if ('product' in request) await assertMacOsBrowserApplicationRunning(request.product, timeoutMs);
230
248
  if (runtimeHooks.runAppleScript) return await runtimeHooks.runAppleScript(testScript, testArgs, timeoutMs);
231
249
  const result = await callBrowserAutomationBroker(request, timeoutMs);
232
250
  if (typeof result.value !== 'string') {
@@ -239,6 +257,76 @@ async function runBrowserAutomationText(
239
257
  return result.value;
240
258
  }
241
259
 
260
+ /**
261
+ * Non-launching precondition for every Apple Events browser operation.
262
+ * Path existence is checked without scripting the application, and process
263
+ * liveness comes from the process table, so neither probe can start a browser.
264
+ */
265
+ export async function assertMacOsBrowserApplicationRunning(
266
+ product: MacOsBrowserProduct,
267
+ timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
268
+ ): Promise<void> {
269
+ const browser = browserDefinition(product);
270
+ const details = { browserProduct: product, bundleId: browser.bundleId, appName: browser.appName };
271
+ if (runtimeHooks.platform !== 'darwin') {
272
+ throw new AssistantPluginError(
273
+ 'PLUGIN_BROWSER_NATIVE_APP_UNSUPPORTED_PLATFORM',
274
+ `Native ${browser.appName} Apple Events are unavailable on ${runtimeHooks.platform}.`,
275
+ { retryable: false, details },
276
+ );
277
+ }
278
+ if (!browser.appPaths.some((path) => runtimeHooks.appExists(path))) {
279
+ throw new AssistantPluginError(
280
+ 'PLUGIN_BROWSER_NATIVE_APP_NOT_INSTALLED',
281
+ `${browser.appName} is not installed; Forge never installs or substitutes a browser application.`,
282
+ { retryable: false, details },
283
+ );
284
+ }
285
+ if (!(await runtimeHooks.processRunning(browser.processName, timeoutMs))) {
286
+ throw new AssistantPluginError(
287
+ 'PLUGIN_BROWSER_NATIVE_APP_NOT_RUNNING',
288
+ `${browser.appName} is not running; Forge never launches a browser application implicitly.`,
289
+ { retryable: true, details },
290
+ );
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Start only the browser family already chosen by the Computer target. This
296
+ * is deliberately narrower than a generic browser launch: it never guesses a
297
+ * product, profile, or authentication identity. The caller must have a
298
+ * durable preferred product and must still verify the resulting ChatGPT page
299
+ * before any provider mutation.
300
+ */
301
+ export async function ensureMacOsBrowserApplicationRunning(
302
+ product: MacOsBrowserProduct,
303
+ timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
304
+ ): Promise<void> {
305
+ const browser = browserDefinition(product);
306
+ if (runtimeHooks.platform !== 'darwin') throw new AssistantPluginError(
307
+ 'PLUGIN_BROWSER_NATIVE_APP_UNSUPPORTED_PLATFORM',
308
+ `Native ${browser.appName} Apple Events are unavailable on ${runtimeHooks.platform}.`,
309
+ { retryable: false, details: { browserProduct: product } },
310
+ );
311
+ if (!browser.appPaths.some((path) => runtimeHooks.appExists(path))) throw new AssistantPluginError(
312
+ 'PLUGIN_BROWSER_NATIVE_APP_NOT_INSTALLED',
313
+ `${browser.appName} is not installed.`,
314
+ { retryable: false, details: { browserProduct: product } },
315
+ );
316
+ if (await runtimeHooks.processRunning(browser.processName, timeoutMs)) return;
317
+ await execFileText('/usr/bin/open', ['-a', browser.appName], timeoutMs);
318
+ const deadline = Date.now() + Math.max(1_000, timeoutMs);
319
+ while (Date.now() < deadline) {
320
+ if (await runtimeHooks.processRunning(browser.processName, Math.min(1_000, timeoutMs))) return;
321
+ await new Promise((resolve) => setTimeout(resolve, 100));
322
+ }
323
+ throw new AssistantPluginError(
324
+ 'PLUGIN_BROWSER_NATIVE_APP_START_TIMEOUT',
325
+ `${browser.appName} did not become available after Forge started the remembered browser family.`,
326
+ { retryable: true, details: { browserProduct: product } },
327
+ );
328
+ }
329
+
242
330
  async function captureBrowserAutomation(
243
331
  region: { x: number; y: number; width: number; height: number },
244
332
  path: string,
@@ -251,9 +339,10 @@ async function captureBrowserAutomation(
251
339
  }
252
340
 
253
341
  async function sendBrowserTrustedInput(
254
- request: Extract<BrowserAutomationBrokerAction, { action: 'trusted_input' }>,
342
+ request: BrowserAutomationTrustedInputRequest,
255
343
  timeoutMs: number,
256
344
  ): Promise<void> {
345
+ await assertMacOsBrowserApplicationRunning(request.product, timeoutMs);
257
346
  if (runtimeHooks.trustedInput) {
258
347
  await runtimeHooks.trustedInput(request, timeoutMs);
259
348
  return;
@@ -405,6 +494,8 @@ set maxTabs to 256
405
494
  set returnedCount to 0
406
495
  set truncatedInventory to false
407
496
  set outputText to "false"
497
+ set frontWindowId to ""
498
+ if (count of windows) is greater than 0 then set frontWindowId to ((id of front window) as text)
408
499
  repeat with candidateWindow in windows
409
500
  set activeTabId to ""
410
501
  try
@@ -417,10 +508,11 @@ repeat with candidateWindow in windows
417
508
  end if
418
509
  set candidateWindowId to ((id of candidateWindow) as text)
419
510
  set candidateTabId to ((id of candidateTab) as text)
511
+ set candidateFrontmost to (candidateWindowId is frontWindowId)
420
512
  set candidateURL to my cleanField((URL of candidateTab as text), recordSeparator, fieldSeparator)
421
513
  set candidateTitle to my cleanField((title of candidateTab as text), recordSeparator, fieldSeparator)
422
514
  set candidateActive to (candidateTabId is activeTabId)
423
- set outputText to outputText & recordSeparator & candidateWindowId & fieldSeparator & candidateTabId & fieldSeparator & (candidateActive as text) & fieldSeparator & candidateURL & fieldSeparator & candidateTitle
515
+ set outputText to outputText & recordSeparator & candidateWindowId & fieldSeparator & candidateTabId & fieldSeparator & (candidateActive as text) & fieldSeparator & (candidateFrontmost as text) & fieldSeparator & candidateURL & fieldSeparator & candidateTitle
424
516
  set returnedCount to returnedCount + 1
425
517
  end repeat
426
518
  if truncatedInventory then exit repeat
@@ -458,14 +550,23 @@ function parseTabInventory(product: MacOsBrowserProduct, raw: string): MacOsBrow
458
550
  const windowId = (fields[0] ?? '').trim();
459
551
  const tabId = (fields[1] ?? '').trim();
460
552
  const activeText = (fields[2] ?? '').trim().toLowerCase();
461
- if (!windowId || !tabId || (activeText !== 'true' && activeText !== 'false')) {
553
+ const hasFrontmostField = fields.length >= 6 && ['true', 'false'].includes((fields[3] ?? '').trim().toLowerCase());
554
+ const frontmostText = hasFrontmostField ? (fields[3] ?? '').trim().toLowerCase() : undefined;
555
+ if (!windowId || !tabId || (activeText !== 'true' && activeText !== 'false') || (fields.length >= 6 && !hasFrontmostField)) {
462
556
  throw new AssistantPluginError(
463
557
  'PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR',
464
558
  'Stable Forge macOS capability broker returned invalid browser tab inventory metadata.',
465
559
  { retryable: true },
466
560
  );
467
561
  }
468
- tabs.push({ windowId, tabId, active: activeText === 'true', url: fields[3] ?? '', title: fields.slice(4).join(fieldSeparator) });
562
+ tabs.push({
563
+ windowId,
564
+ tabId,
565
+ active: activeText === 'true',
566
+ ...(frontmostText ? { frontmost: frontmostText === 'true' } : {}),
567
+ url: fields[hasFrontmostField ? 4 : 3] ?? '',
568
+ title: fields.slice(hasFrontmostField ? 5 : 4).join(fieldSeparator),
569
+ });
469
570
  if (tabs.length > 256) {
470
571
  throw new AssistantPluginError(
471
572
  'PLUGIN_MACOS_CAPABILITY_BROKER_PROTOCOL_ERROR',
@@ -507,30 +608,78 @@ return (frontmost as text) & separator & (URL of targetTab as text) & separator
507
608
  function activateTargetTabScript(browser: MacOsBrowserDefinition, ref: MacOsBrowserTabRef): string {
508
609
  return browserTellScript(browser, `
509
610
  ${targetTabPreamble(ref)}
510
- set targetTabIndex to 1
511
- repeat with candidateTab in tabs of targetWindow
512
- if ((id of candidateTab) as text) is ((id of targetTab) as text) then exit repeat
513
- set targetTabIndex to targetTabIndex + 1
611
+ -- Activating by position is only safe inside the same enumeration that produced
612
+ -- the index. A tab created or closed by the provider between those two steps
613
+ -- makes the positional write fail with -1719 ("Can't get item N of every tab"),
614
+ -- so each attempt re-reads the tab list, bounds the index and verifies the exact
615
+ -- tab became active before it is reported as done.
616
+ set targetTabIdText to ((id of targetTab) as text)
617
+ set activationConfirmed to false
618
+ repeat 3 times
619
+ set targetTabIndex to 0
620
+ set candidateIndex to 0
621
+ repeat with candidateTab in tabs of targetWindow
622
+ set candidateIndex to candidateIndex + 1
623
+ if ((id of candidateTab) as text) is targetTabIdText then set targetTabIndex to candidateIndex
624
+ end repeat
625
+ if targetTabIndex is greater than 0 then
626
+ try
627
+ if targetTabIndex is less than or equal to (count of tabs of targetWindow) then
628
+ set active tab index of targetWindow to targetTabIndex
629
+ if ((id of active tab of targetWindow) as text) is targetTabIdText then set activationConfirmed to true
630
+ end if
631
+ end try
632
+ end if
633
+ if activationConfirmed then exit repeat
634
+ delay 0.05
514
635
  end repeat
515
- set active tab index of targetWindow to targetTabIndex
516
636
  set index of targetWindow to 1
517
637
  activate
518
638
  `);
519
639
  }
520
640
 
521
- function createOwnedTabScript(browser: MacOsBrowserDefinition): string {
641
+ function createOwnedTabScript(browser: MacOsBrowserDefinition, preferredWindowId?: string): string {
522
642
  return `on run argv
523
643
  set targetUrl to item 1 of argv
524
644
  ${browserTellScript(browser, `
525
- if (count of windows) is 0 then
645
+ set preferredWindowId to "${preferredWindowId ?? ''}"
646
+ set targetWindow to missing value
647
+ if preferredWindowId is not "" then
648
+ repeat with candidateWindow in windows
649
+ if ((id of candidateWindow) as text) is preferredWindowId then
650
+ set targetWindow to candidateWindow
651
+ exit repeat
652
+ end if
653
+ end repeat
654
+ end if
655
+ if preferredWindowId is not "" and targetWindow is missing value then error "FORGE_BROWSER_PREFERRED_WINDOW_MISSING:" & preferredWindowId
656
+ if targetWindow is missing value and (count of windows) is greater than 0 then set targetWindow to front window
657
+ if targetWindow is missing value then
526
658
  set targetWindow to make new window
527
659
  set targetTab to active tab of targetWindow
528
660
  set URL of targetTab to targetUrl
529
661
  else
530
- set targetWindow to front window
531
- set originalActiveIndex to active tab index of targetWindow
662
+ -- Preserve the selected background window; front-window reassignment here
663
+ -- previously discarded the caller's stable target and opened the wrong tab.
664
+ set originalActiveTabId to ((id of active tab of targetWindow) as text)
532
665
  set targetTab to make new tab at end of tabs of targetWindow with properties {URL:targetUrl}
533
- set active tab index of targetWindow to originalActiveIndex
666
+ -- Restore the previously active tab by identity, never by a stale position:
667
+ -- the new tab can shift Chrome's tab order and make a pre-create index invalid.
668
+ repeat 3 times
669
+ if ((id of active tab of targetWindow) as text) is originalActiveTabId then exit repeat
670
+ set restoreIndex to 0
671
+ set candidateIndex to 0
672
+ repeat with candidateTab in tabs of targetWindow
673
+ set candidateIndex to candidateIndex + 1
674
+ if ((id of candidateTab) as text) is originalActiveTabId then set restoreIndex to candidateIndex
675
+ end repeat
676
+ if restoreIndex is 0 then exit repeat
677
+ try
678
+ if restoreIndex is less than or equal to (count of tabs of targetWindow) then set active tab index of targetWindow to restoreIndex
679
+ end try
680
+ if ((id of active tab of targetWindow) as text) is originalActiveTabId then exit repeat
681
+ delay 0.05
682
+ end repeat
534
683
  end if
535
684
  set targetTabId to id of targetTab
536
685
  set separator to ASCII character 30
@@ -804,8 +953,25 @@ export class MacOsAppleEventsPage {
804
953
  return await runBrowserAutomationText(effectiveRequest, effectiveScript, args, timeoutMs);
805
954
  } catch (error) {
806
955
  const message = error instanceof Error ? error.message : String(error);
807
- throw new AssistantPluginError('PLUGIN_BROWSER_NATIVE_OPERATION_FAILED', `${this.browser.appName} Apple Events operation failed: ${message}`, {
808
- retryable: true,
956
+ // Preserve the provider's *typed* process-identity refusal. In particular,
957
+ // do not turn ambiguous Chrome app roots into a retryable generic error:
958
+ // retries must never guess another user's running browser instance.
959
+ const nativeCode = error instanceof AssistantPluginError ? error.code : undefined;
960
+ const nativeFailure = nativeCode === 'BROWSER_AUTOMATION_PROCESS_TARGET_AMBIGUOUS'
961
+ || nativeCode === 'BROWSER_AUTOMATION_PROCESS_IDENTITY_LOST'
962
+ || nativeCode === 'BROWSER_AUTOMATION_PROCESS_PROBE_UNAVAILABLE'
963
+ || nativeCode === 'BROWSER_AUTOMATION_NATIVE_EVENT_FAILED'
964
+ || nativeCode === 'BROWSER_AUTOMATION_MUTATION_OUTCOME_UNKNOWN'
965
+ ? nativeCode : undefined;
966
+ const nonRetryable = nativeFailure && nativeFailure !== 'BROWSER_AUTOMATION_PROCESS_PROBE_UNAVAILABLE';
967
+ // An Apple Event can mutate the page even if its callback fails.
968
+ // Preserve uncertainty and never retry such a mutation blindly.
969
+ const outcomeUnknown = nativeFailure === 'BROWSER_AUTOMATION_MUTATION_OUTCOME_UNKNOWN'
970
+ || (error instanceof AssistantPluginError && error.effectOutcome === 'outcome_unknown');
971
+ const brokerFailure = nativeFailure ?? /\b(BROWSER_AUTOMATION_TIMEOUT|BROWSER_AUTOMATION_SERIALIZATION_BUSY)\b/.exec(message)?.[1];
972
+ throw new AssistantPluginError(brokerFailure ?? 'PLUGIN_BROWSER_NATIVE_OPERATION_FAILED', `${this.browser.appName} Apple Events operation failed: ${message}`, {
973
+ retryable: !nonRetryable && !outcomeUnknown,
974
+ effectOutcome: outcomeUnknown ? 'outcome_unknown' : 'failed',
809
975
  details: { browserProduct: this.browser.product, bundleId: this.browser.bundleId },
810
976
  });
811
977
  }
@@ -834,8 +1000,12 @@ export class MacOsAppleEventsPage {
834
1000
  if (macOsBrowserJavaScriptAutomationDisabled(error)) {
835
1001
  throw new AssistantPluginError(
836
1002
  'PLUGIN_BROWSER_JAVASCRIPT_PERMISSION_REQUIRED',
837
- `${this.browser.appName} allows tab/window automation, but DOM actions require Settings > Privacy > Apple Events > Allow JavaScript from Apple Events.`,
838
- { retryable: false, details: { browserProduct: this.browser.product, bundleId: this.browser.bundleId } },
1003
+ `${this.browser.appName} rejected JavaScript from Apple Events for the browser instance addressed by macOS automation. This does not prove that the user's visible Chrome instance has this setting disabled. Verify the exact Chrome process/profile and View > Developer > Allow JavaScript from Apple Events before changing settings.`,
1004
+ { retryable: false, details: {
1005
+ browserProduct: this.browser.product, bundleId: this.browser.bundleId,
1006
+ processIdentity: 'unverified',
1007
+ ...(this.targetRef ? { windowId: this.targetRef.windowId, tabId: this.targetRef.tabId } : {}),
1008
+ } },
839
1009
  );
840
1010
  }
841
1011
  throw error;
@@ -973,15 +1143,16 @@ export class MacOsAppleEventsPage {
973
1143
  if (filePaths.length === 0 || filePaths.length > 32) {
974
1144
  throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Native browser file input accepts 1-32 local files.', { retryable: false });
975
1145
  }
976
- for (const filePath of filePaths) {
1146
+
1147
+ // Validate the entire batch before sending bytes or changing input.files.
1148
+ const attachments = filePaths.map((filePath) => {
977
1149
  if (!filePath || !existsSync(filePath)) {
978
1150
  throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Local file does not exist.', { retryable: false });
979
1151
  }
980
- }
981
-
982
- if (filePaths.length === 1) {
983
- const filePath = filePaths[0];
984
- const bytes = readFileSync(filePath);
1152
+ const metadata = statSync(filePath);
1153
+ if (!metadata.isFile()) {
1154
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Native browser can attach only regular files.', { retryable: false });
1155
+ }
985
1156
  const fileName = basename(filePath);
986
1157
  const extension = extname(fileName).toLowerCase();
987
1158
  const mimeType = extension === '.pdf' ? 'application/pdf'
@@ -989,123 +1160,98 @@ export class MacOsAppleEventsPage {
989
1160
  : extension === '.png' ? 'image/png'
990
1161
  : extension === '.jpg' || extension === '.jpeg' ? 'image/jpeg'
991
1162
  : 'application/octet-stream';
992
- const bufferKey = `__forgeLocalFile_${Date.now()}_${Math.random().toString(16).slice(2)}`;
993
- const encoded = bytes.toString('base64');
994
- const chunkChars = 192 * 1024;
995
- try {
1163
+ return { filePath, fileName, size: metadata.size, mimeType };
1164
+ });
1165
+ const totalBytes = attachments.reduce((sum, attachment) => sum + attachment.size, 0);
1166
+ if (totalBytes > MAX_NATIVE_BROWSER_FILE_ATTACH_BYTES) {
1167
+ throw new AssistantPluginError('PLUGIN_BROWSER_NATIVE_FILE_TOO_LARGE',
1168
+ 'Native browser upload is bounded to 16 MiB per batch; select a smaller batch instead of queuing unbounded Apple Events.',
1169
+ { retryable: false, details: { totalBytes, limitBytes: MAX_NATIVE_BROWSER_FILE_ATTACH_BYTES } });
1170
+ }
1171
+
1172
+ // A read-only DOM preflight must pass before staging starts. In particular,
1173
+ // multi-file uploads cannot reset a previous file selection on preflight failure.
1174
+ await this.evaluate(`(() => { // __forgeFileInputPreflight
1175
+ const matches = document.querySelectorAll(${JSON.stringify(selector)});
1176
+ if (matches.length !== 1) throw new Error('File input selector must resolve to exactly one element.');
1177
+ const element = matches[0];
1178
+ if (!(element instanceof HTMLInputElement) || element.type !== 'file') throw new Error('Selector must resolve to an input[type=file].');
1179
+ if (element.disabled) throw new Error('Target file input is disabled.');
1180
+ if (${attachments.length > 1} && !element.multiple) throw new Error('Target input does not allow multiple files.');
1181
+ return true;
1182
+ })()`);
1183
+
1184
+ const batchKey = `__forgeStagedFiles_${Date.now()}_${Math.random().toString(16).slice(2)}`;
1185
+ const bufferKey = `__forgeEncodedFile_${Date.now()}_${Math.random().toString(16).slice(2)}`;
1186
+ const expected = attachments.map(({ fileName, size }) => ({ name: fileName, size }));
1187
+ const chunkChars = 192 * 1024;
1188
+ try {
1189
+ for (const attachment of attachments) {
1190
+ // Stage one file at a time. No DOM selection or change event occurs
1191
+ // until the complete batch has arrived and its sizes have been checked.
1192
+ const encoded = readFileSync(attachment.filePath).toString('base64');
996
1193
  await this.evaluate(`(() => { globalThis[${JSON.stringify(bufferKey)}] = ''; return true; })()`);
997
1194
  for (let offset = 0; offset < encoded.length; offset += chunkChars) {
998
1195
  const chunk = encoded.slice(offset, offset + chunkChars);
999
1196
  await this.evaluate(`(() => { globalThis[${JSON.stringify(bufferKey)}] += ${JSON.stringify(chunk)}; return true; })()`);
1000
1197
  }
1001
- const attached = await this.evaluate<{ name?: string; size?: number }>(`(() => {
1002
- const element = document.querySelector(${JSON.stringify(selector)});
1003
- if (!(element instanceof HTMLInputElement) || element.type !== 'file') throw new Error('Selector must resolve to an input[type=file].');
1004
- const expectedName = ${JSON.stringify(fileName)};
1005
- const expectedSize = ${bytes.length};
1198
+ const staged = await this.evaluate<{ name?: string; size?: number }>(`(() => { // __forgeFileInputStaged
1006
1199
  const encoded = String(globalThis[${JSON.stringify(bufferKey)}] || '');
1007
1200
  const binary = atob(encoded);
1008
1201
  const data = new Uint8Array(binary.length);
1009
1202
  for (let i = 0; i < binary.length; i += 1) data[i] = binary.charCodeAt(i);
1010
- const file = new File([data], expectedName, { type: ${JSON.stringify(mimeType)} });
1011
- const transfer = new DataTransfer();
1012
- transfer.items.add(file);
1013
- element.files = transfer.files;
1014
- element.dispatchEvent(new Event('input', { bubbles: true }));
1015
- element.dispatchEvent(new Event('change', { bubbles: true }));
1016
- const selected = element.files && element.files[0];
1017
- return selected ? { name: selected.name, size: selected.size, expectedName, expectedSize } : null;
1203
+ const file = new File([data], ${JSON.stringify(attachment.fileName)}, { type: ${JSON.stringify(attachment.mimeType)} });
1204
+ const staged = globalThis[${JSON.stringify(batchKey)}] || [];
1205
+ staged.push(file);
1206
+ globalThis[${JSON.stringify(batchKey)}] = staged;
1207
+ delete globalThis[${JSON.stringify(bufferKey)}];
1208
+ return { name: file.name, size: file.size };
1018
1209
  })()`);
1019
- if (attached?.name !== fileName || attached?.size !== bytes.length) {
1020
- throw new AssistantPluginError('PLUGIN_BROWSER_NATIVE_FILE_ATTACH_FAILED', 'Native browser did not retain the requested local file on the target input.', {
1021
- retryable: true,
1022
- details: { expectedName: fileName, expectedSize: bytes.length, actualName: attached?.name, actualSize: attached?.size },
1023
- });
1210
+ if (staged?.name !== attachment.fileName || staged?.size !== attachment.size) {
1211
+ throw new AssistantPluginError('PLUGIN_BROWSER_NATIVE_FILE_ATTACH_FAILED',
1212
+ 'Browser did not stage the expected local file.', { retryable: false });
1024
1213
  }
1025
- } finally {
1026
- await this.evaluate(`(() => { try { delete globalThis[${JSON.stringify(bufferKey)}]; } catch {} return true; })()`).catch(() => undefined);
1027
1214
  }
1028
- return;
1029
- }
1030
1215
 
1031
- const attachments = filePaths.map((filePath) => {
1032
- const bytes = readFileSync(filePath);
1033
- const fileName = basename(filePath);
1034
- const extension = extname(fileName).toLowerCase();
1035
- const mimeType = extension === '.pdf' ? 'application/pdf'
1036
- : extension === '.zip' ? 'application/zip'
1037
- : extension === '.png' ? 'image/png'
1038
- : extension === '.jpg' || extension === '.jpeg' ? 'image/jpeg'
1039
- : 'application/octet-stream';
1040
- return {
1041
- bytes,
1042
- fileName,
1043
- mimeType,
1044
- bufferKey: `__forgeLocalFile_${Date.now()}_${Math.random().toString(16).slice(2)}`,
1045
- };
1046
- });
1047
-
1048
- if (attachments.length > 1) {
1049
- await this.evaluate(`(() => {
1050
- const element = document.querySelector(${JSON.stringify(selector)});
1051
- if (!(element instanceof HTMLInputElement) || element.type !== 'file') throw new Error('Selector must resolve to an input[type=file].');
1052
- if (!element.multiple) throw new Error('Target input does not allow multiple files.');
1216
+ // One commit to input.files and one event pair, after all File objects
1217
+ // are staged and verified. Failed transfer never partially selects files.
1218
+ const selected = await this.evaluate<Array<{ name?: string; size?: number }>>(`(() => { // __forgeFileInputCommit
1219
+ const matches = document.querySelectorAll(${JSON.stringify(selector)});
1220
+ if (matches.length !== 1) throw new Error('File input selector changed during transfer.');
1221
+ const element = matches[0];
1222
+ if (!(element instanceof HTMLInputElement) || element.type !== 'file' || element.disabled) {
1223
+ throw new Error('File input became unavailable during transfer.');
1224
+ }
1225
+ if (${attachments.length > 1} && !element.multiple) throw new Error('File input no longer supports multiple files.');
1226
+ const staged = globalThis[${JSON.stringify(batchKey)}];
1227
+ const expected = ${JSON.stringify(expected)};
1228
+ if (!Array.isArray(staged) || staged.length !== expected.length ||
1229
+ !staged.every((file, i) => file instanceof File && file.name === expected[i].name && file.size === expected[i].size)) {
1230
+ throw new Error('Staged file batch does not match the requested selection.');
1231
+ }
1053
1232
  const transfer = new DataTransfer();
1233
+ staged.forEach((file) => transfer.items.add(file));
1054
1234
  element.files = transfer.files;
1055
- return true;
1056
- })()`);
1057
- }
1058
-
1059
- const chunkChars = 192 * 1024;
1060
- try {
1061
- for (let index = 0; index < attachments.length; index += 1) {
1062
- const attachment = attachments[index];
1063
- const encoded = attachment.bytes.toString('base64');
1064
- await this.evaluate(`(() => { globalThis[${JSON.stringify(attachment.bufferKey)}] = ''; return true; })()`);
1065
- for (let offset = 0; offset < encoded.length; offset += chunkChars) {
1066
- const chunk = encoded.slice(offset, offset + chunkChars);
1067
- await this.evaluate(`(() => { globalThis[${JSON.stringify(attachment.bufferKey)}] += ${JSON.stringify(chunk)}; return true; })()`);
1235
+ const selected = Array.from(element.files || []).map((file) => ({ name: file.name, size: file.size }));
1236
+ if (selected.length !== expected.length ||
1237
+ !selected.every((file, i) => file.name === expected[i].name && file.size === expected[i].size)) {
1238
+ throw new Error('Browser did not retain the requested file selection.');
1068
1239
  }
1069
- const append = attachments.length > 1 && index > 0;
1070
- await this.evaluate(`(() => {
1071
- const element = document.querySelector(${JSON.stringify(selector)});
1072
- if (!(element instanceof HTMLInputElement) || element.type !== 'file') throw new Error('Selector must resolve to an input[type=file].');
1073
- const expectedName = ${JSON.stringify(attachment.fileName)};
1074
- const expectedSize = ${attachment.bytes.length};
1075
- const encoded = String(globalThis[${JSON.stringify(attachment.bufferKey)}] || '');
1076
- const binary = atob(encoded);
1077
- const data = new Uint8Array(binary.length);
1078
- for (let i = 0; i < binary.length; i += 1) data[i] = binary.charCodeAt(i);
1079
- const file = new File([data], expectedName, { type: ${JSON.stringify(attachment.mimeType)} });
1080
- const transfer = new DataTransfer();
1081
- if (${append ? 'true' : 'false'}) {
1082
- for (const existing of Array.from(element.files || [])) transfer.items.add(existing);
1083
- }
1084
- transfer.items.add(file);
1085
- element.files = transfer.files;
1086
- return Array.from(element.files || []).map((selected) => ({ name: selected.name, size: selected.size }));
1087
- })()`);
1088
- }
1089
-
1090
- const selected = await this.evaluate<Array<{ name?: string; size?: number }>>(`(() => {
1091
- const element = document.querySelector(${JSON.stringify(selector)});
1092
- if (!(element instanceof HTMLInputElement) || element.type !== 'file') throw new Error('Selector must resolve to an input[type=file].');
1093
1240
  element.dispatchEvent(new Event('input', { bubbles: true }));
1094
1241
  element.dispatchEvent(new Event('change', { bubbles: true }));
1095
- return Array.from(element.files || []).map((file) => ({ name: file.name, size: file.size }));
1242
+ return selected;
1096
1243
  })()`);
1097
- const expected = attachments.map((attachment) => ({ name: attachment.fileName, size: attachment.bytes.length }));
1098
- const matches = selected.length === expected.length && expected.every((item, index) => selected[index]?.name === item.name && selected[index]?.size === item.size);
1099
- if (!matches) {
1100
- throw new AssistantPluginError('PLUGIN_BROWSER_NATIVE_FILE_ATTACH_FAILED', 'Native browser did not retain the requested local files on the target input.', {
1101
- retryable: true,
1102
- details: { expected, actual: selected },
1103
- });
1244
+ if (selected.length !== expected.length ||
1245
+ !expected.every((item, index) => selected[index]?.name === item.name && selected[index]?.size === item.size)) {
1246
+ throw new AssistantPluginError('PLUGIN_BROWSER_NATIVE_FILE_ATTACH_FAILED',
1247
+ 'Native browser did not retain the requested local files on the target input.', { retryable: false });
1104
1248
  }
1105
1249
  } finally {
1106
- for (const attachment of attachments) {
1107
- await this.evaluate(`(() => { try { delete globalThis[${JSON.stringify(attachment.bufferKey)}]; } catch {} return true; })()`).catch(() => undefined);
1108
- }
1250
+ // Unknown effect outcomes must be reconciled by observation, never by replay.
1251
+ await this.evaluate(`(() => {
1252
+ try { delete globalThis[${JSON.stringify(bufferKey)}]; delete globalThis[${JSON.stringify(batchKey)}]; } catch {}
1253
+ return true;
1254
+ })()`).catch(() => undefined);
1109
1255
  }
1110
1256
  }
1111
1257
 
@@ -1123,6 +1269,57 @@ export class MacOsAppleEventsPage {
1123
1269
  await this.refreshMetadata();
1124
1270
  }
1125
1271
 
1272
+ /**
1273
+ * Supervisor-only semantic text boundary. Browser owns exact tab + DOM focus;
1274
+ * Computer receives only a one-shot semantic target proof and remains the OS
1275
+ * mutation transport. Generic Browser trusted text keeps its compatibility
1276
+ * semantics and does not implicitly become ChatGPT-specific.
1277
+ */
1278
+ async trustedSemanticTextInput(text: string): Promise<void> {
1279
+ if (!this.targetRef) {
1280
+ throw new AssistantPluginError('PLUGIN_BROWSER_TRUSTED_INPUT_UNAVAILABLE', 'Semantic trusted text requires an exact saved browser tab.', { retryable: false });
1281
+ }
1282
+ this.targetRef = await resolveCurrentMacOsBrowserTabRef(this.browser.product, this.targetRef, this.timeoutMs);
1283
+ const metadata = await readResolvedMacOsBrowserOwnedTabMetadata(this.browser.product, this.targetRef, this.timeoutMs);
1284
+ if (!metadata.frontmost || metadata.active !== true) {
1285
+ throw new AssistantPluginError(
1286
+ 'PLUGIN_BROWSER_FOREGROUND_REQUIRED',
1287
+ 'Semantic trusted text requires the exact saved browser tab to be frontmost and active.',
1288
+ { retryable: true, details: { browserProduct: this.browser.product, windowId: this.targetRef.windowId, tabId: this.targetRef.tabId } },
1289
+ );
1290
+ }
1291
+ const semanticTarget = await this.evaluate<BrowserAutomationSemanticTargetProof & { valid?: boolean }>(`(() => {
1292
+ const element = document.activeElement;
1293
+ if (!(element instanceof HTMLElement)) return { valid: false };
1294
+ const tag = element.tagName.toLowerCase();
1295
+ const inputType = element instanceof HTMLInputElement ? String(element.type || 'text').toLowerCase() : '';
1296
+ const textInput = element instanceof HTMLTextAreaElement
1297
+ || element.isContentEditable
1298
+ || (element instanceof HTMLInputElement && !['button','checkbox','color','file','hidden','image','radio','range','reset','submit'].includes(inputType));
1299
+ const accessibleName = String(element.getAttribute('aria-label') || element.getAttribute('placeholder') || element.getAttribute('title') || element.getAttribute('name') || '').trim();
1300
+ const domRole = String(element.getAttribute('role') || (textInput ? 'textbox' : '')).trim();
1301
+ const multiline = element instanceof HTMLTextAreaElement || element.isContentEditable || element.getAttribute('aria-multiline') === 'true';
1302
+ return { valid: textInput && document.activeElement === element && Boolean(domRole) && Boolean(accessibleName), domRole, accessibleName, editable: true, focused: true, multiline };
1303
+ })()`);
1304
+ if (semanticTarget.valid !== true || semanticTarget.editable !== true || semanticTarget.focused !== true
1305
+ || !semanticTarget.domRole || !semanticTarget.accessibleName) {
1306
+ throw new AssistantPluginError(
1307
+ 'PLUGIN_BROWSER_TRUSTED_INPUT_SEMANTIC_TARGET_UNPROVEN',
1308
+ 'Browser could not prove one focused editable DOM target with an accessible identity before trusted text mutation.',
1309
+ { retryable: true, details: { browserProduct: this.browser.product, windowId: this.targetRef.windowId, tabId: this.targetRef.tabId } },
1310
+ );
1311
+ }
1312
+ const { valid: _valid, ...proof } = semanticTarget;
1313
+ await sendBrowserTrustedInput({
1314
+ action: 'trusted_input',
1315
+ product: this.browser.product,
1316
+ ref: this.targetRef,
1317
+ input: { kind: 'text', text },
1318
+ semanticTarget: proof,
1319
+ }, this.timeoutMs);
1320
+ await this.refreshMetadata();
1321
+ }
1322
+
1126
1323
  async screenshot(options: Record<string, unknown>): Promise<Buffer> {
1127
1324
  const path = typeof options.path === 'string' ? options.path : undefined;
1128
1325
  if (!path) throw new Error('Native browser screenshot requires an output path.');
@@ -1191,8 +1388,15 @@ export class MacOsAppleEventsPage {
1191
1388
  const prototype = element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
1192
1389
  const setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
1193
1390
  if (setter) setter.call(element, ${JSON.stringify(value)}); else element.value = ${JSON.stringify(value)};
1194
- } else if (element.isContentEditable) element.textContent = ${JSON.stringify(value)};
1195
- else throw new Error('Element does not accept text input.');
1391
+ } else if (element.isContentEditable) {
1392
+ element.focus();
1393
+ const selection = window.getSelection();
1394
+ if (!selection) throw new Error('Contenteditable selection unavailable.');
1395
+ const range = document.createRange();
1396
+ range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range);
1397
+ if (!document.execCommand('insertText', false, ${JSON.stringify(value)})) throw new Error('Contenteditable insertion rejected.');
1398
+ return true;
1399
+ } else throw new Error('Element does not accept text input.');
1196
1400
  element.dispatchEvent(new Event('input', { bubbles: true }));
1197
1401
  element.dispatchEvent(new Event('change', { bubbles: true }));
1198
1402
  return true;
@@ -1200,8 +1404,26 @@ export class MacOsAppleEventsPage {
1200
1404
  }
1201
1405
 
1202
1406
  async type(selector: string, text: string): Promise<void> {
1203
- const current = await this.evaluate<string>(selectorSource(selector, `return 'value' in element ? String(element.value || '') : String(element.textContent || '');`));
1204
- await this.fill(selector, `${current}${text}`);
1407
+ await this.evaluate(selectorSource(selector, `
1408
+ if ('value' in element) {
1409
+ element.focus();
1410
+ const value = String(element.value || '');
1411
+ const prototype = element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
1412
+ const setter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
1413
+ if (setter) setter.call(element, value + ${JSON.stringify(text)}); else element.value = value + ${JSON.stringify(text)};
1414
+ element.dispatchEvent(new Event('input', { bubbles: true }));
1415
+ return true;
1416
+ }
1417
+ if (!element.isContentEditable) throw new Error('Element does not accept text input.');
1418
+ element.focus();
1419
+ const selection = window.getSelection();
1420
+ if (!selection) throw new Error('Contenteditable selection unavailable.');
1421
+ const range = document.createRange();
1422
+ range.selectNodeContents(element); range.collapse(false);
1423
+ selection.removeAllRanges(); selection.addRange(range);
1424
+ if (!document.execCommand('insertText', false, ${JSON.stringify(text)})) throw new Error('Contenteditable insertion rejected.');
1425
+ return true;
1426
+ `));
1205
1427
  }
1206
1428
 
1207
1429
  async press(selector: string, key: string): Promise<void> {
@@ -1359,7 +1581,9 @@ async function resolveCurrentMacOsBrowserTabRef(
1359
1581
  ? await runtimeHooks.tabInventory(product, timeoutMs)
1360
1582
  : runtimeHooks.runAppleScript
1361
1583
  ? undefined
1362
- : await listMacOsBrowserTabs(product, timeoutMs);
1584
+ // Stable identity needs the complete inventory, not UI-current flags.
1585
+ // Input/activation checks read their exact target's foreground state.
1586
+ : await listMacOsBrowserTabs(product, timeoutMs, { correctFrontmost: false });
1363
1587
  if (!inventory) return ref;
1364
1588
 
1365
1589
  const matches = inventory.tabs.filter((candidate) => candidate.tabId === ref.tabId);
@@ -1388,18 +1612,58 @@ async function resolveCurrentMacOsBrowserTabRef(
1388
1612
  return { windowId: match.windowId, tabId: match.tabId };
1389
1613
  }
1390
1614
 
1615
+ export interface MacOsBrowserTabInventoryOptions {
1616
+ /**
1617
+ * Correct stale active/frontmost bits through one extra foreground metadata
1618
+ * read. Exact-target callers that only need stable windowId/tabId/url identity
1619
+ * can disable this optional probe so a blocked foreground Apple Events read
1620
+ * cannot starve the inventory itself.
1621
+ */
1622
+ correctFrontmost?: boolean;
1623
+ }
1624
+
1391
1625
  export async function listMacOsBrowserTabs(
1392
1626
  product: MacOsBrowserProduct,
1393
1627
  timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1628
+ options: MacOsBrowserTabInventoryOptions = {},
1394
1629
  ): Promise<MacOsBrowserTabInventory> {
1395
1630
  const browser = browserDefinition(product);
1631
+ // Capture the frontmost tab before reading the stable inventory when callers
1632
+ // need corrected UI-current metadata. Exact-target identity does not depend on
1633
+ // this optional foreground probe.
1634
+ const inspected = options.correctFrontmost === false
1635
+ ? undefined
1636
+ : await inspectBrowser(product, timeoutMs);
1396
1637
  const raw = await runBrowserAutomationText(
1397
1638
  { action: 'list_tabs', product },
1398
1639
  listTabsScript(browser),
1399
1640
  [],
1400
1641
  timeoutMs,
1401
1642
  );
1402
- return parseTabInventory(product, raw);
1643
+ const inventory = parseTabInventory(product, raw);
1644
+ if (!inspected) return inventory;
1645
+ const metadata = inspected.metadata;
1646
+ const current = metadata?.frontmost === true && metadata.active !== false
1647
+ ? { windowId: metadata.windowId, tabId: metadata.tabId, url: metadata.url.trim() }
1648
+ : undefined;
1649
+ if (!current) return inventory;
1650
+ const hasIdentity = Boolean(current.windowId && current.tabId);
1651
+ const currentMatches = inventory.tabs.filter(tab => (
1652
+ hasIdentity
1653
+ ? tab.windowId === current.windowId && tab.tabId === current.tabId
1654
+ : Boolean(current.url) && tab.url.trim() === current.url
1655
+ ));
1656
+ const currentMatch = currentMatches.length === 1 ? currentMatches[0] : undefined;
1657
+ if (!currentMatch) return inventory;
1658
+ return {
1659
+ ...inventory,
1660
+ tabs: inventory.tabs.map(tab => ({
1661
+ ...tab,
1662
+ ...(currentMatch.windowId === tab.windowId && currentMatch.tabId === tab.tabId
1663
+ ? { active: true, frontmost: true }
1664
+ : { frontmost: false }),
1665
+ })),
1666
+ };
1403
1667
  }
1404
1668
 
1405
1669
  export async function readMacOsBrowserOwnedTabMetadata(
@@ -1407,11 +1671,24 @@ export async function readMacOsBrowserOwnedTabMetadata(
1407
1671
  ref: MacOsBrowserTabRef,
1408
1672
  timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1409
1673
  ): Promise<MacOsBrowserMetadata> {
1410
- const browser = browserDefinition(product);
1411
1674
  const resolvedRef = await resolveCurrentMacOsBrowserTabRef(product, ref, timeoutMs);
1675
+ return await readResolvedMacOsBrowserOwnedTabMetadata(product, resolvedRef, timeoutMs);
1676
+ }
1677
+
1678
+ /**
1679
+ * Read one exact native tab whose stable identity is already authoritative for
1680
+ * the caller. The targeted AppleScript still fails closed when that tab id is
1681
+ * gone, but does not perform another full browser inventory.
1682
+ */
1683
+ export async function readResolvedMacOsBrowserOwnedTabMetadata(
1684
+ product: MacOsBrowserProduct,
1685
+ ref: MacOsBrowserTabRef,
1686
+ timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1687
+ ): Promise<MacOsBrowserMetadata> {
1688
+ const browser = browserDefinition(product);
1412
1689
  return parseMetadata(product, await runBrowserAutomationText(
1413
- { action: 'metadata', product, ref: resolvedRef },
1414
- targetMetadataScript(browser, resolvedRef),
1690
+ { action: 'metadata', product, ref },
1691
+ targetMetadataScript(browser, ref),
1415
1692
  [],
1416
1693
  timeoutMs,
1417
1694
  ));
@@ -1481,12 +1758,13 @@ export async function createMacOsBrowserOwnedPageForProduct(
1481
1758
  product: MacOsBrowserProduct,
1482
1759
  url: string,
1483
1760
  attempts: MacOsBrowserAttachAttempt[],
1761
+ preferredWindowId?: string,
1484
1762
  timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1485
1763
  ): Promise<{ page: MacOsAppleEventsPage; attachment: MacOsBrowserAttachment }> {
1486
1764
  const browser = browserDefinition(product);
1487
1765
  const creationEvidence = await runCreateTabAutomation(
1488
1766
  { action: 'create_tab', product, url },
1489
- createOwnedTabScript(browser),
1767
+ createOwnedTabScript(browser, preferredWindowId),
1490
1768
  [url],
1491
1769
  timeoutMs,
1492
1770
  );
@@ -1544,12 +1822,28 @@ export async function closeMacOsBrowserOwnedTab(
1544
1822
  ref: MacOsBrowserTabRef,
1545
1823
  timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1546
1824
  ): Promise<void> {
1547
- const browser = browserDefinition(product);
1548
1825
  try {
1549
1826
  const resolvedRef = await resolveCurrentMacOsBrowserTabRef(product, ref, timeoutMs);
1827
+ await closeResolvedMacOsBrowserOwnedTab(product, resolvedRef, timeoutMs);
1828
+ } finally {
1829
+ invalidateMacOsBrowserPageHandle(product, ref);
1830
+ }
1831
+ }
1832
+
1833
+ /**
1834
+ * Close one exact native tab whose stable identity was already proven by the
1835
+ * caller's live inventory snapshot. This deliberately does not inventory again.
1836
+ */
1837
+ export async function closeResolvedMacOsBrowserOwnedTab(
1838
+ product: MacOsBrowserProduct,
1839
+ ref: MacOsBrowserTabRef,
1840
+ timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS,
1841
+ ): Promise<void> {
1842
+ const browser = browserDefinition(product);
1843
+ try {
1550
1844
  await runBrowserAutomationText(
1551
- { action: 'close_tab', product, ref: resolvedRef },
1552
- closeOwnedTabScript(browser, resolvedRef),
1845
+ { action: 'close_tab', product, ref },
1846
+ closeOwnedTabScript(browser, ref),
1553
1847
  [],
1554
1848
  timeoutMs,
1555
1849
  );