@moretea-labs/forge 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +46 -0
  3. package/CLAUDE.md +2 -0
  4. package/DESIGN.md +7 -2
  5. package/README.md +4 -4
  6. package/README.zh-CN.md +2 -2
  7. package/SKILL.md +2 -2
  8. package/THIRD_PARTY_NOTICES.md +1 -1
  9. package/agents/openai.yaml +1 -1
  10. package/assets/hooks/hook-input.sh +11 -8
  11. package/assets/hooks/lib/session-state.sh +10 -7
  12. package/assets/hooks/lib/workflow-state.sh +13 -177
  13. package/assets/hooks/post-tool-observer.sh +8 -2
  14. package/assets/hooks/pre-edit-guard.sh +8 -39
  15. package/assets/hooks/prompt-guard.sh +7 -83
  16. package/assets/hooks/session-start-context.sh +56 -1
  17. package/assets/partials/05-workflow.partial.md +1 -1
  18. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  19. package/assets/plugin-registry.v1.json +54 -3
  20. package/assets/reference-configs/document-generation.md +1 -1
  21. package/assets/reference-configs/harness-overview.md +1 -1
  22. package/assets/reference-configs/hook-operations.md +1 -1
  23. package/assets/reference-configs/sprint-contracts.md +3 -3
  24. package/assets/skill-version.json +2 -2
  25. package/assets/templates/contract.template.md +2 -7
  26. package/assets/templates/helpers/capture-plan.sh +2 -2
  27. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  28. package/assets/templates/helpers/contract-worktree.sh +1 -21
  29. package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/new-plan.sh +1 -1
  32. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  33. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  34. package/assets/templates/helpers/sprint-backlog.sh +6 -6
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/plan.template.md +1 -1
  38. package/assets/templates/review.template.md +9 -34
  39. package/assets/workflow-contract.v1.json +10 -19
  40. package/docs/README.md +10 -10
  41. package/docs/architecture/CURRENT.md +170 -0
  42. package/docs/architecture/EVOLUTION.md +42 -0
  43. package/docs/architecture/current/README.md +4 -156
  44. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  45. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  46. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  47. package/docs/architecture/history/README.md +7 -0
  48. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  49. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  50. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  51. package/docs/architecture/history.md +3 -34
  52. package/docs/architecture/index.md +15 -98
  53. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  54. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  55. package/docs/architecture/versions/1.6.md +38 -0
  56. package/docs/architecture/versions/1.7.md +41 -0
  57. package/docs/forge-chatgpt-browser-engine.md +2 -2
  58. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  59. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  60. package/docs/forge-github-issue-launcher.md +1 -1
  61. package/docs/forge-runtime-self-healing-loop.md +1 -1
  62. package/docs/operations/controller-browser-plugin.md +38 -22
  63. package/docs/operations/controller-performance-and-502.md +124 -0
  64. package/docs/operations/controller-reliability-runbook.md +1 -1
  65. package/docs/operations/features.md +3 -3
  66. package/docs/operations/github-repository.md +1 -1
  67. package/docs/operations/homebrew.md +2 -2
  68. package/docs/operations/mcp-tool-exposure.md +5 -3
  69. package/docs/tutorials/01-install-and-start.md +1 -1
  70. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Installation.md +1 -1
  74. package/docs/wiki/Integrations.md +2 -2
  75. package/docs/wiki/Quick-Start.md +1 -1
  76. package/docs/wiki/Work-Lifecycle.md +1 -1
  77. package/package.json +29 -10
  78. package/scripts/activate-source-baseline.ts +17 -13
  79. package/scripts/benchmark-background-check-overlap.ts +228 -0
  80. package/scripts/benchmark-mcp-host.sh +117 -0
  81. package/scripts/benchmark-mcp-transport.ts +170 -0
  82. package/scripts/benchmark-quality-harness.ts +146 -0
  83. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  84. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  85. package/scripts/benchmark-typescript-navigation.ts +186 -0
  86. package/scripts/benchmark-work-efficiency.ts +334 -0
  87. package/scripts/capture-plan.sh +3 -3
  88. package/scripts/check-agent-tooling.sh +8 -35
  89. package/scripts/check-architecture-sync.sh +17 -86
  90. package/scripts/check-mcp-compatibility.ts +14 -0
  91. package/scripts/check-public-docs.sh +14 -1
  92. package/scripts/check-runtime-architecture.mjs +107 -64
  93. package/scripts/check-stable-baseline.ts +92 -0
  94. package/scripts/check-task-workflow.sh +4 -4
  95. package/scripts/contract-worktree.sh +1 -21
  96. package/scripts/ensure-task-workflow.sh +22 -55
  97. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  98. package/scripts/install-standalone-recovery.ts +24 -0
  99. package/scripts/lib/project-init-lib.sh +15 -44
  100. package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
  101. package/scripts/migrate-project-template.sh +23 -1179
  102. package/scripts/new-plan.sh +1 -1
  103. package/scripts/plan-to-todo.sh +11 -36
  104. package/scripts/run-governed-gate.ts +2 -1
  105. package/scripts/ship-worktrees.sh +2 -29
  106. package/scripts/smoke-ios-physical-device.ts +4 -2
  107. package/scripts/smoke-runtime-recovery.ts +1 -35
  108. package/scripts/smoke-schedule-engine.ts +1 -20
  109. package/scripts/sprint-backlog.sh +1 -1
  110. package/scripts/verify-contract.sh +1 -111
  111. package/scripts/verify-forge-runtime.sh +0 -1
  112. package/scripts/verify-sprint.sh +3 -180
  113. package/scripts/workflow-contract.ts +1 -5
  114. package/src/cli/commands/chatgpt.ts +31 -2
  115. package/src/cli/commands/init-hook.ts +3 -3
  116. package/src/cli/commands/mcp.ts +9 -5
  117. package/src/cli/commands/plugin.ts +37 -10
  118. package/src/cli/commands/recovery.ts +25 -3
  119. package/src/cli/commands/runtime.ts +55 -3
  120. package/src/cli/commands/security.ts +12 -1
  121. package/src/cli/commands/setup-profile.ts +31 -21
  122. package/src/cli/controller/check-runner.ts +106 -3
  123. package/src/cli/controller/composite-result.ts +1 -1
  124. package/src/cli/controller/context/focus.ts +51 -0
  125. package/src/cli/controller/context/instruction-context.ts +114 -0
  126. package/src/cli/controller/context/known-paths.ts +76 -0
  127. package/src/cli/controller/context/query-planning.ts +110 -0
  128. package/src/cli/controller/context/rule-resolution.ts +532 -0
  129. package/src/cli/controller/context/source-materializer.ts +278 -0
  130. package/src/cli/controller/context/structural.ts +16 -0
  131. package/src/cli/controller/context/types.ts +286 -0
  132. package/src/cli/controller/context-pack.ts +898 -430
  133. package/src/cli/controller/launch-agents.ts +77 -2
  134. package/src/cli/controller/operational-plan.ts +2 -2
  135. package/src/cli/controller/work-mode.ts +6 -4
  136. package/src/cli/editing/edit-session.ts +60 -7
  137. package/src/cli/github/github.ts +133 -0
  138. package/src/cli/hook/prompt-guard-decision.ts +17 -18
  139. package/src/cli/hook/prompt-intents.ts +1 -1
  140. package/src/cli/installer/managed-entries.ts +15 -1
  141. package/src/cli/local-bridge/console-automations.ts +225 -67
  142. package/src/cli/local-bridge/console-view-models.ts +3 -35
  143. package/src/cli/local-bridge/facade-api.ts +2 -217
  144. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  145. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
  146. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  147. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
  148. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
  149. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
  150. package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
  151. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  152. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
  153. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
  154. package/src/cli/local-bridge/frontend/src/styles.css +27 -11
  155. package/src/cli/local-bridge/frontend/src/types.ts +8 -7
  156. package/src/cli/local-bridge/job-store.ts +1 -490
  157. package/src/cli/local-bridge/server.ts +29 -532
  158. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  159. package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
  160. package/src/cli/mcp/auth.ts +5 -5
  161. package/src/cli/mcp/instructions.ts +3 -2
  162. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  163. package/src/cli/mcp/multi-repository.ts +16 -5
  164. package/src/cli/mcp/oauth.ts +175 -26
  165. package/src/cli/mcp/paths.ts +8 -2
  166. package/src/cli/mcp/repository-tools.ts +356 -101
  167. package/src/cli/mcp/server.ts +625 -54
  168. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  169. package/src/cli/mcp/toolset-selection.ts +1 -1
  170. package/src/cli/mcp/toolset.ts +42 -5
  171. package/src/cli/mcp/transports/http.ts +125 -15
  172. package/src/cli/mcp/transports/session-registry.ts +67 -5
  173. package/src/cli/mcp/types.ts +3 -3
  174. package/src/cli/repositories/command-classifier.ts +209 -14
  175. package/src/cli/repositories/command-executor.ts +55 -521
  176. package/src/cli/repositories/command-process.ts +108 -0
  177. package/src/cli/repositories/command-scope.ts +99 -41
  178. package/src/cli/repositories/controller-home.ts +7 -8
  179. package/src/cli/repositories/git-command-executor.ts +2 -0
  180. package/src/cli/repositories/locks.ts +53 -16
  181. package/src/cli/repositories/repository-snapshot.ts +121 -0
  182. package/src/cli/repositories/safe-patch.ts +6 -4
  183. package/src/cli/repositories/selected-path-actions.ts +17 -9
  184. package/src/cli/repositories/structured-git.ts +110 -10
  185. package/src/cli/repository/inspector.ts +565 -62
  186. package/src/cli/repository/session-cache.ts +58 -17
  187. package/src/cli/v81-entry.ts +1 -4
  188. package/src/effects/fs-transaction.ts +85 -17
  189. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  190. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  191. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  192. package/src/runtime/context/lsp-client.ts +283 -0
  193. package/src/runtime/context/semantic-navigation.ts +275 -0
  194. package/src/runtime/context/swift-navigation.ts +565 -0
  195. package/src/runtime/context/typescript-navigation.ts +226 -0
  196. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  197. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
  198. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  199. package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
  200. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  201. package/src/runtime/control-plane/execution/validation.ts +15 -2
  202. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  203. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  204. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  205. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  206. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  207. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
  208. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
  209. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
  210. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  211. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  212. package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
  213. package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
  214. package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
  215. package/src/runtime/control-plane/facade/index.ts +1 -0
  216. package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
  217. package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
  218. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  219. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  220. package/src/runtime/control-plane/facade/types.ts +55 -11
  221. package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
  222. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  223. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  224. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  225. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  226. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  227. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  228. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  229. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  230. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  231. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  232. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  233. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  234. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  235. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  236. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  237. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  238. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  239. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  240. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  241. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
  242. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  243. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  244. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  245. package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
  246. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  247. package/src/runtime/control-plane/release-retention.ts +62 -2
  248. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  249. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  250. package/src/runtime/diagnostics/mcp-timing.ts +19 -0
  251. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  252. package/src/runtime/diagnostics/performance.ts +24 -1
  253. package/src/runtime/evidence/event-ledger.ts +1 -1
  254. package/src/runtime/execution/jobs/store.ts +58 -4
  255. package/src/runtime/execution/jobs/types.ts +1 -1
  256. package/src/runtime/execution/managed-workspace.ts +203 -1
  257. package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
  258. package/src/runtime/execution/process-runtime/check-result.ts +63 -2
  259. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  260. package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
  261. package/src/runtime/execution/process-runtime/gc.ts +98 -12
  262. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  263. package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
  264. package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
  265. package/src/runtime/execution/process-runtime/runtime.ts +59 -4
  266. package/src/runtime/execution/process-runtime/store.ts +7 -0
  267. package/src/runtime/execution/process-runtime/types.ts +2 -0
  268. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  269. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  270. package/src/runtime/execution/workers/executor.ts +0 -48
  271. package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
  272. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  273. package/src/runtime/gateway/mcp/router.ts +185 -83
  274. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  275. package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
  276. package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
  277. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  278. package/src/runtime/plugins/browser-adapter.ts +2925 -318
  279. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  280. package/src/runtime/plugins/browser-handoff-host.ts +6 -31
  281. package/src/runtime/plugins/browser-handoff.ts +92 -5
  282. package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
  283. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  284. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  285. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  286. package/src/runtime/plugins/browser-runtime.ts +183 -0
  287. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  288. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  289. package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
  290. package/src/runtime/plugins/external-adapter.ts +324 -3
  291. package/src/runtime/plugins/external-registration.ts +84 -5
  292. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  293. package/src/runtime/plugins/first-party-registry.ts +4 -2
  294. package/src/runtime/plugins/github-adapter.ts +125 -0
  295. package/src/runtime/plugins/gmail-adapter.ts +28 -2
  296. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  297. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  298. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  299. package/src/runtime/plugins/ios-adapter.ts +106 -16
  300. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  301. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  302. package/src/runtime/plugins/lightweight-action.ts +153 -0
  303. package/src/runtime/plugins/local-system-adapter.ts +125 -8
  304. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  305. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  306. package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
  307. package/src/runtime/plugins/resend-adapter.ts +1 -1
  308. package/src/runtime/plugins/store.ts +366 -24
  309. package/src/runtime/plugins/types.ts +39 -2
  310. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  311. package/src/runtime/projections/git-status-sampler.ts +16 -16
  312. package/src/runtime/projections/materialized-view.ts +28 -10
  313. package/src/runtime/recovery/actions.ts +0 -10
  314. package/src/runtime/recovery/classifier.ts +0 -9
  315. package/src/runtime/recovery/maintenance-executor.ts +415 -18
  316. package/src/runtime/recovery/probe.ts +0 -1
  317. package/src/runtime/recovery/types.ts +0 -1
  318. package/src/runtime/root/mcp-transport.ts +180 -10
  319. package/src/runtime/root/package-connector-service.ts +186 -8
  320. package/src/runtime/root/package-runtime-release.ts +143 -17
  321. package/src/runtime/root/package-runtime-service.ts +430 -22
  322. package/src/runtime/root/release-manifest.ts +7 -2
  323. package/src/runtime/root/release-materialize.ts +162 -19
  324. package/src/runtime/root/release-store.ts +48 -11
  325. package/src/runtime/root/runtime.ts +23 -5
  326. package/src/runtime/root/service.ts +100 -27
  327. package/src/runtime/root/types.ts +2 -0
  328. package/src/runtime/safe-tooling/index.ts +0 -1
  329. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  330. package/src/runtime/safe-tooling/types.ts +0 -30
  331. package/src/runtime/shared/trusted-executable.ts +62 -0
  332. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  333. package/src/runtime/standalone-recovery/core.ts +702 -94
  334. package/src/runtime/standalone-recovery/entry.ts +99 -23
  335. package/src/runtime/standalone-recovery/installer.ts +19 -3
  336. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  337. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  338. package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
  339. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
  340. package/src/runtime/workflow/schedules/engine.ts +509 -80
  341. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  342. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  343. package/src/runtime/workflow/schedules/store.ts +131 -10
  344. package/src/runtime/workflow/schedules/types.ts +6 -3
  345. package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
  346. package/src/testing/test-governance.ts +8 -40
  347. package/assets/reference-configs/heartbeat-triage.md +0 -57
  348. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  349. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  350. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  351. package/bin/forge-desktop-helper.mjs +0 -204
  352. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  353. package/docs/architecture/current/architecture-invariants.md +0 -331
  354. package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
  355. package/docs/architecture/current/codegraph-context-provider.md +0 -226
  356. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  357. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  358. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
  359. package/docs/architecture/current/entity-model.md +0 -592
  360. package/docs/architecture/current/external-plugin-governance.md +0 -396
  361. package/docs/architecture/current/failure-recovery.md +0 -447
  362. package/docs/architecture/current/governance.md +0 -213
  363. package/docs/architecture/current/human-interaction-plane.md +0 -87
  364. package/docs/architecture/current/implementation-status.md +0 -128
  365. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  366. package/docs/architecture/current/legacy-governance.md +0 -58
  367. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  368. package/docs/architecture/current/migration-roadmap.md +0 -105
  369. package/docs/architecture/current/multi-repository-execution.md +0 -371
  370. package/docs/architecture/current/operations-runbook.md +0 -45
  371. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  372. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  373. package/docs/architecture/current/repository-access-modes.md +0 -102
  374. package/docs/architecture/current/route-policy.md +0 -15
  375. package/docs/architecture/current/route-session-concurrency.md +0 -85
  376. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  377. package/docs/architecture/current/runtime-directory-map.md +0 -49
  378. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  379. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  380. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  381. package/docs/architecture/current/system-overview.md +0 -144
  382. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  383. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  384. package/docs/architecture/current/thin-harness-v1.md +0 -311
  385. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  386. package/docs/forge-autonomous-goal-loop.md +0 -9
  387. package/docs/forge-provider-configuration.md +0 -7
  388. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
  389. package/docs/operations/controller-desktop-plugin.md +0 -112
  390. package/docs/operations/gmail-assistant-routines.md +0 -186
  391. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  392. package/docs/operations/provider-configuration.md +0 -31
  393. package/scripts/harness-trace-grade.sh +0 -150
  394. package/scripts/heartbeat-triage.sh +0 -361
  395. package/scripts/maintenance-triage.sh +0 -167
  396. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  397. package/src/cli/repositories/goal-registry.ts +0 -239
  398. package/src/runtime/assistant/action-proposals.ts +0 -279
  399. package/src/runtime/assistant/intent.ts +0 -395
  400. package/src/runtime/assistant/model-provider.ts +0 -460
  401. package/src/runtime/assistant/openapi.ts +0 -223
  402. package/src/runtime/assistant/policy.ts +0 -89
  403. package/src/runtime/assistant/readiness.ts +0 -258
  404. package/src/runtime/assistant/routine-runtime.ts +0 -477
  405. package/src/runtime/assistant/schedule-binding.ts +0 -171
  406. package/src/runtime/assistant/standing-grants.ts +0 -375
  407. package/src/runtime/assistant/store.ts +0 -243
  408. package/src/runtime/assistant/types.ts +0 -135
  409. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  410. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  411. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  412. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  413. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  414. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  415. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  416. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  417. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  418. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  419. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  420. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  421. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  422. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  423. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  424. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  425. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  426. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  427. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  428. package/src/runtime/plugins/desktop-adapter.ts +0 -374
  429. package/src/runtime/safe-tooling/browser-safe.ts +0 -133
  430. package/src/runtime/workflow/findings/store.ts +0 -204
  431. package/src/runtime/workflow/findings/types.ts +0 -58
  432. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  433. package/src/runtime/workflow/portfolio/store.ts +0 -185
  434. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -2,13 +2,19 @@ import { createHash } from 'crypto';
