auditor-lambda 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/audit-code-wrapper-lib.mjs +149 -129
- package/dist/adapters/normalizeExternal.js +6 -3
- package/dist/cli/args.d.ts +0 -1
- package/dist/cli/args.js +0 -6
- package/dist/cli/dispatch.js +3 -2
- package/dist/cli/lineIndex.js +4 -1
- package/dist/cli/mergeAndIngestCommand.d.ts +1 -0
- package/dist/cli/mergeAndIngestCommand.js +219 -0
- package/dist/cli/nextStepCommand.js +5 -1
- package/dist/cli/runToCompletion.d.ts +9 -0
- package/dist/cli/runToCompletion.js +655 -480
- package/dist/cli/statusCommand.d.ts +1 -0
- package/dist/cli/statusCommand.js +113 -0
- package/dist/cli/submitPacketCommand.d.ts +1 -0
- package/dist/cli/submitPacketCommand.js +155 -0
- package/dist/cli/workerResult.d.ts +1 -1
- package/dist/cli/workerRunCommand.d.ts +1 -0
- package/dist/cli/workerRunCommand.js +88 -0
- package/dist/cli.js +14 -563
- package/dist/extractors/analyzers/sql.js +4 -1
- package/dist/extractors/analyzers/treeSitter.js +29 -15
- package/dist/extractors/analyzers/typescript.js +10 -8
- package/dist/extractors/designAssessment.js +43 -24
- package/dist/extractors/graph.js +139 -73
- package/dist/extractors/pathPatterns.js +17 -5
- package/dist/io/runArtifactTypes.d.ts +18 -0
- package/dist/io/runArtifactTypes.js +1 -0
- package/dist/io/runArtifacts.d.ts +2 -18
- package/dist/io/runArtifacts.js +14 -3
- package/dist/mcp/server.js +9 -0
- package/dist/orchestrator/advance.js +37 -22
- package/dist/orchestrator/artifactFreshness.js +2 -2
- package/dist/orchestrator/autoFixExecutor.d.ts +1 -1
- package/dist/orchestrator/autoFixExecutor.js +16 -8
- package/dist/orchestrator/dependencyMap.d.ts +1 -1
- package/dist/orchestrator/dependencyMap.js +7 -1
- package/dist/orchestrator/fileAnchors.js +14 -3
- package/dist/orchestrator/flowCoverage.js +1 -0
- package/dist/orchestrator/flowRequeue.js +4 -1
- package/dist/orchestrator/{internalExecutors.d.ts → ingestionExecutors.d.ts} +0 -6
- package/dist/orchestrator/ingestionExecutors.js +237 -0
- package/dist/orchestrator/intakeExecutors.d.ts +3 -0
- package/dist/orchestrator/intakeExecutors.js +25 -0
- package/dist/orchestrator/planningExecutors.d.ts +4 -0
- package/dist/orchestrator/planningExecutors.js +95 -0
- package/dist/orchestrator/runtimeCommand.js +7 -15
- package/dist/orchestrator/selectiveDeepening/conflict.d.ts +8 -0
- package/dist/orchestrator/selectiveDeepening/conflict.js +71 -0
- package/dist/orchestrator/selectiveDeepening/findingFollowup.d.ts +10 -0
- package/dist/orchestrator/selectiveDeepening/findingFollowup.js +52 -0
- package/dist/orchestrator/selectiveDeepening/highRiskClean.d.ts +7 -0
- package/dist/orchestrator/selectiveDeepening/highRiskClean.js +44 -0
- package/dist/orchestrator/selectiveDeepening/index.d.ts +18 -0
- package/dist/orchestrator/selectiveDeepening/index.js +128 -0
- package/dist/orchestrator/selectiveDeepening/lensVerification.d.ts +12 -0
- package/dist/orchestrator/selectiveDeepening/lensVerification.js +242 -0
- package/dist/orchestrator/selectiveDeepening/runtimeValidation.d.ts +13 -0
- package/dist/orchestrator/selectiveDeepening/runtimeValidation.js +57 -0
- package/dist/orchestrator/selectiveDeepening/shared.d.ts +45 -0
- package/dist/orchestrator/selectiveDeepening/shared.js +128 -0
- package/dist/orchestrator/selectiveDeepening/stewardFollowup.d.ts +6 -0
- package/dist/orchestrator/selectiveDeepening/stewardFollowup.js +72 -0
- package/dist/orchestrator/selectiveDeepening.d.ts +2 -20
- package/dist/orchestrator/selectiveDeepening.js +6 -760
- package/dist/orchestrator/staleness.js +3 -3
- package/dist/orchestrator/structureExecutors.d.ts +5 -0
- package/dist/orchestrator/structureExecutors.js +94 -0
- package/dist/orchestrator/taskBuilder.d.ts +2 -2
- package/dist/orchestrator/taskBuilder.js +101 -82
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.js +14 -95
- package/dist/quota/discoveredLimits.d.ts +1 -0
- package/dist/quota/discoveredLimits.js +7 -1
- package/dist/quota/index.d.ts +0 -2
- package/dist/quota/index.js +1 -2
- package/dist/reporting/workBlocks.js +7 -4
- package/dist/types/reviewPlanning.d.ts +23 -16
- package/dist/validation/auditResults.js +97 -95
- package/dist/validation/sessionConfig.d.ts +2 -2
- package/dist/validation/sessionConfig.js +14 -7
- package/package.json +4 -3
- package/schemas/audit_findings.schema.json +3 -3
- package/schemas/critical_flows.schema.json +3 -2
- package/schemas/dispatch_quota.schema.json +1 -1
- package/schemas/graph_bundle.schema.json +1 -1
- package/schemas/review_packets.schema.json +1 -1
- package/schemas/step_contract.schema.json +80 -0
- package/scripts/postinstall.mjs +19 -2
- package/skills/audit-code/opencode-command-template.txt +3 -3
- package/dist/orchestrator/internalExecutors.js +0 -424
- package/dist/providers/localSubprocessProvider.d.ts +0 -9
- package/dist/providers/localSubprocessProvider.js +0 -18
- package/dist/providers/subprocessTemplateProvider.d.ts +0 -8
- package/dist/providers/subprocessTemplateProvider.js +0 -59
- package/dist/providers/vscodeTaskProvider.d.ts +0 -7
- package/dist/providers/vscodeTaskProvider.js +0 -14
- package/dist/quota/probe.d.ts +0 -10
- package/dist/quota/probe.js +0 -18
|
@@ -24,6 +24,564 @@ import { WORKER_RESULT_CONTRACT_VERSION, buildWorkerResult, persistWorkerRunArti
|
|
|
24
24
|
import { readWaveManifest, writeWaveManifest, removeWaveManifest, buildWaveSlotEntry, } from "./waveManifest.js";
|
|
25
25
|
import { cleanupStaleArtifactsDir } from "./cleanup.js";
|
|
26
26
|
import { getRootDir, warnIfNotGitRepo, getArtifactsDir, getUiMode, getMaxRuns, getAgentBatchSize, getParallelWorkers, getTimeoutMs, getHostModel, getBatchResultsDir, getFlag, listBatchResultFiles, getExplicitProvider, chunkArray, resolveHostDispatchCapability, getOptionalBooleanFlag, getHostMaxActiveSubagents, summarizeLaunchExit, } from "./args.js";
|
|
27
|
+
async function recoverInterruptedWave(params) {
|
|
28
|
+
const { artifactsDir, root, artifactsWritten } = params;
|
|
29
|
+
let anyProgress = params.anyProgress;
|
|
30
|
+
// Resume interrupted parallel wave: ingest any results that workers
|
|
31
|
+
// wrote before the previous process exited.
|
|
32
|
+
const priorWave = await readWaveManifest(artifactsDir);
|
|
33
|
+
if (priorWave) {
|
|
34
|
+
process.stderr.write(`[audit-code] Recovering interrupted wave (${priorWave.slots.length} slot(s), obligation ${priorWave.obligation_id}).\n`);
|
|
35
|
+
let recoveredProgress = false;
|
|
36
|
+
for (const entry of priorWave.slots) {
|
|
37
|
+
try {
|
|
38
|
+
const results = await readJsonFile(entry.audit_results_path);
|
|
39
|
+
if (!results || results.length === 0)
|
|
40
|
+
continue;
|
|
41
|
+
const stepResult = await runAuditStep({
|
|
42
|
+
root,
|
|
43
|
+
artifactsDir,
|
|
44
|
+
preferredExecutor: "result_ingestion_executor",
|
|
45
|
+
auditResultsPath: entry.audit_results_path,
|
|
46
|
+
});
|
|
47
|
+
if (stepResult.progress_made) {
|
|
48
|
+
recoveredProgress = true;
|
|
49
|
+
anyProgress = true;
|
|
50
|
+
for (const a of stepResult.artifacts_written)
|
|
51
|
+
artifactsWritten.add(a);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await removeWaveManifest(artifactsDir);
|
|
59
|
+
if (recoveredProgress)
|
|
60
|
+
return { recovered: true, anyProgress };
|
|
61
|
+
}
|
|
62
|
+
return { recovered: false, anyProgress };
|
|
63
|
+
}
|
|
64
|
+
async function buildParallelWaveSlots(params) {
|
|
65
|
+
const { root, artifactsDir, selfCliPath, taskGroups, obligationId, timeoutMs } = params;
|
|
66
|
+
let runCount = params.runCountStart;
|
|
67
|
+
const workerSlots = [];
|
|
68
|
+
for (const rawGroup of taskGroups) {
|
|
69
|
+
const group = await addFileLineCountHints(root, rawGroup);
|
|
70
|
+
runCount += 1;
|
|
71
|
+
const slotRunId = buildRunId(obligationId, runCount);
|
|
72
|
+
const slotPaths = getRunPaths(artifactsDir, slotRunId);
|
|
73
|
+
const slotAuditResultsPath = join(slotPaths.runDir, "audit-results.json");
|
|
74
|
+
const slotPendingTasksPath = join(slotPaths.runDir, "pending-audit-tasks.json");
|
|
75
|
+
const slotReadPaths = new Set();
|
|
76
|
+
for (const t of group) {
|
|
77
|
+
for (const fp of t.file_paths)
|
|
78
|
+
slotReadPaths.add(fp);
|
|
79
|
+
}
|
|
80
|
+
const slotTask = {
|
|
81
|
+
contract_version: "audit-code-worker/v1alpha1",
|
|
82
|
+
run_id: slotRunId,
|
|
83
|
+
repo_root: root,
|
|
84
|
+
artifacts_dir: artifactsDir,
|
|
85
|
+
obligation_id: obligationId,
|
|
86
|
+
preferred_executor: "agent",
|
|
87
|
+
result_path: slotPaths.resultPath,
|
|
88
|
+
worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
|
|
89
|
+
audit_results_path: slotAuditResultsPath,
|
|
90
|
+
pending_audit_tasks_path: slotPendingTasksPath,
|
|
91
|
+
worker_command_mode: "deferred",
|
|
92
|
+
timeout_ms: timeoutMs,
|
|
93
|
+
max_retries: 0,
|
|
94
|
+
access: {
|
|
95
|
+
read_paths: [...slotReadPaths],
|
|
96
|
+
write_paths: [slotAuditResultsPath, slotPaths.resultPath],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
const slotPrompt = renderWorkerPrompt(slotTask);
|
|
100
|
+
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
|
|
101
|
+
await writeJsonFile(slotPendingTasksPath, group);
|
|
102
|
+
workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
|
|
103
|
+
}
|
|
104
|
+
await writeDispatchBatchFiles(artifactsDir, workerSlots.map((slot) => ({
|
|
105
|
+
run_id: slot.runId,
|
|
106
|
+
task_path: slot.paths.taskPath,
|
|
107
|
+
prompt_path: slot.paths.promptPath,
|
|
108
|
+
result_path: slot.paths.resultPath,
|
|
109
|
+
status_path: slot.paths.statusPath,
|
|
110
|
+
audit_results_path: slot.auditResultsPath,
|
|
111
|
+
pending_audit_tasks_path: slot.pendingTasksPath,
|
|
112
|
+
})), workerSlots.flatMap((slot) => slot.group));
|
|
113
|
+
return { slots: workerSlots, runCountAfter: runCount };
|
|
114
|
+
}
|
|
115
|
+
async function ingestParallelWaveResults(params) {
|
|
116
|
+
const { root, artifactsDir, workerSlots, launchErrorsByRunId, obligationId, parallelStartedAt, providerName, artifactsWritten, } = params;
|
|
117
|
+
let anyProgress = params.anyProgress;
|
|
118
|
+
// Result ingestion is intentionally sequential even though agent launch
|
|
119
|
+
// was parallel. Writing to coverage_matrix.json is not atomic, so
|
|
120
|
+
// concurrent ingest calls would race and corrupt coverage state.
|
|
121
|
+
let batchProgress = false;
|
|
122
|
+
const batchErrors = [];
|
|
123
|
+
for (const slot of workerSlots) {
|
|
124
|
+
const parallelEndedAt = new Date().toISOString();
|
|
125
|
+
let workerResult = buildWorkerResult({
|
|
126
|
+
runId: slot.runId,
|
|
127
|
+
obligationId,
|
|
128
|
+
status: "no_progress",
|
|
129
|
+
progressMade: false,
|
|
130
|
+
selectedExecutor: "agent",
|
|
131
|
+
artifactsWritten: [],
|
|
132
|
+
summary: "Parallel worker batch made no progress.",
|
|
133
|
+
nextLikelyStep: obligationId,
|
|
134
|
+
errors: [],
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
const launchError = launchErrorsByRunId.get(slot.runId);
|
|
138
|
+
if (launchError) {
|
|
139
|
+
throw new Error(`Worker launch failed: ${launchError}`);
|
|
140
|
+
}
|
|
141
|
+
const auditResults = await readJsonFile(slot.auditResultsPath);
|
|
142
|
+
const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
|
|
143
|
+
const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
|
|
144
|
+
if (slot.group.length > 0 && matchedCount === 0) {
|
|
145
|
+
throw new Error("Worker did not emit any audit results for the assigned tasks.");
|
|
146
|
+
}
|
|
147
|
+
const issues = validateAuditResults(auditResults, slot.group, {
|
|
148
|
+
lineIndex: await buildLineIndexForPaths(root, slot.group.flatMap((task) => task.file_paths)),
|
|
149
|
+
});
|
|
150
|
+
const errors = issues.filter((issue) => issue.severity === "error");
|
|
151
|
+
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
152
|
+
if (warnings.length > 0) {
|
|
153
|
+
process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
|
|
154
|
+
formatAuditResultIssues(warnings) + "\n");
|
|
155
|
+
}
|
|
156
|
+
if (errors.length > 0) {
|
|
157
|
+
throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
|
|
158
|
+
formatAuditResultIssues(errors));
|
|
159
|
+
}
|
|
160
|
+
const stepResult = await runAuditStep({
|
|
161
|
+
root,
|
|
162
|
+
artifactsDir,
|
|
163
|
+
preferredExecutor: "result_ingestion_executor",
|
|
164
|
+
auditResultsPath: slot.auditResultsPath,
|
|
165
|
+
});
|
|
166
|
+
workerResult = buildWorkerResult({
|
|
167
|
+
runId: slot.runId,
|
|
168
|
+
obligationId,
|
|
169
|
+
status: stepResult.progress_made ? "completed" : "no_progress",
|
|
170
|
+
progressMade: stepResult.progress_made,
|
|
171
|
+
selectedExecutor: stepResult.selected_executor,
|
|
172
|
+
artifactsWritten: stepResult.artifacts_written,
|
|
173
|
+
summary: stepResult.progress_summary,
|
|
174
|
+
nextLikelyStep: stepResult.next_likely_step,
|
|
175
|
+
errors: [],
|
|
176
|
+
});
|
|
177
|
+
batchProgress ||= stepResult.progress_made;
|
|
178
|
+
if (stepResult.progress_made)
|
|
179
|
+
anyProgress = true;
|
|
180
|
+
for (const a of stepResult.artifacts_written)
|
|
181
|
+
artifactsWritten.add(a);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
+
batchErrors.push(`${slot.runId}: ${message}`);
|
|
186
|
+
workerResult = buildWorkerResult({
|
|
187
|
+
runId: slot.runId,
|
|
188
|
+
obligationId,
|
|
189
|
+
status: "failed",
|
|
190
|
+
progressMade: false,
|
|
191
|
+
selectedExecutor: "agent",
|
|
192
|
+
artifactsWritten: [],
|
|
193
|
+
summary: `Worker failed for executor agent: ${message}`,
|
|
194
|
+
nextLikelyStep: obligationId,
|
|
195
|
+
errors: [message],
|
|
196
|
+
});
|
|
197
|
+
process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
|
|
198
|
+
}
|
|
199
|
+
await persistWorkerRunArtifacts(slot.paths, workerResult, "parallel-deferred-agent");
|
|
200
|
+
await appendRunLedgerEntry(artifactsDir, {
|
|
201
|
+
run_id: slot.runId,
|
|
202
|
+
provider: providerName,
|
|
203
|
+
obligation_id: obligationId,
|
|
204
|
+
selected_executor: workerResult.selected_executor,
|
|
205
|
+
status: workerResult.status,
|
|
206
|
+
started_at: parallelStartedAt,
|
|
207
|
+
ended_at: parallelEndedAt,
|
|
208
|
+
result_path: slot.paths.resultPath,
|
|
209
|
+
});
|
|
210
|
+
artifactsWritten.add("run-ledger.json");
|
|
211
|
+
}
|
|
212
|
+
return { batchProgress, batchErrors, anyProgress, artifactsWritten };
|
|
213
|
+
}
|
|
214
|
+
async function recordWaveQuota(params) {
|
|
215
|
+
const { providerModelKey, providerName, workerSlots, slotTokenEstimates, batchErrors, halfLifeHours, } = params;
|
|
216
|
+
// Record outcome for adaptive learning (best-effort — never blocks dispatch)
|
|
217
|
+
{
|
|
218
|
+
const rateLimitResults = batchErrors.map((e) => detectRateLimitError(e));
|
|
219
|
+
const rateLimitHit = rateLimitResults.find((r) => r.isRateLimited);
|
|
220
|
+
const retryAfterMs = rateLimitHit?.retryAfterMs ?? null;
|
|
221
|
+
await recordWaveOutcome(providerModelKey, {
|
|
222
|
+
concurrency: workerSlots.length,
|
|
223
|
+
estimated_tokens: slotTokenEstimates.slice(0, workerSlots.length).reduce((a, b) => a + b, 0),
|
|
224
|
+
outcome: rateLimitHit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
|
|
225
|
+
cooldown_until: rateLimitHit ? computeCooldownUntil(retryAfterMs) : null,
|
|
226
|
+
}, halfLifeHours).catch(() => undefined);
|
|
227
|
+
}
|
|
228
|
+
// Extract rate-limit headers from worker stderr (best-effort)
|
|
229
|
+
{
|
|
230
|
+
const extractor = getHeaderExtractorForProvider(providerName);
|
|
231
|
+
for (const slot of workerSlots) {
|
|
232
|
+
try {
|
|
233
|
+
const stderr = await readFile(slot.paths.stderrPath, "utf8");
|
|
234
|
+
const extracted = extractor.extract(stderr);
|
|
235
|
+
if (extracted && (extracted.requests_per_minute != null || extracted.input_tokens_per_minute != null)) {
|
|
236
|
+
await updateDiscoveredLimits(providerModelKey, {
|
|
237
|
+
requests_per_minute: extracted.requests_per_minute,
|
|
238
|
+
input_tokens_per_minute: extracted.input_tokens_per_minute,
|
|
239
|
+
source: "header_extraction",
|
|
240
|
+
});
|
|
241
|
+
break; // one successful extraction is enough
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// stderr file missing or unreadable — skip
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function runInlineStep(params) {
|
|
251
|
+
const { root, artifactsDir, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, analyzers, since, artifactsWritten, providerName, decision, pendingBatchAuditResults, } = params;
|
|
252
|
+
let anyProgress = params.anyProgress;
|
|
253
|
+
let pendingAuditResultsPath = params.pendingAuditResultsPath;
|
|
254
|
+
let pendingRuntimeUpdatesPath = params.pendingRuntimeUpdatesPath;
|
|
255
|
+
let pendingExternalAnalyzerPath = params.pendingExternalAnalyzerPath;
|
|
256
|
+
await clearDispatchFiles(artifactsDir);
|
|
257
|
+
const startedAt = new Date().toISOString();
|
|
258
|
+
let workerResult;
|
|
259
|
+
try {
|
|
260
|
+
const result = await runAuditStep({
|
|
261
|
+
root,
|
|
262
|
+
artifactsDir,
|
|
263
|
+
preferredExecutor,
|
|
264
|
+
auditResultsPath,
|
|
265
|
+
runtimeUpdatesPath,
|
|
266
|
+
externalAnalyzerPath,
|
|
267
|
+
analyzers,
|
|
268
|
+
since,
|
|
269
|
+
});
|
|
270
|
+
workerResult = {
|
|
271
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
272
|
+
run_id: runId,
|
|
273
|
+
obligation_id: obligationId,
|
|
274
|
+
status: result.progress_made ? "completed" : "no_progress",
|
|
275
|
+
progress_made: result.progress_made,
|
|
276
|
+
selected_executor: result.selected_executor,
|
|
277
|
+
artifacts_written: result.artifacts_written,
|
|
278
|
+
summary: result.progress_summary,
|
|
279
|
+
next_likely_step: result.next_likely_step,
|
|
280
|
+
errors: [],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
285
|
+
workerResult = {
|
|
286
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
287
|
+
run_id: runId,
|
|
288
|
+
obligation_id: obligationId,
|
|
289
|
+
status: "failed",
|
|
290
|
+
progress_made: false,
|
|
291
|
+
selected_executor: preferredExecutor,
|
|
292
|
+
artifacts_written: [],
|
|
293
|
+
summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
|
|
294
|
+
next_likely_step: decision.selected_obligation,
|
|
295
|
+
errors: [message],
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
await persistWorkerRunArtifacts(paths, workerResult, "inline");
|
|
299
|
+
await appendRunLedgerEntry(artifactsDir, {
|
|
300
|
+
run_id: runId,
|
|
301
|
+
provider: providerName,
|
|
302
|
+
obligation_id: obligationId,
|
|
303
|
+
selected_executor: workerResult.selected_executor,
|
|
304
|
+
status: workerResult.status,
|
|
305
|
+
started_at: startedAt,
|
|
306
|
+
ended_at: new Date().toISOString(),
|
|
307
|
+
result_path: paths.resultPath,
|
|
308
|
+
});
|
|
309
|
+
const lastResult = workerResult;
|
|
310
|
+
if (workerResult.progress_made) {
|
|
311
|
+
anyProgress = true;
|
|
312
|
+
}
|
|
313
|
+
for (const artifact of workerResult.artifacts_written) {
|
|
314
|
+
artifactsWritten.add(artifact);
|
|
315
|
+
}
|
|
316
|
+
artifactsWritten.add("run-ledger.json");
|
|
317
|
+
if (externalAnalyzerPath)
|
|
318
|
+
pendingExternalAnalyzerPath = undefined;
|
|
319
|
+
if (auditResultsPath &&
|
|
320
|
+
pendingBatchAuditResults[0] === auditResultsPath &&
|
|
321
|
+
preferredExecutor === "result_ingestion_executor" &&
|
|
322
|
+
workerResult.status !== "failed" &&
|
|
323
|
+
workerResult.status !== "blocked") {
|
|
324
|
+
pendingBatchAuditResults.shift();
|
|
325
|
+
}
|
|
326
|
+
if (auditResultsPath)
|
|
327
|
+
pendingAuditResultsPath = undefined;
|
|
328
|
+
if (runtimeUpdatesPath)
|
|
329
|
+
pendingRuntimeUpdatesPath = undefined;
|
|
330
|
+
if (workerResult.status === "failed" ||
|
|
331
|
+
workerResult.status === "blocked" ||
|
|
332
|
+
workerResult.status === "no_progress") {
|
|
333
|
+
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
334
|
+
const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
|
|
335
|
+
const state = shouldBlock
|
|
336
|
+
? buildBlockedAuditState({
|
|
337
|
+
state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
|
|
338
|
+
obligationId: workerResult.obligation_id,
|
|
339
|
+
executor: workerResult.selected_executor,
|
|
340
|
+
blocker: buildWorkerFailureBlocker(workerResult),
|
|
341
|
+
})
|
|
342
|
+
: bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
|
|
343
|
+
if (shouldBlock) {
|
|
344
|
+
await writeCoreArtifacts(artifactsDir, {
|
|
345
|
+
...bundleAfter,
|
|
346
|
+
audit_state: state,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
await emitEnvelope({
|
|
350
|
+
root,
|
|
351
|
+
artifactsDir,
|
|
352
|
+
bundle: shouldBlock
|
|
353
|
+
? { ...bundleAfter, audit_state: state }
|
|
354
|
+
: bundleAfter,
|
|
355
|
+
audit_state: state,
|
|
356
|
+
selected_obligation: workerResult.obligation_id,
|
|
357
|
+
selected_executor: workerResult.selected_executor,
|
|
358
|
+
progress_made: anyProgress,
|
|
359
|
+
artifacts_written: Array.from(shouldBlock
|
|
360
|
+
? new Set([...artifactsWritten, "audit_state.json"])
|
|
361
|
+
: artifactsWritten),
|
|
362
|
+
progress_summary: buildWorkerFailureBlocker(workerResult),
|
|
363
|
+
next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
|
|
364
|
+
providerName,
|
|
365
|
+
});
|
|
366
|
+
return {
|
|
367
|
+
done: true,
|
|
368
|
+
lastResult,
|
|
369
|
+
anyProgress,
|
|
370
|
+
artifactsWritten,
|
|
371
|
+
pendingBatchAuditResults,
|
|
372
|
+
pendingAuditResultsPath,
|
|
373
|
+
pendingRuntimeUpdatesPath,
|
|
374
|
+
pendingExternalAnalyzerPath,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
done: false,
|
|
379
|
+
lastResult,
|
|
380
|
+
anyProgress,
|
|
381
|
+
artifactsWritten,
|
|
382
|
+
pendingBatchAuditResults,
|
|
383
|
+
pendingAuditResultsPath,
|
|
384
|
+
pendingRuntimeUpdatesPath,
|
|
385
|
+
pendingExternalAnalyzerPath,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async function runSingleWorkerStep(params) {
|
|
389
|
+
const { root, artifactsDir, selfCliPath, runId, paths, preferredExecutor, obligationId, auditResultsPath, runtimeUpdatesPath, externalAnalyzerPath, bundle, timeoutMs, uiMode, provider, artifactsWritten, decision, pendingBatchAuditResults, } = params;
|
|
390
|
+
let anyProgress = params.anyProgress;
|
|
391
|
+
let pendingAuditResultsPath = params.pendingAuditResultsPath;
|
|
392
|
+
let pendingRuntimeUpdatesPath = params.pendingRuntimeUpdatesPath;
|
|
393
|
+
let pendingExternalAnalyzerPath = params.pendingExternalAnalyzerPath;
|
|
394
|
+
const pendingAuditTasks = preferredExecutor === "agent"
|
|
395
|
+
? await addFileLineCountHints(root, buildPendingAuditTasks(bundle))
|
|
396
|
+
: undefined;
|
|
397
|
+
const pendingAuditTasksPath = preferredExecutor === "agent"
|
|
398
|
+
? join(paths.runDir, "pending-audit-tasks.json")
|
|
399
|
+
: undefined;
|
|
400
|
+
const providerAuditResultsPath = preferredExecutor === "agent"
|
|
401
|
+
? join(paths.runDir, "audit-results.json")
|
|
402
|
+
: auditResultsPath;
|
|
403
|
+
const providerReadPaths = new Set();
|
|
404
|
+
if (pendingAuditTasks) {
|
|
405
|
+
for (const pt of pendingAuditTasks) {
|
|
406
|
+
for (const fp of pt.file_paths)
|
|
407
|
+
providerReadPaths.add(fp);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const task = {
|
|
411
|
+
contract_version: "audit-code-worker/v1alpha1",
|
|
412
|
+
run_id: runId,
|
|
413
|
+
repo_root: root,
|
|
414
|
+
artifacts_dir: artifactsDir,
|
|
415
|
+
obligation_id: obligationId,
|
|
416
|
+
preferred_executor: preferredExecutor,
|
|
417
|
+
result_path: paths.resultPath,
|
|
418
|
+
worker_command: [
|
|
419
|
+
process.execPath,
|
|
420
|
+
selfCliPath,
|
|
421
|
+
"worker-run",
|
|
422
|
+
"--task",
|
|
423
|
+
paths.taskPath,
|
|
424
|
+
],
|
|
425
|
+
audit_results_path: providerAuditResultsPath,
|
|
426
|
+
pending_audit_tasks_path: pendingAuditTasksPath,
|
|
427
|
+
runtime_updates_path: runtimeUpdatesPath,
|
|
428
|
+
external_analyzer_results_path: externalAnalyzerPath,
|
|
429
|
+
timeout_ms: timeoutMs,
|
|
430
|
+
max_retries: 0,
|
|
431
|
+
access: providerReadPaths.size > 0 ? {
|
|
432
|
+
read_paths: [...providerReadPaths],
|
|
433
|
+
write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
|
|
434
|
+
} : undefined,
|
|
435
|
+
};
|
|
436
|
+
const prompt = renderWorkerPrompt(task);
|
|
437
|
+
await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
|
|
438
|
+
if (pendingAuditTasksPath && pendingAuditTasks) {
|
|
439
|
+
await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
|
|
440
|
+
}
|
|
441
|
+
const startedAt = new Date().toISOString();
|
|
442
|
+
let workerResult;
|
|
443
|
+
let launchResult = null;
|
|
444
|
+
try {
|
|
445
|
+
launchResult = await provider.launch({
|
|
446
|
+
repoRoot: root,
|
|
447
|
+
runId,
|
|
448
|
+
obligationId,
|
|
449
|
+
promptPath: paths.promptPath,
|
|
450
|
+
taskPath: paths.taskPath,
|
|
451
|
+
resultPath: paths.resultPath,
|
|
452
|
+
stdoutPath: paths.stdoutPath,
|
|
453
|
+
stderrPath: paths.stderrPath,
|
|
454
|
+
uiMode,
|
|
455
|
+
timeoutMs,
|
|
456
|
+
});
|
|
457
|
+
const candidate = await readJsonFile(paths.resultPath);
|
|
458
|
+
if (isWorkerResult(candidate)) {
|
|
459
|
+
workerResult = candidate;
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
const launchExitSummary = summarizeLaunchExit(launchResult);
|
|
463
|
+
workerResult = {
|
|
464
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
465
|
+
run_id: runId,
|
|
466
|
+
obligation_id: obligationId,
|
|
467
|
+
status: "failed",
|
|
468
|
+
progress_made: false,
|
|
469
|
+
selected_executor: preferredExecutor,
|
|
470
|
+
artifacts_written: [],
|
|
471
|
+
summary: launchExitSummary
|
|
472
|
+
? `Worker did not emit a valid worker result after provider exit: ${launchExitSummary}`
|
|
473
|
+
: "Worker did not emit a valid worker result.",
|
|
474
|
+
next_likely_step: decision.selected_obligation,
|
|
475
|
+
errors: ["Invalid worker result contract."],
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
481
|
+
const launchExitSummary = launchResult && summarizeLaunchExit(launchResult);
|
|
482
|
+
workerResult = {
|
|
483
|
+
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
484
|
+
run_id: runId,
|
|
485
|
+
obligation_id: obligationId,
|
|
486
|
+
status: "failed",
|
|
487
|
+
progress_made: false,
|
|
488
|
+
selected_executor: preferredExecutor,
|
|
489
|
+
artifacts_written: [],
|
|
490
|
+
summary: `Worker launch failed for ${preferredExecutor}: ${launchExitSummary ?? message}`,
|
|
491
|
+
next_likely_step: decision.selected_obligation,
|
|
492
|
+
errors: launchExitSummary ? [message, launchExitSummary] : [message],
|
|
493
|
+
};
|
|
494
|
+
await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
|
|
495
|
+
}
|
|
496
|
+
await appendRunLedgerEntry(artifactsDir, {
|
|
497
|
+
run_id: runId,
|
|
498
|
+
provider: provider.name,
|
|
499
|
+
obligation_id: obligationId,
|
|
500
|
+
selected_executor: workerResult.selected_executor,
|
|
501
|
+
status: workerResult.status,
|
|
502
|
+
started_at: startedAt,
|
|
503
|
+
ended_at: new Date().toISOString(),
|
|
504
|
+
result_path: paths.resultPath,
|
|
505
|
+
});
|
|
506
|
+
const lastResult = workerResult;
|
|
507
|
+
if (workerResult.progress_made) {
|
|
508
|
+
anyProgress = true;
|
|
509
|
+
}
|
|
510
|
+
for (const artifact of workerResult.artifacts_written) {
|
|
511
|
+
artifactsWritten.add(artifact);
|
|
512
|
+
}
|
|
513
|
+
artifactsWritten.add("run-ledger.json");
|
|
514
|
+
if (externalAnalyzerPath)
|
|
515
|
+
pendingExternalAnalyzerPath = undefined;
|
|
516
|
+
if (auditResultsPath &&
|
|
517
|
+
pendingBatchAuditResults[0] === auditResultsPath &&
|
|
518
|
+
preferredExecutor === "result_ingestion_executor" &&
|
|
519
|
+
workerResult.status !== "failed" &&
|
|
520
|
+
workerResult.status !== "blocked") {
|
|
521
|
+
pendingBatchAuditResults.shift();
|
|
522
|
+
}
|
|
523
|
+
if (providerAuditResultsPath)
|
|
524
|
+
pendingAuditResultsPath = undefined;
|
|
525
|
+
if (runtimeUpdatesPath)
|
|
526
|
+
pendingRuntimeUpdatesPath = undefined;
|
|
527
|
+
if (workerResult.status === "failed" ||
|
|
528
|
+
workerResult.status === "blocked" ||
|
|
529
|
+
workerResult.status === "no_progress") {
|
|
530
|
+
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
531
|
+
const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
|
|
532
|
+
const state = shouldBlock
|
|
533
|
+
? buildBlockedAuditState({
|
|
534
|
+
state: deriveAuditState(bundleAfter),
|
|
535
|
+
obligationId: workerResult.obligation_id,
|
|
536
|
+
executor: workerResult.selected_executor,
|
|
537
|
+
blocker: buildWorkerFailureBlocker(workerResult),
|
|
538
|
+
})
|
|
539
|
+
: deriveAuditState(bundleAfter);
|
|
540
|
+
if (shouldBlock) {
|
|
541
|
+
await writeCoreArtifacts(artifactsDir, {
|
|
542
|
+
...bundleAfter,
|
|
543
|
+
audit_state: state,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
await emitEnvelope({
|
|
547
|
+
root,
|
|
548
|
+
artifactsDir,
|
|
549
|
+
bundle: shouldBlock
|
|
550
|
+
? { ...bundleAfter, audit_state: state }
|
|
551
|
+
: bundleAfter,
|
|
552
|
+
audit_state: state,
|
|
553
|
+
selected_obligation: workerResult.obligation_id,
|
|
554
|
+
selected_executor: workerResult.selected_executor,
|
|
555
|
+
progress_made: anyProgress,
|
|
556
|
+
artifacts_written: Array.from(shouldBlock
|
|
557
|
+
? new Set([...artifactsWritten, "audit_state.json"])
|
|
558
|
+
: artifactsWritten),
|
|
559
|
+
progress_summary: buildWorkerFailureBlocker(workerResult),
|
|
560
|
+
next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
|
|
561
|
+
providerName: provider.name,
|
|
562
|
+
});
|
|
563
|
+
return {
|
|
564
|
+
done: true,
|
|
565
|
+
lastResult,
|
|
566
|
+
anyProgress,
|
|
567
|
+
artifactsWritten,
|
|
568
|
+
pendingBatchAuditResults,
|
|
569
|
+
pendingAuditResultsPath,
|
|
570
|
+
pendingRuntimeUpdatesPath,
|
|
571
|
+
pendingExternalAnalyzerPath,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
done: false,
|
|
576
|
+
lastResult,
|
|
577
|
+
anyProgress,
|
|
578
|
+
artifactsWritten,
|
|
579
|
+
pendingBatchAuditResults,
|
|
580
|
+
pendingAuditResultsPath,
|
|
581
|
+
pendingRuntimeUpdatesPath,
|
|
582
|
+
pendingExternalAnalyzerPath,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
27
585
|
export async function cmdRunToCompletion(argv) {
|
|
28
586
|
const root = getRootDir(argv);
|
|
29
587
|
warnIfNotGitRepo(root);
|
|
@@ -73,34 +631,15 @@ export async function cmdRunToCompletion(argv) {
|
|
|
73
631
|
const decision = decideNextStep(bundle);
|
|
74
632
|
// Resume interrupted parallel wave: ingest any results that workers
|
|
75
633
|
// wrote before the previous process exited.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const stepResult = await runAuditStep({
|
|
86
|
-
root,
|
|
87
|
-
artifactsDir,
|
|
88
|
-
preferredExecutor: "result_ingestion_executor",
|
|
89
|
-
auditResultsPath: entry.audit_results_path,
|
|
90
|
-
});
|
|
91
|
-
if (stepResult.progress_made) {
|
|
92
|
-
recoveredProgress = true;
|
|
93
|
-
anyProgress = true;
|
|
94
|
-
for (const a of stepResult.artifacts_written)
|
|
95
|
-
artifactsWritten.add(a);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
process.stderr.write(`[audit-code] Skipping unreadable results for ${entry.run_id}.\n`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
await removeWaveManifest(artifactsDir);
|
|
103
|
-
if (recoveredProgress)
|
|
634
|
+
{
|
|
635
|
+
const recovery = await recoverInterruptedWave({
|
|
636
|
+
artifactsDir,
|
|
637
|
+
root,
|
|
638
|
+
anyProgress,
|
|
639
|
+
artifactsWritten,
|
|
640
|
+
});
|
|
641
|
+
anyProgress = recovery.anyProgress;
|
|
642
|
+
if (recovery.recovered)
|
|
104
643
|
continue;
|
|
105
644
|
}
|
|
106
645
|
if (decision.selected_executor === "agent" &&
|
|
@@ -306,52 +845,16 @@ export async function cmdRunToCompletion(argv) {
|
|
|
306
845
|
}
|
|
307
846
|
}
|
|
308
847
|
const taskGroups = candidateGroups.slice(0, waveSize);
|
|
309
|
-
const workerSlots =
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (const fp of t.file_paths)
|
|
320
|
-
slotReadPaths.add(fp);
|
|
321
|
-
}
|
|
322
|
-
const slotTask = {
|
|
323
|
-
contract_version: "audit-code-worker/v1alpha1",
|
|
324
|
-
run_id: slotRunId,
|
|
325
|
-
repo_root: root,
|
|
326
|
-
artifacts_dir: artifactsDir,
|
|
327
|
-
obligation_id: obligationId,
|
|
328
|
-
preferred_executor: "agent",
|
|
329
|
-
result_path: slotPaths.resultPath,
|
|
330
|
-
worker_command: [process.execPath, selfCliPath, "worker-run", "--task", slotPaths.taskPath],
|
|
331
|
-
audit_results_path: slotAuditResultsPath,
|
|
332
|
-
pending_audit_tasks_path: slotPendingTasksPath,
|
|
333
|
-
worker_command_mode: "deferred",
|
|
334
|
-
timeout_ms: timeoutMs,
|
|
335
|
-
max_retries: 0,
|
|
336
|
-
access: {
|
|
337
|
-
read_paths: [...slotReadPaths],
|
|
338
|
-
write_paths: [slotAuditResultsPath, slotPaths.resultPath],
|
|
339
|
-
},
|
|
340
|
-
};
|
|
341
|
-
const slotPrompt = renderWorkerPrompt(slotTask);
|
|
342
|
-
await writeWorkerTaskFiles(slotTask, slotPrompt, slotPaths, artifactsDir, group, { updateDispatch: false });
|
|
343
|
-
await writeJsonFile(slotPendingTasksPath, group);
|
|
344
|
-
workerSlots.push({ runId: slotRunId, paths: slotPaths, auditResultsPath: slotAuditResultsPath, pendingTasksPath: slotPendingTasksPath, group });
|
|
345
|
-
}
|
|
346
|
-
await writeDispatchBatchFiles(artifactsDir, workerSlots.map((slot) => ({
|
|
347
|
-
run_id: slot.runId,
|
|
348
|
-
task_path: slot.paths.taskPath,
|
|
349
|
-
prompt_path: slot.paths.promptPath,
|
|
350
|
-
result_path: slot.paths.resultPath,
|
|
351
|
-
status_path: slot.paths.statusPath,
|
|
352
|
-
audit_results_path: slot.auditResultsPath,
|
|
353
|
-
pending_audit_tasks_path: slot.pendingTasksPath,
|
|
354
|
-
})), workerSlots.flatMap((slot) => slot.group));
|
|
848
|
+
const { slots: workerSlots, runCountAfter } = await buildParallelWaveSlots({
|
|
849
|
+
root,
|
|
850
|
+
artifactsDir,
|
|
851
|
+
selfCliPath,
|
|
852
|
+
taskGroups,
|
|
853
|
+
obligationId,
|
|
854
|
+
runCountStart: runCount,
|
|
855
|
+
timeoutMs,
|
|
856
|
+
});
|
|
857
|
+
runCount = runCountAfter;
|
|
355
858
|
const parallelStartedAt = new Date().toISOString();
|
|
356
859
|
await writeWaveManifest(artifactsDir, {
|
|
357
860
|
obligation_id: obligationId ?? "unknown",
|
|
@@ -386,133 +889,28 @@ export async function cmdRunToCompletion(argv) {
|
|
|
386
889
|
}
|
|
387
890
|
}
|
|
388
891
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
const auditResults = await readJsonFile(slot.auditResultsPath);
|
|
413
|
-
const pendingTaskIds = new Set(slot.group.map((t) => t.task_id));
|
|
414
|
-
const matchedCount = auditResults.filter((r) => pendingTaskIds.has(r.task_id)).length;
|
|
415
|
-
if (slot.group.length > 0 && matchedCount === 0) {
|
|
416
|
-
throw new Error("Worker did not emit any audit results for the assigned tasks.");
|
|
417
|
-
}
|
|
418
|
-
const issues = validateAuditResults(auditResults, slot.group, {
|
|
419
|
-
lineIndex: await buildLineIndexForPaths(root, slot.group.flatMap((task) => task.file_paths)),
|
|
420
|
-
});
|
|
421
|
-
const errors = issues.filter((issue) => issue.severity === "error");
|
|
422
|
-
const warnings = issues.filter((issue) => issue.severity === "warning");
|
|
423
|
-
if (warnings.length > 0) {
|
|
424
|
-
process.stderr.write(`audit-results validation: ${warnings.length} warning(s) for ${slot.runId}:\n` +
|
|
425
|
-
formatAuditResultIssues(warnings) + "\n");
|
|
426
|
-
}
|
|
427
|
-
if (errors.length > 0) {
|
|
428
|
-
throw new Error(`audit-results validation failed with ${errors.length} error(s):\n` +
|
|
429
|
-
formatAuditResultIssues(errors));
|
|
430
|
-
}
|
|
431
|
-
const stepResult = await runAuditStep({
|
|
432
|
-
root,
|
|
433
|
-
artifactsDir,
|
|
434
|
-
preferredExecutor: "result_ingestion_executor",
|
|
435
|
-
auditResultsPath: slot.auditResultsPath,
|
|
436
|
-
});
|
|
437
|
-
workerResult = buildWorkerResult({
|
|
438
|
-
runId: slot.runId,
|
|
439
|
-
obligationId,
|
|
440
|
-
status: stepResult.progress_made ? "completed" : "no_progress",
|
|
441
|
-
progressMade: stepResult.progress_made,
|
|
442
|
-
selectedExecutor: stepResult.selected_executor,
|
|
443
|
-
artifactsWritten: stepResult.artifacts_written,
|
|
444
|
-
summary: stepResult.progress_summary,
|
|
445
|
-
nextLikelyStep: stepResult.next_likely_step,
|
|
446
|
-
errors: [],
|
|
447
|
-
});
|
|
448
|
-
batchProgress ||= stepResult.progress_made;
|
|
449
|
-
if (stepResult.progress_made)
|
|
450
|
-
anyProgress = true;
|
|
451
|
-
for (const a of stepResult.artifacts_written)
|
|
452
|
-
artifactsWritten.add(a);
|
|
453
|
-
}
|
|
454
|
-
catch (error) {
|
|
455
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
456
|
-
batchErrors.push(`${slot.runId}: ${message}`);
|
|
457
|
-
workerResult = buildWorkerResult({
|
|
458
|
-
runId: slot.runId,
|
|
459
|
-
obligationId,
|
|
460
|
-
status: "failed",
|
|
461
|
-
progressMade: false,
|
|
462
|
-
selectedExecutor: "agent",
|
|
463
|
-
artifactsWritten: [],
|
|
464
|
-
summary: `Worker failed for executor agent: ${message}`,
|
|
465
|
-
nextLikelyStep: obligationId,
|
|
466
|
-
errors: [message],
|
|
467
|
-
});
|
|
468
|
-
process.stderr.write(`[agent-batch] ${slot.runId} failed: ${message}\n`);
|
|
469
|
-
}
|
|
470
|
-
await persistWorkerRunArtifacts(slot.paths, workerResult, "parallel-deferred-agent");
|
|
471
|
-
await appendRunLedgerEntry(artifactsDir, {
|
|
472
|
-
run_id: slot.runId,
|
|
473
|
-
provider: provider.name,
|
|
474
|
-
obligation_id: obligationId,
|
|
475
|
-
selected_executor: workerResult.selected_executor,
|
|
476
|
-
status: workerResult.status,
|
|
477
|
-
started_at: parallelStartedAt,
|
|
478
|
-
ended_at: parallelEndedAt,
|
|
479
|
-
result_path: slot.paths.resultPath,
|
|
480
|
-
});
|
|
481
|
-
artifactsWritten.add("run-ledger.json");
|
|
482
|
-
}
|
|
483
|
-
// Record outcome for adaptive learning (best-effort — never blocks dispatch)
|
|
484
|
-
{
|
|
485
|
-
const rateLimitResults = batchErrors.map((e) => detectRateLimitError(e));
|
|
486
|
-
const rateLimitHit = rateLimitResults.find((r) => r.isRateLimited);
|
|
487
|
-
const retryAfterMs = rateLimitHit?.retryAfterMs ?? null;
|
|
488
|
-
await recordWaveOutcome(providerModelKey, {
|
|
489
|
-
concurrency: workerSlots.length,
|
|
490
|
-
estimated_tokens: slotTokenEstimates.slice(0, workerSlots.length).reduce((a, b) => a + b, 0),
|
|
491
|
-
outcome: rateLimitHit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
|
|
492
|
-
cooldown_until: rateLimitHit ? computeCooldownUntil(retryAfterMs) : null,
|
|
493
|
-
}, sessionConfig.quota?.empirical_half_life_hours ?? 24).catch(() => undefined);
|
|
494
|
-
}
|
|
495
|
-
// Extract rate-limit headers from worker stderr (best-effort)
|
|
496
|
-
{
|
|
497
|
-
const extractor = getHeaderExtractorForProvider(provider.name);
|
|
498
|
-
for (const slot of workerSlots) {
|
|
499
|
-
try {
|
|
500
|
-
const stderr = await readFile(slot.paths.stderrPath, "utf8");
|
|
501
|
-
const extracted = extractor.extract(stderr);
|
|
502
|
-
if (extracted && (extracted.requests_per_minute != null || extracted.input_tokens_per_minute != null)) {
|
|
503
|
-
await updateDiscoveredLimits(providerModelKey, {
|
|
504
|
-
requests_per_minute: extracted.requests_per_minute,
|
|
505
|
-
input_tokens_per_minute: extracted.input_tokens_per_minute,
|
|
506
|
-
source: "header_extraction",
|
|
507
|
-
});
|
|
508
|
-
break; // one successful extraction is enough
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
catch {
|
|
512
|
-
// stderr file missing or unreadable — skip
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
}
|
|
892
|
+
const ingestion = await ingestParallelWaveResults({
|
|
893
|
+
root,
|
|
894
|
+
artifactsDir,
|
|
895
|
+
workerSlots,
|
|
896
|
+
launchErrorsByRunId,
|
|
897
|
+
obligationId,
|
|
898
|
+
parallelStartedAt,
|
|
899
|
+
providerName: provider.name,
|
|
900
|
+
anyProgress,
|
|
901
|
+
artifactsWritten,
|
|
902
|
+
});
|
|
903
|
+
const batchProgress = ingestion.batchProgress;
|
|
904
|
+
const batchErrors = ingestion.batchErrors;
|
|
905
|
+
anyProgress = ingestion.anyProgress;
|
|
906
|
+
await recordWaveQuota({
|
|
907
|
+
providerModelKey,
|
|
908
|
+
providerName: provider.name,
|
|
909
|
+
workerSlots,
|
|
910
|
+
slotTokenEstimates,
|
|
911
|
+
batchErrors,
|
|
912
|
+
halfLifeHours: sessionConfig.quota?.empirical_half_life_hours ?? 24,
|
|
913
|
+
});
|
|
516
914
|
await removeWaveManifest(artifactsDir);
|
|
517
915
|
if (batchErrors.length > 0) {
|
|
518
916
|
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
@@ -567,291 +965,68 @@ export async function cmdRunToCompletion(argv) {
|
|
|
567
965
|
const runId = buildRunId(obligationId, runCount);
|
|
568
966
|
const paths = getRunPaths(artifactsDir, runId);
|
|
569
967
|
if (shouldRunInlineExecutor(preferredExecutor)) {
|
|
570
|
-
await
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
try {
|
|
574
|
-
const result = await runAuditStep({
|
|
575
|
-
root,
|
|
576
|
-
artifactsDir,
|
|
577
|
-
preferredExecutor,
|
|
578
|
-
auditResultsPath,
|
|
579
|
-
runtimeUpdatesPath,
|
|
580
|
-
externalAnalyzerPath,
|
|
581
|
-
analyzers: sessionConfig.analyzers,
|
|
582
|
-
since: getFlag(argv, "--since"),
|
|
583
|
-
});
|
|
584
|
-
workerResult = {
|
|
585
|
-
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
586
|
-
run_id: runId,
|
|
587
|
-
obligation_id: obligationId,
|
|
588
|
-
status: result.progress_made ? "completed" : "no_progress",
|
|
589
|
-
progress_made: result.progress_made,
|
|
590
|
-
selected_executor: result.selected_executor,
|
|
591
|
-
artifacts_written: result.artifacts_written,
|
|
592
|
-
summary: result.progress_summary,
|
|
593
|
-
next_likely_step: result.next_likely_step,
|
|
594
|
-
errors: [],
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
catch (error) {
|
|
598
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
599
|
-
workerResult = {
|
|
600
|
-
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
601
|
-
run_id: runId,
|
|
602
|
-
obligation_id: obligationId,
|
|
603
|
-
status: "failed",
|
|
604
|
-
progress_made: false,
|
|
605
|
-
selected_executor: preferredExecutor,
|
|
606
|
-
artifacts_written: [],
|
|
607
|
-
summary: `Inline executor failed for ${preferredExecutor}: ${message}`,
|
|
608
|
-
next_likely_step: decision.selected_obligation,
|
|
609
|
-
errors: [message],
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
await persistWorkerRunArtifacts(paths, workerResult, "inline");
|
|
613
|
-
await appendRunLedgerEntry(artifactsDir, {
|
|
614
|
-
run_id: runId,
|
|
615
|
-
provider: provider.name,
|
|
616
|
-
obligation_id: obligationId,
|
|
617
|
-
selected_executor: workerResult.selected_executor,
|
|
618
|
-
status: workerResult.status,
|
|
619
|
-
started_at: startedAt,
|
|
620
|
-
ended_at: new Date().toISOString(),
|
|
621
|
-
result_path: paths.resultPath,
|
|
622
|
-
});
|
|
623
|
-
lastResult = workerResult;
|
|
624
|
-
if (workerResult.progress_made) {
|
|
625
|
-
anyProgress = true;
|
|
626
|
-
}
|
|
627
|
-
for (const artifact of workerResult.artifacts_written) {
|
|
628
|
-
artifactsWritten.add(artifact);
|
|
629
|
-
}
|
|
630
|
-
artifactsWritten.add("run-ledger.json");
|
|
631
|
-
if (externalAnalyzerPath)
|
|
632
|
-
pendingExternalAnalyzerPath = undefined;
|
|
633
|
-
if (auditResultsPath &&
|
|
634
|
-
pendingBatchAuditResults[0] === auditResultsPath &&
|
|
635
|
-
preferredExecutor === "result_ingestion_executor" &&
|
|
636
|
-
workerResult.status !== "failed" &&
|
|
637
|
-
workerResult.status !== "blocked") {
|
|
638
|
-
pendingBatchAuditResults.shift();
|
|
639
|
-
}
|
|
640
|
-
if (auditResultsPath)
|
|
641
|
-
pendingAuditResultsPath = undefined;
|
|
642
|
-
if (runtimeUpdatesPath)
|
|
643
|
-
pendingRuntimeUpdatesPath = undefined;
|
|
644
|
-
if (workerResult.status === "failed" ||
|
|
645
|
-
workerResult.status === "blocked" ||
|
|
646
|
-
workerResult.status === "no_progress") {
|
|
647
|
-
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
648
|
-
const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
|
|
649
|
-
const state = shouldBlock
|
|
650
|
-
? buildBlockedAuditState({
|
|
651
|
-
state: bundleAfter.audit_state ?? deriveAuditState(bundleAfter),
|
|
652
|
-
obligationId: workerResult.obligation_id,
|
|
653
|
-
executor: workerResult.selected_executor,
|
|
654
|
-
blocker: buildWorkerFailureBlocker(workerResult),
|
|
655
|
-
})
|
|
656
|
-
: bundleAfter.audit_state ?? deriveAuditState(bundleAfter);
|
|
657
|
-
if (shouldBlock) {
|
|
658
|
-
await writeCoreArtifacts(artifactsDir, {
|
|
659
|
-
...bundleAfter,
|
|
660
|
-
audit_state: state,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
await emitEnvelope({
|
|
664
|
-
root,
|
|
665
|
-
artifactsDir,
|
|
666
|
-
bundle: shouldBlock
|
|
667
|
-
? { ...bundleAfter, audit_state: state }
|
|
668
|
-
: bundleAfter,
|
|
669
|
-
audit_state: state,
|
|
670
|
-
selected_obligation: workerResult.obligation_id,
|
|
671
|
-
selected_executor: workerResult.selected_executor,
|
|
672
|
-
progress_made: anyProgress,
|
|
673
|
-
artifacts_written: Array.from(shouldBlock
|
|
674
|
-
? new Set([...artifactsWritten, "audit_state.json"])
|
|
675
|
-
: artifactsWritten),
|
|
676
|
-
progress_summary: buildWorkerFailureBlocker(workerResult),
|
|
677
|
-
next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
|
|
678
|
-
providerName: provider.name,
|
|
679
|
-
});
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
const pendingAuditTasks = preferredExecutor === "agent"
|
|
685
|
-
? await addFileLineCountHints(root, buildPendingAuditTasks(bundle))
|
|
686
|
-
: undefined;
|
|
687
|
-
const pendingAuditTasksPath = preferredExecutor === "agent"
|
|
688
|
-
? join(paths.runDir, "pending-audit-tasks.json")
|
|
689
|
-
: undefined;
|
|
690
|
-
const providerAuditResultsPath = preferredExecutor === "agent"
|
|
691
|
-
? join(paths.runDir, "audit-results.json")
|
|
692
|
-
: auditResultsPath;
|
|
693
|
-
const providerReadPaths = new Set();
|
|
694
|
-
if (pendingAuditTasks) {
|
|
695
|
-
for (const pt of pendingAuditTasks) {
|
|
696
|
-
for (const fp of pt.file_paths)
|
|
697
|
-
providerReadPaths.add(fp);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const task = {
|
|
701
|
-
contract_version: "audit-code-worker/v1alpha1",
|
|
702
|
-
run_id: runId,
|
|
703
|
-
repo_root: root,
|
|
704
|
-
artifacts_dir: artifactsDir,
|
|
705
|
-
obligation_id: obligationId,
|
|
706
|
-
preferred_executor: preferredExecutor,
|
|
707
|
-
result_path: paths.resultPath,
|
|
708
|
-
worker_command: [
|
|
709
|
-
process.execPath,
|
|
710
|
-
selfCliPath,
|
|
711
|
-
"worker-run",
|
|
712
|
-
"--task",
|
|
713
|
-
paths.taskPath,
|
|
714
|
-
],
|
|
715
|
-
audit_results_path: providerAuditResultsPath,
|
|
716
|
-
pending_audit_tasks_path: pendingAuditTasksPath,
|
|
717
|
-
runtime_updates_path: runtimeUpdatesPath,
|
|
718
|
-
external_analyzer_results_path: externalAnalyzerPath,
|
|
719
|
-
timeout_ms: timeoutMs,
|
|
720
|
-
max_retries: 0,
|
|
721
|
-
access: providerReadPaths.size > 0 ? {
|
|
722
|
-
read_paths: [...providerReadPaths],
|
|
723
|
-
write_paths: [providerAuditResultsPath ?? paths.resultPath, paths.resultPath],
|
|
724
|
-
} : undefined,
|
|
725
|
-
};
|
|
726
|
-
const prompt = renderWorkerPrompt(task);
|
|
727
|
-
await writeWorkerTaskFiles(task, prompt, paths, artifactsDir, pendingAuditTasks);
|
|
728
|
-
if (pendingAuditTasksPath && pendingAuditTasks) {
|
|
729
|
-
await writeJsonFile(pendingAuditTasksPath, pendingAuditTasks);
|
|
730
|
-
}
|
|
731
|
-
const startedAt = new Date().toISOString();
|
|
732
|
-
let workerResult;
|
|
733
|
-
let launchResult = null;
|
|
734
|
-
try {
|
|
735
|
-
launchResult = await provider.launch({
|
|
736
|
-
repoRoot: root,
|
|
968
|
+
const inline = await runInlineStep({
|
|
969
|
+
root,
|
|
970
|
+
artifactsDir,
|
|
737
971
|
runId,
|
|
972
|
+
paths,
|
|
973
|
+
preferredExecutor,
|
|
738
974
|
obligationId,
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
975
|
+
auditResultsPath,
|
|
976
|
+
runtimeUpdatesPath,
|
|
977
|
+
externalAnalyzerPath,
|
|
978
|
+
analyzers: sessionConfig.analyzers,
|
|
979
|
+
since: getFlag(argv, "--since"),
|
|
980
|
+
anyProgress,
|
|
981
|
+
artifactsWritten,
|
|
982
|
+
providerName: provider.name,
|
|
983
|
+
decision,
|
|
984
|
+
pendingBatchAuditResults,
|
|
985
|
+
pendingAuditResultsPath,
|
|
986
|
+
pendingRuntimeUpdatesPath,
|
|
987
|
+
pendingExternalAnalyzerPath,
|
|
746
988
|
});
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
obligation_id: obligationId,
|
|
757
|
-
status: "failed",
|
|
758
|
-
progress_made: false,
|
|
759
|
-
selected_executor: preferredExecutor,
|
|
760
|
-
artifacts_written: [],
|
|
761
|
-
summary: launchExitSummary
|
|
762
|
-
? `Worker did not emit a valid worker result after provider exit: ${launchExitSummary}`
|
|
763
|
-
: "Worker did not emit a valid worker result.",
|
|
764
|
-
next_likely_step: decision.selected_obligation,
|
|
765
|
-
errors: ["Invalid worker result contract."],
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
catch (error) {
|
|
770
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
771
|
-
const launchExitSummary = launchResult && summarizeLaunchExit(launchResult);
|
|
772
|
-
workerResult = {
|
|
773
|
-
contract_version: WORKER_RESULT_CONTRACT_VERSION,
|
|
774
|
-
run_id: runId,
|
|
775
|
-
obligation_id: obligationId,
|
|
776
|
-
status: "failed",
|
|
777
|
-
progress_made: false,
|
|
778
|
-
selected_executor: preferredExecutor,
|
|
779
|
-
artifacts_written: [],
|
|
780
|
-
summary: `Worker launch failed for ${preferredExecutor}: ${launchExitSummary ?? message}`,
|
|
781
|
-
next_likely_step: decision.selected_obligation,
|
|
782
|
-
errors: launchExitSummary ? [message, launchExitSummary] : [message],
|
|
783
|
-
};
|
|
784
|
-
await persistWorkerRunArtifacts(paths, workerResult, "provider-launch");
|
|
989
|
+
lastResult = inline.lastResult;
|
|
990
|
+
anyProgress = inline.anyProgress;
|
|
991
|
+
pendingBatchAuditResults = inline.pendingBatchAuditResults;
|
|
992
|
+
pendingAuditResultsPath = inline.pendingAuditResultsPath;
|
|
993
|
+
pendingRuntimeUpdatesPath = inline.pendingRuntimeUpdatesPath;
|
|
994
|
+
pendingExternalAnalyzerPath = inline.pendingExternalAnalyzerPath;
|
|
995
|
+
if (inline.done)
|
|
996
|
+
return;
|
|
997
|
+
continue;
|
|
785
998
|
}
|
|
786
|
-
await
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
999
|
+
const single = await runSingleWorkerStep({
|
|
1000
|
+
root,
|
|
1001
|
+
artifactsDir,
|
|
1002
|
+
selfCliPath,
|
|
1003
|
+
runId,
|
|
1004
|
+
paths,
|
|
1005
|
+
preferredExecutor,
|
|
1006
|
+
obligationId,
|
|
1007
|
+
auditResultsPath,
|
|
1008
|
+
runtimeUpdatesPath,
|
|
1009
|
+
externalAnalyzerPath,
|
|
1010
|
+
bundle,
|
|
1011
|
+
timeoutMs,
|
|
1012
|
+
uiMode,
|
|
1013
|
+
provider,
|
|
1014
|
+
anyProgress,
|
|
1015
|
+
artifactsWritten,
|
|
1016
|
+
decision,
|
|
1017
|
+
pendingBatchAuditResults,
|
|
1018
|
+
pendingAuditResultsPath,
|
|
1019
|
+
pendingRuntimeUpdatesPath,
|
|
1020
|
+
pendingExternalAnalyzerPath,
|
|
795
1021
|
});
|
|
796
|
-
lastResult =
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
artifactsWritten.add("run-ledger.json");
|
|
804
|
-
if (externalAnalyzerPath)
|
|
805
|
-
pendingExternalAnalyzerPath = undefined;
|
|
806
|
-
if (auditResultsPath &&
|
|
807
|
-
pendingBatchAuditResults[0] === auditResultsPath &&
|
|
808
|
-
preferredExecutor === "result_ingestion_executor" &&
|
|
809
|
-
workerResult.status !== "failed" &&
|
|
810
|
-
workerResult.status !== "blocked") {
|
|
811
|
-
pendingBatchAuditResults.shift();
|
|
812
|
-
}
|
|
813
|
-
if (providerAuditResultsPath)
|
|
814
|
-
pendingAuditResultsPath = undefined;
|
|
815
|
-
if (runtimeUpdatesPath)
|
|
816
|
-
pendingRuntimeUpdatesPath = undefined;
|
|
817
|
-
if (workerResult.status === "failed" ||
|
|
818
|
-
workerResult.status === "blocked" ||
|
|
819
|
-
workerResult.status === "no_progress") {
|
|
820
|
-
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
821
|
-
const shouldBlock = workerResult.status === "failed" || workerResult.status === "blocked";
|
|
822
|
-
const state = shouldBlock
|
|
823
|
-
? buildBlockedAuditState({
|
|
824
|
-
state: deriveAuditState(bundleAfter),
|
|
825
|
-
obligationId: workerResult.obligation_id,
|
|
826
|
-
executor: workerResult.selected_executor,
|
|
827
|
-
blocker: buildWorkerFailureBlocker(workerResult),
|
|
828
|
-
})
|
|
829
|
-
: deriveAuditState(bundleAfter);
|
|
830
|
-
if (shouldBlock) {
|
|
831
|
-
await writeCoreArtifacts(artifactsDir, {
|
|
832
|
-
...bundleAfter,
|
|
833
|
-
audit_state: state,
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
await emitEnvelope({
|
|
837
|
-
root,
|
|
838
|
-
artifactsDir,
|
|
839
|
-
bundle: shouldBlock
|
|
840
|
-
? { ...bundleAfter, audit_state: state }
|
|
841
|
-
: bundleAfter,
|
|
842
|
-
audit_state: state,
|
|
843
|
-
selected_obligation: workerResult.obligation_id,
|
|
844
|
-
selected_executor: workerResult.selected_executor,
|
|
845
|
-
progress_made: anyProgress,
|
|
846
|
-
artifacts_written: Array.from(shouldBlock
|
|
847
|
-
? new Set([...artifactsWritten, "audit_state.json"])
|
|
848
|
-
: artifactsWritten),
|
|
849
|
-
progress_summary: buildWorkerFailureBlocker(workerResult),
|
|
850
|
-
next_likely_step: shouldBlock ? null : workerResult.next_likely_step,
|
|
851
|
-
providerName: provider.name,
|
|
852
|
-
});
|
|
1022
|
+
lastResult = single.lastResult;
|
|
1023
|
+
anyProgress = single.anyProgress;
|
|
1024
|
+
pendingBatchAuditResults = single.pendingBatchAuditResults;
|
|
1025
|
+
pendingAuditResultsPath = single.pendingAuditResultsPath;
|
|
1026
|
+
pendingRuntimeUpdatesPath = single.pendingRuntimeUpdatesPath;
|
|
1027
|
+
pendingExternalAnalyzerPath = single.pendingExternalAnalyzerPath;
|
|
1028
|
+
if (single.done)
|
|
853
1029
|
return;
|
|
854
|
-
}
|
|
855
1030
|
}
|
|
856
1031
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
857
1032
|
const decision = decideNextStep(bundle);
|