@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
@@ -2,9 +2,9 @@ import { bindRepositoryEntities } from '../repositories/entity-migration';
2
2
  import { bootstrapLocalProject, diagnoseLatestLocalProjectSource } from '../repositories/local-project-onboarding';
3
3
  import { resolveEphemeralWorkspaceTarget } from '../repositories/ephemeral-workspace';
4
4
  import type { ResolvedExecutionIdentity } from '../../runtime/control-plane/execution/execution-identity';
5
- import { readExecutionSession } from '../../runtime/control-plane/execution/session-store';
5
+ import { assertNoBoundExecutionSessionMutation, resolveClaimedRepositoryWorkId, resolveExplicitClaimedRepositoryWork, type RepositoryWorkAttributionCaller } from '../../runtime/control-plane/execution/repository-work-attribution';
6
6
  import { getWorkContract } from '../../runtime/control-plane/facade/work-contract-store';
7
- import { getControllerSession, listControllerSessions } from '../../runtime/control-plane/facade/controller-session-store';
7
+ import { assertWorkPathsWithinScope } from '../../runtime/control-plane/execution/work-path-scope';
8
8
  import { isTerminalWorkContractStatus } from '../../runtime/control-plane/facade/types';
9
9
  import { executeRepositoryCommand, previewRepositoryCommandExecution } from '../repositories/command-executor';
10
10
  import { withControllerLock } from '../repositories/locks';
@@ -24,9 +24,8 @@ import {
24
24
  } from '../repositories/registry';
25
25
  import { buildControllerWorkbench } from '../repositories/workbench';
26
26
  import { applySafePatch, buildSafePatchPlan } from '../repositories/safe-patch';
27
- import { getEditSessionDiff, type EditSessionBinding } from '../editing/edit-session';
27
+ import { getEditSession, getEditSessionDiff, type EditSessionBinding } from '../editing/edit-session';
28
28
  import { buildSyncOperationDigest, classifyUserFacingError } from '../../runtime/control-plane/facade/operation-digest';
29
- import { diagnoseRepositoryStuckState, listRepositoryGoalRuns, readRepositoryGoalRegistry, runRepositoryGoal, upsertRepositoryGoal } from '../repositories/goal-registry';
30
29
  import {
31
30
  repositoryGitCommit,
32
31
  repositoryGitCreateBranch,
@@ -54,8 +53,12 @@ import {
54
53
  executeRepositoryCommandViaProcessRuntime,
55
54
  } from '../../runtime/execution/process-runtime/command-facade';
56
55
  import { assessWorkMode, parseExplicitTaskMode } from '../controller/work-mode';
56
+ import { classifyRepositoryCommand } from '../repositories/command-classifier';
57
57
  import { normalizeRepositoryCommand } from '../repositories/command-normalization';
58
- import type { CallToolResult, McpToolDefinition } from './tools';
58
+ import { readRepositoryRange } from '../repository/inspector';
59
+ import { getMcpPolicy } from './policy';
60
+ import { redactMcpText } from './redaction';
61
+ import type { CallToolResult, McpToolDefinition } from './tool-contract';
59
62
  import {
60
63
  boundUtf8,
61
64
  compactCommandOutput,
@@ -117,6 +120,13 @@ export const repositoryToolDefinitions: McpToolDefinition[] = [
117
120
  include_removed: { type: 'boolean' },
118
121
  detail_level: { type: 'string', enum: ['summary', 'detail'], description: 'Defaults to summary; detail returns the complete checkout history.' },
119
122
  }, ['repo_id'], true),
123
+ definition('read_repository_file', 'Read a line range from one policy-readable repository file.', {
124
+ repo_id: repoId,
125
+ checkout_id: { type: 'string' },
126
+ path: { type: 'string' },
127
+ start_line: { type: 'number' },
128
+ end_line: { type: 'number' },
129
+ }, ['path'], true),
120
130
  definition('repository_validate', 'Validate repository identity and migrate legacy ownership.', {
121
131
  repo_id: repoId,
122
132
  detail_level: { type: 'string', enum: ['summary', 'detail'], description: 'Defaults to summary; detail returns full migration evidence.' },
@@ -136,7 +146,7 @@ export const repositoryToolDefinitions: McpToolDefinition[] = [
136
146
  }, ['repo_id']),
137
147
  definition('repository_remove', 'Soft-remove a repository while retaining audit history.', {
138
148
  repo_id: repoId,
139
- }, ['repo_id'], true),
149
+ }, ['repo_id']),
140
150
  definition('repository_workbench', 'Return Workbench state or invoke one bounded Thin Harness operation without adding top-level MCP tools. Prefer batch_execute for multi-step status→search→read→diff or read→patch→check flows so ChatGPT pays one routing/receipt ownership cycle.', {
141
151
  repo_id: repoId,
142
152
  checkout_id: { type: 'string', description: 'Optional checkout identity for repository-scoped operations.' },
@@ -152,38 +162,6 @@ export const repositoryToolDefinitions: McpToolDefinition[] = [
152
162
  additionalProperties: true,
153
163
  },
154
164
  }),