2
2
  import { join } from 'path';
3
3
  import type { RepositoryRecord } from '../../cli/repositories/types';
4
4
  import { CONTROLLER_SCOPE_REPO_ID, controllerSystemRoot, ensureControllerHome, repositoryControllerRoot } from '../../cli/repositories/controller-home';
5
- import { existsSync, mkdirSync, readdirSync} from 'fs';
5
+ import { existsSync, mkdirSync, readdirSync, rmSync } from 'fs';
6
6
  import type { ExecutionJob, ResourceClaimSpec } from '../execution/jobs/types';
7
7
  import { appendRuntimeEvent } from '../evidence/event-ledger';
8
8
  import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../shared/json-files';
9
9
  import { createFirstPartyPluginAdapterMap } from './first-party-registry';
10
10
  import { getExternalPluginAdapter, listExternalPluginAdapters } from './external-adapter';
11
11
  import { AssistantPluginError, toAssistantPluginError } from './errors';
12
+ import {
13
+ findActivePluginCapabilityAuthorization,
14
+ pluginCapabilityAuthorizationOwnerScope,
15
+ recordPluginCapabilityAuthorization,
16
+ type PluginCapabilityAuthorizationGrant,
17
+ } from './capability-authorization-grants';
12
18
  import { markControllerContextProjectionDirty } from '../projections/controller-context';
