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.
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +58 -0
- package/LICENSE +21 -0
- package/PRIVACY.md +49 -0
- package/README.md +264 -0
- package/SECURITY.md +31 -0
- package/dist/bridges/chatgptWebBridge.d.ts +2 -0
- package/dist/bridges/chatgptWebBridge.js +981 -0
- package/dist/bridges/chatgptWebBridge.js.map +1 -0
- package/dist/cli/args.d.ts +9 -0
- package/dist/cli/args.js +82 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/format.d.ts +56 -0
- package/dist/cli/format.js +752 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +996 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/mcpConfig.d.ts +36 -0
- package/dist/cli/mcpConfig.js +98 -0
- package/dist/cli/mcpConfig.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/protocol.d.ts +44 -0
- package/dist/mcp/protocol.js +33 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +10 -0
- package/dist/mcp/tools.js +2200 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/validation.d.ts +8 -0
- package/dist/mcp/validation.js +67 -0
- package/dist/mcp/validation.js.map +1 -0
- package/dist/providers/chatgptWeb.d.ts +2 -0
- package/dist/providers/chatgptWeb.js +26 -0
- package/dist/providers/chatgptWeb.js.map +1 -0
- package/dist/providers/claudeCode.d.ts +4 -0
- package/dist/providers/claudeCode.js +32 -0
- package/dist/providers/claudeCode.js.map +1 -0
- package/dist/providers/codexCli.d.ts +6 -0
- package/dist/providers/codexCli.js +25 -0
- package/dist/providers/codexCli.js.map +1 -0
- package/dist/providers/codexCliModels.d.ts +23 -0
- package/dist/providers/codexCliModels.js +147 -0
- package/dist/providers/codexCliModels.js.map +1 -0
- package/dist/providers/localCommand.d.ts +26 -0
- package/dist/providers/localCommand.js +614 -0
- package/dist/providers/localCommand.js.map +1 -0
- package/dist/providers/markdownPayload.d.ts +7 -0
- package/dist/providers/markdownPayload.js +29 -0
- package/dist/providers/markdownPayload.js.map +1 -0
- package/dist/providers/responseSchema.d.ts +3 -0
- package/dist/providers/responseSchema.js +187 -0
- package/dist/providers/responseSchema.js.map +1 -0
- package/dist/providers/router.d.ts +3 -0
- package/dist/providers/router.js +21 -0
- package/dist/providers/router.js.map +1 -0
- package/dist/providers/stub.d.ts +2 -0
- package/dist/providers/stub.js +177 -0
- package/dist/providers/stub.js.map +1 -0
- package/dist/providers/types.d.ts +37 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/runtime/agentBoard.d.ts +79 -0
- package/dist/runtime/agentBoard.js +166 -0
- package/dist/runtime/agentBoard.js.map +1 -0
- package/dist/runtime/agentBoardArtifact.d.ts +9 -0
- package/dist/runtime/agentBoardArtifact.js +171 -0
- package/dist/runtime/agentBoardArtifact.js.map +1 -0
- package/dist/runtime/agentRunner.d.ts +17 -0
- package/dist/runtime/agentRunner.js +92 -0
- package/dist/runtime/agentRunner.js.map +1 -0
- package/dist/runtime/approvalInbox.d.ts +54 -0
- package/dist/runtime/approvalInbox.js +143 -0
- package/dist/runtime/approvalInbox.js.map +1 -0
- package/dist/runtime/approvalPolicy.d.ts +11 -0
- package/dist/runtime/approvalPolicy.js +58 -0
- package/dist/runtime/approvalPolicy.js.map +1 -0
- package/dist/runtime/artifacts.d.ts +23 -0
- package/dist/runtime/artifacts.js +48 -0
- package/dist/runtime/artifacts.js.map +1 -0
- package/dist/runtime/browserManager.d.ts +37 -0
- package/dist/runtime/browserManager.js +356 -0
- package/dist/runtime/browserManager.js.map +1 -0
- package/dist/runtime/canonicalMemory.d.ts +23 -0
- package/dist/runtime/canonicalMemory.js +134 -0
- package/dist/runtime/canonicalMemory.js.map +1 -0
- package/dist/runtime/chatGptPageReadiness.d.ts +16 -0
- package/dist/runtime/chatGptPageReadiness.js +74 -0
- package/dist/runtime/chatGptPageReadiness.js.map +1 -0
- package/dist/runtime/commandRunner.d.ts +18 -0
- package/dist/runtime/commandRunner.js +115 -0
- package/dist/runtime/commandRunner.js.map +1 -0
- package/dist/runtime/commandSafety.d.ts +7 -0
- package/dist/runtime/commandSafety.js +61 -0
- package/dist/runtime/commandSafety.js.map +1 -0
- package/dist/runtime/config.d.ts +10 -0
- package/dist/runtime/config.js +107 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/crewLanes.d.ts +2 -0
- package/dist/runtime/crewLanes.js +123 -0
- package/dist/runtime/crewLanes.js.map +1 -0
- package/dist/runtime/criticPolicy.d.ts +17 -0
- package/dist/runtime/criticPolicy.js +50 -0
- package/dist/runtime/criticPolicy.js.map +1 -0
- package/dist/runtime/defaults.d.ts +5 -0
- package/dist/runtime/defaults.js +100 -0
- package/dist/runtime/defaults.js.map +1 -0
- package/dist/runtime/directives.d.ts +3 -0
- package/dist/runtime/directives.js +218 -0
- package/dist/runtime/directives.js.map +1 -0
- package/dist/runtime/doctor.d.ts +36 -0
- package/dist/runtime/doctor.js +185 -0
- package/dist/runtime/doctor.js.map +1 -0
- package/dist/runtime/errors.d.ts +20 -0
- package/dist/runtime/errors.js +41 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/externalTransfer.d.ts +20 -0
- package/dist/runtime/externalTransfer.js +156 -0
- package/dist/runtime/externalTransfer.js.map +1 -0
- package/dist/runtime/fanout.d.ts +64 -0
- package/dist/runtime/fanout.js +263 -0
- package/dist/runtime/fanout.js.map +1 -0
- package/dist/runtime/gitEvidence.d.ts +10 -0
- package/dist/runtime/gitEvidence.js +80 -0
- package/dist/runtime/gitEvidence.js.map +1 -0
- package/dist/runtime/handoffs.d.ts +32 -0
- package/dist/runtime/handoffs.js +100 -0
- package/dist/runtime/handoffs.js.map +1 -0
- package/dist/runtime/ids.d.ts +2 -0
- package/dist/runtime/ids.js +4 -0
- package/dist/runtime/ids.js.map +1 -0
- package/dist/runtime/localStateIgnore.d.ts +9 -0
- package/dist/runtime/localStateIgnore.js +98 -0
- package/dist/runtime/localStateIgnore.js.map +1 -0
- package/dist/runtime/loop.d.ts +14 -0
- package/dist/runtime/loop.js +1863 -0
- package/dist/runtime/loop.js.map +1 -0
- package/dist/runtime/loopRecommendation.d.ts +109 -0
- package/dist/runtime/loopRecommendation.js +566 -0
- package/dist/runtime/loopRecommendation.js.map +1 -0
- package/dist/runtime/loopResponsibilities.d.ts +2 -0
- package/dist/runtime/loopResponsibilities.js +395 -0
- package/dist/runtime/loopResponsibilities.js.map +1 -0
- package/dist/runtime/loopRunner.d.ts +28 -0
- package/dist/runtime/loopRunner.js +81 -0
- package/dist/runtime/loopRunner.js.map +1 -0
- package/dist/runtime/operatorNextActions.d.ts +2 -0
- package/dist/runtime/operatorNextActions.js +344 -0
- package/dist/runtime/operatorNextActions.js.map +1 -0
- package/dist/runtime/paths.d.ts +9 -0
- package/dist/runtime/paths.js +14 -0
- package/dist/runtime/paths.js.map +1 -0
- package/dist/runtime/permissions.d.ts +9 -0
- package/dist/runtime/permissions.js +73 -0
- package/dist/runtime/permissions.js.map +1 -0
- package/dist/runtime/progressPacket.d.ts +12 -0
- package/dist/runtime/progressPacket.js +512 -0
- package/dist/runtime/progressPacket.js.map +1 -0
- package/dist/runtime/protectedPaths.d.ts +6 -0
- package/dist/runtime/protectedPaths.js +48 -0
- package/dist/runtime/protectedPaths.js.map +1 -0
- package/dist/runtime/providers.d.ts +13 -0
- package/dist/runtime/providers.js +60 -0
- package/dist/runtime/providers.js.map +1 -0
- package/dist/runtime/reconciliation.d.ts +17 -0
- package/dist/runtime/reconciliation.js +283 -0
- package/dist/runtime/reconciliation.js.map +1 -0
- package/dist/runtime/redaction.d.ts +1 -0
- package/dist/runtime/redaction.js +5 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/remoteRepoContext.d.ts +77 -0
- package/dist/runtime/remoteRepoContext.js +316 -0
- package/dist/runtime/remoteRepoContext.js.map +1 -0
- package/dist/runtime/repoContext.d.ts +50 -0
- package/dist/runtime/repoContext.js +399 -0
- package/dist/runtime/repoContext.js.map +1 -0
- package/dist/runtime/repoGateway.d.ts +64 -0
- package/dist/runtime/repoGateway.js +308 -0
- package/dist/runtime/repoGateway.js.map +1 -0
- package/dist/runtime/responseContracts.d.ts +3 -0
- package/dist/runtime/responseContracts.js +86 -0
- package/dist/runtime/responseContracts.js.map +1 -0
- package/dist/runtime/reviewLanes.d.ts +2 -0
- package/dist/runtime/reviewLanes.js +343 -0
- package/dist/runtime/reviewLanes.js.map +1 -0
- package/dist/runtime/reviewRouting.d.ts +51 -0
- package/dist/runtime/reviewRouting.js +152 -0
- package/dist/runtime/reviewRouting.js.map +1 -0
- package/dist/runtime/revisionPacket.d.ts +38 -0
- package/dist/runtime/revisionPacket.js +144 -0
- package/dist/runtime/revisionPacket.js.map +1 -0
- package/dist/runtime/revisionTrail.d.ts +2 -0
- package/dist/runtime/revisionTrail.js +162 -0
- package/dist/runtime/revisionTrail.js.map +1 -0
- package/dist/runtime/role-assignment.d.ts +4 -0
- package/dist/runtime/role-assignment.js +21 -0
- package/dist/runtime/role-assignment.js.map +1 -0
- package/dist/runtime/roleRoster.d.ts +28 -0
- package/dist/runtime/roleRoster.js +96 -0
- package/dist/runtime/roleRoster.js.map +1 -0
- package/dist/runtime/runInsights.d.ts +121 -0
- package/dist/runtime/runInsights.js +305 -0
- package/dist/runtime/runInsights.js.map +1 -0
- package/dist/runtime/runMonitor.d.ts +33 -0
- package/dist/runtime/runMonitor.js +143 -0
- package/dist/runtime/runMonitor.js.map +1 -0
- package/dist/runtime/runtime.d.ts +15 -0
- package/dist/runtime/runtime.js +199 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/runtimeInfo.d.ts +9 -0
- package/dist/runtime/runtimeInfo.js +76 -0
- package/dist/runtime/runtimeInfo.js.map +1 -0
- package/dist/runtime/store.d.ts +4 -0
- package/dist/runtime/store.js +48 -0
- package/dist/runtime/store.js.map +1 -0
- package/dist/runtime/summons.d.ts +25 -0
- package/dist/runtime/summons.js +403 -0
- package/dist/runtime/summons.js.map +1 -0
- package/dist/runtime/teamPresets.d.ts +14 -0
- package/dist/runtime/teamPresets.js +153 -0
- package/dist/runtime/teamPresets.js.map +1 -0
- package/dist/runtime/types.d.ts +505 -0
- package/dist/runtime/types.js +28 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/validationCommands.d.ts +18 -0
- package/dist/runtime/validationCommands.js +106 -0
- package/dist/runtime/validationCommands.js.map +1 -0
- package/dist/tui/dashboard.d.ts +41 -0
- package/dist/tui/dashboard.js +1115 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/docs/ARCHITECTURE.md +277 -0
- package/docs/CLI_SPEC.md +396 -0
- package/docs/CODEX_SETUP.md +288 -0
- package/docs/COMPLETION_CONTRACT.md +52 -0
- package/docs/CONTRIBUTOR_GUIDE.md +70 -0
- package/docs/DEMO.md +62 -0
- package/docs/GLOSSARY.md +46 -0
- package/docs/GOAL_LOOP_SCHEDULE.md +50 -0
- package/docs/KNOWN_LIMITATIONS.md +29 -0
- package/docs/LICENSING.md +21 -0
- package/docs/LOOP_RECIPES.md +290 -0
- package/docs/LOOP_SELECTION_UX.md +118 -0
- package/docs/MCP_SPEC.md +689 -0
- package/docs/MEMORY_AND_RECONCILIATION.md +222 -0
- package/docs/NPM_PUBLISHING.md +51 -0
- package/docs/OPEN_DECISIONS.md +81 -0
- package/docs/PROMPT_SCHEMAS.md +411 -0
- package/docs/PROVIDER_ADAPTERS.md +323 -0
- package/docs/PROVIDER_MATRIX.md +21 -0
- package/docs/PUBLIC_REPO_READINESS.md +49 -0
- package/docs/RESEARCH_NOTES.md +92 -0
- package/docs/ROADMAP.md +94 -0
- package/docs/ROLE_CONTRACTS.md +252 -0
- package/docs/RUNTIME_LOOP.md +240 -0
- package/docs/SECURITY_AND_PRIVACY.md +161 -0
- package/docs/TESTING_AND_VERIFICATION.md +180 -0
- package/docs/TRUST_MODEL.md +65 -0
- package/docs/decisions/0001-runtime-first-cli-and-mcp.md +23 -0
- package/docs/decisions/0002-provider-neutral-role-mapping.md +43 -0
- package/docs/decisions/0003-separate-implementation-and-verification.md +27 -0
- package/docs/product/README.md +14 -0
- package/docs/product/model-selection.md +88 -0
- package/docs/product/positioning.md +37 -0
- package/docs/product/pro-usage-modes.md +70 -0
- package/docs/product/roadmap.md +57 -0
- package/docs/product/role-policy.md +89 -0
- package/docs/product/runtime-invariants.md +44 -0
- package/docs/release/v0.1.0-preview.0.md +48 -0
- package/examples/stub-demo/README.md +25 -0
- 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
|