@moretea-labs/forge 1.5.0-rc.1 → 1.5.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 (264) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +27 -1
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +52 -55
  7. package/SKILL.md +8 -11
  8. package/THIRD_PARTY_NOTICES.md +9 -3
  9. package/assets/branding/README.md +4 -2
  10. package/assets/hooks/lib/workflow-state.sh +19 -18
  11. package/assets/hooks/post-edit-guard.sh +6 -75
  12. package/assets/hooks/prompt-guard.sh +24 -12
  13. package/assets/hooks/session-start-context.sh +17 -17
  14. package/assets/hooks/stop-orchestrator.sh +2 -2
  15. package/assets/partials/04-project-structure.partial.md +6 -6
  16. package/assets/partials/05-workflow.partial.md +6 -6
  17. package/assets/partials/07-footer.partial.md +1 -1
  18. package/assets/partials/08-orchestration.partial.md +2 -2
  19. package/assets/partials-agents/02-operating-mode.partial.md +5 -5
  20. package/assets/partials-agents/03-orchestration.partial.md +3 -3
  21. package/assets/partials-agents/04-task-protocol.partial.md +3 -3
  22. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  23. package/assets/reference-configs/agentic-development-flow.md +49 -86
  24. package/assets/reference-configs/external-tooling.md +21 -61
  25. package/assets/reference-configs/handoff-protocol.md +25 -20
  26. package/assets/reference-configs/harness-overview.md +8 -8
  27. package/assets/skill-version.json +2 -2
  28. package/assets/templates/helpers/archive-workflow.sh +0 -1
  29. package/assets/templates/helpers/capture-plan.sh +3 -3
  30. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  31. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  32. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  33. package/assets/templates/helpers/new-plan.sh +1 -1
  34. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  35. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  36. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  37. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  38. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  39. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  40. package/assets/templates/helpers/verify-sprint.sh +7 -7
  41. package/assets/templates/helpers/workstream-sync.sh +1 -1
  42. package/assets/templates/plan.template.md +1 -1
  43. package/assets/workflow-contract.v1.json +6 -3
  44. package/docs/architecture/current/README.md +10 -9
  45. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  46. package/docs/architecture/current/architecture-invariants.md +2 -2
  47. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  48. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  49. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  50. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  51. package/docs/architecture/current/entity-model.md +0 -27
  52. package/docs/architecture/current/failure-recovery.md +0 -24
  53. package/docs/architecture/current/governance.md +1 -1
  54. package/docs/architecture/current/implementation-status.md +1 -1
  55. package/docs/architecture/current/legacy-governance.md +58 -0
  56. package/docs/architecture/current/multi-repository-execution.md +1 -1
  57. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  58. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  59. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  60. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  61. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  62. package/docs/architecture/current/thin-harness-v1.md +9 -11
  63. package/docs/architecture/domains/runtime-harness.md +1 -1
  64. package/docs/forge-autonomous-goal-loop.md +3 -1
  65. package/docs/forge-chatgpt-mcp-setup.md +67 -176
  66. package/docs/images/forge-banner-cn.svg +26 -1
  67. package/docs/images/forge-banner.svg +26 -1
  68. package/docs/operations/features.md +1 -1
  69. package/docs/operations/homebrew.md +3 -3
  70. package/docs/operations/mcp-tool-exposure.md +5 -5
  71. package/docs/operations/platform-support.md +45 -37
  72. package/docs/operations/platform-support.zh-CN.md +40 -38
  73. package/docs/operations/releasing.md +3 -3
  74. package/docs/operations/releasing.zh-CN.md +3 -3
  75. package/docs/operations/troubleshooting.md +11 -6
  76. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  77. package/docs/public-usage-guide.md +28 -21
  78. package/docs/public-usage-guide.zh-CN.md +28 -21
  79. package/docs/tutorials/01-install-and-start.md +58 -35
  80. package/docs/tutorials/01-install-and-start.zh-CN.md +58 -35
  81. package/docs/tutorials/02-connect-chatgpt.md +118 -46
  82. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +119 -45
  83. package/docs/wiki/Architecture.md +1 -1
  84. package/docs/wiki/Installation.md +2 -2
  85. package/docs/wiki/Quick-Start.md +1 -1
  86. package/docs/wiki/Work-Lifecycle.md +1 -1
  87. package/install.ps1 +4 -5
  88. package/install.sh +4 -6
  89. package/package.json +15 -5
  90. package/scripts/archive-workflow.sh +0 -1
  91. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  92. package/scripts/build-controller-ui.ts +28 -0
  93. package/scripts/capture-plan.sh +4 -4
  94. package/scripts/check-agent-tooling.sh +12 -1
  95. package/scripts/check-platform-support.mjs +2 -2
  96. package/scripts/check-public-docs.sh +16 -5
  97. package/scripts/check-release-readiness.sh +5 -11
  98. package/scripts/check-runtime-architecture.mjs +14 -22
  99. package/scripts/check-tarball-install-smoke.sh +3 -2
  100. package/scripts/check-task-workflow.sh +2 -2
  101. package/scripts/codex-handoff-resume.sh +5 -5
  102. package/scripts/create-project-dirs.sh +3 -3
  103. package/scripts/ensure-task-workflow.sh +45 -35
  104. package/scripts/init-project.sh +4 -4
  105. package/scripts/install-desktop-operator-registration.ts +2 -2
  106. package/scripts/lib/project-init-lib.sh +46 -44
  107. package/scripts/migrate-project-template.sh +7 -4
  108. package/scripts/new-plan.sh +1 -1
  109. package/scripts/plan-to-todo.sh +2 -2
  110. package/scripts/prepare-codex-handoff.sh +2 -2
  111. package/scripts/prepare-handoff.sh +9 -9
  112. package/scripts/refresh-current-status.sh +2 -2
  113. package/scripts/setup-plugins.sh +3 -3
  114. package/scripts/ship-worktrees.sh +1 -1
  115. package/scripts/sprint-backlog.sh +5 -5
  116. package/scripts/sync-codex-installed-copies.sh +2 -3
  117. package/scripts/verify-sprint.sh +7 -7
  118. package/scripts/workstream-sync.sh +1 -1
  119. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  120. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  121. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  122. package/src/cli/commands/chatgpt.ts +51 -0
  123. package/src/cli/commands/doctor.ts +1 -1
  124. package/src/cli/commands/init-hook.ts +213 -70
  125. package/src/cli/commands/init.ts +5 -6
  126. package/src/cli/commands/mcp.ts +31 -5
  127. package/src/cli/commands/runtime.ts +61 -8
  128. package/src/cli/commands/setup-profile.ts +472 -0
  129. package/src/cli/controller/check-runner.ts +20 -3
  130. package/src/cli/controller/codex-continuation.ts +4 -7
  131. package/src/cli/controller/context-pack.ts +118 -14
  132. package/src/cli/controller/operational-plan.ts +4 -4
  133. package/src/cli/controller/task-ledger.ts +10 -10
  134. package/src/cli/controller/work-mode.ts +6 -13
  135. package/src/cli/index.ts +14 -35
  136. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  137. package/src/cli/local-bridge/console-assets.ts +17 -0
  138. package/src/cli/local-bridge/console-automations.ts +160 -0
  139. package/src/cli/local-bridge/dashboard.ts +8 -1303
  140. package/src/cli/local-bridge/facade-api.ts +16 -7
  141. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  142. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  144. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  146. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  147. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  148. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  149. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  150. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  152. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  153. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  155. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  156. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  157. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  158. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  159. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  160. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  161. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  162. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  163. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  164. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  165. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  166. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  167. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  168. package/src/cli/local-bridge/server.ts +45 -0
  169. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  170. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  171. package/src/cli/mcp/auth.ts +2 -0
  172. package/src/cli/mcp/instructions.ts +3 -3
  173. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  174. package/src/cli/mcp/multi-repository.ts +2 -0
  175. package/src/cli/mcp/policy.ts +7 -5
  176. package/src/cli/mcp/repository-tools.ts +53 -14
  177. package/src/cli/mcp/server.ts +5 -10
  178. package/src/cli/mcp/setup.ts +220 -276
  179. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  180. package/src/cli/mcp/toolset-names.ts +1 -1
  181. package/src/cli/mcp/toolset.ts +0 -1
  182. package/src/cli/mcp/transports/http.ts +51 -4
  183. package/src/cli/repositories/branch-name-policy.ts +0 -7
  184. package/src/cli/repositories/controller-home.ts +110 -3
  185. package/src/cli/repositories/safe-patch.ts +1 -1
  186. package/src/cli/repositories/selected-path-actions.ts +26 -24
  187. package/src/core/adoption/gitignore-plan.ts +2 -3
  188. package/src/core/adoption/plan.ts +4 -1
  189. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  190. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  191. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  192. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  193. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  194. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  195. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  196. package/src/runtime/control-plane/facade/types.ts +0 -1
  197. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  198. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  199. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  200. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  201. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  202. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  203. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  204. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  205. package/src/runtime/evidence/event-ledger.ts +1 -1
  206. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  207. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  208. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  209. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  210. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  211. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  212. package/src/runtime/execution/process-runtime/index.ts +3 -0
  213. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  214. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  215. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  216. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  217. package/src/runtime/execution/process-runtime/store.ts +1 -19
  218. package/src/runtime/execution/process-runtime/types.ts +4 -0
  219. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  220. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  221. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  222. package/src/runtime/execution/thin-harness/types.ts +0 -1
  223. package/src/runtime/execution/workers/executor.ts +1 -1
  224. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  225. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  226. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  227. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  228. package/src/runtime/gateway/mcp/router.ts +117 -182
  229. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  230. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  231. package/src/runtime/plugins/browser-adapter.ts +5 -0
  232. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  233. package/src/runtime/plugins/store.ts +14 -6
  234. package/src/runtime/plugins/types.ts +1 -1
  235. package/src/runtime/projections/materialized-view.ts +1 -23
  236. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  237. package/src/runtime/resources/ownership.ts +0 -1
  238. package/src/runtime/root/gateway-adapter.ts +2 -0
  239. package/src/runtime/root/mcp-transport.ts +10 -2
  240. package/src/runtime/root/package-connector-service.ts +136 -0
  241. package/src/runtime/root/package-runtime-release.ts +161 -0
  242. package/src/runtime/root/package-runtime-service.ts +185 -0
  243. package/src/runtime/root/release-materialize.ts +19 -0
  244. package/src/runtime/root/runtime.ts +2 -1
  245. package/src/runtime/root/scheduler.ts +5 -0
  246. package/src/runtime/root/service.ts +18 -5
  247. package/src/runtime/standalone-recovery/core.ts +65 -16
  248. package/src/runtime/workflow/findings/store.ts +145 -106
  249. package/src/runtime/workflow/findings/types.ts +22 -0
  250. package/src/runtime/workflow/schedules/engine.ts +132 -2
  251. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  252. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  253. package/docs/CHANGELOG.md +0 -978
  254. package/docs/README.zh-CN.md +0 -58
  255. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  256. package/src/cli/commands/controller.ts +0 -680
  257. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  258. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  259. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  260. package/src/runtime/workflow/campaigns/review.ts +0 -315
  261. package/src/runtime/workflow/campaigns/store.ts +0 -526
  262. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  263. package/src/runtime/workflow/campaigns/types.ts +0 -290
  264. /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