155
-
156
-
157
-
158
- definition('repository_goal_list', 'List durable repository goals stored inside the checkout.', {
159
- repo_id: repoId,
160
- checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
161
- }, [], true),
162
- definition('repository_goal_upsert', 'Create or update one durable repository goal for repeated assistant workflows.', {
163
- repo_id: repoId,
164
- checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
165
- id: { type: 'string' },
166
- title: { type: 'string' },
167
- status: { type: 'string', enum: ['active', 'paused', 'done'] },
168
- checks: { type: 'array', items: { type: 'string' } },
169
- notes: { type: 'string' },
170
- }, []),
171
- definition('repository_stuck_diagnose', 'Diagnose likely repository workflow blockers from Git state and registered goals.', {
172
- repo_id: repoId,
173
- checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
174
- }, [], true),
175
- definition('repository_goal_run', 'Run one durable repository goal iteration, optionally executing its configured checks and recording a goal-run artifact.', {
176
- repo_id: repoId,
177
- checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
178
- goal_id: { type: 'string', description: 'Goal id. Defaults to the first active goal.' },
179
- run_checks: { type: 'boolean', description: 'When true, execute configured checks. Defaults to diagnosis-only.' },
180
- }, []),
181
- definition('repository_goal_runs', 'List recent repository goal-run artifacts.', {
182
- repo_id: repoId,
183
- checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
184
- limit: { type: 'number' },
185
- }, [], true),
186
-
187
165
  definition('repository_git_status', 'Return a structured Git status snapshot for the selected repository checkout.', {
188
166
  repo_id: repoId,
189
167
  checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
@@ -270,7 +248,7 @@ export const repositoryToolDefinitions: McpToolDefinition[] = [
270
248
  cwd: { type: 'string', description: 'Optional root-relative working directory.' },
271
249
  workspace_root: { type: 'string', description: 'Absolute existing local directory used as an ephemeral execution root. Mutually exclusive with repo_id/checkout_id; does not register or initialize the directory.' },
272
250
  }, ['command'], true),
273
- definition('repository_command_execute', 'Execute one repository-scoped local command through Full Access, Goal delegation, or a resumable approval request. Legacy preview-token callers remain compatible.', {
251
+ definition('repository_command_execute', 'Execute one repository-scoped command through the thinnest eligible lane: ephemeral for ordinary local work, an in-memory lightweight handle when it outlives the interaction budget, or explicit Durable handling for Work/external/release effects. Lightweight handles have no SQLite, Lease, recovery, or replay membership. Use rh_context for routine code discovery/reading; shell exploration is fallback-only.', {
274
252
  repo_id: repoId,
275
253
  checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
276
254
  work_id: { type: 'string', description: 'Optional durable Work identity. Workflow controllers should pass the exact claimed Work id so attribution survives transient MCP transport sessions.' },
@@ -310,59 +288,15 @@ function withResponseMeta(payload: Record<string, unknown>, startedAt: number):
310
288
  return response;
311
289
  }
312
290
 
313
- export interface RepositoryToolCallerContext {
314
- sessionId?: string;
315
- principalId?: string;
316
- controllerInstanceId?: string;
317
- }
291
+ export type RepositoryToolCallerContext = RepositoryWorkAttributionCaller;
318
292
 
319
- function claimedSessionWorkId(
320
- controllerHome: string,
321
- repository: ReturnType<typeof resolveRepositorySelection>,
322
- caller?: RepositoryToolCallerContext,
323
- explicitWorkId?: unknown,
324
- ): string | undefined {
325
- if (!caller?.principalId?.trim()) return undefined;
326
- const requestedWorkId = typeof explicitWorkId === 'string' ? explicitWorkId.trim() : '';
327
- if (requestedWorkId) {
328
- const work = getWorkContract({ controllerHome, repoId: repository.repoId }, requestedWorkId);
329
- if (!work || isTerminalWorkContractStatus(work.status)) throw new Error(`WORK_ATTRIBUTION_INVALID: ${requestedWorkId}`);
330
- if (work.checkoutId && work.checkoutId !== repository.activeCheckoutId) throw new Error(`WORK_CHECKOUT_MISMATCH: ${requestedWorkId}:${repository.activeCheckoutId}`);
331
- const owner = getControllerSession({ controllerHome, repoId: repository.repoId }, requestedWorkId);
332
- if (!owner) throw new Error(`WORK_CONTROLLER_CLAIM_REQUIRED: ${requestedWorkId}`);
333
- if ((owner.principalId?.trim() || owner.controllerId) !== caller.principalId.trim()) throw new Error(`WORK_CONTROLLER_OWNERSHIP_MISMATCH: ${requestedWorkId}`);
334
- return requestedWorkId;
335
- }
336
- if (caller.sessionId?.trim()) {
337
- const executionSession = readExecutionSession(controllerHome, {
338
- sessionId: caller.sessionId,
339
- principalId: caller.principalId,
340
- controllerInstanceId: caller.controllerInstanceId,
341
- });
342
- const workId = executionSession?.activeWorkId?.trim();
343
- if (workId && executionSession?.activeRepositoryId === repository.repoId && (!executionSession.activeCheckoutId || executionSession.activeCheckoutId === repository.activeCheckoutId)) {
344
- const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
345
- const owner = getControllerSession({ controllerHome, repoId: repository.repoId }, workId);
346
- if (work && !isTerminalWorkContractStatus(work.status) && owner?.sessionId === caller.sessionId && (owner.principalId?.trim() || owner.controllerId) === caller.principalId.trim()) return workId;
347
- }
348
- }
349
- const principal = caller.principalId.trim();
350
- const candidates = listControllerSessions({ controllerHome, repoId: repository.repoId })
351
- .filter((owner) => (owner.principalId?.trim() || owner.controllerId) === principal)
352
- .map((owner) => ({ owner, work: getWorkContract({ controllerHome, repoId: repository.repoId }, owner.workId) }))
353
- .filter((entry): entry is { owner: ReturnType<typeof listControllerSessions>[number]; work: NonNullable<ReturnType<typeof getWorkContract>> } => Boolean(entry.work && !isTerminalWorkContractStatus(entry.work.status) && (!entry.work.checkoutId || entry.work.checkoutId === repository.activeCheckoutId)));
354
- if (candidates.length === 1) return candidates[0].work.workId;
355
- if (candidates.length > 1) throw new Error(`WORK_ATTRIBUTION_AMBIGUOUS: principal ${principal} owns ${candidates.length} active Works on checkout ${repository.activeCheckoutId}`);
356
- return undefined;
357
- }
358
-
359
- function claimedSessionEditBinding(
293
+ export function claimedSessionEditBinding(
360
294
  controllerHome: string,
361
295
  repository: ReturnType<typeof resolveRepositorySelection>,
362
296
  caller?: RepositoryToolCallerContext,
363
297
  explicitWorkId?: unknown,
364
298
  ): EditSessionBinding | undefined {
365
- const workId = claimedSessionWorkId(controllerHome, repository, caller, explicitWorkId);
299
+ const workId = resolveClaimedRepositoryWorkId(controllerHome, repository, caller, explicitWorkId);
366
300
  if (!workId || !caller?.principalId?.trim()) return undefined;
367
301
  const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
368
302
  if (!work) return undefined;
@@ -376,8 +310,52 @@ function claimedSessionEditBinding(
376
310
  };
377
311
  }
378
312
 
379
- function activeWorkflowRawDefaultMergeBlocked(repository: ReturnType<typeof resolveRepositorySelection>, workId: string | undefined, command: unknown): boolean {
380
- if (!workId || !repository.defaultBranch) return false;
313
+ function safePatchEditBinding(
314
+ controllerHome: string,
315
+ repository: ReturnType<typeof resolveRepositorySelection>,
316
+ caller: RepositoryToolCallerContext | undefined,
317
+ args: Record<string, unknown>,
318
+ ): EditSessionBinding | undefined {
319
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
320
+ if (explicitWorkId) return claimedSessionEditBinding(controllerHome, repository, caller, explicitWorkId);
321
+ const sessionId = typeof args.session_id === 'string' ? args.session_id.trim() : '';
322
+ if (sessionId) {
323
+ const existingWorkId = getEditSession(repository.canonicalRoot, sessionId).workId?.trim();
324
+ return existingWorkId
325
+ ? claimedSessionEditBinding(controllerHome, repository, caller, existingWorkId)
326
+ : undefined;
327
+ }
328
+ assertNoBoundExecutionSessionMutation(controllerHome, repository, caller);
329
+ return undefined;
330
+ }
331
+
332
+ function resolveRepositorySelectionForClaimedWork(
333
+ controllerHome: string,
334
+ args: Record<string, unknown>,
335
+ repoIdValue: string,
336
+ caller?: RepositoryToolCallerContext,
337
+ ): ReturnType<typeof resolveRepositorySelection> {
338
+ const checkoutId = typeof args.checkout_id === 'string' ? args.checkout_id.trim() : '';
339
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
340
+ const repository = resolveRepositorySelection({
341
+ repoId: repoIdValue || undefined,
342
+ checkoutId: checkoutId || undefined,
343
+ controllerHome,
344
+ allowSoleRepository: true,
345
+ });
346
+ if (!explicitWorkId || checkoutId) return repository;
347
+ const work = resolveExplicitClaimedRepositoryWork(controllerHome, repository, caller, explicitWorkId);
348
+ if (!work?.checkoutId || work.checkoutId === repository.activeCheckoutId) return repository;
349
+ return resolveRepositorySelection({
350
+ repoId: repository.repoId,
351
+ checkoutId: work.checkoutId,
352
+ controllerHome,
353
+ allowSoleRepository: true,
354
+ });
355
+ }
356
+
357
+ function rawDefaultBranchMergeCommand(repository: ReturnType<typeof resolveRepositorySelection>, command: unknown): boolean {
358
+ if (!repository.defaultBranch) return false;
381
359
  const checkout = repository.checkouts.find((entry) => entry.checkoutId === repository.activeCheckoutId);
382
360
  if (checkout?.branch !== repository.defaultBranch) return false;
383
361
  const normalized = normalizeRepositoryCommand(command);
@@ -388,6 +366,32 @@ function activeWorkflowRawDefaultMergeBlocked(repository: ReturnType<typeof reso
388
366
  return /(?:^|[;&|]\s*)git\s+merge\b/.test(normalized.shellCommand ?? '');
389
367
  }
390
368
 
369
+ interface HistoricalReadOnlyWorkContext {
370
+ workId: string;
371
+ status: string;
372
+ mode: 'historical_read_only_observation';
373
+ attribution: 'not_active_work';
374
+ }
375
+
376
+ function historicalReadOnlyWorkContext(
377
+ controllerHome: string,
378
+ repository: ReturnType<typeof resolveRepositorySelection>,
379
+ args: Record<string, unknown>,
380
+ ): HistoricalReadOnlyWorkContext | undefined {
381
+ const requestedWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
382
+ if (!requestedWorkId) return undefined;
383
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, requestedWorkId);
384
+ if (!work || !isTerminalWorkContractStatus(work.status)) return undefined;
385
+ const classification = classifyRepositoryCommand(args.command as string | string[], repository.defaultBranch);
386
+ if (classification.risk !== 'readonly') return undefined;
387
+ return {
388
+ workId: work.workId,
389
+ status: work.status,
390
+ mode: 'historical_read_only_observation',
391
+ attribution: 'not_active_work',
392
+ };
393
+ }
394
+
391
395
  function resolveRepositoryCommandTarget(
392
396
  controllerHome: string,
393
397
  args: Record<string, unknown>,
@@ -397,6 +401,7 @@ function resolveRepositoryCommandTarget(
397
401
  repository: ReturnType<typeof resolveRepositorySelection>;
398
402
  executionIdentity: ResolvedExecutionIdentity;
399
403
  workspace?: { workspaceId: string; root: string; registered: false };
404
+ historicalWorkContext?: HistoricalReadOnlyWorkContext;
400
405
  } {
401
406
  const workspaceRoot = typeof args.workspace_root === 'string' ? args.workspace_root.trim() : '';
402
407
  const checkoutId = typeof args.checkout_id === 'string' ? args.checkout_id.trim() : '';
@@ -417,13 +422,38 @@ function resolveRepositoryCommandTarget(
417
422
  workspace: { workspaceId: target.workspaceId, root: target.canonicalRoot, registered: false },
418
423
  };
419
424
  }
420
- const repository = resolveRepositorySelection({
425
+ // A terminal Work no longer owns mutation or checkout authority. An exact
426
+ // terminal work_id may still accompany a read-only observation immediately
427
+ // after finalize, so preserve it as explicit historical context while
428
+ // executing against the caller-selected/current repository checkout. Any
429
+ // non-read-only command continues through claimed Work resolution and fails
430
+ // closed for terminal attribution.
431
+ const selectedRepository = resolveRepositorySelection({
421
432
  repoId: repoIdValue || undefined,
422
433
  checkoutId: checkoutId || undefined,
423
434
  controllerHome,
424
435
  allowSoleRepository: true,
425
436
  });
426
- const workId = claimedSessionWorkId(controllerHome, repository, caller, args.work_id);
437
+ const historicalWorkContext = historicalReadOnlyWorkContext(controllerHome, selectedRepository, args);
438
+ if (historicalWorkContext) {
439
+ return {
440
+ repository: selectedRepository,
441
+ executionIdentity: {
442
+ schemaVersion: 1,
443
+ authority: 'repository',
444
+ repositoryId: selectedRepository.repoId,
445
+ checkoutId: selectedRepository.activeCheckoutId,
446
+ canonicalRoot: selectedRepository.canonicalRoot,
447
+ },
448
+ historicalWorkContext,
449
+ };
450
+ }
451
+
452
+ const repository = resolveRepositorySelectionForClaimedWork(controllerHome, args, repoIdValue, caller);
453
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
454
+ const workId = explicitWorkId
455
+ ? resolveClaimedRepositoryWorkId(controllerHome, repository, caller, explicitWorkId)
456
+ : undefined;
427
457
  return {
428
458
  repository,
429
459
  executionIdentity: {
@@ -523,6 +553,33 @@ function failure(error: unknown): RepositoryToolResult {
523
553
  return { ...result({ error: { code, message, ...(compactDetails !== undefined ? { details: compactDetails } : {}) } }), isError: true };
524
554
  }
525
555
 
556
+ interface RepositoryExplorationGuidance {
557
+ code: 'FRAGMENTED_REPOSITORY_EXPLORATION';
558
+ recommendedTool: 'rh_context';
559
+ recommendedOperation: 'search';
560
+ message: string;
561
+ }
562
+
563
+ function fragmentedRepositoryExplorationGuidance(command: unknown): RepositoryExplorationGuidance | undefined {
564
+ if (typeof command !== 'string') return undefined;
565
+ const segments = command.split(/(?:&&|\|\||[;|])/g);
566
+ const readPrograms = new Set<string>();
567
+ let readStepCount = 0;
568
+ for (const segment of segments) {
569
+ const match = segment.trim().match(/^(?:(?:command|env)\s+)?(?:[A-Za-z_][A-Za-z0-9_]*=[^\s]+\s+)*(rg|grep|sed|cat|head|tail)\b/i);
570
+ if (!match) continue;
571
+ readStepCount += 1;
572
+ readPrograms.add(match[1]!.toLowerCase());
573
+ }
574
+ if (readStepCount < 2) return undefined;
575
+ return {
576
+ code: 'FRAGMENTED_REPOSITORY_EXPLORATION',
577
+ recommendedTool: 'rh_context',
578
+ recommendedOperation: 'search',
579
+ message: `Detected ${readStepCount} chained repository read steps (${[...readPrograms].join(', ')}). Prefer a broad rh_context request first, then repeat rh_context with selected paths or relationships when more evidence can improve correctness; keep shell search as fallback when the Context Plane cannot supply the needed evidence.`,
580
+ };
581
+ }
582
+
526
583
  function compactProcessCommandPayload(input: {
527
584
  accepted?: boolean;
528
585
  mode: string;
@@ -533,6 +590,7 @@ function compactProcessCommandPayload(input: {
533
590
  repoId: string;
534
591
  checkoutId?: string;
535
592
  workspace?: { workspaceId: string; root: string; registered: false };
593
+ historicalWorkContext?: HistoricalReadOnlyWorkContext;
536
594
  processId?: string;
537
595
  process?: unknown;
538
596
  completed?: boolean;
@@ -545,6 +603,7 @@ function compactProcessCommandPayload(input: {
545
603
  /** summary (default) omits nested process / routing dumps; detail restores diagnostics. */
546
604
  detailLevel?: 'summary' | 'detail';
547
605
  includeFullProcess?: boolean;
606
+ guidance?: RepositoryExplorationGuidance;
548
607
  }): Record<string, unknown> {
549
608
  const output = compactCommandOutput(input.stdout, input.stderr, { ok: input.ok === true });
550
609
  const detail = input.detailLevel === 'detail' || input.includeFullProcess === true;
@@ -570,12 +629,17 @@ function compactProcessCommandPayload(input: {
570
629
  repoId: input.repoId,
571
630
  ...(input.checkoutId ? { checkoutId: input.checkoutId } : {}),
572
631
  ...(input.workspace ? { workspace: input.workspace } : {}),
632
+ ...(input.historicalWorkContext ? { historicalWorkContext: input.historicalWorkContext } : {}),
573
633
  ...(input.processId ? { processId: input.processId } : {}),
574
634
  ...(completed ? { exitCode: input.exitCode ?? (ok ? 0 : 1) } : {}),
635
+ // A running default response otherwise loses its only dependency-bound
636
+ // continuation advice when the detailed Process record is omitted.
637
+ ...(!completed ? { next: input.next } : {}),
575
638
  stdout: output.stdout ?? '',
576
639
  stderr: output.stderr ?? '',
577
640
  ...(output.stdoutTruncated ? { stdoutTruncated: true, stdoutBytes: output.stdoutBytes } : {}),
578
641
  ...(output.stderrTruncated ? { stderrTruncated: true, stderrBytes: output.stderrBytes } : {}),
642
+ ...(input.guidance ? { guidance: input.guidance, suggestedOperation: 'rh_context' } : {}),
579
643
  };
580
644
  if (completed && !ok) {
581
645
  payload.error = {
@@ -606,6 +670,7 @@ function compactProcessCommandPayload(input: {
606
670
  repoId: input.repoId,
607
671
  ...(input.checkoutId ? { checkoutId: input.checkoutId } : {}),
608
672
  ...(input.workspace ? { workspace: input.workspace } : {}),
673
+ ...(input.historicalWorkContext ? { historicalWorkContext: input.historicalWorkContext } : {}),
609
674
  ...(input.processId ? { processId: input.processId } : {}),
610
675
  ok: input.ok,
611
676
  exitCode: input.exitCode,
@@ -623,6 +688,7 @@ function compactProcessCommandPayload(input: {
623
688
  }
624
689
  : {}),
625
690
  durableSideEffects: effects,
691
+ ...(input.guidance ? { guidance: input.guidance, suggestedOperation: 'rh_context' } : {}),
626
692
  next: input.next,
627
693
  detailLevel: 'detail',
628
694
  };
@@ -664,10 +730,33 @@ export async function callRepositoryTool(
664
730
  args: Record<string, unknown>,
665
731
  caller?: RepositoryToolCallerContext,
666
732
  ): Promise<RepositoryToolResult | undefined> {
667
- if (!name.startsWith('repository_')) return undefined;
733
+ if (!name.startsWith('repository_') && name !== 'read_repository_file') return undefined;
668
734
  try {
669
735
  const repoIdValue = typeof args.repo_id === 'string' ? args.repo_id.trim() : '';
670
736
  switch (name) {
737
+ case 'read_repository_file': {
738
+ const repository = resolveRepositorySelection({
739
+ repoId: typeof args.repo_id === 'string' ? args.repo_id : undefined,
740
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
741
+ controllerHome,
742
+ allowSoleRepository: true,
743
+ });
744
+ const path = String(args.path ?? '').trim();
745
+ if (!path) throw new Error('REPOSITORY_PATH_REQUIRED: path is required');
746
+ const session = caller?.sessionId
747
+ ? { sessionId: caller.sessionId, repoId: repository.repoId, checkoutId: repository.activeCheckoutId }
748
+ : undefined;
749
+ const range = readRepositoryRange(
750
+ repository.canonicalRoot,
751
+ getMcpPolicy('controller', { repoRoot: repository.canonicalRoot }),
752
+ path,
753
+ typeof args.start_line === 'number' ? args.start_line : 1,
754
+ typeof args.end_line === 'number' ? args.end_line : 200,
755
+ session,
756
+ );
757
+ const redacted = redactMcpText(range.content);
758
+ return result({ ...range, content: redacted.text, redactions: redacted.redactions });
759
+ }
671
760
  case 'repository_register': {
672
761
  const startedAt = performance.now();
673
762
  const registerInput = {
@@ -824,38 +913,6 @@ export async function callRepositoryTool(
824
913
  checkout_id: typeof args.checkout_id === 'string' ? args.checkout_id : payload.checkout_id,
825
914
  });
826
915
  }
827
-
828
-
829
-
830
- case 'repository_goal_list': {
831
- const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
832
- return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, registry: readRepositoryGoalRegistry(repository) });
833
- }
834
- case 'repository_goal_upsert': {
835
- const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
836
- return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, ...upsertRepositoryGoal(repository, { id: args.id, title: args.title, status: args.status, checks: args.checks, notes: args.notes }) as unknown as Record<string, unknown> });
837
- }
838
- case 'repository_stuck_diagnose': {
839
- const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
840
- return result({ diagnosis: diagnoseRepositoryStuckState(repository) });
841
- }
842
-
843
- case 'repository_goal_run': {
844
- const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
845
- const ran = withControllerLock(
846
- controllerHome,
847
- { scope: 'repository', repoId: repository.repoId },
848
- 'mcp:repository_goal_run',
849
- () => runRepositoryGoal(repository, { goalId: args.goal_id, runChecks: args.run_checks }),
850
- 60_000,
851
- );
852
- return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, ...ran as unknown as Record<string, unknown> });
853
- }
854
- case 'repository_goal_runs': {
855
- const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
856
- const limit = typeof args.limit === 'number' ? args.limit : typeof args.limit === 'string' ? Number(args.limit) : undefined;
857
- return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, runs: listRepositoryGoalRuns(repository, limit) });
858
- }
859
916
  case 'repository_git_status': {
860
917
  const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
861
918
  const status = args.refresh === true
@@ -917,13 +974,18 @@ export async function callRepositoryTool(
917
974
  });
918
975
  }
919
976
  case 'repository_safe_patch_apply': {
920
- const repository = resolveRepositorySelection({
921
- repoId: repoIdValue || undefined,
922
- checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
923
- controllerHome,
924
- allowSoleRepository: true,
925
- });
926
- const binding = claimedSessionEditBinding(controllerHome, repository, caller, args.work_id);
977
+ const repository = resolveRepositorySelectionForClaimedWork(controllerHome, args, repoIdValue, caller);
978
+ const binding = safePatchEditBinding(controllerHome, repository, caller, args);
979
+ if (binding?.workId) {
980
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, binding.workId);
981
+ if (!work) throw new Error(`WORK_NOT_FOUND: ${binding.workId}`);
982
+ const requestedPaths = buildSafePatchPlan(repository, { operations: args.operations, chunkSize: args.chunk_size })
983
+ .chunks.flatMap((chunk) => chunk.paths);
984
+ assertWorkPathsWithinScope(work, requestedPaths, {
985
+ forbidden: 'WORK_MUTATION_FORBIDDEN_PATH',
986
+ outOfScope: 'WORK_MUTATION_PATH_OUT_OF_SCOPE',
987
+ });
988
+ }
927
989
  const applied = withControllerLock(
928
990
  controllerHome,
929
991
  { scope: 'repository', repoId: repository.repoId },
@@ -1022,10 +1084,16 @@ export async function callRepositoryTool(
1022
1084
  });
1023
1085
  }
1024
1086
  case 'repository_command_execute': {
1087
+ const explorationGuidance = fragmentedRepositoryExplorationGuidance(args.command);
1025
1088
  const target = resolveRepositoryCommandTarget(controllerHome, args, repoIdValue, caller);
1026
- const { repository, executionIdentity } = target;
1027
- if (activeWorkflowRawDefaultMergeBlocked(repository, executionIdentity.workId, args.command)) {
1028
- throw new Error(`WORK_DELIVERY_REQUIRES_FINALIZE: ${executionIdentity.workId} must pass Work verification and use rh_work finalize before default-branch integration`);
1089
+ const { repository, executionIdentity, historicalWorkContext } = target;
1090
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
1091
+ if (!explicitWorkId) assertNoBoundExecutionSessionMutation(controllerHome, repository, caller);
1092
+ const deliveryWorkId = rawDefaultBranchMergeCommand(repository, args.command)
1093
+ ? executionIdentity.workId
1094
+ : undefined;
1095
+ if (deliveryWorkId) {
1096
+ throw new Error(`WORK_DELIVERY_REQUIRES_FINALIZE: ${deliveryWorkId} must pass Work verification and use rh_work finalize before default-branch integration`);
1029
1097
  }
1030
1098
  const timeoutMs = typeof args.timeout_ms === 'number'
1031
1099
  ? args.timeout_ms
@@ -1052,16 +1120,33 @@ export async function callRepositoryTool(
1052
1120
  const forceDurable = fromDurableWorker
1053
1121
  || args.mode === 'durable'
1054
1122
  || args.force_durable === true;
1055
- const routingDecision = routeExecution({
1056
- operation: 'repository_command_execute',
1057
- mode: forceDurable ? 'durable' : args.mode === 'fast' ? 'fast' : 'auto',
1058
- command: args.command as string | string[] | undefined,
1059
- timeoutMs,
1060
- background: forceDurable && (args.background === true || args.async === true),
1061
- defaultBranch: repository.defaultBranch,
1062
- approvalContinuation: typeof args.approval_request_id === 'string'
1063
- || typeof args.approval_token === 'string',
1064
- });
1123
+ // repository_command_execute owns its command execution architecture.
1124
+ // Do not send ordinary command text through Thin Harness semantic risk
1125
+ // routing: that creates duplicate regex/classifier policy and turns
1126
+ // unknown CLI shapes into false durable/reject decisions. Only an
1127
+ // explicitly requested durable mode uses the generic durable router.
1128
+ const routingDecision = forceDurable
1129
+ ? routeExecution({
1130
+ operation: 'repository_command_execute',
1131
+ mode: 'durable',
1132
+ timeoutMs,
1133
+ background: args.background === true || args.async === true,
1134
+ })
1135
+ : {
1136
+ mode: 'fast' as const,
1137
+ reasons: ['repository_command_process_runtime_authoritative'],
1138
+ risk: 'unknown' as const,
1139
+ estimatedClass: 'unknown' as const,
1140
+ requiresIsolation: false,
1141
+ requiresRecovery: false,
1142
+ suggestedOperation: undefined,
1143
+ effects: {
1144
+ readsWorkspace: true,
1145
+ mutatesWorkspace: true,
1146
+ mutatesGitRefs: false,
1147
+ remoteWrite: false,
1148
+ },
1149
+ };
1065
1150
  // Unified Process Runtime for local commands (Direct/Managed) when not forced durable.
1066
1151
  // Ephemeral workspaces cannot persist a recoverable async Process handle,
1067
1152
  // so their async requests remain promotion-required below.
@@ -1072,7 +1157,50 @@ export async function callRepositoryTool(
1072
1157
  defaultBranch: repository.defaultBranch,
1073
1158
  timeoutMs,
1074
1159
  });
1075
- if (routeClass.route === 'process_direct' || routeClass.route === 'process_managed') {
1160
+ if (routeClass.route === 'reject') {
1161
+ return result({
1162
+ accepted: false,
1163
+ mode: 'reject',
1164
+ path: routeClass.reason,
1165
+ route: 'reject',
1166
+ status: 'rejected',
1167
+ repoId: repository.repoId,
1168
+ checkoutId: repository.activeCheckoutId,
1169
+ workspace: target.workspace,
1170
+ message: routeClass.reason === 'standalone_recovery_lifecycle_required'
1171
+ ? 'Canonical Runtime and connector lifecycle operations must be submitted to the standalone Forge Recovery owner, not executed as a child of repository Process Runtime.'
1172
+ : 'Repository command execution was rejected by Process Runtime routing policy.',
1173
+ suggestedOperation: routeClass.reason === 'standalone_recovery_lifecycle_required'
1174
+ ? 'Use the standalone Forge Recovery connector.'
1175
+ : routingDecision.suggestedOperation,
1176
+ durableSideEffects: {
1177
+ executionJobCount: 0,
1178
+ localJobCount: 0,
1179
+ workerSpawnCount: 0,
1180
+ projectionUpdateCount: 0,
1181
+ },
1182
+ });
1183
+ }
1184
+ if (routeClass.route === 'durable' && target.workspace) {
1185
+ return result({
1186
+ accepted: false,
1187
+ mode: 'durable',
1188
+ path: 'ephemeral_workspace_promotion_required',
1189
+ repoId: repository.repoId,
1190
+ checkoutId: repository.activeCheckoutId,
1191
+ workspace: target.workspace,
1192
+ message: 'Ephemeral workspaces must be registered before durable remote effects so Process identity and reconciliation have a stable repository authority.',
1193
+ suggestedOperation: 'repository_register',
1194
+ });
1195
+ }
1196
+ if (routeClass.route === 'process_direct' || routeClass.route === 'process_managed' || routeClass.route === 'durable') {
1197
+ const processRequestId = typeof args.request_id === 'string' ? args.request_id.trim() : '';
1198
+ if (executionIdentity.workId && !processRequestId) {
1199
+ throw new Error(
1200
+ `WORK_PROCESS_REQUEST_ID_REQUIRED: ${executionIdentity.workId}; `
1201
+ + 'Work-attributed Process Runtime commands require a stable request_id so reconnect/retry can resume the original Process without duplicate execution.',
1202
+ );
1203
+ }
1076
1204
  const processResult = await executeRepositoryCommandViaProcessRuntime({
1077
1205
  controllerHome,
1078
1206
  repository,
@@ -1084,7 +1212,7 @@ export async function callRepositoryTool(
1084
1212
  : typeof args.interactive_wait_ms === 'number' ? args.interactive_wait_ms : undefined,
1085
1213
  maxOutputBytes,
1086
1214
  returnHandleImmediately,
1087
- requestId: typeof args.request_id === 'string' ? args.request_id : undefined,
1215
+ requestId: processRequestId || undefined,
1088
1216
  workId: executionIdentity.workId,
1089
1217
  executionIdentity,
1090
1218
  allowNonGitWorkspace: target.workspace !== undefined,
@@ -1139,6 +1267,7 @@ export async function callRepositoryTool(
1139
1267
  repoId: repository.repoId,
1140
1268
  checkoutId: repository.activeCheckoutId,
1141
1269
  workspace: target.workspace,
1270
+ historicalWorkContext,
1142
1271
  processId: handle?.processId,
1143
1272
  process: detailLevel === 'detail' ? handle : undefined,
1144
1273
  completed,
@@ -1147,13 +1276,14 @@ export async function callRepositoryTool(
1147
1276
  stdout: processResult.stdout,
1148
1277
  stderr: processResult.stderr,
1149
1278
  durableSideEffects: processResult.durableSideEffects,
1279
+ guidance: explorationGuidance,
1150
1280
  next: directWithoutHandle
1151
1281
  ? processResult.reason === 'readonly_fast_path'
1152
1282
  ? 'Bounded readonly execution completed without Process record / Lease / Local Job / ExecutionJob / Worker.'
1153
1283
  : 'Bounded ephemeral workspace execution completed without Process record / Lease / Local Job / ExecutionJob / Worker.'
1154
1284
  : processResult.route === 'process_direct' || completed
1155
1285
  ? 'Process Runtime completed without Local Job / ExecutionJob / Worker.'
1156
- : `Process Runtime is managing ${handle?.processId}; poll process_get/process_wait instead of creating a Local Job.`,
1286
+ : `Process Runtime is managing ${handle?.processId}. Continue independent work; use process_get only if an observation can change the next decision, then join with process_wait when this result becomes a dependency. Do not re-run or periodically poll.`,
1157
1287
  detailLevel,
1158
1288
  });
1159
1289
  payload.status = status;
@@ -1270,6 +1400,12 @@ export async function callRepositoryTool(
1270
1400
  routing: compactRoutingSummary({ path: 'durable', mode: 'durable', reasons: [routingDecision.reasons.join(','), 'local_bridge_execution_retired'] }),
1271
1401
  message: 'This command requires explicit external Controller handling; Local Bridge Jobs are retired for repository commands.',
1272
1402
  suggestedOperation: 'Create or claim WorkContract, then use rh_work.launcher_start or a Process Runtime-compatible command.',
1403
+ externalEffect: {
1404
+ outcome: 'not_started',
1405
+ ambiguousFailureOutcome: 'outcome_unknown',
1406
+ replayPolicy: 'never_auto_retry',
1407
+ reconciliation: 'After an ambiguous dispatch failure, inspect the remote ref/registry/release state before deciding whether to retry.',
1408
+ },
1273
1409
  });
1274
1410
  }
1275
1411
  case 'repository_batch_execute': {