13
19
  import { classifyRepositoryCommand } from '../../cli/repositories/command-classifier';
14
20
  import {
@@ -18,12 +24,13 @@ import {
18
24
  recordWorkCompletionReceipt,
19
25
  updateWorkContract,
20
26
  } from '../control-plane/facade/work-contract-store';
21
- import type { LocalEffectCompletionReceipt, WorkRisk } from '../control-plane/facade/types';
27
+ import { isTerminalWorkContractStatus, type LocalEffectCompletionReceipt, type RemoteEffectCompletionReceipt, type WorkContract, type WorkRisk } from '../control-plane/facade/types';
22
28
  import type {
23
29
  AssistantPluginAdapter,
24
30
  AssistantPluginActionDescriptor,
25
31
  AssistantPluginActionExecutionInput,
26
32
  AssistantPluginActionRequest,
33
+ AssistantPluginAuthorizationContext,
27
34
  AssistantPluginManifest,
28
35
  AssistantPluginRegistryIndex,
29
36
  AssistantPluginRegistryIndexEntry,
@@ -44,7 +51,8 @@ export function assistantPluginScope(pluginId: string, controllerHome?: string):
44
51
  return PLUGIN_ADAPTERS.get(pluginId)?.scope ?? (controllerHome ? getExternalPluginAdapter(controllerHome, pluginId)?.scope : undefined);
45
52
  }
46
53
 
47
- const PLUGIN_MANIFEST_CACHE_TTL_MS = 5_000;
54
+ const PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS = 5_000;
55
+ const PLUGIN_MANIFEST_STORED_CACHE_TTL_MS = Number.POSITIVE_INFINITY;
48
56
 
49
57
 
50
58
  interface PluginManifestCacheEntry<T> {
@@ -83,6 +91,7 @@ export function controllerPluginRepository(controllerHome: string): RepositoryRe
83
91
 
84
92
  function adapterMatchesRepository(adapter: AssistantPluginAdapter, repository: RepositoryRecord): boolean {
85
93
  const scope = adapter.scope ?? 'repository';
94
+ if (scope === 'controller_with_repository_overlay') return true;
86
95
  return repository.repoId === CONTROLLER_SCOPE_REPO_ID ? scope === 'controller' : scope === 'repository';
87
96
  }
88
97
 
@@ -101,10 +110,11 @@ function itemCacheKey(controllerHome: string, repoId: string, pluginId: string,
101
110
  function readPluginManifestCache<T>(
102
111
  cache: Map<string, PluginManifestCacheEntry<T>>,
103
112
  key: string,
113
+ maxAgeMs = PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS,
104
114
  ): T | undefined {
105
115
  const cached = cache.get(key);
106
116
  if (!cached) return undefined;
107
- if (Date.now() - cached.createdAt > PLUGIN_MANIFEST_CACHE_TTL_MS) {
117
+ if (Number.isFinite(maxAgeMs) && Date.now() - cached.createdAt > maxAgeMs) {
108
118
  cache.delete(key);
109
119
  return undefined;
110
120
  }
@@ -231,8 +241,11 @@ function cachedManifestForRepository(
231
241
  repoId: string,
232
242
  pluginId: string,
233
243
  ): AssistantPluginManifest | undefined {
234
- return readPluginManifestCache(pluginManifestItemCache, itemCacheKey(controllerHome, repoId, pluginId, true))
235
- ?? readPluginManifestCache(pluginManifestItemCache, itemCacheKey(controllerHome, repoId, pluginId, false));
244
+ return readPluginManifestCache(
245
+ pluginManifestItemCache,
246
+ itemCacheKey(controllerHome, repoId, pluginId, true),
247
+ PLUGIN_MANIFEST_STORED_CACHE_TTL_MS,
248
+ ) ?? readPluginManifestCache(pluginManifestItemCache, itemCacheKey(controllerHome, repoId, pluginId, false));
236
249
  }
237
250
 
238
251
  function computeManifest(
@@ -244,7 +257,12 @@ function computeManifest(
244
257
  const adapter = resolvedAdapter ?? resolvePluginAdapter(controllerHome, pluginId);
245
258
  if (!adapter || !adapterMatchesRepository(adapter, repository)) throw new Error(`PLUGIN_NOT_FOUND: ${pluginId}`);
246
259
  const previous = readStoredManifest(controllerHome, repository.repoId, pluginId);
247
- const built = adapter.buildManifest(previous?.revision ?? 0, previous?.updatedAt, repository.canonicalRoot);
260
+ const built = adapter.buildManifest(previous?.revision ?? 0, previous?.updatedAt, repository.canonicalRoot, {
261
+ controllerHome,
262
+ repoId: repository.repoId,
263
+ repoRoot: repository.canonicalRoot,
264
+ controllerScoped: repository.repoId === CONTROLLER_SCOPE_REPO_ID,
265
+ });
248
266
  const changed = !previous || fingerprintManifest(previous) !== fingerprintManifest(built);
249
267
  return {
250
268
  ...built,
@@ -356,27 +374,63 @@ function enforceConfirmation(action: AssistantPluginActionDescriptor, request: A
356
374
  }
357
375
  }
358
376
 
377
+ export interface PluginActionAuthorizationEvidence {
378
+ source: 'none' | 'host_permission_model' | 'capability_grant' | 'strong_confirmation';
379
+ reusable: boolean;
380
+ capabilityId?: string;
381
+ target?: { kind: string; id: string };
382
+ grantId?: string;
383
+ established?: boolean;
384
+ lookupError?: string;
385
+ persistenceError?: string;
386
+ }
387
+
359
388
  function actionForManifest(manifest: AssistantPluginManifest, actionId: string): AssistantPluginActionDescriptor {
360
389
  const action = manifest.actions.find((entry) => entry.actionId === actionId);
361
390
  if (!action) throw new Error(`PLUGIN_ACTION_NOT_FOUND: ${manifest.pluginId}/${actionId}`);
362
391
  return action;
363
392
  }
364
393
 
365
- const STANDING_GRANT_SAFE_ACTIONS = new Set([
366
- 'gmail/create_draft',
367
- 'gmail/archive_message',
368
- 'gmail/mark_message_read',
369
- 'gmail/mark_message_unread',
370
- 'google_tasks/create_task',
371
- ]);
394
+ function reusableCapabilityId(manifest: AssistantPluginManifest, action: AssistantPluginActionDescriptor): string {
395
+ const matches = manifest.capabilities
396
+ .filter((capability) => capability.actions.includes(action.actionId))
397
+ .map((capability) => capability.capabilityId)
398
+ .filter(Boolean)
399
+ .sort();
400
+ // Overlapping capability groups are ambiguous privilege boundaries. Fall back
401
+ // to action-local reuse instead of silently choosing a broader capability.
402
+ return matches.length === 1 ? matches[0]! : `action:${action.actionId}`;
403
+ }
372
404
 
373
- function denyAutomatedWrite(manifest: AssistantPluginManifest, action: AssistantPluginActionDescriptor, origin: AssistantPluginActionExecutionInput['origin']): void {
374
- if (origin.surface === 'standing-grant') {
375
- const key = `${manifest.pluginId}/${action.actionId}`;
376
- if (STANDING_GRANT_SAFE_ACTIONS.has(key) && action.confirmation !== 'strong_confirmation') return;
377
- throw new Error(`STANDING_GRANT_ACTION_NOT_ALLOWED: ${key}`);
405
+ const INTERACTIVE_PLUGIN_AUTHORIZATION_SURFACES = new Set(['mcp', 'local-ui', 'chatgpt-action']);
406
+
407
+ function originMayEstablishCapabilityAuthorization(origin: AssistantPluginActionExecutionInput['origin']): boolean {
408
+ return INTERACTIVE_PLUGIN_AUTHORIZATION_SURFACES.has(origin.surface);
409
+ }
410
+
411
+ function authorizationTargetSummary(context: AssistantPluginAuthorizationContext): { kind: string; id: string } {
412
+ return { kind: context.target.kind, id: context.target.id };
413
+ }
414
+
415
+ function authorizationError(error: unknown): string {
416
+ const message = error instanceof Error ? error.message : String(error);
417
+ return message.slice(0, 500);
418
+ }
419
+
420
+ function initialAuthorizationEvidence(
421
+ manifest: AssistantPluginManifest,
422
+ action: AssistantPluginActionDescriptor,
423
+ ): PluginActionAuthorizationEvidence {
424
+ if (action.confirmation === 'none') return { source: 'none', reusable: false };
425
+ const capabilityId = reusableCapabilityId(manifest, action);
426
+ if (action.confirmation === 'strong_confirmation') {
427
+ return { source: 'strong_confirmation', reusable: false, capabilityId };
378
428
  }
379
- if (!['schedule', 'reconciliation', 'system', 'assistant-routine'].includes(origin.surface)) return;
429
+ return { source: 'host_permission_model', reusable: false, capabilityId };
430
+ }
431
+
432
+ function denyAutomatedWrite(manifest: AssistantPluginManifest, action: AssistantPluginActionDescriptor, origin: AssistantPluginActionExecutionInput['origin']): void {
433
+ if (!['schedule', 'reconciliation', 'system'].includes(origin.surface)) return;
380
434
  if (action.readOnly) return;
381
435
  throw new Error(`EXTERNAL_EFFECT_AUTHORIZATION_REQUIRED: ${manifest.pluginId}/${action.actionId} cannot run from ${origin.surface}`);
382
436
  }
@@ -406,7 +460,14 @@ export function listAssistantPluginManifests(
406
460
  const preferStored = options.preferStored === true && options.forceRefresh !== true;
407
461
  const cacheKey = listCacheKey(controllerHome, repository.repoId, preferStored);
408
462
  if (options.forceRefresh !== true) {
409
- const cached = readPluginManifestCache(pluginManifestListCache, cacheKey);
463
+ // Stored manifests are persisted snapshots. Their freshness is governed by
464
+ // explicit registry/config mutation invalidation, not a wall-clock trust
465
+ // window. Live execution manifests retain the short identity-validation TTL.
466
+ const cached = readPluginManifestCache(
467
+ pluginManifestListCache,
468
+ cacheKey,
469
+ preferStored ? PLUGIN_MANIFEST_STORED_CACHE_TTL_MS : PLUGIN_MANIFEST_LIVE_CACHE_TTL_MS,
470
+ );
410
471
  if (cached) return cached;
411
472
  }
412
473
  const manifests = listAssistantPluginIds(controllerHome, repository)
@@ -544,6 +605,7 @@ export interface PluginActionReceipt {
544
605
  createdAt: string;
545
606
  workId?: string;
546
607
  origin?: { surface: string; actor?: string; correlationId?: string };
608
+ authorization?: PluginActionAuthorizationEvidence;
547
609
  result?: Record<string, unknown>;
548
610
  error?: { code: string; message: string };
549
611
  }
@@ -671,6 +733,137 @@ function localEffectTarget(
671
733
  };
672
734
  }
673
735
 
736
+ function remoteEffectWorkForPluginAction(
737
+ controllerHome: string,
738
+ repository: RepositoryRecord,
739
+ action: AssistantPluginActionDescriptor,
740
+ request: AssistantPluginActionRequest,
741
+ receiptId?: string,
742
+ ): WorkContract | undefined {
743
+ const workId = request.workId?.trim();
744
+ if (!workId) return undefined;
745
+ if (action.risk !== 'remote_write') {
746
+ throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_REQUIRES_REMOTE_WRITE: ${action.actionId}`);
747
+ }
748
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
749
+ if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
750
+ if (work.workKind !== 'remote_effect') {
751
+ throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
752
+ }
753
+ if (work.checks.length > 0) {
754
+ throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CHECKS_PRESENT: ${workId} declares repository checks and cannot be completed by a plugin receipt alone`);
755
+ }
756
+ if (isTerminalWorkContractStatus(work.status)) {
757
+ const receiptAlreadyBound = Boolean(receiptId) && (
758
+ work.completionReceipt?.receiptId === receiptId
759
+ || work.evidenceRefs.some((evidence) => evidence.evidenceId === receiptId)
760
+ );
761
+ if (work.status === 'completed' && receiptAlreadyBound) return work;
762
+ throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_TERMINAL: ${workId} is ${work.status}`);
763
+ }
764
+ return work;
765
+ }
766
+
767
+ function attributedWorkForPluginAction(
768
+ controllerHome: string,
769
+ repository: RepositoryRecord,
770
+ action: AssistantPluginActionDescriptor,
771
+ request: AssistantPluginActionRequest,
772
+ ): WorkContract | undefined {
773
+ const workId = request.workId?.trim();
774
+ if (!workId || action.risk === 'remote_write') return undefined;
775
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
776
+ if (!work) throw new Error(`WORK_PLUGIN_ATTRIBUTION_NOT_FOUND: ${workId}`);
777
+ if (isTerminalWorkContractStatus(work.status)) {
778
+ throw new Error(`WORK_PLUGIN_ATTRIBUTION_TERMINAL: ${workId} is ${work.status}`);
779
+ }
780
+ return work;
781
+ }
782
+
783
+ export function finalizeRemoteEffectWorkFromActionReceipt(
784
+ controllerHome: string,
785
+ repoId: string,
786
+ workId: string,
787
+ ): WorkContract {
788
+ const work = getWorkContract({ controllerHome, repoId }, workId);
789
+ if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
790
+ if (work.workKind !== 'remote_effect') {
791
+ throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
792
+ }
793
+ if (work.status === 'completed' && work.completionReceipt?.source === 'remote_effect') return work;
794
+ if (isTerminalWorkContractStatus(work.status)) {
795
+ throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_TERMINAL: ${workId} is ${work.status}`);
796
+ }
797
+ const receipt = work.evidenceRefs
798
+ .map((evidence) => evidence.evidenceId ? readPluginActionReceipt(controllerHome, repoId, evidence.evidenceId) : undefined)
799
+ .find((candidate) => candidate?.workId === workId && candidate.status === 'succeeded');
800
+ if (!receipt) {
801
+ throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_RECEIPT_REQUIRED: ${workId} has no successful durable plugin action receipt`);
802
+ }
803
+ const remoteReceipt: RemoteEffectCompletionReceipt = {
804
+ schemaVersion: 1,
805
+ receiptId: receipt.receiptId,
806
+ source: 'remote_effect',
807
+ workId,
808
+ pluginId: receipt.pluginId,
809
+ actionId: receipt.actionId,
810
+ requestId: receipt.requestId,
811
+ semanticKey: receipt.semanticKey,
812
+ resultDigest: createHash('sha256').update(JSON.stringify(receipt.result ?? null)).digest('hex'),
813
+ recordedAt: new Date().toISOString(),
814
+ };
815
+ return recordWorkCompletionReceipt(
816
+ { controllerHome, repoId },
817
+ workId,
818
+ remoteReceipt,
819
+ 'completed_remote',
820
+ 'remote_effect',
821
+ );
822
+ }
823
+
824
+ function bindRemoteEffectReceiptToWork(
825
+ controllerHome: string,
826
+ repository: RepositoryRecord,
827
+ action: AssistantPluginActionDescriptor,
828
+ request: AssistantPluginActionRequest,
829
+ receipt: PluginActionReceipt,
830
+ ): WorkContract | undefined {
831
+ if (!request.workId) return undefined;
832
+ if (receipt.status !== 'succeeded') return remoteEffectWorkForPluginAction(controllerHome, repository, action, request);
833
+ const work = remoteEffectWorkForPluginAction(controllerHome, repository, action, request, receipt.receiptId);
834
+ if (!work) return undefined;
835
+ if (work.status === 'completed') return work;
836
+ let updated = work;
837
+ if (!work.evidenceRefs.some((evidence) => evidence.evidenceId === receipt.receiptId)) {
838
+ updated = appendWorkEvidence({ controllerHome, repoId: repository.repoId }, work.workId, {
839
+ evidenceId: receipt.receiptId,
840
+ title: 'typed remote plugin effect completed',
841
+ summary: `${receipt.pluginId}/${receipt.actionId} completed with durable receipt ${receipt.receiptId}.`,
842
+ detailLevel: 'summary',
843
+ });
844
+ }
845
+ if (action.remoteEffectWorkCompletion !== 'terminal') return updated;
846
+ const remoteReceipt: RemoteEffectCompletionReceipt = {
847
+ schemaVersion: 1,
848
+ receiptId: receipt.receiptId,
849
+ source: 'remote_effect',
850
+ workId: work.workId,
851
+ pluginId: receipt.pluginId,
852
+ actionId: receipt.actionId,
853
+ requestId: receipt.requestId,
854
+ semanticKey: receipt.semanticKey,
855
+ resultDigest: createHash('sha256').update(JSON.stringify(receipt.result ?? null)).digest('hex'),
856
+ recordedAt: new Date().toISOString(),
857
+ };
858
+ return recordWorkCompletionReceipt(
859
+ { controllerHome, repoId: repository.repoId },
860
+ work.workId,
861
+ remoteReceipt,
862
+ 'completed_remote',
863
+ 'remote_effect',
864
+ );
865
+ }
866
+
674
867
  function compatibilityJobFromReceipt(receipt: PluginActionReceipt, checkoutId: string): ExecutionJob {
675
868
  return {
676
869
  schemaVersion: 1,
@@ -717,8 +910,11 @@ export async function submitAssistantPluginAction(
717
910
  deduplicated: boolean;
718
911
  result?: Record<string, unknown>;
719
912
  receipt: PluginActionReceipt;
913
+ authorization?: PluginActionAuthorizationEvidence;
720
914
  workId?: string;
721
915
  }> {
916
+ const adapter = resolvePluginAdapter(controllerHome, request.pluginId);
917
+ if (!adapter) throw new Error(`PLUGIN_NOT_FOUND: ${request.pluginId}`);
722
918
  const manifest = getAssistantPluginManifest(controllerHome, repository, request.pluginId);
723
919
  const action = actionForManifest(manifest, request.actionId);
724
920
  if (!manifest.enabled && action.actionId !== 'configure') {
@@ -741,6 +937,10 @@ export async function submitAssistantPluginAction(
741
937
  if (!existing) {
742
938
  throw new Error(`PLUGIN_RECEIPT_LOST: ${request.requestId}`);
743
939
  }
940
+ if (request.workId && existing.workId !== request.workId) {
941
+ throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CONFLICT: ${request.requestId} already belongs to Work ${existing.workId ?? 'none'}`);
942
+ }
943
+ if (request.workId && action.risk === 'remote_write') bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, existing);
744
944
  return {
745
945
  manifest,
746
946
  action,
@@ -748,18 +948,29 @@ export async function submitAssistantPluginAction(
748
948
  deduplicated: true,
749
949
  result: existing.result,
750
950
  receipt: existing,
951
+ authorization: existing.authorization,
751
952
  workId: existing.workId,
752
953
  };
753
954
  }
754
955
 
755
956
  const createdAt = new Date().toISOString();
756
957
  const receiptId = `PLG-${Date.now()}-${createHash('sha256').update(request.requestId).digest('hex').slice(0, 8)}`;
958
+ const capabilityId = reusableCapabilityId(manifest, action);
959
+ let authorization = initialAuthorizationEvidence(manifest, action);
960
+ let authorizationContext: AssistantPluginAuthorizationContext | undefined;
961
+ let activeAuthorizationGrant: PluginCapabilityAuthorizationGrant | undefined;
757
962
  const requiresLocalEffectWork = localSystemActionRequiresWork(
758
963
  repository,
759
964
  request.pluginId,
760
965
  request.actionId,
761
966
  normalizedArgs,
762
967
  );
968
+ const boundRemoteWork = action.risk === 'remote_write'
969
+ ? remoteEffectWorkForPluginAction(controllerHome, repository, action, request)
970
+ : undefined;
971
+ const attributedWork = !requiresLocalEffectWork
972
+ ? attributedWorkForPluginAction(controllerHome, repository, action, request)
973
+ : undefined;
763
974
  const acceptedWork = requiresLocalEffectWork
764
975
  ? acceptSubmittedWorkContract(controllerHome, {
765
976
  requestId: request.requestId,
@@ -812,10 +1023,63 @@ export async function submitAssistantPluginAction(
812
1023
  receiptId,
813
1024
  risk: action.risk,
814
1025
  confirmation: action.confirmation,
1026
+ capabilityId,
1027
+ authorizationReuseSupported: action.confirmation === 'authorization' && Boolean(adapter.resolveAuthorizationContext),
815
1028
  },
816
1029
  });
817
1030
 
818
1031
  try {
1032
+ if (action.confirmation === 'authorization'
1033
+ && adapter.resolveAuthorizationContext
1034
+ && originMayEstablishCapabilityAuthorization(request.origin)) {
1035
+ authorizationContext = await adapter.resolveAuthorizationContext({
1036
+ controllerHome,
1037
+ repoId: repository.repoId,
1038
+ repoRoot: repository.canonicalRoot,
1039
+ pluginId: request.pluginId,
1040
+ actionId: request.actionId,
1041
+ requestId: request.requestId,
1042
+ args: normalizedArgs,
1043
+ origin: request.origin,
1044
+ jobId: receiptId,
1045
+ timeoutMs: request.timeoutMs,
1046
+ signal: request.signal,
1047
+ });
1048
+ if (authorizationContext) {
1049
+ authorization = {
1050
+ source: 'host_permission_model',
1051
+ reusable: false,
1052
+ capabilityId,
1053
+ target: authorizationTargetSummary(authorizationContext),
1054
+ };
1055
+ try {
1056
+ activeAuthorizationGrant = findActivePluginCapabilityAuthorization(controllerHome, {
1057
+ ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
1058
+ repoId: repository.repoId,
1059
+ pluginId: request.pluginId,
1060
+ capabilityId,
1061
+ target: authorizationContext.target,
1062
+ scopes: action.scopes,
1063
+ risk: action.risk,
1064
+ });
1065
+ if (activeAuthorizationGrant) {
1066
+ authorization = {
1067
+ source: 'capability_grant',
1068
+ reusable: true,
1069
+ capabilityId,
1070
+ target: authorizationTargetSummary(authorizationContext),
1071
+ grantId: activeAuthorizationGrant.grantId,
1072
+ };
1073
+ }
1074
+ } catch (error) {
1075
+ authorization = {
1076
+ ...authorization,
1077
+ lookupError: authorizationError(error),
1078
+ };
1079
+ }
1080
+ }
1081
+ }
1082
+
819
1083
  const result = await executeAssistantPluginAction({
820
1084
  controllerHome,
821
1085
  repoId: repository.repoId,
@@ -832,7 +1096,52 @@ export async function submitAssistantPluginAction(
832
1096
  ? Date.now() + Math.max(1, Math.trunc(request.timeoutMs))
833
1097
  : undefined,
834
1098
  });
1099
+ if (action.confirmation === 'authorization'
1100
+ && authorizationContext
1101
+ && !activeAuthorizationGrant
1102
+ && originMayEstablishCapabilityAuthorization(request.origin)) {
1103
+ try {
1104
+ const grant = recordPluginCapabilityAuthorization(controllerHome, {
1105
+ ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
1106
+ repoId: repository.repoId,
1107
+ pluginId: request.pluginId,
1108
+ capabilityId,
1109
+ target: authorizationContext.target,
1110
+ scopes: action.scopes,
1111
+ riskCeiling: action.risk,
1112
+ expiresInMinutes: authorizationContext.expiresInMinutes,
1113
+ });
1114
+ authorization = {
1115
+ source: 'host_permission_model',
1116
+ reusable: true,
1117
+ capabilityId,
1118
+ target: authorizationTargetSummary(authorizationContext),
1119
+ grantId: grant.grantId,
1120
+ established: true,
1121
+ ...(authorization.lookupError ? { lookupError: authorization.lookupError } : {}),
1122
+ };
1123
+ } catch (error) {
1124
+ authorization = {
1125
+ ...authorization,
1126
+ reusable: false,
1127
+ persistenceError: authorizationError(error),
1128
+ };
1129
+ }
1130
+ }
1131
+
835
1132
  let resultWithLineage = result;
1133
+ if (boundRemoteWork) {
1134
+ const terminalRemoteEffect = action.remoteEffectWorkCompletion === 'terminal';
1135
+ resultWithLineage = {
1136
+ ...result,
1137
+ work: {
1138
+ workId: boundRemoteWork.workId,
1139
+ workKind: 'remote_effect',
1140
+ ...(terminalRemoteEffect ? { completionOutcome: 'completed_remote' as const } : {}),
1141
+ status: terminalRemoteEffect ? 'completed' : 'running',
1142
+ },
1143
+ };
1144
+ }
836
1145
  if (acceptedWork) {
837
1146
  const recordedAt = new Date().toISOString();
838
1147
  const target = localEffectTarget(normalizedArgs, result);
@@ -877,8 +1186,15 @@ export async function submitAssistantPluginAction(
877
1186
  semanticKey: key,
878
1187
  status: 'succeeded',
879
1188
  createdAt,
880
- ...(acceptedWork ? { workId: acceptedWork.workId } : {}),
1189
+ ...(acceptedWork
1190
+ ? { workId: acceptedWork.workId }
1191
+ : boundRemoteWork
1192
+ ? { workId: boundRemoteWork.workId }
1193
+ : attributedWork
1194
+ ? { workId: attributedWork.workId }
1195
+ : {}),
881
1196
  origin: request.origin,
1197
+ authorization,
882
1198
  result: resultWithLineage,
883
1199
  };
884
1200
  writeJsonAtomic(pluginActionReceiptPath(controllerHome, repository.repoId, receiptId), receipt);
@@ -889,6 +1205,7 @@ export async function submitAssistantPluginAction(
889
1205
  semanticKey: key,
890
1206
  createdAt,
891
1207
  } satisfies PluginActionRequestIndex);
1208
+ if (boundRemoteWork) bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, receipt);
892
1209
  const nextManifest = getAssistantPluginManifest(controllerHome, repository, request.pluginId);
893
1210
  return {
894
1211
  manifest: nextManifest,
@@ -897,11 +1214,19 @@ export async function submitAssistantPluginAction(
897
1214
  deduplicated: false,
898
1215
  result: resultWithLineage,
899
1216
  receipt,
900
- workId: acceptedWork?.workId,
1217
+ authorization,
1218
+ workId: acceptedWork?.workId ?? boundRemoteWork?.workId ?? attributedWork?.workId,
901
1219
  };
902
1220
  } catch (error) {
903
1221
  const message = error instanceof Error ? error.message : String(error);
904
1222
  const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'PLUGIN_ACTION_FAILED';
1223
+ if (boundRemoteWork) {
1224
+ appendWorkEvidence({ controllerHome, repoId: repository.repoId }, boundRemoteWork.workId, {
1225
+ title: 'typed remote plugin effect failed',
1226
+ summary: `${request.pluginId}/${request.actionId}: ${message}`.slice(0, 1_000),
1227
+ detailLevel: 'summary',
1228
+ });
1229
+ }
905
1230
  if (acceptedWork) {
906
1231
  const current = getWorkContract({ controllerHome, repoId: repository.repoId }, acceptedWork.workId);
907
1232
  updateWorkContract({ controllerHome, repoId: repository.repoId }, acceptedWork.workId, {
@@ -926,8 +1251,15 @@ export async function submitAssistantPluginAction(
926
1251
  semanticKey: key,
927
1252
  status: 'failed',
928
1253
  createdAt,
929
- ...(acceptedWork ? { workId: acceptedWork.workId } : {}),
1254
+ ...(acceptedWork
1255
+ ? { workId: acceptedWork.workId }
1256
+ : boundRemoteWork
1257
+ ? { workId: boundRemoteWork.workId }
1258
+ : attributedWork
1259
+ ? { workId: attributedWork.workId }
1260
+ : {}),
930
1261
  origin: request.origin,
1262
+ authorization,
931
1263
  error: { code, message },
932
1264
  };
933
1265
  writeJsonAtomic(pluginActionReceiptPath(controllerHome, repository.repoId, receiptId), receipt);
@@ -963,6 +1295,16 @@ export async function executeAssistantPluginAction(
963
1295
  args: normalizedArgs,
964
1296
  providerIdentityPrevalidated: manifestLookup.providerIdentityPrevalidated,
965
1297
  });
1298
+ for (const affectedPluginId of adapter.affectedPluginIdsAfterAction?.(input.actionId, result) ?? []) {
1299
+ if (!affectedPluginId || affectedPluginId === input.pluginId) continue;
1300
+ invalidateAssistantPluginManifestCache(input.controllerHome, repository.repoId, affectedPluginId);
1301
+ const affectedAdapter = resolvePluginAdapter(input.controllerHome, affectedPluginId);
1302
+ if (affectedAdapter && adapterMatchesRepository(affectedAdapter, repository)) {
1303
+ syncAssistantPluginManifest(input.controllerHome, repository, affectedPluginId);
1304
+ } else {
1305
+ rmSync(manifestPath(input.controllerHome, repository.repoId, affectedPluginId), { force: true });
1306
+ }
1307
+ }
966
1308
  const refreshManifest = adapter.shouldRefreshManifestAfterAction?.(input.actionId) ?? true;
967
1309
  const nextManifest = refreshManifest
968
1310
  ? syncAssistantPluginManifest(input.controllerHome, repository, input.pluginId).manifest
@@ -4,8 +4,24 @@ export type AssistantPluginLifecycleState = 'enabled' | 'disabled' | 'degraded'
4
4
  export type AssistantPluginHealthState = 'ready' | 'disabled' | 'degraded' | 'error';
5
5
  export type AssistantPluginActionRisk = 'readonly' | 'workspace_write' | 'remote_write' | 'destructive';
6
6
  export type AssistantPluginActionConfirmation = 'none' | 'authorization' | 'strong_confirmation';
7
+ export type AssistantPluginForegroundEffect = 'none' | 'possible' | 'required';
7
8
  export type AssistantPluginResource = 'repo-state' | 'workspace' | 'remote' | 'git-refs' | 'provider-state';
8
9
 
10
+ export interface AssistantPluginAuthorizationTarget {
11
+ /** Stable provider resource class, for example ios-physical-device. */
12
+ kind: string;
13
+ /** Stable exact provider identity. Ephemeral request/session ids must never be used here. */
14
+ id: string;
15
+ /** Optional provider identity fence. A changed fingerprint invalidates reuse. */
16
+ identityFingerprint?: string;
17
+ }
18
+
19
+ export interface AssistantPluginAuthorizationContext {
20
+ target: AssistantPluginAuthorizationTarget;
21
+ /** Optional grant lifetime. The grant authority applies its own upper bound. */
22
+ expiresInMinutes?: number;
23
+ }
24
+
9
25
  export interface AssistantPluginPermissionScope {
10
26
  scope: string;
11
27
  mode: 'read' | 'write';
@@ -38,6 +54,14 @@ export interface AssistantPluginActionDescriptor {
38
54
  defaultTimeoutMs: number;
39
55
  cancellable: boolean;
40
56
  idempotent: boolean;
57
+ /**
58
+ * Successful remote-write actions normally append a durable Work receipt only.
59
+ * Set terminal only when this action itself is authoritative semantic completion
60
+ * for a bound remote_effect Work.
61
+ */
62
+ remoteEffectWorkCompletion?: 'intermediate' | 'terminal';
63
+ executionMode?: 'runtime' | 'lightweight_process';
64
+ foregroundEffect?: AssistantPluginForegroundEffect;
41
65
  scopes: string[];
42
66
  resourceClaims: AssistantPluginActionResourceClaim[];
43
67
  argumentsSchema: Record<string, unknown>;
@@ -100,6 +124,8 @@ export interface AssistantPluginActionRequest {
100
124
  pluginId: string;
101
125
  actionId: string;
102
126
  requestId: string;
127
+ /** Optional existing external-effect Work that owns this typed action receipt. */
128
+ workId?: string;
103
129
  args: Record<string, unknown>;
104
130
  timeoutMs?: number;
105
131
  signal?: AbortSignal;
@@ -126,13 +152,24 @@ export interface AssistantPluginActionExecutionInput {
126
152
  providerIdentityPrevalidated?: boolean;
127
153
  }
128
154
 
129
- export type AssistantPluginScope = 'repository' | 'controller';
155
+ export type AssistantPluginScope = 'repository' | 'controller' | 'controller_with_repository_overlay';
156
+
157
+ export interface AssistantPluginBuildContext {
158
+ controllerHome: string;
159
+ repoId: string;
160
+ repoRoot: string;
161
+ controllerScoped: boolean;
162
+ }
130
163
 
131
164
  export interface AssistantPluginAdapter {
132
165
  pluginId: string;
133
166
  scope?: AssistantPluginScope;
134
- buildManifest(previousRevision?: number, previousUpdatedAt?: string, repoRoot?: string): AssistantPluginManifest;
167
+ buildManifest(previousRevision?: number, previousUpdatedAt?: string, repoRoot?: string, context?: AssistantPluginBuildContext): AssistantPluginManifest;
168
+ /** Resolve an exact stable resource identity for reusable authorization-class actions. */
169
+ resolveAuthorizationContext?(input: AssistantPluginActionExecutionInput): Promise<AssistantPluginAuthorizationContext | undefined>;
135
170
  executeAction(input: AssistantPluginActionExecutionInput): Promise<Record<string, unknown>>;
136
171
  /** Defaults to true. External providers can defer routine health refresh until the normal manifest cache expires. */
137
172
  shouldRefreshManifestAfterAction?(actionId: string): boolean;
173
+ /** Other plugin manifests whose canonical authority changed as a side effect of this action. */
174
+ affectedPluginIdsAfterAction?(actionId: string, result: Record<string, unknown>): string[];
138
175
  }