@@ -14,9 +14,10 @@ import { repositoryControllerRoot } from '../../../cli/repositories/controller-h
14
14
  import { cancelExecutionJob, findExecutionJob, getExecutionJob, getExecutionJobByRequestId, listExecutionJobs } from '../../execution/jobs/store';
15
15
  import { waitForExecutionJob } from '../../execution/jobs/wait';
16
16
  import type { ExecutionJob } from '../../execution/jobs/types';
17
- import { getProcessHandle, processRuntimeResourceDiagnostics, waitForProcess } from '../../execution/process-runtime';
17
+ import { getProcessHandle, getProcessRecord, processCheckCompletionReceipt, processRuntimeResourceDiagnostics, readPersistedCheckResultReceipt, runPersistedCheckViaProcessRuntime, waitForProcess } from '../../execution/process-runtime';
18
18
  import { buildJobOperationDigest } from '../../control-plane/facade/operation-digest';
19
19
  import { readWorkHandle, type WorkHandleState } from '../../control-plane/execution/work-handle-store';
20
+ import { executionIdentityForRepository } from '../../control-plane/execution/execution-identity';
20
21
  import { reconcileFinalizedDirectEditWorksAfterCommit } from '../../control-plane/execution/direct-edit-work-completion';
21
22
  import { readJobEvents } from '../../evidence/event-ledger';
22
23
  import { readExecutionArtifact } from '../../evidence/artifact-store';
@@ -41,6 +42,15 @@ import {
41
42
  } from '../../health';
42
43
  import { applyScheduleDedupe, buildScheduleDedupeReport, createSchedule, getSchedule, getScheduleDecision, listOccurrences, listSchedules, saveSchedule } from '../../workflow/schedules/store';
43
44
  import { evaluateSchedule } from '../../workflow/schedules/engine';
45
+ import {
46
+ createWorkContinuationSchedule,
47
+ getWorkContinuationSchedule,
48
+ listWorkContinuationSchedules,
49
+ pauseWorkContinuationSchedule,
50
+ resumeWorkContinuationSchedule,
51
+ triggerWorkContinuationSchedule,
52
+ type ContinuationControllerType,
53
+ } from '../../workflow/schedules/work-continuation';
44
54
  import { createPortfolioWorkflow, getPortfolioWorkflow, listPortfolioWorkflows } from '../../workflow/portfolio/store';
45
55
  import { claimsForMcpOperation } from './resource-policy';
