thehood 0.1.0-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/CODE_OF_CONDUCT.md +21 -0
  2. package/CONTRIBUTING.md +58 -0
  3. package/LICENSE +21 -0
  4. package/PRIVACY.md +49 -0
  5. package/README.md +264 -0
  6. package/SECURITY.md +31 -0
  7. package/dist/bridges/chatgptWebBridge.d.ts +2 -0
  8. package/dist/bridges/chatgptWebBridge.js +981 -0
  9. package/dist/bridges/chatgptWebBridge.js.map +1 -0
  10. package/dist/cli/args.d.ts +9 -0
  11. package/dist/cli/args.js +82 -0
  12. package/dist/cli/args.js.map +1 -0
  13. package/dist/cli/format.d.ts +56 -0
  14. package/dist/cli/format.js +752 -0
  15. package/dist/cli/format.js.map +1 -0
  16. package/dist/cli/main.d.ts +2 -0
  17. package/dist/cli/main.js +996 -0
  18. package/dist/cli/main.js.map +1 -0
  19. package/dist/cli/mcpConfig.d.ts +36 -0
  20. package/dist/cli/mcpConfig.js +98 -0
  21. package/dist/cli/mcpConfig.js.map +1 -0
  22. package/dist/index.d.ts +37 -0
  23. package/dist/index.js +38 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/mcp/protocol.d.ts +44 -0
  26. package/dist/mcp/protocol.js +33 -0
  27. package/dist/mcp/protocol.js.map +1 -0
  28. package/dist/mcp/server.d.ts +1 -0
  29. package/dist/mcp/server.js +106 -0
  30. package/dist/mcp/server.js.map +1 -0
  31. package/dist/mcp/tools.d.ts +10 -0
  32. package/dist/mcp/tools.js +2200 -0
  33. package/dist/mcp/tools.js.map +1 -0
  34. package/dist/mcp/validation.d.ts +8 -0
  35. package/dist/mcp/validation.js +67 -0
  36. package/dist/mcp/validation.js.map +1 -0
  37. package/dist/providers/chatgptWeb.d.ts +2 -0
  38. package/dist/providers/chatgptWeb.js +26 -0
  39. package/dist/providers/chatgptWeb.js.map +1 -0
  40. package/dist/providers/claudeCode.d.ts +4 -0
  41. package/dist/providers/claudeCode.js +32 -0
  42. package/dist/providers/claudeCode.js.map +1 -0
  43. package/dist/providers/codexCli.d.ts +6 -0
  44. package/dist/providers/codexCli.js +25 -0
  45. package/dist/providers/codexCli.js.map +1 -0
  46. package/dist/providers/codexCliModels.d.ts +23 -0
  47. package/dist/providers/codexCliModels.js +147 -0
  48. package/dist/providers/codexCliModels.js.map +1 -0
  49. package/dist/providers/localCommand.d.ts +26 -0
  50. package/dist/providers/localCommand.js +614 -0
  51. package/dist/providers/localCommand.js.map +1 -0
  52. package/dist/providers/markdownPayload.d.ts +7 -0
  53. package/dist/providers/markdownPayload.js +29 -0
  54. package/dist/providers/markdownPayload.js.map +1 -0
  55. package/dist/providers/responseSchema.d.ts +3 -0
  56. package/dist/providers/responseSchema.js +187 -0
  57. package/dist/providers/responseSchema.js.map +1 -0
  58. package/dist/providers/router.d.ts +3 -0
  59. package/dist/providers/router.js +21 -0
  60. package/dist/providers/router.js.map +1 -0
  61. package/dist/providers/stub.d.ts +2 -0
  62. package/dist/providers/stub.js +177 -0
  63. package/dist/providers/stub.js.map +1 -0
  64. package/dist/providers/types.d.ts +37 -0
  65. package/dist/providers/types.js +2 -0
  66. package/dist/providers/types.js.map +1 -0
  67. package/dist/runtime/agentBoard.d.ts +79 -0
  68. package/dist/runtime/agentBoard.js +166 -0
  69. package/dist/runtime/agentBoard.js.map +1 -0
  70. package/dist/runtime/agentBoardArtifact.d.ts +9 -0
  71. package/dist/runtime/agentBoardArtifact.js +171 -0
  72. package/dist/runtime/agentBoardArtifact.js.map +1 -0
  73. package/dist/runtime/agentRunner.d.ts +17 -0
  74. package/dist/runtime/agentRunner.js +92 -0
  75. package/dist/runtime/agentRunner.js.map +1 -0
  76. package/dist/runtime/approvalInbox.d.ts +54 -0
  77. package/dist/runtime/approvalInbox.js +143 -0
  78. package/dist/runtime/approvalInbox.js.map +1 -0
  79. package/dist/runtime/approvalPolicy.d.ts +11 -0
  80. package/dist/runtime/approvalPolicy.js +58 -0
  81. package/dist/runtime/approvalPolicy.js.map +1 -0
  82. package/dist/runtime/artifacts.d.ts +23 -0
  83. package/dist/runtime/artifacts.js +48 -0
  84. package/dist/runtime/artifacts.js.map +1 -0
  85. package/dist/runtime/browserManager.d.ts +37 -0
  86. package/dist/runtime/browserManager.js +356 -0
  87. package/dist/runtime/browserManager.js.map +1 -0
  88. package/dist/runtime/canonicalMemory.d.ts +23 -0
  89. package/dist/runtime/canonicalMemory.js +134 -0
  90. package/dist/runtime/canonicalMemory.js.map +1 -0
  91. package/dist/runtime/chatGptPageReadiness.d.ts +16 -0
  92. package/dist/runtime/chatGptPageReadiness.js +74 -0
  93. package/dist/runtime/chatGptPageReadiness.js.map +1 -0
  94. package/dist/runtime/commandRunner.d.ts +18 -0
  95. package/dist/runtime/commandRunner.js +115 -0
  96. package/dist/runtime/commandRunner.js.map +1 -0
  97. package/dist/runtime/commandSafety.d.ts +7 -0
  98. package/dist/runtime/commandSafety.js +61 -0
  99. package/dist/runtime/commandSafety.js.map +1 -0
  100. package/dist/runtime/config.d.ts +10 -0
  101. package/dist/runtime/config.js +107 -0
  102. package/dist/runtime/config.js.map +1 -0
  103. package/dist/runtime/crewLanes.d.ts +2 -0
  104. package/dist/runtime/crewLanes.js +123 -0
  105. package/dist/runtime/crewLanes.js.map +1 -0
  106. package/dist/runtime/criticPolicy.d.ts +17 -0
  107. package/dist/runtime/criticPolicy.js +50 -0
  108. package/dist/runtime/criticPolicy.js.map +1 -0
  109. package/dist/runtime/defaults.d.ts +5 -0
  110. package/dist/runtime/defaults.js +100 -0
  111. package/dist/runtime/defaults.js.map +1 -0
  112. package/dist/runtime/directives.d.ts +3 -0
  113. package/dist/runtime/directives.js +218 -0
  114. package/dist/runtime/directives.js.map +1 -0
  115. package/dist/runtime/doctor.d.ts +36 -0
  116. package/dist/runtime/doctor.js +185 -0
  117. package/dist/runtime/doctor.js.map +1 -0
  118. package/dist/runtime/errors.d.ts +20 -0
  119. package/dist/runtime/errors.js +41 -0
  120. package/dist/runtime/errors.js.map +1 -0
  121. package/dist/runtime/externalTransfer.d.ts +20 -0
  122. package/dist/runtime/externalTransfer.js +156 -0
  123. package/dist/runtime/externalTransfer.js.map +1 -0
  124. package/dist/runtime/fanout.d.ts +64 -0
  125. package/dist/runtime/fanout.js +263 -0
  126. package/dist/runtime/fanout.js.map +1 -0
  127. package/dist/runtime/gitEvidence.d.ts +10 -0
  128. package/dist/runtime/gitEvidence.js +80 -0
  129. package/dist/runtime/gitEvidence.js.map +1 -0
  130. package/dist/runtime/handoffs.d.ts +32 -0
  131. package/dist/runtime/handoffs.js +100 -0
  132. package/dist/runtime/handoffs.js.map +1 -0
  133. package/dist/runtime/ids.d.ts +2 -0
  134. package/dist/runtime/ids.js +4 -0
  135. package/dist/runtime/ids.js.map +1 -0
  136. package/dist/runtime/localStateIgnore.d.ts +9 -0
  137. package/dist/runtime/localStateIgnore.js +98 -0
  138. package/dist/runtime/localStateIgnore.js.map +1 -0
  139. package/dist/runtime/loop.d.ts +14 -0
  140. package/dist/runtime/loop.js +1863 -0
  141. package/dist/runtime/loop.js.map +1 -0
  142. package/dist/runtime/loopRecommendation.d.ts +109 -0
  143. package/dist/runtime/loopRecommendation.js +566 -0
  144. package/dist/runtime/loopRecommendation.js.map +1 -0
  145. package/dist/runtime/loopResponsibilities.d.ts +2 -0
  146. package/dist/runtime/loopResponsibilities.js +395 -0
  147. package/dist/runtime/loopResponsibilities.js.map +1 -0
  148. package/dist/runtime/loopRunner.d.ts +28 -0
  149. package/dist/runtime/loopRunner.js +81 -0
  150. package/dist/runtime/loopRunner.js.map +1 -0
  151. package/dist/runtime/operatorNextActions.d.ts +2 -0
  152. package/dist/runtime/operatorNextActions.js +344 -0
  153. package/dist/runtime/operatorNextActions.js.map +1 -0
  154. package/dist/runtime/paths.d.ts +9 -0
  155. package/dist/runtime/paths.js +14 -0
  156. package/dist/runtime/paths.js.map +1 -0
  157. package/dist/runtime/permissions.d.ts +9 -0
  158. package/dist/runtime/permissions.js +73 -0
  159. package/dist/runtime/permissions.js.map +1 -0
  160. package/dist/runtime/progressPacket.d.ts +12 -0
  161. package/dist/runtime/progressPacket.js +512 -0
  162. package/dist/runtime/progressPacket.js.map +1 -0
  163. package/dist/runtime/protectedPaths.d.ts +6 -0
  164. package/dist/runtime/protectedPaths.js +48 -0
  165. package/dist/runtime/protectedPaths.js.map +1 -0
  166. package/dist/runtime/providers.d.ts +13 -0
  167. package/dist/runtime/providers.js +60 -0
  168. package/dist/runtime/providers.js.map +1 -0
  169. package/dist/runtime/reconciliation.d.ts +17 -0
  170. package/dist/runtime/reconciliation.js +283 -0
  171. package/dist/runtime/reconciliation.js.map +1 -0
  172. package/dist/runtime/redaction.d.ts +1 -0
  173. package/dist/runtime/redaction.js +5 -0
  174. package/dist/runtime/redaction.js.map +1 -0
  175. package/dist/runtime/remoteRepoContext.d.ts +77 -0
  176. package/dist/runtime/remoteRepoContext.js +316 -0
  177. package/dist/runtime/remoteRepoContext.js.map +1 -0
  178. package/dist/runtime/repoContext.d.ts +50 -0
  179. package/dist/runtime/repoContext.js +399 -0
  180. package/dist/runtime/repoContext.js.map +1 -0
  181. package/dist/runtime/repoGateway.d.ts +64 -0
  182. package/dist/runtime/repoGateway.js +308 -0
  183. package/dist/runtime/repoGateway.js.map +1 -0
  184. package/dist/runtime/responseContracts.d.ts +3 -0
  185. package/dist/runtime/responseContracts.js +86 -0
  186. package/dist/runtime/responseContracts.js.map +1 -0
  187. package/dist/runtime/reviewLanes.d.ts +2 -0
  188. package/dist/runtime/reviewLanes.js +343 -0
  189. package/dist/runtime/reviewLanes.js.map +1 -0
  190. package/dist/runtime/reviewRouting.d.ts +51 -0
  191. package/dist/runtime/reviewRouting.js +152 -0
  192. package/dist/runtime/reviewRouting.js.map +1 -0
  193. package/dist/runtime/revisionPacket.d.ts +38 -0
  194. package/dist/runtime/revisionPacket.js +144 -0
  195. package/dist/runtime/revisionPacket.js.map +1 -0
  196. package/dist/runtime/revisionTrail.d.ts +2 -0
  197. package/dist/runtime/revisionTrail.js +162 -0
  198. package/dist/runtime/revisionTrail.js.map +1 -0
  199. package/dist/runtime/role-assignment.d.ts +4 -0
  200. package/dist/runtime/role-assignment.js +21 -0
  201. package/dist/runtime/role-assignment.js.map +1 -0
  202. package/dist/runtime/roleRoster.d.ts +28 -0
  203. package/dist/runtime/roleRoster.js +96 -0
  204. package/dist/runtime/roleRoster.js.map +1 -0
  205. package/dist/runtime/runInsights.d.ts +121 -0
  206. package/dist/runtime/runInsights.js +305 -0
  207. package/dist/runtime/runInsights.js.map +1 -0
  208. package/dist/runtime/runMonitor.d.ts +33 -0
  209. package/dist/runtime/runMonitor.js +143 -0
  210. package/dist/runtime/runMonitor.js.map +1 -0
  211. package/dist/runtime/runtime.d.ts +15 -0
  212. package/dist/runtime/runtime.js +199 -0
  213. package/dist/runtime/runtime.js.map +1 -0
  214. package/dist/runtime/runtimeInfo.d.ts +9 -0
  215. package/dist/runtime/runtimeInfo.js +76 -0
  216. package/dist/runtime/runtimeInfo.js.map +1 -0
  217. package/dist/runtime/store.d.ts +4 -0
  218. package/dist/runtime/store.js +48 -0
  219. package/dist/runtime/store.js.map +1 -0
  220. package/dist/runtime/summons.d.ts +25 -0
  221. package/dist/runtime/summons.js +403 -0
  222. package/dist/runtime/summons.js.map +1 -0
  223. package/dist/runtime/teamPresets.d.ts +14 -0
  224. package/dist/runtime/teamPresets.js +153 -0
  225. package/dist/runtime/teamPresets.js.map +1 -0
  226. package/dist/runtime/types.d.ts +505 -0
  227. package/dist/runtime/types.js +28 -0
  228. package/dist/runtime/types.js.map +1 -0
  229. package/dist/runtime/validationCommands.d.ts +18 -0
  230. package/dist/runtime/validationCommands.js +106 -0
  231. package/dist/runtime/validationCommands.js.map +1 -0
  232. package/dist/tui/dashboard.d.ts +41 -0
  233. package/dist/tui/dashboard.js +1115 -0
  234. package/dist/tui/dashboard.js.map +1 -0
  235. package/docs/ARCHITECTURE.md +277 -0
  236. package/docs/CLI_SPEC.md +396 -0
  237. package/docs/CODEX_SETUP.md +288 -0
  238. package/docs/COMPLETION_CONTRACT.md +52 -0
  239. package/docs/CONTRIBUTOR_GUIDE.md +70 -0
  240. package/docs/DEMO.md +62 -0
  241. package/docs/GLOSSARY.md +46 -0
  242. package/docs/GOAL_LOOP_SCHEDULE.md +50 -0
  243. package/docs/KNOWN_LIMITATIONS.md +29 -0
  244. package/docs/LICENSING.md +21 -0
  245. package/docs/LOOP_RECIPES.md +290 -0
  246. package/docs/LOOP_SELECTION_UX.md +118 -0
  247. package/docs/MCP_SPEC.md +689 -0
  248. package/docs/MEMORY_AND_RECONCILIATION.md +222 -0
  249. package/docs/NPM_PUBLISHING.md +51 -0
  250. package/docs/OPEN_DECISIONS.md +81 -0
  251. package/docs/PROMPT_SCHEMAS.md +411 -0
  252. package/docs/PROVIDER_ADAPTERS.md +323 -0
  253. package/docs/PROVIDER_MATRIX.md +21 -0
  254. package/docs/PUBLIC_REPO_READINESS.md +49 -0
  255. package/docs/RESEARCH_NOTES.md +92 -0
  256. package/docs/ROADMAP.md +94 -0
  257. package/docs/ROLE_CONTRACTS.md +252 -0
  258. package/docs/RUNTIME_LOOP.md +240 -0
  259. package/docs/SECURITY_AND_PRIVACY.md +161 -0
  260. package/docs/TESTING_AND_VERIFICATION.md +180 -0
  261. package/docs/TRUST_MODEL.md +65 -0
  262. package/docs/decisions/0001-runtime-first-cli-and-mcp.md +23 -0
  263. package/docs/decisions/0002-provider-neutral-role-mapping.md +43 -0
  264. package/docs/decisions/0003-separate-implementation-and-verification.md +27 -0
  265. package/docs/product/README.md +14 -0
  266. package/docs/product/model-selection.md +88 -0
  267. package/docs/product/positioning.md +37 -0
  268. package/docs/product/pro-usage-modes.md +70 -0
  269. package/docs/product/roadmap.md +57 -0
  270. package/docs/product/role-policy.md +89 -0
  271. package/docs/product/runtime-invariants.md +44 -0
  272. package/docs/release/v0.1.0-preview.0.md +48 -0
  273. package/examples/stub-demo/README.md +25 -0
  274. package/package.json +55 -0
