@moretea-labs/forge 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +4 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  17. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  18. package/assets/plugin-registry.v1.json +3 -3
  19. package/assets/reference-configs/document-generation.md +1 -1
  20. package/assets/reference-configs/harness-overview.md +1 -1
  21. package/assets/reference-configs/sprint-contracts.md +3 -3
  22. package/assets/skill-version.json +2 -2
  23. package/assets/templates/contract.template.md +2 -7
  24. package/assets/templates/helpers/capture-plan.sh +2 -2
  25. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  26. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  27. package/assets/templates/helpers/contract-worktree.sh +1 -21
  28. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  29. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  34. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/helpers/workflow-contract.ts +5 -1
  38. package/assets/templates/plan.template.md +1 -1
  39. package/assets/templates/review.template.md +9 -34
  40. package/assets/workflow-contract.v1.json +10 -19
  41. package/docs/README.md +10 -10
  42. package/docs/architecture/CURRENT.md +178 -0
  43. package/docs/architecture/EVOLUTION.md +42 -0
  44. package/docs/architecture/current/README.md +4 -156
  45. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  46. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  47. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  48. package/docs/architecture/history/README.md +7 -0
  49. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  50. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  51. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  52. package/docs/architecture/history.md +3 -34
  53. package/docs/architecture/index.md +15 -98
  54. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  55. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  56. package/docs/architecture/versions/1.6.md +38 -0
  57. package/docs/architecture/versions/1.7.md +41 -0
  58. package/docs/forge-chatgpt-browser-engine.md +2 -2
  59. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  60. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  61. package/docs/forge-github-issue-launcher.md +1 -1
  62. package/docs/forge-plugin-management.md +1 -1
  63. package/docs/forge-runtime-self-healing-loop.md +1 -1
  64. package/docs/operations/controller-browser-plugin.md +26 -6
  65. package/docs/operations/controller-performance-and-502.md +143 -0
  66. package/docs/operations/controller-reliability-runbook.md +1 -1
  67. package/docs/operations/features.md +2 -2
  68. package/docs/operations/github-repository.md +1 -1
  69. package/docs/operations/homebrew.md +2 -2
  70. package/docs/operations/mcp-tool-exposure.md +5 -3
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Integrations.md +2 -2
  74. package/docs/wiki/Work-Lifecycle.md +1 -1
  75. package/package.json +33 -11
  76. package/scripts/activate-source-baseline.ts +17 -13
  77. package/scripts/architecture-event.ts +2 -820
  78. package/scripts/architecture-queue.sh +2 -645
  79. package/scripts/archive-architecture-request.sh +2 -242
  80. package/scripts/archive-workflow.sh +1 -291
  81. package/scripts/benchmark-background-check-overlap.ts +228 -0
  82. package/scripts/benchmark-mcp-host.sh +117 -0
  83. package/scripts/benchmark-mcp-transport.ts +170 -0
  84. package/scripts/benchmark-quality-harness.ts +146 -0
  85. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  86. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  87. package/scripts/benchmark-typescript-navigation.ts +186 -0
  88. package/scripts/benchmark-work-efficiency.ts +334 -0
  89. package/scripts/capability-config.ts +2 -392
  90. package/scripts/capability-resolver.ts +2 -503
  91. package/scripts/capture-plan.sh +1 -332
  92. package/scripts/check-agent-tooling.sh +2 -1558
  93. package/scripts/check-architecture-sync.sh +2 -371
  94. package/scripts/check-brain-manifest.sh +2 -253
  95. package/scripts/check-context-files.sh +2 -47
  96. package/scripts/check-deploy-sql-order.sh +2 -115
  97. package/scripts/check-mcp-compatibility.ts +14 -0
  98. package/scripts/check-runtime-architecture.mjs +349 -79
  99. package/scripts/check-skill-version.ts +3 -191
  100. package/scripts/check-source-duplication.mjs +61 -0
  101. package/scripts/check-stable-baseline.ts +92 -0
  102. package/scripts/check-task-sync.sh +2 -63
  103. package/scripts/check-task-workflow.sh +2 -1000
  104. package/scripts/codex-handoff-resume.sh +2 -267
  105. package/scripts/context-contract-sync.sh +2 -514
  106. package/scripts/contract-run.ts +2 -463
  107. package/scripts/contract-worktree.sh +1 -722
  108. package/scripts/ensure-task-workflow.sh +1 -1225
  109. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  110. package/scripts/inspect-project-state.ts +4 -279
  111. package/scripts/install-standalone-recovery.ts +24 -0
  112. package/scripts/lib/project-init-lib.sh +22 -46
  113. package/scripts/lib/run-forge-helper.sh +19 -0
  114. package/scripts/lib/run-forge-helper.ts +26 -0
  115. package/scripts/migrate-project-template.sh +1 -1189
  116. package/scripts/migrate-workflow-docs.ts +4 -412
  117. package/scripts/new-plan.sh +1 -180
  118. package/scripts/new-spec.sh +1 -47
  119. package/scripts/new-sprint.sh +1 -46
  120. package/scripts/plan-to-todo.sh +1 -796
  121. package/scripts/prepare-codex-handoff.sh +2 -201
  122. package/scripts/prepare-handoff.sh +1 -78
  123. package/scripts/refresh-current-status.sh +2 -467
  124. package/scripts/run-governed-gate.ts +5 -1
  125. package/scripts/select-agent-context-blocks.sh +2 -64
  126. package/scripts/ship-worktrees.sh +1 -558
  127. package/scripts/smoke-ios-physical-device.ts +4 -2
  128. package/scripts/smoke-runtime-recovery.ts +1 -35
  129. package/scripts/smoke-schedule-engine.ts +1 -20
  130. package/scripts/sprint-backlog.sh +1 -834
  131. package/scripts/stage-runtime-release.ts +2 -1
  132. package/scripts/summarize-failures.sh +2 -123
  133. package/scripts/switch-plan.sh +1 -150
  134. package/scripts/sync-brain-docs.sh +2 -379
  135. package/scripts/sync-generated-authority.mjs +175 -0
  136. package/scripts/verify-contract.sh +2 -703
  137. package/scripts/verify-forge-runtime.sh +0 -1
  138. package/scripts/verify-sprint.sh +1 -677
  139. package/scripts/workflow-contract.ts +4 -205
  140. package/scripts/workstream-sync.sh +2 -261
  141. package/src/cli/agent-jobs/executor-health.ts +1 -1
  142. package/src/cli/commands/init-hook.ts +3 -3
  143. package/src/cli/commands/mcp.ts +9 -5
  144. package/src/cli/commands/plugin.ts +12 -3
  145. package/src/cli/commands/recovery.ts +25 -3
  146. package/src/cli/commands/runtime.ts +55 -3
  147. package/src/cli/commands/security.ts +12 -1
  148. package/src/cli/commands/setup-profile.ts +31 -21
  149. package/src/cli/controller/check-runner.ts +113 -5
  150. package/src/cli/controller/composite-result.ts +1 -1
  151. package/src/cli/controller/context/focus.ts +51 -0
  152. package/src/cli/controller/context/instruction-context.ts +114 -0
  153. package/src/cli/controller/context/known-paths.ts +76 -0
  154. package/src/cli/controller/context/query-planning.ts +110 -0
  155. package/src/cli/controller/context/rule-resolution.ts +532 -0
  156. package/src/cli/controller/context/source-materializer.ts +278 -0
  157. package/src/cli/controller/context/structural.ts +16 -0
  158. package/src/cli/controller/context/types.ts +286 -0
  159. package/src/cli/controller/context-pack.ts +828 -487
  160. package/src/cli/controller/launch-agents.ts +77 -2
  161. package/src/cli/controller/operational-plan.ts +2 -2
  162. package/src/cli/controller/work-mode.ts +6 -4
  163. package/src/cli/editing/edit-session.ts +60 -7
  164. package/src/cli/github/contracts.ts +16 -0
  165. package/src/cli/github/github.ts +135 -16
  166. package/src/cli/github/session.ts +2 -1
  167. package/src/cli/installer/managed-entries.ts +15 -1
  168. package/src/cli/local-bridge/console-automations.ts +54 -78
  169. package/src/cli/local-bridge/console-view-models.ts +1 -35
  170. package/src/cli/local-bridge/facade-api.ts +37 -264
  171. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  172. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  173. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  174. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  175. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  176. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  177. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  178. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  179. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  180. package/src/cli/local-bridge/job-store.ts +1 -490
  181. package/src/cli/local-bridge/server.ts +2 -515
  182. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  183. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  184. package/src/cli/mcp/auth.ts +5 -5
  185. package/src/cli/mcp/instructions.ts +3 -2
  186. package/src/cli/mcp/legacy-tool-service.ts +10 -222
  187. package/src/cli/mcp/multi-repository.ts +16 -5
  188. package/src/cli/mcp/oauth.ts +175 -26
  189. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  190. package/src/cli/mcp/repository-tools.ts +282 -146
  191. package/src/cli/mcp/server.ts +586 -116
  192. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  193. package/src/cli/mcp/tool-contract.ts +14 -0
  194. package/src/cli/mcp/toolset-selection.ts +1 -1
  195. package/src/cli/mcp/toolset.ts +44 -7
  196. package/src/cli/mcp/transports/http.ts +124 -14
  197. package/src/cli/mcp/transports/session-registry.ts +43 -5
  198. package/src/cli/mcp/types.ts +3 -3
  199. package/src/cli/repositories/command-classifier.ts +103 -21
  200. package/src/cli/repositories/command-executor.ts +78 -526
  201. package/src/cli/repositories/command-process.ts +108 -0
  202. package/src/cli/repositories/command-scope.ts +99 -41
  203. package/src/cli/repositories/controller-home.ts +7 -8
  204. package/src/cli/repositories/git-command-executor.ts +2 -0
  205. package/src/cli/repositories/locks.ts +53 -16
  206. package/src/cli/repositories/repository-snapshot.ts +124 -0
  207. package/src/cli/repositories/selected-path-actions.ts +17 -9
  208. package/src/cli/repositories/structured-git.ts +118 -6
  209. package/src/cli/repository/inspector.ts +563 -65
  210. package/src/cli/repository/session-cache.ts +58 -17
  211. package/src/cli/v81-entry.ts +1 -4
  212. package/src/effects/fs-transaction.ts +85 -17
  213. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  214. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  215. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  216. package/src/runtime/context/lsp-client.ts +283 -0
  217. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  218. package/src/runtime/context/semantic-navigation.ts +223 -0
  219. package/src/runtime/context/swift-navigation.ts +565 -0
  220. package/src/runtime/context/typescript-navigation.ts +226 -0
  221. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  222. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
  223. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  224. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  225. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  226. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  227. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  228. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  229. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  230. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  231. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  232. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  233. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  234. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  235. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  236. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  237. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  238. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  239. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  240. package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
  241. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
  242. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  243. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  244. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  245. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
  246. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  247. package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
  248. package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
  249. package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
  250. package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
  251. package/src/runtime/control-plane/facade/index.ts +3 -0
  252. package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
  253. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  254. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  255. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  256. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  257. package/src/runtime/control-plane/facade/types.ts +90 -10
  258. package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
  259. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  260. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  261. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  262. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  263. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  264. package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
  265. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  266. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  267. package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
  268. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  269. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  270. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  271. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  272. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  273. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  274. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  275. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  276. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  277. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  278. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
  279. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
  280. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
  281. package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
  282. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  283. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  284. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  285. package/src/runtime/control-plane/release-retention.ts +62 -2
  286. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  287. package/src/runtime/control-plane/routing/route-policy.ts +24 -21
  288. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  289. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  290. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  291. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  292. package/src/runtime/diagnostics/performance.ts +24 -1
  293. package/src/runtime/evidence/event-ledger.ts +1 -1
  294. package/src/runtime/evidence/process-check-execution.ts +18 -0
  295. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  296. package/src/runtime/execution/jobs/store.ts +58 -4
  297. package/src/runtime/execution/jobs/types.ts +1 -1
  298. package/src/runtime/execution/managed-workspace.ts +203 -1
  299. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  300. package/src/runtime/execution/process-runtime/check-result.ts +88 -0
  301. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  302. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  303. package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
  304. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  305. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
  306. package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
  307. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  308. package/src/runtime/execution/process-runtime/runtime.ts +53 -3
  309. package/src/runtime/execution/process-runtime/store.ts +1 -0
  310. package/src/runtime/execution/process-runtime/types.ts +4 -12
  311. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  312. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  313. package/src/runtime/execution/work-activity.ts +21 -0
  314. package/src/runtime/execution/workers/executor.ts +18 -61
  315. package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
  316. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  317. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  318. package/src/runtime/gateway/mcp/router.ts +186 -84
  319. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
  320. package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
  321. package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
  322. package/src/runtime/plugins/browser-adapter.ts +2662 -829
  323. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  324. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  325. package/src/runtime/plugins/browser-handoff.ts +110 -5
  326. package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
  327. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  328. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  329. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  330. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  331. package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
  332. package/src/runtime/plugins/browser-runtime.ts +193 -0
  333. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  334. package/src/runtime/plugins/browser-session-store.ts +121 -0
  335. package/src/runtime/plugins/browser-session-types.ts +98 -0
  336. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  337. package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
  338. package/src/runtime/plugins/external-adapter.ts +358 -3
  339. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  340. package/src/runtime/plugins/first-party-registry.ts +2 -0
  341. package/src/runtime/plugins/github-adapter.ts +125 -0
  342. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  343. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  344. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  345. package/src/runtime/plugins/ios-adapter.ts +106 -16
  346. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  347. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  348. package/src/runtime/plugins/lightweight-action.ts +153 -0
  349. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  350. package/src/runtime/plugins/macos-capability-broker.ts +47 -9
  351. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  352. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  353. package/src/runtime/plugins/resend-adapter.ts +1 -1
  354. package/src/runtime/plugins/store.ts +330 -33
  355. package/src/runtime/plugins/types.ts +38 -3
  356. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  357. package/src/runtime/projections/git-status-sampler.ts +16 -16
  358. package/src/runtime/projections/materialized-view.ts +28 -10
  359. package/src/runtime/recovery/maintenance-executor.ts +426 -25
  360. package/src/runtime/root/mcp-transport.ts +180 -10
  361. package/src/runtime/root/package-connector-service.ts +230 -10
  362. package/src/runtime/root/package-runtime-release.ts +143 -17
  363. package/src/runtime/root/package-runtime-service.ts +461 -22
  364. package/src/runtime/root/release-manifest.ts +144 -18
  365. package/src/runtime/root/release-materialize.ts +125 -32
  366. package/src/runtime/root/release-store.ts +48 -11
  367. package/src/runtime/root/runtime.ts +23 -5
  368. package/src/runtime/root/service.ts +100 -29
  369. package/src/runtime/root/types.ts +12 -0
  370. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  371. package/src/runtime/shared/trusted-executable.ts +62 -0
  372. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  373. package/src/runtime/standalone-recovery/core.ts +1079 -240
  374. package/src/runtime/standalone-recovery/entry.ts +100 -24
  375. package/src/runtime/standalone-recovery/installer.ts +20 -4
  376. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  377. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  378. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  379. package/src/runtime/workflow/schedules/engine.ts +364 -202
  380. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  381. package/src/runtime/workflow/schedules/settlement.ts +64 -17
  382. package/src/runtime/workflow/schedules/store.ts +209 -19
  383. package/src/runtime/workflow/schedules/types.ts +2 -4
  384. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  385. package/src/testing/test-governance.ts +8 -40
  386. package/assets/reference-configs/heartbeat-triage.md +0 -57
  387. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  388. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  389. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  390. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  391. package/docs/architecture/current/architecture-invariants.md +0 -331
  392. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  393. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  394. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  395. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  396. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  397. package/docs/architecture/current/entity-model.md +0 -612
  398. package/docs/architecture/current/external-plugin-governance.md +0 -396
  399. package/docs/architecture/current/failure-recovery.md +0 -447
  400. package/docs/architecture/current/governance.md +0 -213
  401. package/docs/architecture/current/human-interaction-plane.md +0 -87
  402. package/docs/architecture/current/implementation-status.md +0 -128
  403. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  404. package/docs/architecture/current/legacy-governance.md +0 -58
  405. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  406. package/docs/architecture/current/migration-roadmap.md +0 -105
  407. package/docs/architecture/current/multi-repository-execution.md +0 -371
  408. package/docs/architecture/current/operations-runbook.md +0 -45
  409. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  410. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  411. package/docs/architecture/current/repository-access-modes.md +0 -102
  412. package/docs/architecture/current/route-policy.md +0 -15
  413. package/docs/architecture/current/route-session-concurrency.md +0 -85
  414. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  415. package/docs/architecture/current/runtime-directory-map.md +0 -49
  416. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  417. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  418. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  419. package/docs/architecture/current/system-overview.md +0 -169
  420. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  421. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  422. package/docs/architecture/current/thin-harness-v1.md +0 -321
  423. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  424. package/docs/forge-autonomous-goal-loop.md +0 -9
  425. package/docs/forge-provider-configuration.md +0 -7
  426. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  427. package/docs/operations/gmail-assistant-routines.md +0 -186
  428. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  429. package/docs/operations/provider-configuration.md +0 -31
  430. package/scripts/harness-trace-grade.sh +0 -150
  431. package/scripts/heartbeat-triage.sh +0 -361
  432. package/scripts/maintenance-triage.sh +0 -167
  433. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  434. package/src/cli/repositories/goal-registry.ts +0 -239
  435. package/src/runtime/assistant/action-proposals.ts +0 -279
  436. package/src/runtime/assistant/intent.ts +0 -395
  437. package/src/runtime/assistant/model-provider.ts +0 -460
  438. package/src/runtime/assistant/openapi.ts +0 -223
  439. package/src/runtime/assistant/policy.ts +0 -89
  440. package/src/runtime/assistant/readiness.ts +0 -258
  441. package/src/runtime/assistant/routine-runtime.ts +0 -477
  442. package/src/runtime/assistant/schedule-binding.ts +0 -171
  443. package/src/runtime/assistant/standing-grants.ts +0 -375
  444. package/src/runtime/assistant/store.ts +0 -243
  445. package/src/runtime/assistant/types.ts +0 -135
  446. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  447. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  448. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  449. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  450. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  451. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  452. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  453. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  454. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  455. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  456. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  457. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  458. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  459. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  460. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  461. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  462. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  463. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  464. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  465. package/src/runtime/workflow/findings/store.ts +0 -204
  466. package/src/runtime/workflow/findings/types.ts +0 -58
  467. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  468. package/src/runtime/workflow/portfolio/store.ts +0 -185
  469. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -9,6 +9,12 @@ import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../shared/
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 {
@@ -24,6 +30,7 @@ import type {
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)
@@ -537,6 +598,8 @@ export interface PluginActionReceipt {
537
598
  receiptId: string;
538
599
  requestId: string;
539
600
  repoId: string;
601
+ /** Work authority repository when it differs from controller/provider receipt scope. */
602
+ workRepoId?: string;
540
603
  pluginId: string;
541
604
  actionId: string;
542
605
  semanticKey: string;
@@ -544,6 +607,7 @@ export interface PluginActionReceipt {
544
607
  createdAt: string;
545
608
  workId?: string;
546
609
  origin?: { surface: string; actor?: string; correlationId?: string };
610
+ authorization?: PluginActionAuthorizationEvidence;
547
611
  result?: Record<string, unknown>;
548
612
  error?: { code: string; message: string };
549
613
  }
@@ -551,6 +615,7 @@ export interface PluginActionReceipt {
551
615
  interface PluginActionRequestIndex {
552
616
  requestId: string;
553
617
  repoId: string;
618
+ workRepoId?: string;
554
619
  receiptId: string;
555
620
  semanticKey: string;
556
621
  createdAt: string;
@@ -671,6 +736,15 @@ function localEffectTarget(
671
736
  };
672
737
  }
673
738
 
739
+ function workAttributionRepoId(repository: RepositoryRecord, request: AssistantPluginActionRequest): string {
740
+ const explicit = request.workRepoId?.trim();
741
+ if (!request.workId?.trim()) {
742
+ if (explicit) throw new Error(`WORK_PLUGIN_ATTRIBUTION_REPO_WITHOUT_WORK: ${explicit}`);
743
+ return repository.repoId;
744
+ }
745
+ return explicit || repository.repoId;
746
+ }
747
+
674
748
  function remoteEffectWorkForPluginAction(
675
749
  controllerHome: string,
676
750
  repository: RepositoryRecord,
@@ -683,7 +757,8 @@ function remoteEffectWorkForPluginAction(
683
757
  if (action.risk !== 'remote_write') {
684
758
  throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_REQUIRES_REMOTE_WRITE: ${action.actionId}`);
685
759
  }
686
- const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
760
+ const workRepoId = workAttributionRepoId(repository, request);
761
+ const work = getWorkContract({ controllerHome, repoId: workRepoId }, workId);
687
762
  if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
688
763
  if (work.workKind !== 'remote_effect') {
689
764
  throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
@@ -692,12 +767,101 @@ function remoteEffectWorkForPluginAction(
692
767
  throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CHECKS_PRESENT: ${workId} declares repository checks and cannot be completed by a plugin receipt alone`);
693
768
  }
694
769
  if (isTerminalWorkContractStatus(work.status)) {
695
- if (work.status === 'completed' && receiptId && work.completionReceipt?.receiptId === receiptId) return work;
770
+ const receiptAlreadyBound = Boolean(receiptId) && (
771
+ work.completionReceipt?.receiptId === receiptId
772
+ || work.evidenceRefs.some((evidence) => evidence.evidenceId === receiptId)
773
+ );
774
+ if (work.status === 'completed' && receiptAlreadyBound) return work;
696
775
  throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_TERMINAL: ${workId} is ${work.status}`);
697
776
  }
698
777
  return work;
699
778
  }
700
779
 
780
+ function attributedWorkForPluginAction(
781
+ controllerHome: string,
782
+ repository: RepositoryRecord,
783
+ action: AssistantPluginActionDescriptor,
784
+ request: AssistantPluginActionRequest,
785
+ ): WorkContract | undefined {
786
+ const workId = request.workId?.trim();
787
+ if (!workId || action.risk === 'remote_write') return undefined;
788
+ const workRepoId = workAttributionRepoId(repository, request);
789
+ const work = getWorkContract({ controllerHome, repoId: workRepoId }, workId);
790
+ if (!work) throw new Error(`WORK_PLUGIN_ATTRIBUTION_NOT_FOUND: ${workRepoId}:${workId}`);
791
+ if (isTerminalWorkContractStatus(work.status)) {
792
+ throw new Error(`WORK_PLUGIN_ATTRIBUTION_TERMINAL: ${workId} is ${work.status}`);
793
+ }
794
+ return work;
795
+ }
796
+
797
+ function bindLocalEffectReceiptToAttributedWork(
798
+ controllerHome: string,
799
+ repository: RepositoryRecord,
800
+ action: AssistantPluginActionDescriptor,
801
+ request: AssistantPluginActionRequest,
802
+ receipt: PluginActionReceipt,
803
+ work?: WorkContract,
804
+ ): WorkContract | undefined {
805
+ if (!request.workId || receipt.status !== 'succeeded') return work;
806
+ const workRepoId = workAttributionRepoId(repository, request);
807
+ const attributed = work ?? getWorkContract({ controllerHome, repoId: workRepoId }, request.workId.trim());
808
+ if (!attributed) throw new Error(`WORK_PLUGIN_ATTRIBUTION_NOT_FOUND: ${workRepoId}:${request.workId.trim()}`);
809
+ if (attributed.workKind !== 'local_effect' || action.readOnly || action.risk === 'readonly') return attributed;
810
+ if (attributed.evidenceRefs.some((evidence) => evidence.evidenceId === receipt.receiptId)) return attributed;
811
+ if (isTerminalWorkContractStatus(attributed.status)) return attributed;
812
+ return appendWorkEvidence({ controllerHome, repoId: workRepoId }, attributed.workId, {
813
+ evidenceId: receipt.receiptId,
814
+ title: 'typed local plugin effect completed',
815
+ summary: `${receipt.pluginId}/${receipt.actionId} completed with durable receipt ${receipt.receiptId}.`,
816
+ detailLevel: 'summary',
817
+ });
818
+ }
819
+
820
+ export function finalizeRemoteEffectWorkFromActionReceipt(
821
+ controllerHome: string,
822
+ repoId: string,
823
+ workId: string,
824
+ ): WorkContract {
825
+ const work = getWorkContract({ controllerHome, repoId }, workId);
826
+ if (!work) throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_NOT_FOUND: ${workId}`);
827
+ if (work.workKind !== 'remote_effect') {
828
+ throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_KIND_MISMATCH: ${workId} is ${work.workKind}, expected remote_effect`);
829
+ }
830
+ if (work.status === 'completed' && work.completionReceipt?.source === 'remote_effect') return work;
831
+ if (isTerminalWorkContractStatus(work.status)) {
832
+ throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_TERMINAL: ${workId} is ${work.status}`);
833
+ }
834
+ const receipt = work.evidenceRefs
835
+ .map((evidence) => {
836
+ if (!evidence.evidenceId) return undefined;
837
+ return readPluginActionReceipt(controllerHome, repoId, evidence.evidenceId)
838
+ ?? readPluginActionReceipt(controllerHome, CONTROLLER_SCOPE_REPO_ID, evidence.evidenceId);
839
+ })
840
+ .find((candidate) => candidate?.workId === workId && candidate.status === 'succeeded');
841
+ if (!receipt) {
842
+ throw new Error(`WORK_REMOTE_EFFECT_FINALIZE_RECEIPT_REQUIRED: ${workId} has no successful durable plugin action receipt`);
843
+ }
844
+ const remoteReceipt: RemoteEffectCompletionReceipt = {
845
+ schemaVersion: 1,
846
+ receiptId: receipt.receiptId,
847
+ source: 'remote_effect',
848
+ workId,
849
+ pluginId: receipt.pluginId,
850
+ actionId: receipt.actionId,
851
+ requestId: receipt.requestId,
852
+ semanticKey: receipt.semanticKey,
853
+ resultDigest: createHash('sha256').update(JSON.stringify(receipt.result ?? null)).digest('hex'),
854
+ recordedAt: new Date().toISOString(),
855
+ };
856
+ return recordWorkCompletionReceipt(
857
+ { controllerHome, repoId },
858
+ workId,
859
+ remoteReceipt,
860
+ 'completed_remote',
861
+ 'remote_effect',
862
+ );
863
+ }
864
+
701
865
  function bindRemoteEffectReceiptToWork(
702
866
  controllerHome: string,
703
867
  repository: RepositoryRecord,
@@ -709,15 +873,18 @@ function bindRemoteEffectReceiptToWork(
709
873
  if (receipt.status !== 'succeeded') return remoteEffectWorkForPluginAction(controllerHome, repository, action, request);
710
874
  const work = remoteEffectWorkForPluginAction(controllerHome, repository, action, request, receipt.receiptId);
711
875
  if (!work) return undefined;
712
- if (work.status === 'completed' && work.completionReceipt?.receiptId === receipt.receiptId) return work;
876
+ if (work.status === 'completed') return work;
877
+ const workRepoId = workAttributionRepoId(repository, request);
878
+ let updated = work;
713
879
  if (!work.evidenceRefs.some((evidence) => evidence.evidenceId === receipt.receiptId)) {
714
- appendWorkEvidence({ controllerHome, repoId: repository.repoId }, work.workId, {
880
+ updated = appendWorkEvidence({ controllerHome, repoId: workRepoId }, work.workId, {
715
881
  evidenceId: receipt.receiptId,
716
882
  title: 'typed remote plugin effect completed',
717
883
  summary: `${receipt.pluginId}/${receipt.actionId} completed with durable receipt ${receipt.receiptId}.`,
718
884
  detailLevel: 'summary',
719
885
  });
720
886
  }
887
+ if (action.remoteEffectWorkCompletion !== 'terminal') return updated;
721
888
  const remoteReceipt: RemoteEffectCompletionReceipt = {
722
889
  schemaVersion: 1,
723
890
  receiptId: receipt.receiptId,
@@ -731,7 +898,7 @@ function bindRemoteEffectReceiptToWork(
731
898
  recordedAt: new Date().toISOString(),
732
899
  };
733
900
  return recordWorkCompletionReceipt(
734
- { controllerHome, repoId: repository.repoId },
901
+ { controllerHome, repoId: workRepoId },
735
902
  work.workId,
736
903
  remoteReceipt,
737
904
  'completed_remote',
@@ -785,8 +952,11 @@ export async function submitAssistantPluginAction(
785
952
  deduplicated: boolean;
786
953
  result?: Record<string, unknown>;
787
954
  receipt: PluginActionReceipt;
955
+ authorization?: PluginActionAuthorizationEvidence;
788
956
  workId?: string;
789
957
  }> {
958
+ const adapter = resolvePluginAdapter(controllerHome, request.pluginId);
959
+ if (!adapter) throw new Error(`PLUGIN_NOT_FOUND: ${request.pluginId}`);
790
960
  const manifest = getAssistantPluginManifest(controllerHome, repository, request.pluginId);
791
961
  const action = actionForManifest(manifest, request.actionId);
792
962
  if (!manifest.enabled && action.actionId !== 'configure') {
@@ -812,7 +982,15 @@ export async function submitAssistantPluginAction(
812
982
  if (request.workId && existing.workId !== request.workId) {
813
983
  throw new Error(`WORK_PLUGIN_RECEIPT_BINDING_CONFLICT: ${request.requestId} already belongs to Work ${existing.workId ?? 'none'}`);
814
984
  }
815
- if (request.workId) bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, existing);
985
+ const requestedWorkRepoId = request.workId ? workAttributionRepoId(repository, request) : undefined;
986
+ if (requestedWorkRepoId && (index.workRepoId ?? index.repoId) !== requestedWorkRepoId) {
987
+ throw new Error(`WORK_PLUGIN_ATTRIBUTION_REPO_CONFLICT: ${request.requestId} already belongs to Work repository ${index.workRepoId ?? index.repoId}`);
988
+ }
989
+ if (request.workId && action.risk === 'remote_write') {
990
+ bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, existing);
991
+ } else if (request.workId) {
992
+ bindLocalEffectReceiptToAttributedWork(controllerHome, repository, action, request, existing);
993
+ }
816
994
  return {
817
995
  manifest,
818
996
  action,
@@ -820,19 +998,29 @@ export async function submitAssistantPluginAction(
820
998
  deduplicated: true,
821
999
  result: existing.result,
822
1000
  receipt: existing,
1001
+ authorization: existing.authorization,
823
1002
  workId: existing.workId,
824
1003
  };
825
1004
  }
826
1005
 
827
1006
  const createdAt = new Date().toISOString();
828
1007
  const receiptId = `PLG-${Date.now()}-${createHash('sha256').update(request.requestId).digest('hex').slice(0, 8)}`;
829
- const boundRemoteWork = remoteEffectWorkForPluginAction(controllerHome, repository, action, request);
1008
+ const capabilityId = reusableCapabilityId(manifest, action);
1009
+ let authorization = initialAuthorizationEvidence(manifest, action);
1010
+ let authorizationContext: AssistantPluginAuthorizationContext | undefined;
1011
+ let activeAuthorizationGrant: PluginCapabilityAuthorizationGrant | undefined;
830
1012
  const requiresLocalEffectWork = localSystemActionRequiresWork(
831
1013
  repository,
832
1014
  request.pluginId,
833
1015
  request.actionId,
834
1016
  normalizedArgs,
835
1017
  );
1018
+ const boundRemoteWork = action.risk === 'remote_write'
1019
+ ? remoteEffectWorkForPluginAction(controllerHome, repository, action, request)
1020
+ : undefined;
1021
+ const attributedWork = !requiresLocalEffectWork
1022
+ ? attributedWorkForPluginAction(controllerHome, repository, action, request)
1023
+ : undefined;
836
1024
  const acceptedWork = requiresLocalEffectWork
837
1025
  ? acceptSubmittedWorkContract(controllerHome, {
838
1026
  requestId: request.requestId,
@@ -885,10 +1073,63 @@ export async function submitAssistantPluginAction(
885
1073
  receiptId,
886
1074
  risk: action.risk,
887
1075
  confirmation: action.confirmation,
1076
+ capabilityId,
1077
+ authorizationReuseSupported: action.confirmation === 'authorization' && Boolean(adapter.resolveAuthorizationContext),
888
1078
  },
889
1079
  });
890
1080
 
891
1081
  try {
1082
+ if (action.confirmation === 'authorization'
1083
+ && adapter.resolveAuthorizationContext
1084
+ && originMayEstablishCapabilityAuthorization(request.origin)) {
1085
+ authorizationContext = await adapter.resolveAuthorizationContext({
1086
+ controllerHome,
1087
+ repoId: repository.repoId,
1088
+ repoRoot: repository.canonicalRoot,
1089
+ pluginId: request.pluginId,
1090
+ actionId: request.actionId,
1091
+ requestId: request.requestId,
1092
+ args: normalizedArgs,
1093
+ origin: request.origin,
1094
+ jobId: receiptId,
1095
+ timeoutMs: request.timeoutMs,
1096
+ signal: request.signal,
1097
+ });
1098
+ if (authorizationContext) {
1099
+ authorization = {
1100
+ source: 'host_permission_model',
1101
+ reusable: false,
1102
+ capabilityId,
1103
+ target: authorizationTargetSummary(authorizationContext),
1104
+ };
1105
+ try {
1106
+ activeAuthorizationGrant = findActivePluginCapabilityAuthorization(controllerHome, {
1107
+ ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
1108
+ repoId: repository.repoId,
1109
+ pluginId: request.pluginId,
1110
+ capabilityId,
1111
+ target: authorizationContext.target,
1112
+ scopes: action.scopes,
1113
+ risk: action.risk,
1114
+ });
1115
+ if (activeAuthorizationGrant) {
1116
+ authorization = {
1117
+ source: 'capability_grant',
1118
+ reusable: true,
1119
+ capabilityId,
1120
+ target: authorizationTargetSummary(authorizationContext),
1121
+ grantId: activeAuthorizationGrant.grantId,
1122
+ };
1123
+ }
1124
+ } catch (error) {
1125
+ authorization = {
1126
+ ...authorization,
1127
+ lookupError: authorizationError(error),
1128
+ };
1129
+ }
1130
+ }
1131
+ }
1132
+
892
1133
  const result = await executeAssistantPluginAction({
893
1134
  controllerHome,
894
1135
  repoId: repository.repoId,
@@ -905,15 +1146,49 @@ export async function submitAssistantPluginAction(
905
1146
  ? Date.now() + Math.max(1, Math.trunc(request.timeoutMs))
906
1147
  : undefined,
907
1148
  });
1149
+ if (action.confirmation === 'authorization'
1150
+ && authorizationContext
1151
+ && !activeAuthorizationGrant
1152
+ && originMayEstablishCapabilityAuthorization(request.origin)) {
1153
+ try {
1154
+ const grant = recordPluginCapabilityAuthorization(controllerHome, {
1155
+ ownerScope: pluginCapabilityAuthorizationOwnerScope(request.origin),
1156
+ repoId: repository.repoId,
1157
+ pluginId: request.pluginId,
1158
+ capabilityId,
1159
+ target: authorizationContext.target,
1160
+ scopes: action.scopes,
1161
+ riskCeiling: action.risk,
1162
+ expiresInMinutes: authorizationContext.expiresInMinutes,
1163
+ });
1164
+ authorization = {
1165
+ source: 'host_permission_model',
1166
+ reusable: true,
1167
+ capabilityId,
1168
+ target: authorizationTargetSummary(authorizationContext),
1169
+ grantId: grant.grantId,
1170
+ established: true,
1171
+ ...(authorization.lookupError ? { lookupError: authorization.lookupError } : {}),
1172
+ };
1173
+ } catch (error) {
1174
+ authorization = {
1175
+ ...authorization,
1176
+ reusable: false,
1177
+ persistenceError: authorizationError(error),
1178
+ };
1179
+ }
1180
+ }
1181
+
908
1182
  let resultWithLineage = result;
909
1183
  if (boundRemoteWork) {
1184
+ const terminalRemoteEffect = action.remoteEffectWorkCompletion === 'terminal';
910
1185
  resultWithLineage = {
911
1186
  ...result,
912
1187
  work: {
913
1188
  workId: boundRemoteWork.workId,
914
1189
  workKind: 'remote_effect',
915
- completionOutcome: 'completed_remote',
916
- status: 'completed',
1190
+ ...(terminalRemoteEffect ? { completionOutcome: 'completed_remote' as const } : {}),
1191
+ status: terminalRemoteEffect ? 'completed' : 'running',
917
1192
  },
918
1193
  };
919
1194
  }
@@ -956,24 +1231,37 @@ export async function submitAssistantPluginAction(
956
1231
  receiptId,
957
1232
  requestId: request.requestId,
958
1233
  repoId: repository.repoId,
1234
+ ...(request.workId ? { workRepoId: workAttributionRepoId(repository, request) } : {}),
959
1235
  pluginId: request.pluginId,
960
1236
  actionId: request.actionId,
961
1237
  semanticKey: key,
962
1238
  status: 'succeeded',
963
1239
  createdAt,
964
- ...(acceptedWork ? { workId: acceptedWork.workId } : boundRemoteWork ? { workId: boundRemoteWork.workId } : {}),
1240
+ ...(acceptedWork
1241
+ ? { workId: acceptedWork.workId }
1242
+ : boundRemoteWork
1243
+ ? { workId: boundRemoteWork.workId }
1244
+ : attributedWork
1245
+ ? { workId: attributedWork.workId }
1246
+ : {}),
965
1247
  origin: request.origin,
1248
+ authorization,
966
1249
  result: resultWithLineage,
967
1250
  };
968
1251
  writeJsonAtomic(pluginActionReceiptPath(controllerHome, repository.repoId, receiptId), receipt);
969
1252
  writeJsonAtomic(requestPath, {
970
1253
  requestId: request.requestId,
971
1254
  repoId: repository.repoId,
1255
+ ...(request.workId ? { workRepoId: workAttributionRepoId(repository, request) } : {}),
972
1256
  receiptId,
973
1257
  semanticKey: key,
974
1258
  createdAt,
975
1259
  } satisfies PluginActionRequestIndex);
976
- if (boundRemoteWork) bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, receipt);
1260
+ if (boundRemoteWork) {
1261
+ bindRemoteEffectReceiptToWork(controllerHome, repository, action, request, receipt);
1262
+ } else if (attributedWork) {
1263
+ bindLocalEffectReceiptToAttributedWork(controllerHome, repository, action, request, receipt, attributedWork);
1264
+ }
977
1265
  const nextManifest = getAssistantPluginManifest(controllerHome, repository, request.pluginId);
978
1266
  return {
979
1267
  manifest: nextManifest,
@@ -982,13 +1270,14 @@ export async function submitAssistantPluginAction(
982
1270
  deduplicated: false,
983
1271
  result: resultWithLineage,
984
1272
  receipt,
985
- workId: acceptedWork?.workId ?? boundRemoteWork?.workId,
1273
+ authorization,
1274
+ workId: acceptedWork?.workId ?? boundRemoteWork?.workId ?? attributedWork?.workId,
986
1275
  };
987
1276
  } catch (error) {
988
1277
  const message = error instanceof Error ? error.message : String(error);
989
1278
  const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'PLUGIN_ACTION_FAILED';
990
1279
  if (boundRemoteWork) {
991
- appendWorkEvidence({ controllerHome, repoId: repository.repoId }, boundRemoteWork.workId, {
1280
+ appendWorkEvidence({ controllerHome, repoId: workAttributionRepoId(repository, request) }, boundRemoteWork.workId, {
992
1281
  title: 'typed remote plugin effect failed',
993
1282
  summary: `${request.pluginId}/${request.actionId}: ${message}`.slice(0, 1_000),
994
1283
  detailLevel: 'summary',
@@ -1013,13 +1302,21 @@ export async function submitAssistantPluginAction(
1013
1302
  receiptId,
1014
1303
  requestId: request.requestId,
1015
1304
  repoId: repository.repoId,
1305
+ ...(request.workId ? { workRepoId: workAttributionRepoId(repository, request) } : {}),
1016
1306
  pluginId: request.pluginId,
1017
1307
  actionId: request.actionId,
1018
1308
  semanticKey: key,
1019
1309
  status: 'failed',
1020
1310
  createdAt,
1021
- ...(acceptedWork ? { workId: acceptedWork.workId } : boundRemoteWork ? { workId: boundRemoteWork.workId } : {}),
1311
+ ...(acceptedWork
1312
+ ? { workId: acceptedWork.workId }
1313
+ : boundRemoteWork
1314
+ ? { workId: boundRemoteWork.workId }
1315
+ : attributedWork
1316
+ ? { workId: attributedWork.workId }
1317
+ : {}),
1022
1318
  origin: request.origin,
1319
+ authorization,
1023
1320
  error: { code, message },
1024
1321
  };
1025
1322
  writeJsonAtomic(pluginActionReceiptPath(controllerHome, repository.repoId, receiptId), receipt);
@@ -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,8 +124,10 @@ export interface AssistantPluginActionRequest {
100
124
  pluginId: string;
101
125
  actionId: string;
102
126
  requestId: string;
103
- /** Optional existing external-effect Work that owns this typed action receipt. */
127
+ /** Optional existing Work that owns this typed action receipt. */
104
128
  workId?: string;
129
+ /** Durable repository authority for workId. May differ from provider scope for controller-scoped plugins. */
130
+ workRepoId?: string;
105
131
  args: Record<string, unknown>;
106
132
  timeoutMs?: number;
107
133
  signal?: AbortSignal;
@@ -128,12 +154,21 @@ export interface AssistantPluginActionExecutionInput {
128
154
  providerIdentityPrevalidated?: boolean;
129
155
  }
130
156
 
131
- export type AssistantPluginScope = 'repository' | 'controller';
157
+ export type AssistantPluginScope = 'repository' | 'controller' | 'controller_with_repository_overlay';
158
+
159
+ export interface AssistantPluginBuildContext {
160
+ controllerHome: string;
161
+ repoId: string;
162
+ repoRoot: string;
163
+ controllerScoped: boolean;
164
+ }
132
165
 
133
166
  export interface AssistantPluginAdapter {
134
167
  pluginId: string;
135
168
  scope?: AssistantPluginScope;
136
- buildManifest(previousRevision?: number, previousUpdatedAt?: string, repoRoot?: string): AssistantPluginManifest;
169
+ buildManifest(previousRevision?: number, previousUpdatedAt?: string, repoRoot?: string, context?: AssistantPluginBuildContext): AssistantPluginManifest;
170
+ /** Resolve an exact stable resource identity for reusable authorization-class actions. */
171
+ resolveAuthorizationContext?(input: AssistantPluginActionExecutionInput): Promise<AssistantPluginAuthorizationContext | undefined>;
137
172
  executeAction(input: AssistantPluginActionExecutionInput): Promise<Record<string, unknown>>;
138
173
  /** Defaults to true. External providers can defer routine health refresh until the normal manifest cache expires. */
139
174
  shouldRefreshManifestAfterAction?(actionId: string): boolean;