46
56
  import {
@@ -55,16 +65,6 @@ import {
55
65
  } from './router';
56
66
  import { assertAutomatedOperationAllowed } from '../../control-plane/governance/external-effects';
57
67
  import { getCandidateFinding, listCandidateFindings, recordCandidateFinding } from '../../workflow/findings/store';
58
- import { addCampaignTask, createCampaign, getCampaign, listCampaigns, setCampaignStatus, validateCreateCampaignTasks } from '../../workflow/campaigns/store';
59
- import { reconcileCampaign } from '../../workflow/campaigns/engine';
60
- import { submitCampaignReview } from '../../workflow/campaigns/review';
61
- import type { CampaignReviewPolicy, CampaignSupervisorAction, CreateCampaignTaskInput } from '../../workflow/campaigns/types';
62
- import { currentCampaignWorkspace, ensureCampaignWorkspace } from '../../workflow/campaigns/workspace';
63
- import { cancelCampaign, completeCampaignWorkspace } from '../../workflow/campaigns/cleanup';
64
- import {
65
- assertCampaignOperationSupported,
66
- normalizeCampaignDependencyReferences,
67
- } from '../../workflow/campaigns/normalize';
68
68
  import { ensureRepositoryRuntimeStorage } from '../../../cli/repositories/runtime-storage';
69
69
  import { assessWorkMode, parseExplicitTaskMode } from '../../../cli/controller/work-mode';
70
70
  import { projectBoard } from '../../../cli/controller/issue-store';
@@ -72,16 +72,16 @@ import {
72
72
  buildControllerTaskLedgerProjection,
73
73
  writeControllerTaskLedgerArtifacts,
74
74
  } from '../../../cli/controller/task-ledger';
75
- import { buildControllerContextPack } from '../../../cli/controller/context-pack';
75
+ import { buildControllerContextPack, CONTROLLER_CONTEXT_IMPACT_DOMAINS, type ControllerContextImpactDomain } from '../../../cli/controller/context-pack';
76
76
  import { legacyIssueAuthorityRetired } from '../../../cli/controller/legacy-issue-cutover';
77
77
  import { buildControllerOperationalPlan } from '../../../cli/controller/operational-plan';
78
- import { listControllerChecks, readLatestControllerCheckEvidence, runControllerCheck } from '../../../cli/controller/check-runner';
78
+ import { listControllerChecks, readLatestControllerCheckEvidence } from '../../../cli/controller/check-runner';
79
79
  import { repositoryChangeVerify } from '../../../cli/controller/composite-operations';
80
80
  import { listActiveAgentJobSnapshots } from '../../../cli/agent-jobs/job-manager';
81
81
  import { readAgentExecutableReadinessSnapshot } from '../../../cli/agent-jobs/executable-resolver';
82
82
  import {
83
83
  commitSelectedPaths,
84
- prepareFallbackHandoffArtifacts,
84
+ prepareTransferArtifacts,
85
85
  selectedPathDiff,
86
86
  stageSelectedPaths,
87
87
  } from '../../../cli/repositories/selected-path-actions';
@@ -284,7 +284,9 @@ export const runtimeToolDefinitions: McpToolDefinition[] = [
284
284
  known_paths: { type: 'array', items: { type: 'string' }, description: 'Optional exact paths or globs that should receive highest retrieval priority.' },
285
285
  include_globs: { type: 'array', items: { type: 'string' } },
286
286
  exclude_globs: { type: 'array', items: { type: 'string' } },
287
- structural_context: { type: 'string', enum: ['off', 'auto', 'required'], description: 'Defaults to off. Use auto/required only when call graph, dependency, or impact evidence is useful.' },
287
+ retrieval_mode: { type: 'string', enum: ['implementation', 'plan', 'debug', 'review'], description: 'Defaults to implementation. Implementation returns current raw impact evidence for ChatGPT to judge; plan/debug/review intentionally widen evidence.' },
288
+ impact_domains: { type: 'array', items: { type: 'string', enum: [...CONTROLLER_CONTEXT_IMPACT_DOMAINS] }, description: 'Optional GPT-selected cross-cutting evidence dimensions. Forge expands these mechanically in the same retrieval call; it never treats them as proof of semantic completeness.' },
289
+ structural_context: { type: 'string', enum: ['off', 'auto', 'required'], description: 'Optional override. Defaults to auto for implementation/review and required for plan/debug.' },
288
290
  max_files: { type: 'number' },
289
291
  max_snippets: { type: 'number' },
290
292
  requested_check_ids: { type: 'array', items: { type: 'string' } },
@@ -294,16 +296,41 @@ export const runtimeToolDefinitions: McpToolDefinition[] = [
294
296
  }),
295
297
  definition('rh_work', 'Preferred ChatGPT facade: bounded planning, direct control, controller ownership, and external SuperController launch.', {
296
298
  repo_id: repoId,
297
- operation: { type: 'string', enum: ['start', 'continue', 'verify', 'repair', 'finalize', 'stop', 'delegate', 'controller_claim', 'controller_release', 'controller_get_owner', 'launcher_start', 'plan_create', 'plan_get', 'plan_list', 'plan_approve', 'plan_supersede'], description: 'Defaults to start. Complex starts require plan_id and plan_step_id.' },
299
+ operation: { type: 'string', enum: ['start', 'continue', 'verify', 'repair', 'finalize', 'stop', 'delegate', 'controller_claim', 'controller_release', 'controller_get_owner', 'launcher_start', 'plan_create', 'plan_get', 'plan_list', 'plan_approve', 'plan_supersede', 'schedule_create', 'schedule_list', 'schedule_get', 'schedule_pause', 'schedule_resume', 'schedule_trigger'], description: 'Defaults to start. Complex starts require plan_id and plan_step_id. schedule_* manages bounded external-controller continuation for existing Work.' },
298
300
  objective: { type: 'string' },
299
301
  work_id: { type: 'string' },
300
302
  handoff_id: { type: 'string', description: 'Optional existing HandoffItem used to resume controller context.' },
301
303
  controller_id: { type: 'string', description: 'Stable external SuperController identity for controller_claim/release.' },
302
304
  controller_type: { type: 'string', enum: ['chatgpt', 'codex', 'grok', 'claude', 'human'], description: 'Controller kind for claims and launcher_start.' },
303
305
  session_id: { type: 'string', description: 'Controller-owned session identity used for an exclusive Work lease.' },
304
- lease_ms: { type: 'number', description: 'Controller lease duration, bounded to one hour.' },
306
+ lease_ms: { type: 'number', description: 'Authenticated Controller ownership lease duration, bounded to one hour. Legacy launcher callers may still use this as a launch-reservation TTL.' },
307
+ launch_reservation_ms: { type: 'number', description: 'Short duplicate-spawn reservation TTL for launcher_start or schedule_create; does not grant Work ownership.' },
305
308
  executable: { type: 'string', description: 'External controller executable for launcher_start. This is owned by the Launcher, not the Kernel.' },
306
309
  launch_args: { type: 'array', items: { type: 'string' }, description: 'Provider-specific arguments for launcher_start.' },
310
+ browser_session_id: { type: 'string', description: 'Saved Forge ChatGPT browser session to continue when controller_type=chatgpt.' },
311
+ conversation_url: { type: 'string', description: 'Explicit https://chatgpt.com conversation URL used when no saved Forge browser session exists.' },
312
+ continuation_prompt: { type: 'string', description: 'Additional bounded continuation instruction appended to the Work/Handoff prompt.' },
313
+ schedule_id: { type: 'string', description: 'Schedule identity for schedule_get/pause/resume/trigger.' },
314
+ schedule_name: { type: 'string', description: 'Human-readable schedule name; defaults to Continue Work <work_id>.' },
315
+ schedule_request_id: { type: 'string', description: 'Stable idempotency key for schedule_create.' },
316
+ trigger_type: { type: 'string', enum: ['interval', 'cron', 'calendar', 'condition', 'repository-event', 'dependency-checkpoint', 'manual'] },
317
+ every_minutes: { type: 'number', description: 'Interval trigger cadence in minutes.' },
318
+ cron_expression: { type: 'string' },
319
+ schedule_timezone: { type: 'string' },
320
+ catch_up_minutes: { type: 'number' },
321
+ calendar_at: { type: 'string' },
322
+ condition: { type: 'object' },
323
+ event_name: { type: 'string' },
324
+ event_id: { type: 'string' },
325
+ event_data: { type: 'object' },
326
+ dependency_job_ids: { type: 'array', items: { type: 'string' } },
327
+ max_failures: { type: 'number' },
328
+ cooldown_minutes: { type: 'number' },
329
+ daily_budget_minutes: { type: 'number' },
330
+ shadow_mode: { type: 'boolean', description: 'Defaults to true. Set false only after the continuation path has been validated.' },
331
+ backoff_base_minutes: { type: 'number' },
332
+ backoff_max_minutes: { type: 'number' },
333
+ include_occurrences: { type: 'boolean' },
307
334
  plan_id: { type: 'string' },
308
335
  plan_step_id: { type: 'string', description: 'Approved PlanContract step to bind to a complex Goal Workloop start.' },
309
336
  scope_key: { type: 'string', description: 'Stable normalized scope identity used to prevent overlapping active plans.' },
@@ -405,7 +432,7 @@ export const runtimeToolDefinitions: McpToolDefinition[] = [
405
432
  paths: { type: 'array', items: { type: 'string' } },
406
433
  message: { type: 'string' },
407
434
  }, ['paths', 'message'], false),
408
- definition('prepare_handoff_artifacts', 'Refresh repo-local handoff artifacts and fall back to minimal current/resume files when helper scripts are unavailable.', {
435
+ definition('prepare_transfer_artifacts', 'Refresh rebuildable session continuation plus a selected-path patch transfer artifact when recovery context is needed.', {
409
436
  repo_id: repoId,
410
437
  reason: { type: 'string' },
411
438
  }, [], false),
@@ -887,62 +914,6 @@ export const runtimeToolDefinitions: McpToolDefinition[] = [
887
914
  max_candidates: { type: 'number' },
888
915
  confirm_cleanup: { type: 'boolean' },
889
916
  }, [], false),
890
- definition('create_campaign', 'Create a durable ChatGPT-supervised repository campaign.', {
891
- repo_id: repoId,
892
- checkout_id: { type: 'string', description: 'Optional source checkout used to create the Campaign workspace.' },
893
- request_id: { type: 'string' },
894
- semantic_key: { type: 'string' },
895
- title: { type: 'string' },
896
- goal: { type: 'string' },
897
- acceptance_criteria: { type: 'array', items: { type: 'string' } },
898
- non_goals: { type: 'array', items: { type: 'string' } },
899
- review_policy: { type: 'string', enum: ['every_task', 'failures_and_final', 'final_only'] },
900
- tasks: { type: 'array', items: { type: 'object' } },
901
- budget: { type: 'object' },
902
- supervisor: { type: 'object' },
903
- workspace: { type: 'object', description: 'Workspace policy. Defaults to an isolated long-lived Campaign worktree. Set mode=current to opt out.' },
904
- }, ['request_id', 'title', 'goal', 'tasks'], false),
905
- definition('list_campaigns', 'List durable campaigns for one repository.', { repo_id: repoId, limit: { type: 'number' } }),
906
- definition('get_campaign', 'Read one durable campaign.', { repo_id: repoId, campaign_id: { type: 'string' } }, ['campaign_id']),
907
- definition('add_campaign_task', 'Add one task to a non-terminal campaign.', {
908
- repo_id: repoId,
909
- campaign_id: { type: 'string' },
910
- request_id: { type: 'string' },
911
- expected_revision: { type: 'number' },
912
- task: { type: 'object' },
913
- }, ['campaign_id', 'request_id', 'task'], false),
914
- definition('pause_campaign', 'Pause a campaign without holding execution resources.', {
915
- repo_id: repoId, campaign_id: { type: 'string' }, request_id: { type: 'string' }, expected_revision: { type: 'number' }, reason: { type: 'string' },
916
- }, ['campaign_id', 'request_id'], false),
917
- definition('resume_campaign', 'Resume a paused or supervisor-waiting campaign.', {
918
- repo_id: repoId, campaign_id: { type: 'string' }, request_id: { type: 'string' }, expected_revision: { type: 'number' },
919
- }, ['campaign_id', 'request_id'], false),
920
- definition('cancel_campaign', 'Cancel a campaign and reconcile child Jobs and managed workspace resources before reaching a terminal state.', {
921
- repo_id: repoId, campaign_id: { type: 'string' }, request_id: { type: 'string' }, expected_revision: { type: 'number' }, reason: { type: 'string' },
922
- }, ['campaign_id', 'request_id'], false),
923
- definition('get_campaign_review_packet', 'Read one bounded open review packet for ChatGPT or another supervisor.', {
924
- repo_id: repoId, campaign_id: { type: 'string' }, checkpoint_id: { type: 'string' },
925
- }, ['campaign_id']),
926
- definition('submit_campaign_review', 'Submit one nonce- and goal-revision-bound supervisor decision.', {
927
- repo_id: repoId,
928
- campaign_id: { type: 'string' },
929
- checkpoint_id: { type: 'string' },
930
- checkpoint_nonce: { type: 'string' },
931
- goal_revision: { type: 'number' },
932
- expected_campaign_revision: { type: 'number' },
933
- request_id: { type: 'string' },
934
- action: { type: 'string', enum: ['accept', 'request_changes', 'retry', 'skip', 'pause', 'resume', 'approve_final', 'revise_goal', 'escalate'] },
935
- summary: { type: 'string' },
936
- instructions: { type: 'string' },
937
- revised_goal: { type: 'object' },
938
- submitted_by: { type: 'string' },
939
- }, ['campaign_id', 'checkpoint_id', 'checkpoint_nonce', 'goal_revision', 'request_id', 'action', 'summary'], false),
940
- definition('accept_campaign', 'Record human acceptance after final supervisor approval.', {
941
- repo_id: repoId, campaign_id: { type: 'string' }, request_id: { type: 'string' }, expected_revision: { type: 'number' },
942
- }, ['campaign_id', 'request_id'], false),
943
- definition('reconcile_campaign', 'Run one bounded campaign reconciliation pass immediately.', {
944
- repo_id: repoId, campaign_id: { type: 'string' },
945
- }, ['campaign_id'], false),
946
917
  definition('create_schedule', 'Create a bounded repository Schedule. Shadow mode defaults to true.', {
947
918
  repo_id: repoId,
948
919
  request_id: { type: 'string' },
@@ -1542,7 +1513,7 @@ function compactControllerContextSummaryPayload(payload: Record<string, unknown>
1542
1513
  function authenticatedFacadeControllerIdentity(
1543
1514
  ctx: MultiRepositoryMcpToolContext,
1544
1515
  args: Record<string, unknown>,
1545
- ): { controllerId: string; principalId: string; sessionId: string; controllerInstanceId: string } {
1516
+ ): { controllerId: string; principalId: string; sessionId: string; controllerInstanceId: string; controllerType: 'chatgpt' | 'codex' | 'claude' | 'grok' | 'human' } {
1546
1517
  const principalId = ctx.principalId?.trim();
1547
1518
  const sessionId = ctx.sessionId?.trim();
1548
1519
  if (!principalId || !sessionId) {
@@ -1556,10 +1527,18 @@ function authenticatedFacadeControllerIdentity(
1556
1527
  if (requestedSessionId && requestedSessionId !== sessionId) {
1557
1528
  throw new Error('CONTROLLER_SESSION_CONTEXT_MISMATCH: session_id must match the authenticated MCP session');
1558
1529
  }
1530
+ const requestedControllerType = typeof args.controller_type === 'string' && ['chatgpt', 'codex', 'claude', 'grok', 'human'].includes(args.controller_type)
1531
+ ? args.controller_type as 'chatgpt' | 'codex' | 'claude' | 'grok' | 'human'
1532
+ : undefined;
1533
+ const transportControllerType = ctx.controllerType;
1534
+ if (transportControllerType && requestedControllerType && requestedControllerType !== transportControllerType) {
1535
+ throw new Error('CONTROLLER_TYPE_CONTEXT_MISMATCH: controller_type must match the authenticated transport provider');
1536
+ }
1559
1537
  return {
1560
1538
  controllerId: principalId,
1561
1539
  principalId,
1562
1540
  sessionId,
1541
+ controllerType: transportControllerType ?? requestedControllerType ?? 'chatgpt',
1563
1542
  controllerInstanceId: ctx.controllerInstanceId?.trim() || currentControllerInstanceId(),
1564
1543
  };
1565
1544
  }
@@ -1709,66 +1688,6 @@ function pluginRepository(
1709
1688
  : selected(ctx, args);
1710
1689
  }
1711
1690
 
1712
- const CAMPAIGN_AGENT_OPERATIONS = new Set(['dispatch_task', 'launch_issue', 'dispatch_ready_tasks', 'retry_task_run', 'quick_agent_session']);
1713
- const CAMPAIGN_CONTROL_OPERATIONS = new Set(['create_campaign', 'add_campaign_task', 'pause_campaign', 'resume_campaign', 'cancel_campaign', 'submit_campaign_review', 'accept_campaign', 'reconcile_campaign']);
1714
-
1715
- function campaignAutomationDeprecated(): boolean {
1716
- return true;
1717
- }
1718
-
1719
- function campaignTaskInput(
1720
- raw: unknown,
1721
- repoIdValue: string,
1722
- checkoutId?: string,
1723
- ): CreateCampaignTaskInput {
1724
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('CAMPAIGN_TASK_INVALID');
1725
- const value = raw as Record<string, unknown>;
1726
- const taskId = String(value.task_id ?? value.taskId ?? '').trim();
1727
- const title = String(value.title ?? '').trim();
1728
- const rawOperation = String(value.operation ?? '').trim();
1729
- const operation = rawOperation ? assertCampaignOperationSupported(rawOperation) : '';
1730
- if (!taskId || !title || !operation) throw new Error('CAMPAIGN_TASK_INVALID: task_id, title, and operation are required');
1731
- if (CAMPAIGN_CONTROL_OPERATIONS.has(operation)) throw new Error(`CAMPAIGN_RECURSIVE_OPERATION_DENIED: ${operation}`);
1732
- const argumentsValue = value.arguments && typeof value.arguments === 'object' && !Array.isArray(value.arguments)
1733
- ? { ...(value.arguments as Record<string, unknown>) }
1734
- : {};
1735
- if (CAMPAIGN_AGENT_OPERATIONS.has(operation)) argumentsValue.isolate = true;
1736
- assertAutomatedOperationAllowed(operation, argumentsValue);
1737
- const requestedClaims = Array.isArray(value.resource_claims)
1738
- ? value.resource_claims.flatMap((rawClaim) => {
1739
- if (!rawClaim || typeof rawClaim !== 'object') return [];
1740
- const claim = rawClaim as Record<string, unknown>;
1741
- const resourceKey = String(claim.resource_key ?? claim.resourceKey ?? '').trim();
1742
- const mode = String(claim.mode ?? 'write');
1743
- if (!resourceKey || !['read', 'write', 'exclusive'].includes(mode)) return [];
1744
- return [{ resourceKey, mode: mode as 'read' | 'write' | 'exclusive' }];
1745
- })
1746
- : [];
1747
- const executor = value.executor && typeof value.executor === 'object' && !Array.isArray(value.executor)
1748
- ? value.executor as Record<string, unknown>
1749
- : undefined;
1750
- return {
1751
- taskId,
1752
- title,
1753
- objective: typeof value.objective === 'string' ? value.objective : undefined,
1754
- operation,
1755
- arguments: argumentsValue,
1756
- dependsOn: normalizeCampaignDependencyReferences(Array.isArray(value.depends_on ?? value.dependsOn) ? (value.depends_on ?? value.dependsOn) as unknown[] : []),
1757
- priority: ['P0', 'P1', 'P2', 'P3', 'P4'].includes(String(value.priority)) ? String(value.priority) as 'P0' | 'P1' | 'P2' | 'P3' | 'P4' : 'P1',
1758
- resourceClaims: requestedClaims.length > 0 ? requestedClaims : claimsForMcpOperation(operation, argumentsValue, repoIdValue, checkoutId),
1759
- reviewRequired: value.review_required === undefined ? true : value.review_required === true,
1760
- requiresChanges: value.requires_changes === true || value.requiresChanges === true,
1761
- maxAttempts: typeof value.max_attempts === 'number' ? value.max_attempts : undefined,
1762
- executor: executor ? {
1763
- enableDevRunner: executor.enable_dev_runner === undefined ? undefined : executor.enable_dev_runner === true,
1764
- enableChatgptBrowser: executor.enable_chatgpt_browser === true,
1765
- allowedAgents: Array.isArray(executor.allowed_agents) ? executor.allowed_agents.map(String) : undefined,
1766
- runnerTimeoutMs: typeof executor.runner_timeout_ms === 'number' ? executor.runner_timeout_ms : undefined,
1767
- runnerMaxTimeoutMs: typeof executor.runner_max_timeout_ms === 'number' ? executor.runner_max_timeout_ms : undefined,
1768
- } : undefined,
1769
- };
1770
- }
1771
-
1772
1691
  function expectedRevision(args: Record<string, unknown>, key = 'expected_revision'): number | undefined {
1773
1692
  return typeof args[key] === 'number' ? Math.trunc(args[key] as number) : undefined;
1774
1693
  }
@@ -2574,11 +2493,11 @@ async function runFacadeRepair(
2574
2493
  return result(facade as unknown as Record<string, unknown>, facade.status === 'blocked' || facade.status === 'approval_required' || facade.status === 'failed');
2575
2494
  }
2576
2495
 
2577
- function runFacadeVerify(
2496
+ async function runFacadeVerify(
2578
2497
  ctx: MultiRepositoryMcpToolContext,
2579
2498
  repository: ReturnType<typeof selected>,
2580
2499
  args: Record<string, unknown>,
2581
- ): CallToolResult {
2500
+ ): Promise<CallToolResult> {
2582
2501
  const store = { controllerHome: ctx.controllerHome, repoId: repository.repoId };
2583
2502
  const checks = listControllerChecks(repository.canonicalRoot);
2584
2503
  const workloopCtx = {
@@ -2643,16 +2562,166 @@ function runFacadeVerify(
2643
2562
  return result(facade as unknown as Record<string, unknown>, facade.status === 'failed');
2644
2563
  }
2645
2564
 
2646
- // Real registered check execution path.
2565
+ // Real checks share the same persisted Process Runtime path as run_check and
2566
+ // work_validate. Never synchronously block the MCP facade on long native tests.
2647
2567
  try {
2648
- const executed = runControllerCheck(repository.canonicalRoot, classified.normalizedCheckId!);
2649
- const infrastructureFailed = executed.failureClass === 'infrastructure_failure'
2650
- || executed.timedOut === true;
2651
- const checkFailed = !executed.ok && !infrastructureFailed;
2568
+ const normalizedCheckId = classified.normalizedCheckId!;
2569
+ const requestId = typeof args.request_id === 'string' && args.request_id.trim()
2570
+ ? args.request_id.trim()
2571
+ : undefined;
2572
+ const observedGitHead = repositoryGitStatus(repository).head;
2573
+ const workContract = workId ? getWorkContract(store, workId) : undefined;
2574
+ if (workId && !workContract) throw new Error(`WORK_NOT_FOUND: ${workId}`);
2575
+ const executed = await runPersistedCheckViaProcessRuntime({
2576
+ controllerHome: ctx.controllerHome,
2577
+ repoId: repository.repoId,
2578
+ checkoutId: repository.activeCheckoutId,
2579
+ repoRoot: repository.canonicalRoot,
2580
+ executionIdentity: executionIdentityForRepository(repository, workId ? { workId } : {}),
2581
+ checkId: normalizedCheckId,
2582
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
2583
+ // rh_work.verify is a control-plane continuation primitive: callers should
2584
+ // be able to keep working and later reattach to the exact same Process.
2585
+ interactiveWaitMs: 0,
2586
+ requestId,
2587
+ workId: workId || undefined,
2588
+ commandId: requestId,
2589
+ verificationSnapshot: workContract ? {
2590
+ workId: workContract.workId,
2591
+ allowedPaths: workContract.allowedPaths,
2592
+ forbiddenPaths: workContract.forbiddenPaths,
2593
+ } : undefined,
2594
+ });
2595
+
2596
+ if (executed.mode === 'durable') {
2597
+ return result(buildFacadeResult({
2598
+ status: 'blocked',
2599
+ summary: `Check ${normalizedCheckId} requires an explicit durable workflow; no acceptance result was recorded.`,
2600
+ data: {
2601
+ verification: {
2602
+ checkId: normalizedCheckId,
2603
+ outcome: 'deferred',
2604
+ isAcceptanceFailure: false,
2605
+ isInfrastructureIssue: false,
2606
+ durable: executed.durable,
2607
+ observedGitHead,
2608
+ },
2609
+ },
2610
+ suggestedNextActions: [{
2611
+ label: 'Continue Work with the durable check requirement',
2612
+ tool: 'rh_work',
2613
+ operation: 'continue',
2614
+ payload: { work_id: workId || undefined },
2615
+ risk: 'workspace_write',
2616
+ confidence: 'high',
2617
+ }],
2618
+ }) as unknown as Record<string, unknown>, true);
2619
+ }
2620
+
2621
+ const handle = executed.process;
2622
+ if (!handle) throw new Error(`PROCESS_CHECK_HANDLE_MISSING: ${normalizedCheckId}`);
2623
+ const record = getProcessRecord(ctx.controllerHome, repository.repoId, handle.processId);
2624
+ const checkContentRevision = record?.checkExecution?.revision;
2625
+
2626
+ if (!handle.completed) {
2627
+ return result(buildFacadeResult({
2628
+ status: 'ok',
2629
+ summary: `Check ${normalizedCheckId} is running through Process Runtime; continue other work and reattach to ${handle.processId}.`,
2630
+ data: {
2631
+ verification: {
2632
+ checkId: normalizedCheckId,
2633
+ outcome: 'running',
2634
+ isAcceptanceFailure: false,
2635
+ isInfrastructureIssue: false,
2636
+ executed: true,
2637
+ completed: false,
2638
+ processId: handle.processId,
2639
+ processStatus: handle.status,
2640
+ deduplicated: handle.deduplicated === true,
2641
+ semanticDeduplicated: handle.semanticDeduplicated === true,
2642
+ checkContentRevision,
2643
+ observedGitHead,
2644
+ verificationIsolation: workContract ? 'work_snapshot' : 'shared_checkout',
2645
+ revisionSemantics: 'checkContentRevision is a content-bound Check identity; observedGitHead is Git HEAD and is not interchangeable.',
2646
+ },
2647
+ },
2648
+ rawAvailable: false,
2649
+ }) as unknown as Record<string, unknown>);
2650
+ }
2651
+
2652
+ if (!record) throw new Error(`PROCESS_CHECK_RECORD_MISSING: ${handle.processId}`);
2653
+ const receipt = processCheckCompletionReceipt(record, {
2654
+ repoId: repository.repoId,
2655
+ checkId: normalizedCheckId,
2656
+ processId: handle.processId,
2657
+ ...(record.checkExecution ? {
2658
+ checkoutId: repository.activeCheckoutId,
2659
+ workId: workId || undefined,
2660
+ requestId,
2661
+ checkExecution: {
2662
+ cacheKey: record.checkExecution.cacheKey,
2663
+ revision: record.checkExecution.revision,
2664
+ definitionDigest: record.checkExecution.definitionDigest,
2665
+ environmentFingerprint: record.checkExecution.environmentFingerprint,
2666
+ timeoutMs: record.checkExecution.timeoutMs,
2667
+ scopeKey: record.checkExecution.scopeKey,
2668
+ },
2669
+ } : {}),
2670
+ });
2671
+ const structuredCheckResult = readPersistedCheckResultReceipt(record.origin?.checkResultReceiptPath);
2672
+ const structuredResultMatchesProcess = Boolean(
2673
+ structuredCheckResult
2674
+ && record.checkExecution?.cacheKey
2675
+ && structuredCheckResult.checkId === normalizedCheckId
2676
+ && structuredCheckResult.cacheKey === record.checkExecution.cacheKey,
2677
+ );
2678
+ const legacyEvidence = record.origin?.checkResultReceiptPath
2679
+ ? undefined
2680
+ : readLatestControllerCheckEvidence(repository.canonicalRoot, normalizedCheckId);
2681
+ const legacyEvidenceMatchesProcess = Boolean(
2682
+ legacyEvidence?.cacheKey
2683
+ && record.checkExecution?.cacheKey
2684
+ && legacyEvidence.cacheKey === record.checkExecution.cacheKey,
2685
+ );
2686
+ const resultMatchesProcess = structuredResultMatchesProcess || legacyEvidenceMatchesProcess;
2687
+ const failureClass = structuredResultMatchesProcess
2688
+ ? structuredCheckResult?.failureClass
2689
+ : legacyEvidenceMatchesProcess ? legacyEvidence?.failureClass : undefined;
2690
+ const infrastructureFailed = receipt.timedOut
2691
+ || receipt.cancelled
2692
+ || !resultMatchesProcess
2693
+ || (!receipt.ok && failureClass !== 'acceptance_failure');
2694
+ const checkFailed = !receipt.ok && !infrastructureFailed;
2695
+ const boundedStatus = receipt.ok ? 'pass' : infrastructureFailed ? 'infrastructure_failure' : 'fail';
2696
+ const commonVerification = {
2697
+ checkId: normalizedCheckId,
2698
+ outcome: infrastructureFailed ? 'infrastructure_failure' : receipt.ok ? 'valid_pass' : 'valid_fail',
2699
+ isAcceptanceFailure: checkFailed,
2700
+ isInfrastructureIssue: infrastructureFailed,
2701
+ executed: true,
2702
+ completed: true,
2703
+ processId: receipt.processId,
2704
+ processStatus: receipt.runtimeStatus,
2705
+ ok: receipt.ok,
2706
+ timedOut: receipt.timedOut,
2707
+ cancelled: receipt.cancelled,
2708
+ failureClass: infrastructureFailed ? 'infrastructure_failure' : failureClass,
2709
+ deduplicated: handle.deduplicated === true,
2710
+ semanticDeduplicated: handle.semanticDeduplicated === true,
2711
+ checkContentRevision: receipt.checkRevision,
2712
+ observedGitHead,
2713
+ revisionSemantics: 'checkContentRevision is a content-bound Check identity; observedGitHead is Git HEAD and is not interchangeable.',
2714
+ evidenceArtifactPath: record.origin?.workVerificationSnapshot ? undefined : receipt.artifactPath,
2715
+ evidenceReceiptId: receipt.receiptId,
2716
+ checkResultReceiptId: structuredCheckResult?.receiptId,
2717
+ verificationIsolation: record.origin?.workVerificationSnapshot ? 'work_snapshot' : 'shared_checkout',
2718
+ boundedStatus,
2719
+ };
2720
+
2652
2721
  if (workId) {
2653
2722
  const facade = verifyGoalWorkloop(workloopCtx, {
2654
2723
  workId,
2655
- checkId: classified.normalizedCheckId!,
2724
+ checkId: normalizedCheckId,
2656
2725
  infrastructureFailed,
2657
2726
  checkFailed,
2658
2727
  });
@@ -2663,46 +2732,26 @@ function runFacadeVerify(
2663
2732
  ...data,
2664
2733
  verification: {
2665
2734
  ...(typeof data.verification === 'object' && data.verification ? data.verification as Record<string, unknown> : {}),
2666
- executed: true,
2667
- registeredCheckId: classified.normalizedCheckId,
2668
- ok: executed.ok,
2669
- timedOut: executed.timedOut,
2670
- failureClass: executed.failureClass,
2671
- cacheHit: executed.cacheHit,
2672
- validatedRevision: executed.validatedRevision,
2673
- originalExecutedAt: executed.originalExecutedAt,
2674
- // Never return raw stdout/stderr to ChatGPT by default.
2675
- evidenceArtifactPath: executed.artifactPath,
2676
- boundedStatus: executed.ok ? 'pass' : infrastructureFailed ? 'infrastructure_failure' : 'fail',
2735
+ ...commonVerification,
2677
2736
  },
2678
2737
  },
2738
+ warnings: infrastructureFailed
2739
+ ? [...facade.warnings, resultMatchesProcess ? 'infrastructure_failure is distinct from acceptance failure' : 'check result receipt did not match the terminal Process semantic identity']
2740
+ : facade.warnings,
2679
2741
  } as unknown as Record<string, unknown>, facade.status === 'failed');
2680
2742
  }
2743
+
2681
2744
  return result(buildFacadeResult({
2682
2745
  status: checkFailed ? 'failed' : 'ok',
2683
2746
  summary: infrastructureFailed
2684
- ? `Infrastructure failure while running ${classified.normalizedCheckId}; not an acceptance failure.`
2685
- : executed.ok
2686
- ? `Check ${classified.normalizedCheckId} passed.`
2687
- : `Check ${classified.normalizedCheckId} failed acceptance.`,
2688
- data: {
2689
- verification: {
2690
- checkId: classified.normalizedCheckId,
2691
- outcome: infrastructureFailed ? 'infrastructure_failure' : executed.ok ? 'valid_pass' : 'valid_fail',
2692
- isAcceptanceFailure: checkFailed,
2693
- isInfrastructureIssue: infrastructureFailed,
2694
- executed: true,
2695
- evidenceArtifactPath: executed.artifactPath,
2696
- cacheHit: executed.cacheHit,
2697
- validatedRevision: executed.validatedRevision,
2698
- originalExecutedAt: executed.originalExecutedAt,
2699
- failureClass: executed.failureClass,
2700
- },
2701
- },
2702
- warnings: infrastructureFailed ? ['infrastructure_failure is distinct from acceptance failure'] : [],
2703
- suggestedNextActions: executed.ok
2704
- ? [{ label: 'Continue work', tool: 'rh_work', operation: 'continue', payload: { work_id: workId || undefined }, risk: 'readonly' }]
2705
- : [{ label: 'Diagnose if infrastructure', tool: 'rh_work', operation: 'repair', payload: { repair_operation: 'diagnose', dry_run: true }, risk: 'readonly' }],
2747
+ ? `Infrastructure failure while running ${normalizedCheckId}; not an acceptance failure.`
2748
+ : receipt.ok
2749
+ ? `Check ${normalizedCheckId} passed with persisted Process evidence.`
2750
+ : `Check ${normalizedCheckId} failed acceptance.`,
2751
+ data: { verification: commonVerification },
2752
+ warnings: infrastructureFailed
2753
+ ? [resultMatchesProcess ? 'infrastructure_failure is distinct from acceptance failure' : 'check result receipt did not match the terminal Process semantic identity']
2754
+ : [],
2706
2755
  rawAvailable: false,
2707
2756
  }) as unknown as Record<string, unknown>, checkFailed);
2708
2757
  } catch (error) {
@@ -2724,7 +2773,7 @@ function runFacadeVerify(
2724
2773
  }
2725
2774
  return result(buildFacadeResult({
2726
2775
  status: 'ok',
2727
- summary: `Infrastructure failure invoking check runner for ${classified.normalizedCheckId}; not acceptance failure.`,
2776
+ summary: `Infrastructure failure invoking Process Runtime for ${classified.normalizedCheckId}; not acceptance failure.`,
2728
2777
  data: {
2729
2778
  verification: {
2730
2779
  checkId: classified.normalizedCheckId,
@@ -2993,7 +3042,15 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
2993
3042
  recommendedContinuationPrompt: typeof args.recommended_continuation_prompt === 'string' ? args.recommended_continuation_prompt : undefined,
2994
3043
  suggestedNextActions: [],
2995
3044
  });
2996
- return result(buildFacadeResult({ summary: `Created handoff ${item.id}.`, data: { item: summarizeHandoffItem(item) } }) as unknown as Record<string, unknown>);
3045
+ let ownershipReleased = false;
3046
+ if (item.workId && ctx.principalId?.trim() && ctx.sessionId?.trim()) {
3047
+ const owner = getControllerSession(store, item.workId);
3048
+ if (owner && owner.controllerId === ctx.principalId.trim() && owner.sessionId === ctx.sessionId.trim()) {
3049
+ releaseControllerSession(store, item.workId, owner.controllerId);
3050
+ ownershipReleased = true;
3051
+ }
3052
+ }
3053
+ return result(buildFacadeResult({ summary: `Created handoff ${item.id}.`, data: { item: summarizeHandoffItem(item), ownershipReleased } }) as unknown as Record<string, unknown>);
2997
3054
  }
2998
3055
  // Default list: pending summary only.
2999
3056
  const items = listHandoffItems({ ...store, status: 'pending', limit: typeof args.limit === 'number' ? args.limit : 50 });
@@ -3023,9 +3080,16 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3023
3080
  const list = (value: unknown): string[] => Array.isArray(value)
3024
3081
  ? value.map(String).map((entry) => entry.trim()).filter(Boolean)
3025
3082
  : [];
3026
- const structuralContext = args.structural_context === 'auto' || args.structural_context === 'required'
3083
+ const retrievalMode = args.retrieval_mode === 'plan' || args.retrieval_mode === 'debug' || args.retrieval_mode === 'review'
3084
+ ? args.retrieval_mode
3085
+ : 'implementation';
3086
+ const impactDomains = list(args.impact_domains)
3087
+ .filter((domain): domain is ControllerContextImpactDomain => CONTROLLER_CONTEXT_IMPACT_DOMAINS.includes(domain as ControllerContextImpactDomain));
3088
+ const structuralContext = args.structural_context === 'off' || args.structural_context === 'auto' || args.structural_context === 'required'
3027
3089
  ? args.structural_context
3028
- : 'off';
3090
+ : retrievalMode === 'plan' || retrievalMode === 'debug'
3091
+ ? 'required'
3092
+ : 'auto';
3029
3093
  const pack = buildControllerContextPack(repository.canonicalRoot, ctx.policy, {
3030
3094
  description: query,
3031
3095
  searchTerms: [query],
@@ -3035,6 +3099,8 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3035
3099
  maxFiles: typeof args.max_files === 'number' ? args.max_files : undefined,
3036
3100
  maxSnippets: typeof args.max_snippets === 'number' ? args.max_snippets : undefined,
3037
3101
  structuralContext,
3102
+ retrievalMode,
3103
+ impactDomains,
3038
3104
  });
3039
3105
  const warnings = structuralContext === 'required' && !pack.structuralContext.requiredSatisfied
3040
3106
  ? [pack.structuralContext.fallbackReason ?? 'Required structural context is not ready; lexical retrieval results are returned as degraded evidence.']
@@ -3308,6 +3374,78 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3308
3374
  if (!allowedFacadeOperations('rh_work').includes(operation)) {
3309
3375
  return invalidFacadeOperation('rh_work', operation);
3310
3376
  }
3377
+ if (operation.startsWith('schedule_')) {
3378
+ try {
3379
+ const workId = String(args.work_id ?? '').trim();
3380
+ const scheduleId = String(args.schedule_id ?? '').trim();
3381
+ if (operation === 'schedule_create') {
3382
+ const controllerType = String(args.controller_type ?? 'chatgpt').trim();
3383
+ if (!['chatgpt', 'codex', 'claude', 'grok'].includes(controllerType)) throw new Error('CONTROLLER_TYPE_INVALID');
3384
+ const triggerTypeRaw = String(args.trigger_type ?? '').trim();
3385
+ const triggerType = ['interval', 'cron', 'calendar', 'condition', 'repository-event', 'dependency-checkpoint', 'manual'].includes(triggerTypeRaw)
3386
+ ? triggerTypeRaw as 'interval' | 'cron' | 'calendar' | 'condition' | 'repository-event' | 'dependency-checkpoint' | 'manual'
3387
+ : undefined;
3388
+ const created = createWorkContinuationSchedule(ctx.controllerHome, repository.repoId, {
3389
+ workId,
3390
+ controllerType: controllerType as ContinuationControllerType,
3391
+ executable: typeof args.executable === 'string' ? args.executable : undefined,
3392
+ launchArgs: Array.isArray(args.launch_args) ? args.launch_args.map(String) : undefined,
3393
+ launchReservationMs: typeof args.launch_reservation_ms === 'number' ? args.launch_reservation_ms : typeof args.lease_ms === 'number' ? args.lease_ms : undefined,
3394
+ handoffId: typeof args.handoff_id === 'string' ? args.handoff_id : undefined,
3395
+ browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
3396
+ conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
3397
+ continuationPrompt: typeof args.continuation_prompt === 'string' ? args.continuation_prompt : undefined,
3398
+ scheduleName: typeof args.schedule_name === 'string' ? args.schedule_name : undefined,
3399
+ requestId: typeof args.schedule_request_id === 'string' ? args.schedule_request_id : undefined,
3400
+ triggerType,
3401
+ everyMinutes: typeof args.every_minutes === 'number' ? args.every_minutes : undefined,
3402
+ cronExpression: typeof args.cron_expression === 'string' ? args.cron_expression : undefined,
3403
+ timezone: typeof args.schedule_timezone === 'string' ? args.schedule_timezone : undefined,
3404
+ catchUpMinutes: typeof args.catch_up_minutes === 'number' ? args.catch_up_minutes : undefined,
3405
+ calendarAt: typeof args.calendar_at === 'string' ? args.calendar_at : undefined,
3406
+ condition: args.condition && typeof args.condition === 'object' && !Array.isArray(args.condition) ? args.condition as never : undefined,
3407
+ eventName: typeof args.event_name === 'string' ? args.event_name : undefined,
3408
+ dependencyJobIds: Array.isArray(args.dependency_job_ids) ? args.dependency_job_ids.map(String) : undefined,
3409
+ maxFailures: typeof args.max_failures === 'number' ? args.max_failures : undefined,
3410
+ cooldownMinutes: typeof args.cooldown_minutes === 'number' ? args.cooldown_minutes : undefined,
3411
+ dailyBudgetMinutes: typeof args.daily_budget_minutes === 'number' ? args.daily_budget_minutes : undefined,
3412
+ shadowMode: typeof args.shadow_mode === 'boolean' ? args.shadow_mode : undefined,
3413
+ backoffBaseMinutes: typeof args.backoff_base_minutes === 'number' ? args.backoff_base_minutes : undefined,
3414
+ backoffMaxMinutes: typeof args.backoff_max_minutes === 'number' ? args.backoff_max_minutes : undefined,
3415
+ stopConditions: Array.isArray(args.stop_conditions) ? args.stop_conditions.map(String) : undefined,
3416
+ });
3417
+ return result(buildFacadeResult({ summary: `Continuation schedule ${created.schedule.scheduleId} is configured for Work ${workId}.`, data: { schedule: created.schedule, work: buildWorkContinuationSnapshot(created.work) } }) as unknown as Record<string, unknown>);
3418
+ }
3419
+ if (operation === 'schedule_list') {
3420
+ const listed = listWorkContinuationSchedules(ctx.controllerHome, repository.repoId, { workId: workId || undefined, includeOccurrences: args.include_occurrences === true });
3421
+ return result(buildFacadeResult({ summary: `Found ${listed.schedules.length} Work continuation schedule(s).`, data: listed }) as unknown as Record<string, unknown>);
3422
+ }
3423
+ if (!scheduleId) throw new Error('SCHEDULE_ID_REQUIRED');
3424
+ if (operation === 'schedule_get') {
3425
+ const fetched = getWorkContinuationSchedule(ctx.controllerHome, repository.repoId, scheduleId, args.include_occurrences === true);
3426
+ return result(buildFacadeResult({ summary: `Continuation schedule ${scheduleId}.`, data: fetched }) as unknown as Record<string, unknown>);
3427
+ }
3428
+ if (operation === 'schedule_pause') {
3429
+ const schedule = pauseWorkContinuationSchedule(ctx.controllerHome, repository.repoId, scheduleId, typeof args.reason === 'string' ? args.reason : undefined);
3430
+ return result(buildFacadeResult({ summary: `Continuation schedule ${scheduleId} is paused.`, data: { schedule } }) as unknown as Record<string, unknown>);
3431
+ }
3432
+ if (operation === 'schedule_resume') {
3433
+ const schedule = resumeWorkContinuationSchedule(ctx.controllerHome, repository.repoId, scheduleId);
3434
+ return result(buildFacadeResult({ summary: `Continuation schedule ${scheduleId} is resumed.`, data: { schedule } }) as unknown as Record<string, unknown>);
3435
+ }
3436
+ if (operation === 'schedule_trigger') {
3437
+ const occurrence = await triggerWorkContinuationSchedule(ctx.controllerHome, repository.repoId, scheduleId, {
3438
+ source: typeof args.event_name === 'string' && args.event_name.trim() ? 'repository-event' : 'manual',
3439
+ eventName: typeof args.event_name === 'string' ? args.event_name : undefined,
3440
+ eventId: typeof args.event_id === 'string' ? args.event_id : undefined,
3441
+ data: args.event_data && typeof args.event_data === 'object' && !Array.isArray(args.event_data) ? args.event_data as Record<string, unknown> : undefined,
3442
+ });
3443
+ return result(buildFacadeResult({ summary: occurrence ? `Continuation schedule ${scheduleId} produced ${occurrence.decision}.` : `Continuation schedule ${scheduleId} produced no occurrence.`, data: { occurrence } }) as unknown as Record<string, unknown>);
3444
+ }
3445
+ } catch (error) {
3446
+ return result(buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? error.message : 'Schedule operation failed.', data: {} }) as unknown as Record<string, unknown>, true);
3447
+ }
3448
+ }
3311
3449
  if (operation === 'controller_get_owner') {
3312
3450
  const owner = getControllerSession(store, String(args.work_id ?? '').trim());
3313
3451
  return result(buildFacadeResult({ summary: owner ? `Work is claimed by ${owner.controllerId}.` : 'Work has no active controller owner.', data: { owner } }) as unknown as Record<string, unknown>);
@@ -3354,13 +3492,14 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3354
3492
  executable: typeof args.executable === 'string' && args.executable.trim() ? args.executable.trim() : undefined,
3355
3493
  args: Array.isArray(args.launch_args) ? args.launch_args.map(String) : [],
3356
3494
  workId: String(args.work_id ?? '').trim(),
3357
- controllerId: String(args.controller_id ?? '').trim(),
3358
- sessionId: String(args.session_id ?? '').trim(),
3359
- leaseMs: typeof args.lease_ms === 'number' ? args.lease_ms : undefined,
3495
+ launchReservationMs: typeof args.launch_reservation_ms === 'number' ? args.launch_reservation_ms : typeof args.lease_ms === 'number' ? args.lease_ms : undefined,
3360
3496
  handoffId: typeof args.handoff_id === 'string' ? args.handoff_id : undefined,
3497
+ browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
3498
+ conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
3499
+ continuationPrompt: typeof args.continuation_prompt === 'string' ? args.continuation_prompt : undefined,
3361
3500
  cwd: repository.canonicalRoot,
3362
3501
  });
3363
- return result(buildFacadeResult({ summary: `Thin Launcher started ${launched.controllerType}.`, data: { pid: launched.pid, executable: launched.executable, workId: String(args.work_id ?? ''), controllerId: launched.controllerId, sessionId: launched.sessionId } }) as unknown as Record<string, unknown>);
3502
+ return result(buildFacadeResult({ summary: `Thin Launcher started ${launched.controllerType}.`, data: { pid: launched.pid, executable: launched.executable, workId: String(args.work_id ?? ''), reservationId: launched.reservationId } }) as unknown as Record<string, unknown>);
3364
3503
  } catch (error) {
3365
3504
  return result(buildFacadeResult({ status: 'blocked', summary: error instanceof Error ? error.message : 'Launcher failed.', data: {} }) as unknown as Record<string, unknown>, true);
3366
3505
  }
@@ -3449,7 +3588,7 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3449
3588
  }
3450
3589
 
3451
3590
  if (operation === 'verify') {
3452
- return runFacadeVerify(ctx, repository, args);
3591
+ return await runFacadeVerify(ctx, repository, args);
3453
3592
  }
3454
3593
 
3455
3594
  if (operation === 'delegate') {
@@ -3555,7 +3694,7 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3555
3694
  resumedControllerSession = resumeControllerSession(store, {
3556
3695
  workId,
3557
3696
  controllerId: identity.controllerId,
3558
- controllerType: currentOwner?.controllerType ?? 'chatgpt',
3697
+ controllerType: identity.controllerType,
3559
3698
  sessionId: identity.sessionId,
3560
3699
  principalId: identity.principalId,
3561
3700
  controllerInstanceId: identity.controllerInstanceId,
@@ -3896,17 +4035,17 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
3896
4035
  ...(directEditWorkCompletion ? { directEditWorkCompletion } : {}),
3897
4036
  }, Boolean(committed.error));
3898
4037
  }
3899
- case 'prepare_handoff_artifacts': {
4038
+ case 'prepare_transfer_artifacts': {
3900
4039
  const repository = selected(ctx, args);
3901
- const handoff = prepareFallbackHandoffArtifacts(repository, { reason: args.reason });
4040
+ const transfer = prepareTransferArtifacts(repository, { reason: args.reason });
3902
4041
  const taskLedger = writeControllerTaskLedgerArtifacts(repository.canonicalRoot, { reason: args.reason });
3903
4042
  return result({
3904
4043
  repoId: repository.repoId,
3905
4044
  checkoutId: repository.activeCheckoutId,
3906
- ...handoff,
4045
+ ...transfer,
3907
4046
  taskLedger: taskLedger.projection,
3908
4047
  artifacts: [
3909
- ...handoff.artifacts,
4048
+ ...transfer.artifacts,
3910
4049
  ...taskLedger.artifacts,
3911
4050
  ],
3912
4051
  });
@@ -5109,8 +5248,8 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
5109
5248
  break;
5110
5249
  }
5111
5250
  case 'recovery.create_patch_handoff':
5112
- payload = prepareFallbackHandoffArtifacts(repository, { reason }) as unknown as Record<string, unknown>;
5113
- affectedPaths = ['.ai/handoff'];
5251
+ payload = prepareTransferArtifacts(repository, { reason }) as unknown as Record<string, unknown>;
5252
+ affectedPaths = ['.ai/harness/transfers', '.ai/harness/session'];
5114
5253
  break;
5115
5254
  case 'recovery.workspace_auth_login_prepare':
5116
5255
  payload = { skipped: true, nextTool: 'workspace_auth_login_prepare', reason: 'Auth login is a non-secret handoff and should be prepared through the dedicated typed tool.' };
@@ -5601,223 +5740,6 @@ export async function callRuntimeTool(ctx: MultiRepositoryMcpToolContext, name:
5601
5740
  model: typeof args.model === 'string' ? args.model : undefined,
5602
5741
  }) });
5603
5742
  }
5604
- case 'create_campaign': {
5605
- const repository = selected(ctx, args);
5606
- if (campaignAutomationDeprecated()) {
5607
- return result({
5608
- error: {
5609
- code: 'CAMPAIGN_DEPRECATED',
5610
- message: 'Campaign creation is retired. Create a PlanContract and WorkContracts, then launch an external Controller.',
5611
- },
5612
- deprecated: true,
5613
- migration: ['rh_work.plan_create', 'rh_work.plan_approve', 'rh_work.controller_claim', 'rh_work.launcher_start'],
5614
- repoId: repository.repoId,
5615
- }, true);
5616
- }
5617
- const rawTasks = Array.isArray(args.tasks) ? args.tasks : [];
5618
- const requestId = String(args.request_id ?? '').trim();
5619
- const title = String(args.title ?? '').trim();
5620
- const goal = String(args.goal ?? '').trim();
5621
- if (!requestId) throw new Error('CAMPAIGN_REQUEST_ID_REQUIRED');
5622
- if (!title) throw new Error('CAMPAIGN_TITLE_REQUIRED');
5623
- if (!goal) throw new Error('CAMPAIGN_GOAL_REQUIRED');
5624
- const budget = args.budget && typeof args.budget === 'object' && !Array.isArray(args.budget)
5625
- ? args.budget as Record<string, unknown>
5626
- : {};
5627
- const budgetInput = {
5628
- maxParallelTasks: typeof budget.max_parallel_tasks === 'number' ? budget.max_parallel_tasks : undefined,
5629
- maxExecutionJobs: typeof budget.max_execution_jobs === 'number' ? budget.max_execution_jobs : undefined,
5630
- maxSupervisorReviews: typeof budget.max_supervisor_reviews === 'number' ? budget.max_supervisor_reviews : undefined,
5631
- defaultTaskMaxAttempts: typeof budget.default_task_max_attempts === 'number' ? budget.default_task_max_attempts : undefined,
5632
- taskTimeoutMs: typeof budget.task_timeout_ms === 'number' ? budget.task_timeout_ms : undefined,
5633
- retryBaseDelayMs: typeof budget.retry_base_delay_ms === 'number' ? budget.retry_base_delay_ms : undefined,
5634
- retryMaxDelayMs: typeof budget.retry_max_delay_ms === 'number' ? budget.retry_max_delay_ms : undefined,
5635
- reviewPacketMaxBytes: typeof budget.review_packet_max_bytes === 'number' ? budget.review_packet_max_bytes : undefined,
5636
- };
5637
- // Validate task shape, DAG, and governance before creating any Git refs or worktrees.
5638
- const sourceTasks = rawTasks.map((task) => campaignTaskInput(task, repository.repoId, repository.activeCheckoutId));
5639
- validateCreateCampaignTasks(sourceTasks, budgetInput);
5640
- // Validate supervisor policy before allocating a Git worktree. Invalid
5641
- // recursive or external-effect operations must leave no workspace behind.
5642
- const supervisor = args.supervisor && typeof args.supervisor === 'object' && !Array.isArray(args.supervisor)
5643
- ? args.supervisor as Record<string, unknown>
5644
- : {};
5645
- const supervisorMode = supervisor.mode === 'operation'
5646
- ? 'operation'
5647
- : supervisor.mode === 'workspace_agent' ? 'workspace_agent' : 'pull';
5648
- const supervisorOperation = typeof supervisor.operation === 'string' && supervisor.operation.trim()
5649
- ? assertCampaignOperationSupported(supervisor.operation)
5650
- : undefined;
5651
- const workspaceAgentId = typeof supervisor.workspace_agent_id === 'string'
5652
- ? supervisor.workspace_agent_id.trim()
5653
- : typeof supervisor.workspaceAgentId === 'string' ? supervisor.workspaceAgentId.trim() : undefined;
5654
- const conversationKey = typeof supervisor.conversation_key === 'string'
5655
- ? supervisor.conversation_key.trim()
5656
- : typeof supervisor.conversationKey === 'string' ? supervisor.conversationKey.trim() : undefined;
5657
- const supervisorArguments = supervisor.arguments && typeof supervisor.arguments === 'object' && !Array.isArray(supervisor.arguments)
5658
- ? supervisor.arguments as Record<string, unknown>
5659
- : undefined;
5660
- if (supervisorOperation) {
5661
- if (CAMPAIGN_CONTROL_OPERATIONS.has(supervisorOperation)) throw new Error(`CAMPAIGN_RECURSIVE_OPERATION_DENIED: ${supervisorOperation}`);
5662
- assertAutomatedOperationAllowed(supervisorOperation, supervisorArguments ?? {});
5663
- }
5664
- if (supervisorMode === 'operation' && !supervisorOperation) throw new Error('CAMPAIGN_SUPERVISOR_OPERATION_REQUIRED');
5665
- if (supervisorMode === 'workspace_agent' && !workspaceAgentId) throw new Error('CAMPAIGN_WORKSPACE_AGENT_ID_REQUIRED');
5666
-
5667
- const workspaceInput = args.workspace && typeof args.workspace === 'object' && !Array.isArray(args.workspace)
5668
- ? args.workspace as Record<string, unknown>
5669
- : {};
5670
- const workspaceMode = workspaceInput.mode === 'current' ? 'current' : 'isolated';
5671
- const workspace = workspaceMode === 'current'
5672
- ? currentCampaignWorkspace(repository)
5673
- : ensureCampaignWorkspace(ctx.controllerHome, repository, {
5674
- requestId,
5675
- title,
5676
- baseRef: typeof workspaceInput.base_ref === 'string'
5677
- ? workspaceInput.base_ref
5678
- : typeof workspaceInput.baseRef === 'string' ? workspaceInput.baseRef : undefined,
5679
- branchName: typeof workspaceInput.branch_name === 'string'
5680
- ? workspaceInput.branch_name
5681
- : typeof workspaceInput.branchName === 'string' ? workspaceInput.branchName : undefined,
5682
- });
5683
- const campaignCheckoutId = workspace.checkoutId ?? repository.activeCheckoutId;
5684
- const campaign = createCampaign(ctx.controllerHome, {
5685
- repoId: repository.repoId,
5686
- checkoutId: campaignCheckoutId,
5687
- workspace,
5688
- requestId,
5689
- semanticKey: typeof args.semantic_key === 'string' && args.semantic_key.trim()
5690
- ? args.semantic_key.trim()
5691
- : `campaign:${repository.repoId}:${createHash('sha256').update(`${String(args.title ?? '')}:${String(args.goal ?? '')}`).digest('hex').slice(0, 20)}`,
5692
- title,
5693
- goal,
5694
- acceptanceCriteria: stringList(args.acceptance_criteria),
5695
- nonGoals: stringList(args.non_goals),
5696
- reviewPolicy: ['every_task', 'failures_and_final', 'final_only'].includes(String(args.review_policy))
5697
- ? String(args.review_policy) as CampaignReviewPolicy
5698
- : 'every_task',
5699
- tasks: rawTasks.map((task) => campaignTaskInput(task, repository.repoId, campaignCheckoutId)),
5700
- budget: budgetInput,
5701
- supervisor: {
5702
- mode: supervisorMode,
5703
- operation: supervisorMode === 'operation' ? supervisorOperation : undefined,
5704
- workspaceAgentId: supervisorMode === 'workspace_agent' ? workspaceAgentId : undefined,
5705
- conversationKey: supervisorMode === 'workspace_agent' ? conversationKey : undefined,
5706
- arguments: supervisorMode === 'operation' ? supervisorArguments : undefined,
5707
- priority: ['P0', 'P1', 'P2', 'P3', 'P4'].includes(String(supervisor.priority)) ? String(supervisor.priority) as 'P0' | 'P1' | 'P2' | 'P3' | 'P4' : undefined,
5708
- resourceClaims: supervisorMode === 'operation' && supervisorOperation
5709
- ? claimsForMcpOperation(supervisorOperation, supervisorArguments ?? {}, repository.repoId, campaignCheckoutId)
5710
- : undefined,
5711
- triggerCooldownMs: typeof supervisor.trigger_cooldown_ms === 'number' ? supervisor.trigger_cooldown_ms : undefined,
5712
- maxTriggerAttempts: typeof supervisor.max_trigger_attempts === 'number' ? supervisor.max_trigger_attempts : undefined,
5713
- decisionTimeoutMs: typeof supervisor.decision_timeout_ms === 'number' ? supervisor.decision_timeout_ms : undefined,
5714
- },
5715
- createdBy: 'chatgpt',
5716
- });
5717
- return result({ ...campaign, next: 'Use get_campaign_review_packet when the campaign opens a checkpoint.' });
5718
- }
5719
- case 'list_campaigns': {
5720
- const repository = selected(ctx, args);
5721
- return result({ campaigns: listCampaigns(ctx.controllerHome, repository.repoId, typeof args.limit === 'number' ? args.limit : 100) });
5722
- }
5723
- case 'get_campaign': {
5724
- const repository = selected(ctx, args);
5725
- return result({ campaign: getCampaign(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? '')) });
5726
- }
5727
- case 'add_campaign_task': {
5728
- const repository = selected(ctx, args);
5729
- if (campaignAutomationDeprecated()) {
5730
- return result({
5731
- error: { code: 'CAMPAIGN_DEPRECATED', message: 'Campaign task creation is retired. Add a WorkContract to an approved PlanContract instead.' },
5732
- deprecated: true,
5733
- repoId: repository.repoId,
5734
- }, true);
5735
- }
5736
- const campaignId = String(args.campaign_id ?? '');
5737
- const existingCampaign = getCampaign(ctx.controllerHome, repository.repoId, campaignId);
5738
- const campaign = addCampaignTask(
5739
- ctx.controllerHome,
5740
- repository.repoId,
5741
- campaignId,
5742
- String(args.request_id ?? ''),
5743
- campaignTaskInput(args.task, repository.repoId, existingCampaign.checkoutId),
5744
- expectedRevision(args),
5745
- );
5746
- return result({ campaign });
5747
- }
5748
- case 'pause_campaign': {
5749
- const repository = selected(ctx, args);
5750
- return result({ campaign: setCampaignStatus(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? ''), String(args.request_id ?? ''), 'paused', typeof args.reason === 'string' ? args.reason : undefined, expectedRevision(args)) });
5751
- }
5752
- case 'resume_campaign': {
5753
- const repository = selected(ctx, args);
5754
- if (campaignAutomationDeprecated()) {
5755
- return result({
5756
- error: { code: 'CAMPAIGN_DEPRECATED', message: 'Campaign automation cannot be resumed. Use reconcile_campaign once to migrate outstanding tasks to WorkContracts.' },
5757
- deprecated: true,
5758
- repoId: repository.repoId,
5759
- }, true);
5760
- }
5761
- const campaign = setCampaignStatus(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? ''), String(args.request_id ?? ''), 'active', undefined, expectedRevision(args));
5762
- return result({ campaign, warning: 'Campaign reconcile is frozen; use reconcile_campaign once to migrate pending tasks to WorkContracts.' });
5763
- }
5764
- case 'cancel_campaign': {
5765
- const repository = selected(ctx, args);
5766
- return result({ campaign: await cancelCampaign(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? ''), String(args.request_id ?? ''), typeof args.reason === 'string' ? args.reason : undefined, expectedRevision(args)) });
5767
- }
5768
- case 'get_campaign_review_packet': {
5769
- const repository = selected(ctx, args);
5770
- const campaign = getCampaign(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? ''));
5771
- const checkpointId = typeof args.checkpoint_id === 'string' && args.checkpoint_id.trim()
5772
- ? args.checkpoint_id.trim()
5773
- : campaign.checkpoints.filter((checkpoint) => checkpoint.status === 'open').at(-1)?.checkpointId;
5774
- if (!checkpointId) throw new Error('CAMPAIGN_OPEN_CHECKPOINT_NOT_FOUND');
5775
- const checkpoint = campaign.checkpoints.find((entry) => entry.checkpointId === checkpointId);
5776
- if (!checkpoint || checkpoint.status !== 'open') throw new Error(`CAMPAIGN_OPEN_CHECKPOINT_NOT_FOUND: ${checkpointId}`);
5777
- return result({ campaignId: campaign.campaignId, campaignRevision: campaign.revision, checkpoint: { ...checkpoint, packet: checkpoint.packet } });
5778
- }
5779
- case 'submit_campaign_review': {
5780
- const repository = selected(ctx, args);
5781
- const revised = args.revised_goal && typeof args.revised_goal === 'object' && !Array.isArray(args.revised_goal)
5782
- ? args.revised_goal as Record<string, unknown>
5783
- : undefined;
5784
- const campaign = submitCampaignReview(ctx.controllerHome, {
5785
- repoId: repository.repoId,
5786
- campaignId: String(args.campaign_id ?? ''),
5787
- checkpointId: String(args.checkpoint_id ?? ''),
5788
- nonce: String(args.checkpoint_nonce ?? ''),
5789
- goalRevision: Number(args.goal_revision),
5790
- expectedCampaignRevision: expectedRevision(args, 'expected_campaign_revision'),
5791
- requestId: String(args.request_id ?? ''),
5792
- decision: {
5793
- action: String(args.action ?? '') as CampaignSupervisorAction,
5794
- summary: String(args.summary ?? ''),
5795
- instructions: typeof args.instructions === 'string' ? args.instructions : undefined,
5796
- revisedGoal: revised ? {
5797
- statement: String(revised.statement ?? ''),
5798
- acceptanceCriteria: stringList(revised.acceptance_criteria),
5799
- nonGoals: stringList(revised.non_goals),
5800
- reason: typeof revised.reason === 'string' ? revised.reason : undefined,
5801
- } : undefined,
5802
- submittedBy: typeof args.submitted_by === 'string' ? args.submitted_by : 'chatgpt',
5803
- },
5804
- });
5805
- return result({ campaign, warning: 'Campaign reconcile is frozen; review state is retained for migration.' });
5806
- }
5807
- case 'accept_campaign': {
5808
- const repository = selected(ctx, args);
5809
- const current = getCampaign(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? ''));
5810
- if (current.status !== 'ready_for_human_acceptance') throw new Error(`CAMPAIGN_NOT_READY_FOR_ACCEPTANCE: ${current.status}`);
5811
- const requestId = String(args.request_id ?? '');
5812
- setCampaignStatus(ctx.controllerHome, repository.repoId, current.campaignId, requestId, 'completed', 'Accepted by human.', expectedRevision(args));
5813
- const campaign = completeCampaignWorkspace(ctx.controllerHome, repository.repoId, current.campaignId, requestId);
5814
- return result({ campaign });
5815
- }
5816
- case 'reconcile_campaign': {
5817
- const repository = selected(ctx, args);
5818
- const reconciliation = reconcileCampaign(ctx.controllerHome, repository.repoId, String(args.campaign_id ?? ''));
5819
- return result({ reconciliation, campaign: getCampaign(ctx.controllerHome, repository.repoId, reconciliation.campaignId) });
5820
- }
5821
5743
  case 'create_schedule': {
5822
5744
  const repository = selected(ctx, args);
5823
5745
  const operation = String(args.operation ?? '').trim();