@@ -0,0 +1,1863 @@
1
+ import fs from "node:fs/promises";
2
+ import { requiredAssignment, runAgent } from "./agentRunner.js";
3
+ import { autopilotApprovalReason, autopilotPolicyReason, autoApprovalReason, evaluateExternalTransferPolicy, isAutopilotEnabled } from "./approvalPolicy.js";
4
+ import { writeRunArtifact } from "./artifacts.js";
5
+ import { runRuntimeCommand } from "./commandRunner.js";
6
+ import { loadConfig } from "./config.js";
7
+ import { deriveCrewLaneTrail } from "./crewLanes.js";
8
+ import { transferManifestSummary, writeExternalTransferManifestArtifact } from "./externalTransfer.js";
9
+ import { createRunHandoff } from "./handoffs.js";
10
+ import { captureGitEvidence, parseGitStatusPaths } from "./gitEvidence.js";
11
+ import { decideCriticTrigger } from "./criticPolicy.js";
12
+ import { newId, nowIso } from "./ids.js";
13
+ import { findProtectedPathMatches } from "./protectedPaths.js";
14
+ import { writeProgressPacketArtifact } from "./progressPacket.js";
15
+ import { decideRevisionPacket, writeRevisionPacketArtifact } from "./revisionPacket.js";
16
+ import { deriveRevisionTrail } from "./revisionTrail.js";
17
+ import { decideReviewRouting, reviewRoutingJson, reviewRoutingSummary } from "./reviewRouting.js";
18
+ import { deriveReviewLanes } from "./reviewLanes.js";
19
+ import { analyzeRepoContextRequest, captureRepoContext, latestRepoContextArtifact, readCombinedRepoContext, repoContextArtifacts } from "./repoContext.js";
20
+ import { captureRemoteRepoContext, latestRemoteRepoContextArtifact, readLatestRemoteRepoContext, remoteRepoContextArtifacts } from "./remoteRepoContext.js";
21
+ import { loadRun, saveRun } from "./store.js";
22
+ import { captureValidationEvidence } from "./validationCommands.js";
23
+ import { runtimeRoles } from "./types.js";
24
+ const createEvent = (type, message, data) => ({
25
+ id: newId("event"),
26
+ createdAt: nowIso(),
27
+ type,
28
+ message,
29
+ ...(data ? { data } : {})
30
+ });
31
+ const createApprovalEvent = (reason) => ({
32
+ id: newId("approval"),
33
+ createdAt: nowIso(),
34
+ decision: "approve",
35
+ reason
36
+ });
37
+ const autoApproveGate = async (run, input) => {
38
+ const { approvalReason: _approvalReason, ...base } = run;
39
+ const approvalReason = autopilotApprovalReason(input.reason);
40
+ const approval = createApprovalEvent(approvalReason);
41
+ const stateAfter = input.state ?? run.state;
42
+ const updated = {
43
+ ...base,
44
+ updatedAt: nowIso(),
45
+ ...(input.state ? { state: input.state } : {}),
46
+ approvalRequired: false,
47
+ approvalEvents: [...run.approvalEvents, approval],
48
+ handoffs: [
49
+ ...(run.handoffs ?? []),
50
+ createRunHandoff(run, {
51
+ kind: "approval_auto_approved",
52
+ reason: approvalReason,
53
+ stateAfter,
54
+ toRole: input.role,
55
+ gate: input.gate,
56
+ approvalEventId: approval.id,
57
+ artifactRefs: input.artifactRefs
58
+ })
59
+ ],
60
+ events: [
61
+ ...run.events,
62
+ createEvent("approval_auto_approved", approvalReason, {
63
+ gate: input.gate,
64
+ gateReason: input.reason,
65
+ policyDecision: "auto_approve",
66
+ policyReason: autopilotPolicyReason(input.gate),
67
+ ...(input.data ?? {})
68
+ })
69
+ ]
70
+ };
71
+ await saveRun(updated);
72
+ return updated;
73
+ };
74
+ const autoApproveGateIfEnabled = async (run, input) => {
75
+ const config = await loadConfig(run.repoPath);
76
+ return isAutopilotEnabled(config) ? autoApproveGate(run, input) : undefined;
77
+ };
78
+ const approvalGateHandoff = (run, input) => createRunHandoff(run, {
79
+ kind: "approval_gate",
80
+ reason: input.reason,
81
+ stateAfter: "awaiting_approval",
82
+ fromRole: input.role,
83
+ gate: input.gate,
84
+ artifactRefs: input.artifactRefs
85
+ });
86
+ const agentHandoff = (run, input) => createRunHandoff(run, {
87
+ kind: "agent_handoff",
88
+ reason: input.reason,
89
+ stateAfter: input.stateAfter,
90
+ fromRole: input.fromRole,
91
+ toRole: input.toRole,
92
+ artifactRefs: input.artifactRefs
93
+ });
94
+ const completionHandoff = (run, role, reason) => createRunHandoff(run, {
95
+ kind: "completion",
96
+ reason,
97
+ stateAfter: "completed",
98
+ fromRole: role
99
+ });
100
+ const updateRun = async (run, updates, events, handoffs = []) => {
101
+ const { approvalReason: _approvalReason, stopReason: _stopReason, ...base } = run;
102
+ const next = {
103
+ ...base,
104
+ ...updates,
105
+ updatedAt: nowIso(),
106
+ handoffs: [...(run.handoffs ?? []), ...handoffs],
107
+ events: [...run.events, ...events]
108
+ };
109
+ if (updates.approvalReason === undefined && run.approvalReason && !("approvalReason" in updates)) {
110
+ next.approvalReason = run.approvalReason;
111
+ }
112
+ if (updates.stopReason === undefined && run.stopReason && !("stopReason" in updates)) {
113
+ next.stopReason = run.stopReason;
114
+ }
115
+ await saveRun(next);
116
+ return next;
117
+ };
118
+ const terminalStates = new Set(["completed", "failed", "aborted"]);
119
+ const providerCallStates = new Set(["created", "planning", "delegating", "implementing", "verifying"]);
120
+ const providerResponseCount = (run) => run.events.filter((event) => event.type === "agent_response").length;
121
+ const stopForMaxIterations = async (run) => {
122
+ if (!providerCallStates.has(run.state)) {
123
+ return undefined;
124
+ }
125
+ const iterationCount = providerResponseCount(run);
126
+ if (iterationCount < run.maxIterations) {
127
+ return undefined;
128
+ }
129
+ const stopReason = `Max iterations reached (${iterationCount}/${run.maxIterations}).`;
130
+ const failed = await updateRun(run, {
131
+ state: "failed",
132
+ approvalRequired: false,
133
+ stopReason
134
+ }, [
135
+ createEvent("run_failed", stopReason, {
136
+ reason: "max_iterations",
137
+ iterationCount,
138
+ maxIterations: run.maxIterations
139
+ })
140
+ ]);
141
+ return {
142
+ run: failed,
143
+ stopReason
144
+ };
145
+ };
146
+ const verdictFromResponse = (response) => {
147
+ const value = response.data.verificationResult;
148
+ if (value && typeof value === "object" && !Array.isArray(value)) {
149
+ const verdict = value.verdict;
150
+ return typeof verdict === "string" ? verdict : undefined;
151
+ }
152
+ return undefined;
153
+ };
154
+ const decisionFromResponse = (response) => {
155
+ const value = response.data.decision;
156
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
157
+ };
158
+ const actionFromResponse = (response) => {
159
+ const action = decisionFromResponse(response)?.action;
160
+ return typeof action === "string" ? action : undefined;
161
+ };
162
+ const decisionStringField = (decision, field) => {
163
+ const value = decision?.[field];
164
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
165
+ };
166
+ const decisionBooleanField = (decision, field) => {
167
+ const value = decision?.[field];
168
+ return typeof value === "boolean" ? value : undefined;
169
+ };
170
+ const decisionStringArrayField = (decision, field) => {
171
+ const value = decision?.[field];
172
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
173
+ };
174
+ const runtimeRoleFromDecisionField = (decision, field) => {
175
+ const value = decisionStringField(decision, field);
176
+ return value && runtimeRoles.includes(value) ? value : undefined;
177
+ };
178
+ const readyDelegateRole = (decision) => {
179
+ const role = runtimeRoleFromDecisionField(decision, "delegateTo") ??
180
+ runtimeRoleFromDecisionField(decision, "nextRole");
181
+ if (!role) {
182
+ return undefined;
183
+ }
184
+ return decisionBooleanField(decision, "requiresMoreEvidence") === true ? undefined : role;
185
+ };
186
+ const readOnlyDelegateRoles = new Set([
187
+ "orchestrator",
188
+ "planner",
189
+ "researcher",
190
+ "qa",
191
+ "verifier",
192
+ "critic",
193
+ "citation"
194
+ ]);
195
+ const providersRequiringRepoContextApproval = new Set(["chatgpt-web", "openai-api", "anthropic-api"]);
196
+ const readOnlyProvidersRequiringInvocationApproval = new Set([
197
+ "anthropic-api",
198
+ "chatgpt-web",
199
+ "claude-code",
200
+ "codex-cli",
201
+ "openai-api"
202
+ ]);
203
+ const latestRepoContextCapturedAt = (run) => run.events.filter((event) => event.type === "repo_context_captured").at(-1)?.createdAt;
204
+ const approvalMentionsRepoContextShare = (reason, assignment) => {
205
+ const normalized = reason.toLowerCase();
206
+ const providerTokens = [
207
+ assignment.provider,
208
+ assignment.provider.replace(/-/g, " "),
209
+ assignment.model,
210
+ assignment.model.replace(/-/g, " ")
211
+ ];
212
+ return (normalized.includes("repo context") &&
213
+ (normalized.includes("send") || normalized.includes("share")) &&
214
+ providerTokens.some((token) => token && normalized.includes(token.toLowerCase())));
215
+ };
216
+ const hasExternalRepoContextApproval = (run, assignment) => {
217
+ const capturedAt = latestRepoContextCapturedAt(run);
218
+ return run.approvalEvents.some((approval) => approval.decision === "approve" &&
219
+ (!capturedAt || approval.createdAt >= capturedAt) &&
220
+ approvalMentionsRepoContextShare(approval.reason, assignment));
221
+ };
222
+ const approvalMentionsProviderInvocation = (reason, assignment) => {
223
+ const normalized = reason.toLowerCase();
224
+ const providerTokens = [
225
+ assignment.provider,
226
+ assignment.provider.replace(/-/g, " "),
227
+ assignment.model,
228
+ assignment.model.replace(/-/g, " ")
229
+ ];
230
+ return ((normalized.includes("invoke") || normalized.includes("call") || normalized.includes("use")) &&
231
+ providerTokens.some((token) => token && normalized.includes(token.toLowerCase())));
232
+ };
233
+ const hasProviderInvocationApproval = (run, assignment) => run.approvalEvents.some((approval) => approval.decision === "approve" && approvalMentionsProviderInvocation(approval.reason, assignment));
234
+ const hasProviderResponded = (run, role, assignment) => run.events.some((event) => {
235
+ if (event.type !== "agent_response" || !event.data) {
236
+ return false;
237
+ }
238
+ return event.data.role === role && event.data.provider === assignment.provider && event.data.model === assignment.model;
239
+ });
240
+ const providerResponseEventMatches = (event, role, assignment) => {
241
+ if (event.type !== "agent_response" || !event.data || event.data.role !== role) {
242
+ return false;
243
+ }
244
+ return assignment
245
+ ? event.data.provider === assignment.provider && event.data.model === assignment.model
246
+ : true;
247
+ };
248
+ const latestProviderResponseEventIndex = (run, role, assignment) => {
249
+ for (let index = run.events.length - 1; index >= 0; index -= 1) {
250
+ const event = run.events[index];
251
+ if (event && providerResponseEventMatches(event, role, assignment)) {
252
+ return index;
253
+ }
254
+ }
255
+ return undefined;
256
+ };
257
+ const hasProviderRespondedSince = (run, role, assignment, afterIndex) => {
258
+ if (afterIndex === undefined) {
259
+ return hasProviderResponded(run, role, assignment);
260
+ }
261
+ return run.events
262
+ .slice(afterIndex + 1)
263
+ .some((event) => providerResponseEventMatches(event, role, assignment));
264
+ };
265
+ const providerInvocationApprovalReason = (role, assignment) => `Invoking ${assignment.provider}:${assignment.model} for ${role} requires explicit approval. ` +
266
+ `Approval message must mention "invoke ${assignment.provider}".`;
267
+ const externalRepoContextApprovalReason = (role, assignment, artifactSummary, approvalPhrase) => `Sending repo context to ${assignment.provider}:${assignment.model} for ${role} requires explicit approval. ` +
268
+ `Approval message must mention "${approvalPhrase}". Review the transfer manifest before approving. ` +
269
+ `Context artifact: ${artifactSummary}`;
270
+ const createApprovalGateResponse = (role, summary) => {
271
+ const dataForRole = () => {
272
+ switch (role) {
273
+ case "orchestrator":
274
+ case "planner":
275
+ return {
276
+ decision: {
277
+ action: "request_approval",
278
+ reason: summary
279
+ }
280
+ };
281
+ case "researcher":
282
+ return {
283
+ researchResult: {
284
+ status: "blocked",
285
+ summary,
286
+ findings: [],
287
+ sources: []
288
+ }
289
+ };
290
+ case "critic":
291
+ return {
292
+ critiqueResult: {
293
+ verdict: "unclear",
294
+ blockingConcerns: [summary],
295
+ nonBlockingConcerns: []
296
+ }
297
+ };
298
+ case "qa":
299
+ return {
300
+ qaResult: {
301
+ verdict: "blocked",
302
+ summary,
303
+ suggestedCommands: [],
304
+ risks: [summary]
305
+ }
306
+ };
307
+ case "verifier":
308
+ return {
309
+ verificationResult: {
310
+ verdict: "ask_user",
311
+ summary
312
+ }
313
+ };
314
+ case "implementer":
315
+ return {
316
+ implementationResult: {
317
+ status: "blocked",
318
+ changedFiles: [],
319
+ commandsRun: [],
320
+ unresolvedRisks: [summary]
321
+ }
322
+ };
323
+ default:
324
+ return {
325
+ result: {
326
+ status: "blocked",
327
+ summary
328
+ }
329
+ };
330
+ }
331
+ };
332
+ return {
333
+ status: "blocked",
334
+ summary,
335
+ data: dataForRole()
336
+ };
337
+ };
338
+ const isJsonObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
339
+ const implementationPatchArtifact = (response) => {
340
+ const implementationResult = response.data.implementationResult;
341
+ if (!isJsonObject(implementationResult)) {
342
+ return undefined;
343
+ }
344
+ const patchArtifact = implementationResult.patchArtifact;
345
+ if (!isJsonObject(patchArtifact)) {
346
+ return undefined;
347
+ }
348
+ if (patchArtifact.kind !== "diff" ||
349
+ typeof patchArtifact.ref !== "string" ||
350
+ typeof patchArtifact.summary !== "string") {
351
+ return undefined;
352
+ }
353
+ return {
354
+ kind: "diff",
355
+ ref: patchArtifact.ref,
356
+ summary: patchArtifact.summary
357
+ };
358
+ };
359
+ const isolatedPatchApprovalReason = (artifact) => `Implementer produced an isolated patch artifact. Review it before applying to the target checkout. ` +
360
+ `Approval message must mention "apply isolated patch". Patch artifact: ${artifact.summary}`;
361
+ const latestIsolatedPatchArtifact = (run) => {
362
+ const event = run.events
363
+ .filter((candidate) => (candidate.type === "approval_required" && candidate.data?.reason === "isolated_patch_application") ||
364
+ (candidate.type === "approval_auto_approved" && candidate.data?.gate === "isolated_patch_application"))
365
+ .at(-1);
366
+ const artifactRef = event?.data?.artifactRef;
367
+ if (typeof artifactRef !== "string") {
368
+ return undefined;
369
+ }
370
+ return run.artifacts.find((artifact) => artifact.ref === artifactRef && artifact.kind === "diff");
371
+ };
372
+ const patchBodyFromArtifact = async (artifact) => {
373
+ const raw = await fs.readFile(artifact.ref, "utf8");
374
+ const markerIndex = raw.indexOf("diff --git ");
375
+ if (markerIndex < 0) {
376
+ throw new Error(`Patch artifact does not contain a git diff: ${artifact.ref}`);
377
+ }
378
+ return raw.slice(markerIndex);
379
+ };
380
+ const attachRunArtifact = async (run, artifact, event) => {
381
+ const latest = await loadRun(run.repoPath, run.runId);
382
+ const updated = {
383
+ ...latest,
384
+ updatedAt: nowIso(),
385
+ artifacts: [...latest.artifacts, artifact],
386
+ events: [...latest.events, event]
387
+ };
388
+ await saveRun(updated);
389
+ return updated;
390
+ };
391
+ const artifactKindCounts = (artifacts) => {
392
+ const counts = {};
393
+ for (const artifact of artifacts) {
394
+ counts[artifact.kind] = (counts[artifact.kind] ?? 0) + 1;
395
+ }
396
+ return counts;
397
+ };
398
+ const latestProviderResponseArtifactRef = (run, role) => {
399
+ const event = run.events
400
+ .filter((candidate) => candidate.type === "agent_response" && candidate.data?.role === role)
401
+ .at(-1);
402
+ const artifactRef = event?.data?.artifactRef;
403
+ return typeof artifactRef === "string" ? artifactRef : undefined;
404
+ };
405
+ const latestCriticTriggerArtifact = (run) => run.artifacts.filter((artifact) => artifact.kind === "critic_trigger").at(-1);
406
+ const latestRevisionPacketArtifact = (run) => run.artifacts.filter((artifact) => artifact.kind === "revision_packet").at(-1);
407
+ const writeReviewRoutingArtifact = async (run, decision) => {
408
+ const artifact = await writeRunArtifact({
409
+ repoPath: run.repoPath,
410
+ runId: run.runId,
411
+ kind: "review_routing",
412
+ name: `review-routing-${newId("review-routing")}.json`,
413
+ content: `${JSON.stringify({
414
+ ...reviewRoutingJson(decision),
415
+ runId: run.runId,
416
+ createdAt: nowIso()
417
+ }, null, 2)}\n`,
418
+ summary: reviewRoutingSummary(decision)
419
+ });
420
+ const runWithArtifact = await attachRunArtifact(run, artifact, createEvent("review_routing_decided", artifact.summary, {
421
+ artifactRef: artifact.ref,
422
+ riskTier: decision.riskTier,
423
+ action: decision.action,
424
+ required: decision.required,
425
+ reasons: decision.reasons,
426
+ signals: decision.signals
427
+ }));
428
+ return {
429
+ run: runWithArtifact,
430
+ artifact
431
+ };
432
+ };
433
+ const writeCriticTriggerArtifact = async (run, decision, criticResponseRef) => {
434
+ const artifact = await writeRunArtifact({
435
+ repoPath: run.repoPath,
436
+ runId: run.runId,
437
+ kind: "critic_trigger",
438
+ name: `critic-trigger-${newId("critic-trigger")}.json`,
439
+ content: `${JSON.stringify({
440
+ schemaVersion: 1,
441
+ kind: "critic_trigger",
442
+ runId: run.runId,
443
+ called: true,
444
+ reasonCode: decision.reasonCode,
445
+ reason: decision.reason,
446
+ sourceRoles: decision.sourceRoles,
447
+ evidenceRefs: decision.evidenceRefs,
448
+ ...(criticResponseRef ? { criticResponseRef } : {})
449
+ }, null, 2)}\n`,
450
+ summary: `Critic trigger: ${decision.reasonCode ?? "unknown"}`
451
+ });
452
+ return attachRunArtifact(run, artifact, createEvent("critic_trigger_written", decision.reason ?? "Runtime called critic from policy.", {
453
+ artifactRef: artifact.ref,
454
+ ...(decision.reasonCode ? { reasonCode: decision.reasonCode } : {}),
455
+ sourceRoles: decision.sourceRoles,
456
+ evidenceRefs: decision.evidenceRefs,
457
+ ...(criticResponseRef ? { criticResponseRef } : {})
458
+ }));
459
+ };
460
+ const latestRevisionPacketContext = async (run) => {
461
+ const artifact = latestRevisionPacketArtifact(run);
462
+ if (!artifact) {
463
+ return undefined;
464
+ }
465
+ const parsed = JSON.parse(await fs.readFile(artifact.ref, "utf8"));
466
+ if (!isJsonObject(parsed)) {
467
+ return undefined;
468
+ }
469
+ return parsed;
470
+ };
471
+ const writeAndDelegateRevision = async (run, input) => {
472
+ const { artifact, packet } = await writeRevisionPacketArtifact(run, input);
473
+ const runWithPacket = await attachRunArtifact(run, artifact, createEvent("revision_packet_written", `Wrote revision packet from ${packet.sourceRole}.`, {
474
+ artifactRef: artifact.ref,
475
+ sourceRole: packet.sourceRole,
476
+ reasonCode: packet.reasonCode,
477
+ evidenceRefs: packet.evidenceRefs,
478
+ ...(packet.sourceResponseRef ? { sourceResponseRef: packet.sourceResponseRef } : {}),
479
+ ...(packet.criticTriggerRef ? { criticTriggerRef: packet.criticTriggerRef } : {})
480
+ }));
481
+ const stopReason = `Revision delegated to implementer: ${packet.reasonCode}.`;
482
+ const { approvalReason: _approvalReason, ...runForDelegation } = runWithPacket;
483
+ const delegated = await updateRun(runForDelegation, {
484
+ state: "implementing",
485
+ approvalRequired: false
486
+ }, [
487
+ createEvent("revision_delegated", stopReason, {
488
+ artifactRef: artifact.ref,
489
+ sourceRole: packet.sourceRole,
490
+ reasonCode: packet.reasonCode,
491
+ repairObjective: packet.repairObjective
492
+ })
493
+ ], [agentHandoff(runWithPacket, {
494
+ reason: stopReason,
495
+ stateAfter: "implementing",
496
+ fromRole: packet.sourceRole,
497
+ toRole: "implementer",
498
+ artifactRefs: [artifact.ref]
499
+ })]);
500
+ return {
501
+ run: delegated,
502
+ response: input.response,
503
+ advanced: true,
504
+ stopReason
505
+ };
506
+ };
507
+ const critiqueResultFromResponse = (response) => isJsonObject(response.data.critiqueResult) ? response.data.critiqueResult : undefined;
508
+ const stringArrayField = (value) => Array.isArray(value)
509
+ ? value.filter((item) => typeof item === "string" && item.trim().length > 0)
510
+ : [];
511
+ const criticSafetyGateReason = (response) => {
512
+ const critique = critiqueResultFromResponse(response);
513
+ const verdict = typeof critique?.verdict === "string" ? critique.verdict : undefined;
514
+ const blockingConcerns = stringArrayField(critique?.blockingConcerns);
515
+ if (verdict === "unsafe") {
516
+ return "Critic returned unsafe; user review is required before revision or verification can continue.";
517
+ }
518
+ if (verdict === "unclear" && blockingConcerns.length > 0) {
519
+ return "Critic returned unclear with blocking concerns; user review is required before revision or verification can continue.";
520
+ }
521
+ return undefined;
522
+ };
523
+ const stopForCriticSafetyGate = async (run, response) => {
524
+ const approvalReason = criticSafetyGateReason(response);
525
+ if (!approvalReason) {
526
+ return undefined;
527
+ }
528
+ const criticResponseRef = latestProviderResponseArtifactRef(run, "critic");
529
+ const gated = await updateRun(run, {
530
+ state: "awaiting_approval",
531
+ approvalRequired: true,
532
+ approvalReason
533
+ }, [
534
+ createEvent("approval_required", approvalReason, {
535
+ reason: "critic_safety_review",
536
+ ...(criticResponseRef ? { artifactRef: criticResponseRef } : {})
537
+ })
538
+ ], [approvalGateHandoff(run, {
539
+ reason: approvalReason,
540
+ role: "critic",
541
+ gate: "critic_safety_review",
542
+ artifactRefs: criticResponseRef ? [criticResponseRef] : []
543
+ })]);
544
+ return {
545
+ run: gated,
546
+ response,
547
+ advanced: true,
548
+ stopReason: approvalReason
549
+ };
550
+ };
551
+ const writeFinalReport = async (run, input) => {
552
+ const latest = await loadRun(run.repoPath, run.runId);
553
+ const reportArtifact = await writeRunArtifact({
554
+ repoPath: latest.repoPath,
555
+ runId: latest.runId,
556
+ kind: "report",
557
+ name: `final-${newId("report")}.json`,
558
+ content: `${JSON.stringify({
559
+ schemaVersion: 1,
560
+ kind: "final_report",
561
+ runId: latest.runId,
562
+ repoPath: latest.repoPath,
563
+ goal: latest.userGoal,
564
+ mode: latest.mode,
565
+ finalState: "completed",
566
+ stopReason: input.stopReason,
567
+ completedBy: {
568
+ role: input.role,
569
+ responseStatus: input.response.status,
570
+ responseSummary: input.response.summary
571
+ },
572
+ roleMapping: latest.roleMapping,
573
+ artifactCounts: artifactKindCounts(latest.artifacts),
574
+ artifacts: latest.artifacts.map((artifact) => ({
575
+ kind: artifact.kind,
576
+ ref: artifact.ref,
577
+ summary: artifact.summary
578
+ })),
579
+ toolEvents: latest.toolEvents.map((event) => ({
580
+ id: event.id,
581
+ tool: event.tool,
582
+ command: event.command,
583
+ args: event.args,
584
+ cwd: event.cwd,
585
+ exitCode: event.exitCode,
586
+ safetyCategory: event.safetyCategory,
587
+ stdoutRef: event.stdoutRef,
588
+ stderrRef: event.stderrRef
589
+ })),
590
+ approvalEvents: latest.approvalEvents.map((event) => ({
591
+ id: event.id,
592
+ decision: event.decision,
593
+ reason: event.reason
594
+ })),
595
+ ...(latestCriticTriggerArtifact(latest)
596
+ ? { criticTrigger: latestCriticTriggerArtifact(latest) }
597
+ : {}),
598
+ crewLanes: deriveCrewLaneTrail(latest).lanes,
599
+ revisionTrail: deriveRevisionTrail(latest).items,
600
+ reviewLanes: deriveReviewLanes(latest)
601
+ }, null, 2)}\n`,
602
+ summary: `Final report for completed ${latest.mode} run.`
603
+ });
604
+ return attachRunArtifact(latest, reportArtifact, createEvent("final_report_written", "Wrote runtime final report.", {
605
+ artifactRef: reportArtifact.ref,
606
+ role: input.role,
607
+ responseStatus: input.response.status
608
+ }));
609
+ };
610
+ const protectedPatchApprovalReason = (protectedChanges) => `Applied patch changed ${protectedChanges.length} protected test, fixture, snapshot, or eval path(s). ` +
611
+ `Review before verification. Approval message must mention "protected test changes".`;
612
+ const writeIntegrationReport = async (run, sourceArtifact, approvedPatchArtifact, applyEvent, statusEvent, changedPaths, protectedChanges) => {
613
+ const reportArtifact = await writeRunArtifact({
614
+ repoPath: run.repoPath,
615
+ runId: run.runId,
616
+ kind: "report",
617
+ name: `integration-${newId("report")}.json`,
618
+ content: `${JSON.stringify({
619
+ runId: run.runId,
620
+ sourceArtifactRef: sourceArtifact.ref,
621
+ sourceArtifactSummary: sourceArtifact.summary,
622
+ approvedPatchArtifactRef: approvedPatchArtifact.ref,
623
+ approvedPatchArtifactSummary: approvedPatchArtifact.summary,
624
+ applyToolEventId: applyEvent.id,
625
+ applyExitCode: applyEvent.exitCode,
626
+ applyStdoutRef: applyEvent.stdoutRef,
627
+ applyStderrRef: applyEvent.stderrRef,
628
+ postApplyStatusToolEventId: statusEvent.id,
629
+ postApplyStatusRef: statusEvent.stdoutRef,
630
+ changedPaths,
631
+ protectedChanges,
632
+ protectedChangeCount: protectedChanges.length
633
+ }, null, 2)}\n`,
634
+ summary: `Integration report for ${changedPaths.length} changed path(s), ${protectedChanges.length} protected.`
635
+ });
636
+ return attachRunArtifact(run, reportArtifact, createEvent("integration_report_written", "Wrote runtime integration report.", {
637
+ artifactRef: reportArtifact.ref,
638
+ changedPathCount: changedPaths.length,
639
+ protectedChangeCount: protectedChanges.length
640
+ }));
641
+ };
642
+ const applyIsolatedPatchArtifact = async (run) => {
643
+ const artifact = latestIsolatedPatchArtifact(run);
644
+ if (!artifact) {
645
+ const stopReason = "No isolated patch artifact is available for integration.";
646
+ const failed = await updateRun(run, {
647
+ state: "failed",
648
+ approvalRequired: false,
649
+ stopReason
650
+ }, [createEvent("run_failed", stopReason)]);
651
+ return {
652
+ run: failed,
653
+ stopReason
654
+ };
655
+ }
656
+ const status = await runRuntimeCommand({
657
+ repoPath: run.repoPath,
658
+ runId: run.runId,
659
+ tool: "pre_apply_git_status",
660
+ command: "git",
661
+ args: ["status", "--porcelain", "--untracked-files=all", "--", ".", ":(exclude).thehood"]
662
+ });
663
+ if (status.stdout.trim()) {
664
+ const approvalReason = "Target checkout must be clean before applying isolated patch artifact.";
665
+ const gated = await updateRun(status.run, {
666
+ state: "awaiting_approval",
667
+ approvalRequired: true,
668
+ approvalReason
669
+ }, [createEvent("approval_required", approvalReason)], [approvalGateHandoff(status.run, {
670
+ reason: approvalReason,
671
+ role: "integrator",
672
+ gate: "dirty_checkout_before_patch"
673
+ })]);
674
+ return {
675
+ run: gated,
676
+ stopReason: approvalReason
677
+ };
678
+ }
679
+ const patchBody = await patchBodyFromArtifact(artifact);
680
+ const patchInput = await writeRunArtifact({
681
+ repoPath: run.repoPath,
682
+ runId: run.runId,
683
+ kind: "diff",
684
+ name: `approved-${newId("patch")}.patch`,
685
+ content: patchBody,
686
+ summary: `Approved isolated patch body from ${artifact.summary}`
687
+ });
688
+ const runWithPatchInput = await attachRunArtifact(status.run, patchInput, createEvent("integration_patch_prepared", "Prepared isolated patch for target checkout application.", {
689
+ sourceArtifactRef: artifact.ref,
690
+ patchArtifactRef: patchInput.ref
691
+ }));
692
+ const applied = await runRuntimeCommand({
693
+ repoPath: runWithPatchInput.repoPath,
694
+ runId: runWithPatchInput.runId,
695
+ tool: "git_apply_patch",
696
+ command: "git",
697
+ args: ["apply", "--whitespace=nowarn", patchInput.ref]
698
+ });
699
+ if (applied.event.exitCode !== 0) {
700
+ const stopReason = "Applying isolated patch artifact failed.";
701
+ const failed = await updateRun(applied.run, {
702
+ state: "failed",
703
+ approvalRequired: false,
704
+ stopReason
705
+ }, [createEvent("run_failed", stopReason)]);
706
+ return {
707
+ run: failed,
708
+ stopReason
709
+ };
710
+ }
711
+ const postApplyStatus = await runRuntimeCommand({
712
+ repoPath: applied.run.repoPath,
713
+ runId: applied.run.runId,
714
+ tool: "post_apply_git_status",
715
+ command: "git",
716
+ args: ["status", "--short", "--untracked-files=all", "--", ".", ":(exclude).thehood"]
717
+ });
718
+ const changedPaths = parseGitStatusPaths(postApplyStatus.stdout);
719
+ const config = await loadConfig(postApplyStatus.run.repoPath);
720
+ const protectedChanges = findProtectedPathMatches(changedPaths, config.defaults.protectedTestPaths);
721
+ const runWithReport = await writeIntegrationReport(postApplyStatus.run, artifact, patchInput, applied.event, postApplyStatus.event, changedPaths, protectedChanges);
722
+ if (protectedChanges.length > 0) {
723
+ const approvalReason = protectedPatchApprovalReason(protectedChanges);
724
+ const protectedChangesData = protectedChanges.map((match) => ({
725
+ path: match.path,
726
+ pattern: match.pattern
727
+ }));
728
+ const gated = await updateRun(runWithReport, {
729
+ state: "awaiting_approval",
730
+ approvalRequired: true,
731
+ approvalReason
732
+ }, [
733
+ createEvent("patch_applied", "Applied isolated patch artifact to target checkout.", {
734
+ sourceArtifactRef: artifact.ref,
735
+ patchArtifactRef: patchInput.ref
736
+ }),
737
+ createEvent("approval_required", approvalReason, {
738
+ reason: "protected_patch_changes",
739
+ protectedChangeCount: protectedChanges.length,
740
+ protectedChanges: protectedChangesData
741
+ })
742
+ ], [approvalGateHandoff(runWithReport, {
743
+ reason: approvalReason,
744
+ role: "integrator",
745
+ gate: "protected_patch_changes",
746
+ artifactRefs: [artifact.ref, patchInput.ref]
747
+ })]);
748
+ return {
749
+ run: gated,
750
+ stopReason: approvalReason
751
+ };
752
+ }
753
+ const verifying = await updateRun(runWithReport, { state: "verifying" }, [
754
+ createEvent("patch_applied", "Applied isolated patch artifact to target checkout.", {
755
+ sourceArtifactRef: artifact.ref,
756
+ patchArtifactRef: patchInput.ref
757
+ }),
758
+ createEvent("state_changed", "Run entered verification.")
759
+ ], [agentHandoff(runWithReport, {
760
+ reason: "Integrated patch moved to independent verification.",
761
+ stateAfter: "verifying",
762
+ fromRole: "integrator",
763
+ toRole: "verifier",
764
+ artifactRefs: [artifact.ref, patchInput.ref]
765
+ })]);
766
+ return {
767
+ run: verifying
768
+ };
769
+ };
770
+ const stopForProviderInvocationApproval = async (run, role, assignment) => {
771
+ if (!readOnlyProvidersRequiringInvocationApproval.has(assignment.provider) ||
772
+ hasProviderResponded(run, role, assignment) ||
773
+ hasProviderInvocationApproval(run, assignment)) {
774
+ return undefined;
775
+ }
776
+ const approvalReason = providerInvocationApprovalReason(role, assignment);
777
+ const autoApproved = await autoApproveGateIfEnabled(run, {
778
+ gate: "provider_invocation",
779
+ reason: approvalReason,
780
+ role,
781
+ data: {
782
+ role,
783
+ provider: assignment.provider,
784
+ model: assignment.model
785
+ }
786
+ });
787
+ if (autoApproved) {
788
+ return {
789
+ run: autoApproved,
790
+ gated: false
791
+ };
792
+ }
793
+ const gated = await updateRun(run, {
794
+ state: "awaiting_approval",
795
+ approvalRequired: true,
796
+ approvalReason
797
+ }, [
798
+ createEvent("approval_required", approvalReason, {
799
+ role,
800
+ provider: assignment.provider,
801
+ model: assignment.model,
802
+ reason: "provider_invocation"
803
+ })
804
+ ], [approvalGateHandoff(run, {
805
+ reason: approvalReason,
806
+ role,
807
+ gate: "provider_invocation"
808
+ })]);
809
+ return {
810
+ run: gated,
811
+ gated: true,
812
+ response: createApprovalGateResponse(role, approvalReason),
813
+ stopReason: approvalReason
814
+ };
815
+ };
816
+ const stopForProviderStatus = async (run, role, response) => {
817
+ if (response.status === "ok") {
818
+ return undefined;
819
+ }
820
+ const stopReason = `${role} returned ${response.status}: ${response.summary}`;
821
+ if (response.status === "blocked") {
822
+ const blocked = await updateRun(run, {
823
+ state: "awaiting_approval",
824
+ approvalRequired: true,
825
+ approvalReason: stopReason
826
+ }, [createEvent("approval_required", stopReason)], [approvalGateHandoff(run, {
827
+ reason: stopReason,
828
+ role,
829
+ gate: "provider_blocked"
830
+ })]);
831
+ return {
832
+ run: blocked,
833
+ stopReason
834
+ };
835
+ }
836
+ const failed = await updateRun(run, {
837
+ state: "failed",
838
+ approvalRequired: false,
839
+ stopReason
840
+ }, [createEvent("run_failed", stopReason)]);
841
+ return {
842
+ run: failed,
843
+ stopReason
844
+ };
845
+ };
846
+ const runCriticForTrigger = async (run, decision) => {
847
+ if (!decision.callCritic || !decision.reasonCode || !decision.reason) {
848
+ return undefined;
849
+ }
850
+ const assignment = run.roleMapping.critic;
851
+ const implementationResponseIndex = latestProviderResponseEventIndex(run, "implementer");
852
+ if (!assignment || hasProviderRespondedSince(run, "critic", assignment, implementationResponseIndex)) {
853
+ return undefined;
854
+ }
855
+ let criticRun = run;
856
+ const invocationGate = await stopForProviderInvocationApproval(criticRun, "critic", assignment);
857
+ if (invocationGate) {
858
+ criticRun = invocationGate.run;
859
+ }
860
+ if (invocationGate?.gated && invocationGate.response && invocationGate.stopReason) {
861
+ return {
862
+ run: invocationGate.run,
863
+ response: invocationGate.response,
864
+ advanced: true,
865
+ stopReason: invocationGate.stopReason
866
+ };
867
+ }
868
+ const result = await runAgent(criticRun, "critic", {
869
+ phase: "critique",
870
+ criticTrigger: {
871
+ reasonCode: decision.reasonCode,
872
+ reason: decision.reason,
873
+ sourceRoles: decision.sourceRoles,
874
+ evidenceRefs: decision.evidenceRefs
875
+ }
876
+ });
877
+ const criticResponseRef = latestProviderResponseArtifactRef(result.run, "critic");
878
+ const runWithTrigger = await writeCriticTriggerArtifact(result.run, decision, criticResponseRef);
879
+ const stopped = await stopForProviderStatus(runWithTrigger, "critic", result.response);
880
+ if (stopped) {
881
+ return {
882
+ run: stopped.run,
883
+ response: result.response,
884
+ advanced: true,
885
+ stopReason: stopped.stopReason
886
+ };
887
+ }
888
+ const runWithHandoff = await updateRun(runWithTrigger, { state: "verifying" }, [createEvent("critic_completed", "Critic reviewed runtime-triggered risk evidence.")], [agentHandoff(runWithTrigger, {
889
+ reason: decision.reason,
890
+ stateAfter: "verifying",
891
+ toRole: "critic",
892
+ ...(decision.sourceRoles[0] ? { fromRole: decision.sourceRoles[0] } : {}),
893
+ artifactRefs: criticResponseRef ? [criticResponseRef] : []
894
+ })]);
895
+ return {
896
+ run: runWithHandoff,
897
+ response: result.response,
898
+ advanced: true
899
+ };
900
+ };
901
+ const readOnlyRoleForMode = (run) => {
902
+ if (run.preferredRole && run.roleMapping[run.preferredRole]) {
903
+ return run.preferredRole;
904
+ }
905
+ if (run.mode === "review" && run.roleMapping.critic) {
906
+ return "critic";
907
+ }
908
+ if (run.mode === "research" && run.roleMapping.researcher) {
909
+ return "researcher";
910
+ }
911
+ if (run.mode === "plan" && run.roleMapping.planner) {
912
+ return "planner";
913
+ }
914
+ return "orchestrator";
915
+ };
916
+ const readOnlyContext = async (run) => {
917
+ const repoContext = await readCombinedRepoContext(run);
918
+ const contextArtifact = latestRepoContextArtifact(run);
919
+ const contextArtifacts = repoContextArtifacts(run);
920
+ const remoteRepoContext = await readLatestRemoteRepoContext(run);
921
+ const remoteContextArtifact = latestRemoteRepoContextArtifact(run);
922
+ const remoteContextArtifacts = remoteRepoContextArtifacts(run);
923
+ return {
924
+ phase: run.mode,
925
+ ...(repoContext ? { repoContext: repoContext } : {}),
926
+ ...(remoteRepoContext ? { remoteRepoContext: remoteRepoContext } : {}),
927
+ ...(contextArtifact
928
+ ? {
929
+ repoContextArtifact: {
930
+ kind: contextArtifact.kind,
931
+ ref: contextArtifact.ref,
932
+ summary: contextArtifact.summary
933
+ }
934
+ }
935
+ : {}),
936
+ ...(contextArtifacts.length > 0
937
+ ? {
938
+ repoContextArtifacts: contextArtifacts.map((artifact) => ({
939
+ kind: artifact.kind,
940
+ ref: artifact.ref,
941
+ summary: artifact.summary
942
+ }))
943
+ }
944
+ : {}),
945
+ ...(remoteContextArtifact
946
+ ? {
947
+ remoteRepoContextArtifact: {
948
+ kind: remoteContextArtifact.kind,
949
+ ref: remoteContextArtifact.ref,
950
+ summary: remoteContextArtifact.summary
951
+ }
952
+ }
953
+ : {}),
954
+ ...(remoteContextArtifacts.length > 0
955
+ ? {
956
+ remoteRepoContextArtifacts: remoteContextArtifacts.map((artifact) => ({
957
+ kind: artifact.kind,
958
+ ref: artifact.ref,
959
+ summary: artifact.summary
960
+ }))
961
+ }
962
+ : {})
963
+ };
964
+ };
965
+ const completeReadOnlyRun = async (run, input) => {
966
+ const runWithFinalReport = await writeFinalReport(run, {
967
+ role: input.role,
968
+ response: input.response,
969
+ stopReason: input.stopReason
970
+ });
971
+ const completed = await updateRun(runWithFinalReport, { state: "completed", stopReason: input.stopReason }, [createEvent("run_completed", input.eventMessage, input.eventData)], [completionHandoff(runWithFinalReport, input.role, input.handoffReason ?? input.stopReason)]);
972
+ const progress = await writeProgressPacketArtifact(completed);
973
+ return {
974
+ run: progress.run,
975
+ response: input.response,
976
+ advanced: true
977
+ };
978
+ };
979
+ const executeReadOnlyRun = async (run, role) => {
980
+ const assignment = requiredAssignment(run, role);
981
+ const invocationGate = await stopForProviderInvocationApproval(run, role, assignment);
982
+ if (invocationGate) {
983
+ run = invocationGate.run;
984
+ }
985
+ if (invocationGate?.gated && invocationGate.response && invocationGate.stopReason) {
986
+ return {
987
+ run: invocationGate.run,
988
+ response: invocationGate.response,
989
+ advanced: true,
990
+ stopReason: invocationGate.stopReason
991
+ };
992
+ }
993
+ let contextArtifact = latestRepoContextArtifact(run);
994
+ if (!contextArtifact && !latestRemoteRepoContextArtifact(run)) {
995
+ const remoteContext = await captureRemoteRepoContext(run, assignment, {
996
+ action: "inspect_repo",
997
+ reason: "direct_read_only_run",
998
+ role,
999
+ mode: run.mode
1000
+ });
1001
+ if (remoteContext.selected) {
1002
+ run = remoteContext.run;
1003
+ contextArtifact = latestRepoContextArtifact(run);
1004
+ }
1005
+ }
1006
+ if (contextArtifact &&
1007
+ providersRequiringRepoContextApproval.has(assignment.provider) &&
1008
+ !hasExternalRepoContextApproval(run, assignment)) {
1009
+ const approvalPhrase = `send repo context to ${assignment.provider}`;
1010
+ const transfer = await writeExternalTransferManifestArtifact({
1011
+ run,
1012
+ role,
1013
+ destination: assignment,
1014
+ purpose: "repo_context",
1015
+ approvalPhrase,
1016
+ artifacts: [contextArtifact]
1017
+ });
1018
+ const config = await loadConfig(run.repoPath);
1019
+ const policyEvaluation = evaluateExternalTransferPolicy(config, transfer.manifest);
1020
+ const approvalReason = externalRepoContextApprovalReason(role, assignment, contextArtifact.summary, approvalPhrase);
1021
+ const transferEvent = createEvent("external_transfer_manifest_written", transfer.artifact.summary, {
1022
+ role,
1023
+ provider: assignment.provider,
1024
+ model: assignment.model,
1025
+ reason: "repo_context_external_transfer",
1026
+ artifactRef: transfer.artifact.ref,
1027
+ artifactSummary: transfer.artifact.summary,
1028
+ sourceArtifactRef: contextArtifact.ref,
1029
+ sourceArtifactSummary: contextArtifact.summary,
1030
+ transfer: transferManifestSummary(transfer.manifest)
1031
+ });
1032
+ if (policyEvaluation.decision === "auto_approve") {
1033
+ const approval = createApprovalEvent(autoApprovalReason(transfer.manifest, policyEvaluation));
1034
+ run = {
1035
+ ...run,
1036
+ updatedAt: nowIso(),
1037
+ approvalRequired: false,
1038
+ artifacts: [...run.artifacts, transfer.artifact],
1039
+ approvalEvents: [...run.approvalEvents, approval],
1040
+ handoffs: [
1041
+ ...(run.handoffs ?? []),
1042
+ createRunHandoff(run, {
1043
+ kind: "approval_auto_approved",
1044
+ reason: approval.reason,
1045
+ stateAfter: run.state,
1046
+ toRole: role,
1047
+ gate: "repo_context_external_transfer",
1048
+ approvalEventId: approval.id,
1049
+ artifactRefs: [transfer.artifact.ref, contextArtifact.ref]
1050
+ })
1051
+ ],
1052
+ events: [
1053
+ ...run.events,
1054
+ transferEvent,
1055
+ createEvent("approval_auto_approved", approval.reason, {
1056
+ role,
1057
+ provider: assignment.provider,
1058
+ model: assignment.model,
1059
+ reason: "repo_context_external_transfer",
1060
+ policyDecision: policyEvaluation.decision,
1061
+ policyReason: policyEvaluation.reason,
1062
+ artifactRef: transfer.artifact.ref,
1063
+ sourceArtifactRef: contextArtifact.ref,
1064
+ transfer: transferManifestSummary(transfer.manifest)
1065
+ })
1066
+ ]
1067
+ };
1068
+ await saveRun(run);
1069
+ }
1070
+ else {
1071
+ const gated = {
1072
+ ...run,
1073
+ updatedAt: nowIso(),
1074
+ state: "awaiting_approval",
1075
+ approvalRequired: true,
1076
+ approvalReason,
1077
+ artifacts: [...run.artifacts, transfer.artifact],
1078
+ handoffs: [
1079
+ ...(run.handoffs ?? []),
1080
+ approvalGateHandoff(run, {
1081
+ reason: approvalReason,
1082
+ role,
1083
+ gate: "repo_context_external_transfer",
1084
+ artifactRefs: [transfer.artifact.ref, contextArtifact.ref]
1085
+ })
1086
+ ],
1087
+ events: [
1088
+ ...run.events,
1089
+ transferEvent,
1090
+ createEvent("approval_required", approvalReason, {
1091
+ role,
1092
+ provider: assignment.provider,
1093
+ model: assignment.model,
1094
+ reason: "repo_context_external_transfer",
1095
+ artifactRef: transfer.artifact.ref,
1096
+ artifactSummary: transfer.artifact.summary,
1097
+ sourceArtifactRef: contextArtifact.ref,
1098
+ sourceArtifactSummary: contextArtifact.summary,
1099
+ transfer: transferManifestSummary(transfer.manifest)
1100
+ })
1101
+ ]
1102
+ };
1103
+ await saveRun(gated);
1104
+ return {
1105
+ run: gated,
1106
+ response: createApprovalGateResponse(role, approvalReason),
1107
+ advanced: true,
1108
+ stopReason: approvalReason
1109
+ };
1110
+ }
1111
+ }
1112
+ const result = await runAgent(run, role, await readOnlyContext(run));
1113
+ const stopped = await stopForProviderStatus(result.run, role, result.response);
1114
+ if (stopped) {
1115
+ return {
1116
+ run: stopped.run,
1117
+ response: result.response,
1118
+ advanced: true,
1119
+ stopReason: stopped.stopReason
1120
+ };
1121
+ }
1122
+ if (actionFromResponse(result.response) === "delegate") {
1123
+ const decision = decisionFromResponse(result.response);
1124
+ const delegateRole = readyDelegateRole(decision);
1125
+ if (delegateRole) {
1126
+ const sliceName = decisionStringField(decision, "sliceName");
1127
+ const eventData = {
1128
+ reason: "role_handoff_delegate",
1129
+ delegateTo: delegateRole,
1130
+ requiresMoreEvidence: decisionBooleanField(decision, "requiresMoreEvidence") ?? false
1131
+ };
1132
+ if (sliceName) {
1133
+ eventData.sliceName = sliceName;
1134
+ }
1135
+ if (delegateRole === "implementer") {
1136
+ return completeReadOnlyRun(result.run, {
1137
+ role,
1138
+ response: result.response,
1139
+ stopReason: `${role} produced a ready ${delegateRole} handoff.`,
1140
+ eventMessage: `${role} produced a ready ${delegateRole} handoff.`,
1141
+ handoffReason: `${role} completed read-only planning with a ready handoff to ${delegateRole}.`,
1142
+ eventData
1143
+ });
1144
+ }
1145
+ if (readOnlyDelegateRoles.has(delegateRole)) {
1146
+ if (delegateRole === role) {
1147
+ const approvalReason = `${role} delegated back to itself.`;
1148
+ const gated = await updateRun(result.run, {
1149
+ state: "awaiting_approval",
1150
+ approvalRequired: true,
1151
+ approvalReason
1152
+ }, [
1153
+ createEvent("approval_required", approvalReason, {
1154
+ ...eventData,
1155
+ role,
1156
+ reason: "self_read_only_delegate"
1157
+ })
1158
+ ], [approvalGateHandoff(result.run, {
1159
+ reason: approvalReason,
1160
+ role,
1161
+ gate: "self_read_only_delegate"
1162
+ })]);
1163
+ return {
1164
+ run: gated,
1165
+ response: result.response,
1166
+ advanced: true,
1167
+ stopReason: approvalReason
1168
+ };
1169
+ }
1170
+ if (!result.run.roleMapping[delegateRole]) {
1171
+ const approvalReason = `${role} delegated to ${delegateRole}, but ${delegateRole} role is not assigned.`;
1172
+ const gated = await updateRun(result.run, {
1173
+ state: "awaiting_approval",
1174
+ approvalRequired: true,
1175
+ approvalReason
1176
+ }, [
1177
+ createEvent("approval_required", approvalReason, {
1178
+ ...eventData,
1179
+ role,
1180
+ reason: "missing_read_only_delegate_assignment"
1181
+ })
1182
+ ], [approvalGateHandoff(result.run, {
1183
+ reason: approvalReason,
1184
+ role,
1185
+ gate: "missing_read_only_delegate_assignment"
1186
+ })]);
1187
+ return {
1188
+ run: gated,
1189
+ response: result.response,
1190
+ advanced: true,
1191
+ stopReason: approvalReason
1192
+ };
1193
+ }
1194
+ const delegated = await updateRun(result.run, {
1195
+ state: "planning",
1196
+ approvalRequired: false
1197
+ }, [
1198
+ createEvent(`${role}_delegated_read_only_role`, `${role} delegated read-only planning to ${delegateRole}.`, {
1199
+ ...eventData,
1200
+ role,
1201
+ toRole: delegateRole
1202
+ })
1203
+ ], [agentHandoff(result.run, {
1204
+ reason: `${role} delegated read-only planning to ${delegateRole}.`,
1205
+ stateAfter: "planning",
1206
+ fromRole: role,
1207
+ toRole: delegateRole
1208
+ })]);
1209
+ return executeReadOnlyRun(delegated, delegateRole);
1210
+ }
1211
+ const approvalReason = `${role} delegated to unsupported read-only role ${delegateRole}.`;
1212
+ const gated = await updateRun(result.run, {
1213
+ state: "awaiting_approval",
1214
+ approvalRequired: true,
1215
+ approvalReason
1216
+ }, [
1217
+ createEvent("approval_required", approvalReason, {
1218
+ ...eventData,
1219
+ role,
1220
+ reason: "unsupported_read_only_delegate_role"
1221
+ })
1222
+ ], [approvalGateHandoff(result.run, {
1223
+ reason: approvalReason,
1224
+ role,
1225
+ gate: "unsupported_read_only_delegate_role"
1226
+ })]);
1227
+ return {
1228
+ run: gated,
1229
+ response: result.response,
1230
+ advanced: true,
1231
+ stopReason: approvalReason
1232
+ };
1233
+ }
1234
+ const existingContextArtifact = latestRepoContextArtifact(result.run);
1235
+ const existingRemoteContextArtifact = latestRemoteRepoContextArtifact(result.run);
1236
+ let preferredPaths = [];
1237
+ if (existingContextArtifact) {
1238
+ const analysis = await analyzeRepoContextRequest(result.run, decision);
1239
+ preferredPaths = analysis.newRequestedPaths;
1240
+ if (analysis.newRequestedPaths.length === 0) {
1241
+ const approvalReason = analysis.requestedPaths.length > 0
1242
+ ? `${role} requested another delegation, but all requested repo paths were already captured.`
1243
+ : `${role} requested another delegation after repo context was already captured.`;
1244
+ const gated = await updateRun(result.run, {
1245
+ state: "awaiting_approval",
1246
+ approvalRequired: true,
1247
+ approvalReason
1248
+ }, [
1249
+ createEvent("approval_required", approvalReason, {
1250
+ role,
1251
+ reason: "repeated_repo_context_delegate",
1252
+ requestedPaths: analysis.requestedPaths,
1253
+ alreadyCapturedPaths: analysis.alreadyCapturedPaths,
1254
+ incompleteCapturedPaths: analysis.incompleteCapturedPaths,
1255
+ existingContextArtifactRef: existingContextArtifact.ref
1256
+ })
1257
+ ], [approvalGateHandoff(result.run, {
1258
+ reason: approvalReason,
1259
+ role,
1260
+ gate: "repeated_repo_context_delegate",
1261
+ artifactRefs: [existingContextArtifact.ref]
1262
+ })]);
1263
+ return {
1264
+ run: gated,
1265
+ response: result.response,
1266
+ advanced: true,
1267
+ stopReason: approvalReason
1268
+ };
1269
+ }
1270
+ }
1271
+ if (!existingContextArtifact && existingRemoteContextArtifact) {
1272
+ const approvalReason = `${role} requested another delegation after GitHub connector repo context was already selected.`;
1273
+ const gated = await updateRun(result.run, {
1274
+ state: "awaiting_approval",
1275
+ approvalRequired: true,
1276
+ approvalReason
1277
+ }, [
1278
+ createEvent("approval_required", approvalReason, {
1279
+ role,
1280
+ reason: "repeated_remote_repo_context_delegate",
1281
+ existingRemoteContextArtifactRef: existingRemoteContextArtifact.ref
1282
+ })
1283
+ ], [approvalGateHandoff(result.run, {
1284
+ reason: approvalReason,
1285
+ role,
1286
+ gate: "repeated_remote_repo_context_delegate",
1287
+ artifactRefs: [existingRemoteContextArtifact.ref]
1288
+ })]);
1289
+ return {
1290
+ run: gated,
1291
+ response: result.response,
1292
+ advanced: true,
1293
+ stopReason: approvalReason
1294
+ };
1295
+ }
1296
+ if (!existingContextArtifact && !existingRemoteContextArtifact) {
1297
+ const remoteContext = await captureRemoteRepoContext(result.run, assignment, decision);
1298
+ if (remoteContext.selected) {
1299
+ return {
1300
+ run: remoteContext.run,
1301
+ response: result.response,
1302
+ advanced: true,
1303
+ stopReason: "Selected GitHub connector repo context for delegated read-only planning."
1304
+ };
1305
+ }
1306
+ }
1307
+ const repoContext = await captureRepoContext(result.run, decision, { preferredPaths });
1308
+ return {
1309
+ run: repoContext.run,
1310
+ response: result.response,
1311
+ advanced: true,
1312
+ stopReason: existingContextArtifact
1313
+ ? "Captured follow-up repo context for targeted delegated read-only planning."
1314
+ : "Captured repo context for delegated read-only planning."
1315
+ };
1316
+ }
1317
+ if (actionFromResponse(result.response) === "request_approval") {
1318
+ const decision = decisionFromResponse(result.response);
1319
+ const approvalReason = typeof decision?.reason === "string" ? decision.reason : `${role} requested approval.`;
1320
+ const gated = await updateRun(result.run, {
1321
+ state: "awaiting_approval",
1322
+ approvalRequired: true,
1323
+ approvalReason
1324
+ }, [createEvent("approval_required", approvalReason)], [approvalGateHandoff(result.run, {
1325
+ reason: approvalReason,
1326
+ role,
1327
+ gate: "provider_requested_approval"
1328
+ })]);
1329
+ return {
1330
+ run: gated,
1331
+ response: result.response,
1332
+ advanced: true,
1333
+ stopReason: approvalReason
1334
+ };
1335
+ }
1336
+ const stopReason = `${role} run completed by provider response.`;
1337
+ return completeReadOnlyRun(result.run, {
1338
+ role,
1339
+ response: result.response,
1340
+ stopReason,
1341
+ eventMessage: `${role} run completed.`
1342
+ });
1343
+ };
1344
+ const advanceOneStep = async (run) => {
1345
+ if (terminalStates.has(run.state)) {
1346
+ return {
1347
+ run,
1348
+ advanced: false,
1349
+ stopReason: `Run is already ${run.state}.`
1350
+ };
1351
+ }
1352
+ if (run.approvalRequired) {
1353
+ return {
1354
+ run,
1355
+ advanced: false,
1356
+ stopReason: run.approvalReason ?? "Approval required."
1357
+ };
1358
+ }
1359
+ const maxIterationsStop = await stopForMaxIterations(run);
1360
+ if (maxIterationsStop) {
1361
+ return {
1362
+ run: maxIterationsStop.run,
1363
+ advanced: true,
1364
+ stopReason: maxIterationsStop.stopReason
1365
+ };
1366
+ }
1367
+ if (run.state === "created" && run.mode !== "implement") {
1368
+ const role = readOnlyRoleForMode(run);
1369
+ const planned = await updateRun(run, { state: "planning" }, [createEvent("state_changed", `Run entered ${run.mode} execution with ${role}.`)], [agentHandoff(run, {
1370
+ reason: `Runtime selected ${role} for ${run.mode} execution.`,
1371
+ stateAfter: "planning",
1372
+ toRole: role
1373
+ })]);
1374
+ return executeReadOnlyRun(planned, role);
1375
+ }
1376
+ if (run.state === "planning" && run.mode !== "implement") {
1377
+ return executeReadOnlyRun(run, readOnlyRoleForMode(run));
1378
+ }
1379
+ if (run.state === "delegating") {
1380
+ const result = await runAgent(run, "orchestrator", {
1381
+ phase: "delegate"
1382
+ });
1383
+ const stopped = await stopForProviderStatus(result.run, "orchestrator", result.response);
1384
+ if (stopped) {
1385
+ return {
1386
+ run: stopped.run,
1387
+ response: result.response,
1388
+ advanced: true,
1389
+ stopReason: stopped.stopReason
1390
+ };
1391
+ }
1392
+ const decision = decisionFromResponse(result.response);
1393
+ const action = actionFromResponse(result.response);
1394
+ if (action === "complete") {
1395
+ const evidenceRefs = [
1396
+ ...decisionStringArrayField(decision, "evidenceRefs"),
1397
+ ...decisionStringArrayField(decision, "artifactRefs")
1398
+ ];
1399
+ if (evidenceRefs.length === 0) {
1400
+ const approvalReason = "Orchestrator requested implementation completion without evidence refs.";
1401
+ const gated = await updateRun(result.run, {
1402
+ state: "awaiting_approval",
1403
+ approvalRequired: true,
1404
+ approvalReason
1405
+ }, [
1406
+ createEvent("approval_required", approvalReason, {
1407
+ role: "orchestrator",
1408
+ reason: "orchestrator_complete_without_evidence"
1409
+ })
1410
+ ], [approvalGateHandoff(result.run, {
1411
+ reason: approvalReason,
1412
+ role: "orchestrator",
1413
+ gate: "orchestrator_complete_without_evidence"
1414
+ })]);
1415
+ return {
1416
+ run: gated,
1417
+ response: result.response,
1418
+ advanced: true,
1419
+ stopReason: approvalReason
1420
+ };
1421
+ }
1422
+ const next = await updateRun(result.run, { state: "verifying" }, [
1423
+ createEvent("state_changed", "Run entered verification from orchestrator completion.", {
1424
+ reason: "orchestrator_complete",
1425
+ evidenceRefCount: evidenceRefs.length
1426
+ })
1427
+ ], [agentHandoff(result.run, {
1428
+ reason: "Orchestrator completed implementation orchestration with evidence refs.",
1429
+ stateAfter: "verifying",
1430
+ fromRole: "orchestrator",
1431
+ toRole: "verifier"
1432
+ })]);
1433
+ return {
1434
+ run: next,
1435
+ response: result.response,
1436
+ advanced: true,
1437
+ stopReason: "Orchestrator completed implementation orchestration with evidence refs; entering verification."
1438
+ };
1439
+ }
1440
+ if (action === "request_approval") {
1441
+ const approvalReason = typeof decision?.reason === "string" ? decision.reason : "Orchestrator requested approval.";
1442
+ const gated = await updateRun(result.run, {
1443
+ state: "awaiting_approval",
1444
+ approvalRequired: true,
1445
+ approvalReason
1446
+ }, [createEvent("approval_required", approvalReason, {
1447
+ role: "orchestrator",
1448
+ reason: "provider_requested_approval"
1449
+ })], [approvalGateHandoff(result.run, {
1450
+ reason: approvalReason,
1451
+ role: "orchestrator",
1452
+ gate: "provider_requested_approval"
1453
+ })]);
1454
+ return {
1455
+ run: gated,
1456
+ response: result.response,
1457
+ advanced: true,
1458
+ stopReason: approvalReason
1459
+ };
1460
+ }
1461
+ if (action === "abort") {
1462
+ const stopReason = typeof decision?.reason === "string" ? decision.reason : "Orchestrator aborted implementation orchestration.";
1463
+ const aborted = await updateRun(result.run, {
1464
+ state: "aborted",
1465
+ approvalRequired: false,
1466
+ stopReason
1467
+ }, [createEvent("run_aborted", stopReason, {
1468
+ role: "orchestrator",
1469
+ reason: "orchestrator_abort"
1470
+ })]);
1471
+ return {
1472
+ run: aborted,
1473
+ response: result.response,
1474
+ advanced: true,
1475
+ stopReason
1476
+ };
1477
+ }
1478
+ const next = await updateRun(result.run, { state: "implementing" }, [createEvent("state_changed", "Run entered implementation.")], [agentHandoff(result.run, {
1479
+ reason: "Orchestrator delegated implementation to the implementer.",
1480
+ stateAfter: "implementing",
1481
+ fromRole: "orchestrator",
1482
+ toRole: "implementer"
1483
+ })]);
1484
+ return {
1485
+ run: next,
1486
+ response: result.response,
1487
+ advanced: true
1488
+ };
1489
+ }
1490
+ if (run.state === "implementing") {
1491
+ const revisionPacket = await latestRevisionPacketContext(run);
1492
+ const result = await runAgent(run, "implementer", {
1493
+ phase: "implement",
1494
+ ...(revisionPacket ? { latestRevisionPacket: revisionPacket } : {})
1495
+ });
1496
+ const stopped = await stopForProviderStatus(result.run, "implementer", result.response);
1497
+ if (stopped) {
1498
+ return {
1499
+ run: stopped.run,
1500
+ response: result.response,
1501
+ advanced: true,
1502
+ stopReason: stopped.stopReason
1503
+ };
1504
+ }
1505
+ const patchArtifact = implementationPatchArtifact(result.response);
1506
+ if (patchArtifact) {
1507
+ const approvalReason = isolatedPatchApprovalReason(patchArtifact);
1508
+ const autoApproved = await autoApproveGateIfEnabled(result.run, {
1509
+ gate: "isolated_patch_application",
1510
+ reason: approvalReason,
1511
+ state: "integrating",
1512
+ role: "integrator",
1513
+ artifactRefs: [patchArtifact.ref],
1514
+ data: {
1515
+ artifactRef: patchArtifact.ref,
1516
+ artifactSummary: patchArtifact.summary
1517
+ }
1518
+ });
1519
+ if (autoApproved) {
1520
+ return {
1521
+ run: autoApproved,
1522
+ response: result.response,
1523
+ advanced: true,
1524
+ stopReason: approvalReason
1525
+ };
1526
+ }
1527
+ const gated = await updateRun(result.run, {
1528
+ state: "awaiting_approval",
1529
+ approvalRequired: true,
1530
+ approvalReason
1531
+ }, [
1532
+ createEvent("approval_required", approvalReason, {
1533
+ reason: "isolated_patch_application",
1534
+ artifactRef: patchArtifact.ref,
1535
+ artifactSummary: patchArtifact.summary
1536
+ })
1537
+ ], [approvalGateHandoff(result.run, {
1538
+ reason: approvalReason,
1539
+ role: "implementer",
1540
+ gate: "isolated_patch_application",
1541
+ artifactRefs: [patchArtifact.ref]
1542
+ })]);
1543
+ return {
1544
+ run: gated,
1545
+ response: result.response,
1546
+ advanced: true,
1547
+ stopReason: approvalReason
1548
+ };
1549
+ }
1550
+ const next = await updateRun(result.run, { state: "verifying" }, [createEvent("state_changed", "Run entered verification.")], [agentHandoff(result.run, {
1551
+ reason: "Implementer completed work and handed off to verifier.",
1552
+ stateAfter: "verifying",
1553
+ fromRole: "implementer",
1554
+ toRole: "verifier"
1555
+ })]);
1556
+ return {
1557
+ run: next,
1558
+ response: result.response,
1559
+ advanced: true
1560
+ };
1561
+ }
1562
+ if (run.state === "integrating") {
1563
+ const integrated = await applyIsolatedPatchArtifact(run);
1564
+ return {
1565
+ run: integrated.run,
1566
+ advanced: true,
1567
+ stopReason: integrated.stopReason ?? "Applied isolated patch artifact."
1568
+ };
1569
+ }
1570
+ if (run.state === "verifying") {
1571
+ const evidence = await captureGitEvidence(run.repoPath, run.runId);
1572
+ const validation = await captureValidationEvidence(evidence.run);
1573
+ const implementationResponseIndex = latestProviderResponseEventIndex(validation.run, "implementer");
1574
+ const qaAssignment = validation.run.roleMapping.qa;
1575
+ const verifierAssignment = validation.run.roleMapping.verifier;
1576
+ const qaResponded = qaAssignment
1577
+ ? hasProviderRespondedSince(validation.run, "qa", qaAssignment, implementationResponseIndex)
1578
+ : false;
1579
+ const verifierResponded = verifierAssignment
1580
+ ? hasProviderRespondedSince(validation.run, "verifier", verifierAssignment, implementationResponseIndex)
1581
+ : false;
1582
+ const routingDecision = decideReviewRouting({
1583
+ changedPaths: evidence.changedPaths,
1584
+ protectedChangeCount: evidence.protectedChanges.length,
1585
+ validationCommandCount: validation.executedCommands.length,
1586
+ validationFailureCount: validation.failedCommands.length,
1587
+ hasQaAssignment: Boolean(qaAssignment),
1588
+ hasVerifierAssignment: Boolean(verifierAssignment),
1589
+ hasQaResponse: qaResponded,
1590
+ hasVerifierResponse: verifierResponded
1591
+ });
1592
+ const routing = await writeReviewRoutingArtifact(validation.run, routingDecision);
1593
+ const validationRun = routing.run;
1594
+ if (qaAssignment && routingDecision.action === "run_qa") {
1595
+ let qaRun = validationRun;
1596
+ const invocationGate = await stopForProviderInvocationApproval(qaRun, "qa", qaAssignment);
1597
+ if (invocationGate) {
1598
+ qaRun = invocationGate.run;
1599
+ }
1600
+ if (invocationGate?.gated && invocationGate.response && invocationGate.stopReason) {
1601
+ return {
1602
+ run: invocationGate.run,
1603
+ response: invocationGate.response,
1604
+ advanced: true,
1605
+ stopReason: invocationGate.stopReason
1606
+ };
1607
+ }
1608
+ const revisionPacket = await latestRevisionPacketContext(qaRun);
1609
+ const qaResult = await runAgent(qaRun, "qa", {
1610
+ phase: "qa",
1611
+ changedPathCount: evidence.changedPaths.length,
1612
+ protectedChangeCount: evidence.protectedChanges.length,
1613
+ validationCommandCount: validation.executedCommands.length,
1614
+ validationFailureCount: validation.failedCommands.length,
1615
+ validationSummaryRef: validation.artifact.ref,
1616
+ reviewRoutingRef: routing.artifact.ref,
1617
+ ...(revisionPacket ? { latestRevisionPacket: revisionPacket } : {})
1618
+ });
1619
+ const stopped = await stopForProviderStatus(qaResult.run, "qa", qaResult.response);
1620
+ if (stopped) {
1621
+ return {
1622
+ run: stopped.run,
1623
+ response: qaResult.response,
1624
+ advanced: true,
1625
+ stopReason: stopped.stopReason
1626
+ };
1627
+ }
1628
+ const qaCompleted = await updateRun(qaResult.run, { state: "verifying" }, [createEvent("qa_tester_completed", "QA tester reviewed runtime evidence before verifier review.")], [agentHandoff(qaResult.run, {
1629
+ reason: "QA tester reviewed runtime evidence; verifier remains the required approval lane.",
1630
+ stateAfter: "verifying",
1631
+ fromRole: "qa",
1632
+ toRole: "verifier"
1633
+ })]);
1634
+ const qaResponseRef = latestProviderResponseArtifactRef(qaCompleted, "qa");
1635
+ const qaEvidenceRefs = [
1636
+ validation.artifact.ref,
1637
+ ...[qaResponseRef].filter((ref) => Boolean(ref))
1638
+ ];
1639
+ const qaRevision = decideRevisionPacket("qa", qaResult.response);
1640
+ const criticDecision = decideCriticTrigger({
1641
+ qaResponse: qaResult.response,
1642
+ validationFailureCount: validation.failedCommands.length,
1643
+ evidenceRefs: qaEvidenceRefs
1644
+ });
1645
+ const critic = await runCriticForTrigger(qaCompleted, criticDecision);
1646
+ if (critic) {
1647
+ if (critic.response) {
1648
+ const criticSafetyGate = await stopForCriticSafetyGate(critic.run, critic.response);
1649
+ if (criticSafetyGate) {
1650
+ return criticSafetyGate;
1651
+ }
1652
+ const criticResponseRef = latestProviderResponseArtifactRef(critic.run, "critic");
1653
+ const criticTriggerRef = latestCriticTriggerArtifact(critic.run)?.ref;
1654
+ const criticRevision = decideRevisionPacket("critic", critic.response);
1655
+ if (criticRevision.shouldRevise) {
1656
+ return writeAndDelegateRevision(critic.run, {
1657
+ decision: criticRevision,
1658
+ response: critic.response,
1659
+ evidenceRefs: [
1660
+ ...qaEvidenceRefs,
1661
+ ...[criticResponseRef, criticTriggerRef].filter((ref) => Boolean(ref))
1662
+ ],
1663
+ ...(criticResponseRef ? { sourceResponseRef: criticResponseRef } : {}),
1664
+ ...(criticTriggerRef ? { criticTriggerRef } : {})
1665
+ });
1666
+ }
1667
+ if (qaRevision.shouldRevise) {
1668
+ return writeAndDelegateRevision(critic.run, {
1669
+ decision: qaRevision,
1670
+ response: qaResult.response,
1671
+ evidenceRefs: [
1672
+ ...qaEvidenceRefs,
1673
+ ...[criticResponseRef, criticTriggerRef].filter((ref) => Boolean(ref))
1674
+ ],
1675
+ ...(qaResponseRef ? { sourceResponseRef: qaResponseRef } : {}),
1676
+ ...(criticTriggerRef ? { criticTriggerRef } : {})
1677
+ });
1678
+ }
1679
+ }
1680
+ return critic;
1681
+ }
1682
+ if (qaRevision.shouldRevise) {
1683
+ return writeAndDelegateRevision(qaCompleted, {
1684
+ decision: qaRevision,
1685
+ response: qaResult.response,
1686
+ evidenceRefs: qaEvidenceRefs,
1687
+ ...(qaResponseRef ? { sourceResponseRef: qaResponseRef } : {})
1688
+ });
1689
+ }
1690
+ return {
1691
+ run: qaCompleted,
1692
+ response: qaResult.response,
1693
+ advanced: true
1694
+ };
1695
+ }
1696
+ if (!verifierAssignment) {
1697
+ const approvalReason = "Verifier role is not assigned; user review is required before implementation can complete.";
1698
+ const gated = await updateRun(validationRun, {
1699
+ state: "awaiting_approval",
1700
+ approvalRequired: true,
1701
+ approvalReason
1702
+ }, [createEvent("approval_required", approvalReason, {
1703
+ reason: "verifier_unassigned",
1704
+ reviewRoutingRef: routing.artifact.ref
1705
+ })], [approvalGateHandoff(validationRun, {
1706
+ reason: approvalReason,
1707
+ role: "verifier",
1708
+ gate: "verifier_unassigned",
1709
+ artifactRefs: [routing.artifact.ref]
1710
+ })]);
1711
+ return {
1712
+ run: gated,
1713
+ advanced: true,
1714
+ stopReason: approvalReason
1715
+ };
1716
+ }
1717
+ const revisionPacket = await latestRevisionPacketContext(validationRun);
1718
+ const result = await runAgent(validationRun, "verifier", {
1719
+ phase: "verify",
1720
+ changedPathCount: evidence.changedPaths.length,
1721
+ protectedChangeCount: evidence.protectedChanges.length,
1722
+ validationCommandCount: validation.executedCommands.length,
1723
+ validationFailureCount: validation.failedCommands.length,
1724
+ validationSummaryRef: validation.artifact.ref,
1725
+ reviewRoutingRef: routing.artifact.ref,
1726
+ ...(revisionPacket ? { latestRevisionPacket: revisionPacket } : {})
1727
+ });
1728
+ const stopped = await stopForProviderStatus(result.run, "verifier", result.response);
1729
+ if (stopped) {
1730
+ return {
1731
+ run: stopped.run,
1732
+ response: result.response,
1733
+ advanced: true,
1734
+ stopReason: stopped.stopReason
1735
+ };
1736
+ }
1737
+ const verdict = verdictFromResponse(result.response);
1738
+ const verifierResponseRef = latestProviderResponseArtifactRef(result.run, "verifier");
1739
+ if (verdict === "approve") {
1740
+ const stopReason = "Verifier approved runtime evidence.";
1741
+ const runWithFinalReport = await writeFinalReport(result.run, {
1742
+ role: "verifier",
1743
+ response: result.response,
1744
+ stopReason
1745
+ });
1746
+ const completed = await updateRun(runWithFinalReport, { state: "completed", stopReason }, [createEvent("run_completed", stopReason)], [completionHandoff(runWithFinalReport, "verifier", stopReason)]);
1747
+ const progress = await writeProgressPacketArtifact(completed);
1748
+ return {
1749
+ run: progress.run,
1750
+ response: result.response,
1751
+ advanced: true
1752
+ };
1753
+ }
1754
+ const criticDecision = decideCriticTrigger({
1755
+ verifierResponse: result.response,
1756
+ validationFailureCount: validation.failedCommands.length,
1757
+ evidenceRefs: [
1758
+ validation.artifact.ref,
1759
+ ...[verifierResponseRef].filter((ref) => Boolean(ref))
1760
+ ]
1761
+ });
1762
+ const critic = await runCriticForTrigger(result.run, criticDecision);
1763
+ const verifierRevision = decideRevisionPacket("verifier", result.response);
1764
+ if (critic?.response) {
1765
+ const criticSafetyGate = await stopForCriticSafetyGate(critic.run, critic.response);
1766
+ if (criticSafetyGate) {
1767
+ return criticSafetyGate;
1768
+ }
1769
+ const criticResponseRef = latestProviderResponseArtifactRef(critic.run, "critic");
1770
+ const criticTriggerRef = latestCriticTriggerArtifact(critic.run)?.ref;
1771
+ const criticRevision = decideRevisionPacket("critic", critic.response);
1772
+ if (criticRevision.shouldRevise) {
1773
+ return writeAndDelegateRevision(critic.run, {
1774
+ decision: criticRevision,
1775
+ response: critic.response,
1776
+ evidenceRefs: [
1777
+ validation.artifact.ref,
1778
+ ...[verifierResponseRef, criticResponseRef, criticTriggerRef].filter((ref) => Boolean(ref))
1779
+ ],
1780
+ ...(criticResponseRef ? { sourceResponseRef: criticResponseRef } : {}),
1781
+ ...(criticTriggerRef ? { criticTriggerRef } : {})
1782
+ });
1783
+ }
1784
+ if (verifierRevision.shouldRevise) {
1785
+ return writeAndDelegateRevision(critic.run, {
1786
+ decision: verifierRevision,
1787
+ response: result.response,
1788
+ evidenceRefs: [
1789
+ validation.artifact.ref,
1790
+ ...[verifierResponseRef, criticResponseRef, criticTriggerRef].filter((ref) => Boolean(ref))
1791
+ ],
1792
+ ...(verifierResponseRef ? { sourceResponseRef: verifierResponseRef } : {}),
1793
+ ...(criticTriggerRef ? { criticTriggerRef } : {})
1794
+ });
1795
+ }
1796
+ }
1797
+ if (critic) {
1798
+ return critic;
1799
+ }
1800
+ if (verifierRevision.shouldRevise) {
1801
+ return writeAndDelegateRevision(result.run, {
1802
+ decision: verifierRevision,
1803
+ response: result.response,
1804
+ evidenceRefs: [
1805
+ validation.artifact.ref,
1806
+ ...[verifierResponseRef].filter((ref) => Boolean(ref))
1807
+ ],
1808
+ ...(verifierResponseRef ? { sourceResponseRef: verifierResponseRef } : {})
1809
+ });
1810
+ }
1811
+ const approvalReason = `Verifier returned ${verdict ?? "no verdict"}.`;
1812
+ const gated = await updateRun(result.run, {
1813
+ state: "awaiting_approval",
1814
+ approvalRequired: true,
1815
+ approvalReason
1816
+ }, [createEvent("approval_required", approvalReason)], [approvalGateHandoff(result.run, {
1817
+ reason: approvalReason,
1818
+ role: "verifier",
1819
+ gate: "verifier_verdict"
1820
+ })]);
1821
+ return {
1822
+ run: gated,
1823
+ response: result.response,
1824
+ advanced: true,
1825
+ stopReason: approvalReason
1826
+ };
1827
+ }
1828
+ return {
1829
+ run,
1830
+ advanced: false,
1831
+ stopReason: `No transition is available from state ${run.state}.`
1832
+ };
1833
+ };
1834
+ export const advanceRun = async (input) => {
1835
+ const maxSteps = input.maxSteps ?? 10;
1836
+ let run = await loadRun(input.repoPath, input.runId);
1837
+ const providerResponses = [];
1838
+ let advanced = false;
1839
+ let stopReason = "No transition was available.";
1840
+ for (let step = 0; step < maxSteps; step += 1) {
1841
+ const result = await advanceOneStep(run);
1842
+ run = result.run;
1843
+ if (result.response) {
1844
+ providerResponses.push(result.response);
1845
+ }
1846
+ if (!result.advanced) {
1847
+ stopReason = result.stopReason ?? stopReason;
1848
+ break;
1849
+ }
1850
+ advanced = true;
1851
+ stopReason = result.stopReason ?? `Advanced to ${run.state}.`;
1852
+ if (terminalStates.has(run.state) || run.approvalRequired) {
1853
+ break;
1854
+ }
1855
+ }
1856
+ return {
1857
+ run,
1858
+ advanced,
1859
+ stopReason,
1860
+ providerResponses
1861
+ };
1862
+ };
1863
+ //# sourceMappingURL=loop.js.map