auditor-lambda 0.3.20 → 0.3.22
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/README.md +12 -2
- package/audit-code-wrapper-lib.mjs +91 -32
- package/dist/cli.js +738 -11
- package/dist/orchestrator/reviewPackets.d.ts +5 -0
- package/dist/orchestrator/reviewPackets.js +5 -1
- package/dist/prompts/renderWorkerPrompt.js +1 -0
- package/dist/quota/index.d.ts +8 -0
- package/dist/quota/index.js +4 -0
- package/dist/quota/limits.d.ts +16 -0
- package/dist/quota/limits.js +77 -0
- package/dist/quota/probe.d.ts +13 -0
- package/dist/quota/probe.js +21 -0
- package/dist/quota/scheduler.d.ts +14 -0
- package/dist/quota/scheduler.js +76 -0
- package/dist/quota/state.d.ts +12 -0
- package/dist/quota/state.js +101 -0
- package/dist/quota/types.d.ts +50 -0
- package/dist/quota/types.js +1 -0
- package/dist/supervisor/operatorHandoff.js +3 -7
- package/dist/types/sessionConfig.d.ts +28 -0
- package/docs/contracts.md +23 -1
- package/docs/operator-guide.md +11 -4
- package/docs/product.md +4 -3
- package/package.json +1 -1
- package/schemas/dispatch_quota.schema.json +77 -0
- package/scripts/postinstall.mjs +33 -0
- package/skills/audit-code/audit-code.prompt.md +15 -170
package/dist/cli.js
CHANGED
|
@@ -32,9 +32,11 @@ import { buildReviewPackets, orderTasksForPacketReview, } from "./orchestrator/r
|
|
|
32
32
|
import { buildFileAnchorSummary, } from "./orchestrator/fileAnchors.js";
|
|
33
33
|
import { LOCAL_SUBPROCESS_PROVIDER_NAME } from "./providers/constants.js";
|
|
34
34
|
import { runAuditCodeMcpServer } from "./mcp/server.js";
|
|
35
|
+
import { scheduleWave, buildProviderModelKey, readQuotaState, recordWaveOutcome, resolveLimits, probeProvider, computeMaxSafeConcurrency, getQuotaStatePath, } from "./quota/index.js";
|
|
35
36
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
36
37
|
const ADVANCE_AUDIT_CONTRACT_VERSION = "audit-code/v1alpha1";
|
|
37
38
|
const WORKER_RESULT_CONTRACT_VERSION = "audit-code-worker-result/v1alpha1";
|
|
39
|
+
const STEP_CONTRACT_VERSION = "audit-code-step/v1alpha1";
|
|
38
40
|
const LARGE_FILE_PACKET_TARGET_LINES = 2500;
|
|
39
41
|
const SMALL_MODEL_HINT_MAX_LINES = 500;
|
|
40
42
|
const SMALL_MODEL_HINT_MAX_ESTIMATED_TOKENS = 3000;
|
|
@@ -72,6 +74,19 @@ function getFlag(argv, name, fallback) {
|
|
|
72
74
|
function hasFlag(argv, name) {
|
|
73
75
|
return argv.includes(name);
|
|
74
76
|
}
|
|
77
|
+
function getOptionalBooleanFlag(argv, name) {
|
|
78
|
+
const raw = getFlag(argv, name);
|
|
79
|
+
if (raw === undefined) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (raw === "true") {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (raw === "false") {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`${name} must be either true or false.`);
|
|
89
|
+
}
|
|
75
90
|
function toBase64Url(value) {
|
|
76
91
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
77
92
|
}
|
|
@@ -91,6 +106,12 @@ function safeArtifactStem(value) {
|
|
|
91
106
|
function artifactNameForId(value, extension) {
|
|
92
107
|
return `${safeArtifactStem(value)}_${digestId(value)}.${extension}`;
|
|
93
108
|
}
|
|
109
|
+
function quoteCommandArg(value) {
|
|
110
|
+
return /[\s"]/u.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
111
|
+
}
|
|
112
|
+
function renderCommand(argv) {
|
|
113
|
+
return argv.map((item) => quoteCommandArg(item)).join(" ");
|
|
114
|
+
}
|
|
94
115
|
function taskResultPath(taskResultsDir, taskId) {
|
|
95
116
|
return join(taskResultsDir, artifactNameForId(taskId, "json"));
|
|
96
117
|
}
|
|
@@ -158,6 +179,27 @@ function getTimeoutMs(argv, sessionConfig) {
|
|
|
158
179
|
function getExplicitProvider(argv) {
|
|
159
180
|
return getFlag(argv, "--provider");
|
|
160
181
|
}
|
|
182
|
+
function getHostModel(argv) {
|
|
183
|
+
return getFlag(argv, "--host-model") ?? null;
|
|
184
|
+
}
|
|
185
|
+
function getQuotaProbeMode(argv, sessionConfig) {
|
|
186
|
+
const raw = getFlag(argv, "--quota-probe") ?? sessionConfig.quota?.probe ?? "auto";
|
|
187
|
+
if (raw === "auto" || raw === "never" || raw === "force")
|
|
188
|
+
return raw;
|
|
189
|
+
return "auto";
|
|
190
|
+
}
|
|
191
|
+
function detectRateLimitError(errorText) {
|
|
192
|
+
const lower = errorText.toLowerCase();
|
|
193
|
+
return lower.includes("429") || lower.includes("rate limit") || lower.includes("rate_limit");
|
|
194
|
+
}
|
|
195
|
+
function defaultCooldownUntil(resetAtHeader) {
|
|
196
|
+
if (resetAtHeader) {
|
|
197
|
+
const t = new Date(resetAtHeader).getTime();
|
|
198
|
+
if (!Number.isNaN(t))
|
|
199
|
+
return new Date(t).toISOString();
|
|
200
|
+
}
|
|
201
|
+
return new Date(Date.now() + 60_000).toISOString();
|
|
202
|
+
}
|
|
161
203
|
function resolveRunProviderName(argv, sessionConfig) {
|
|
162
204
|
return resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig);
|
|
163
205
|
}
|
|
@@ -324,6 +366,309 @@ async function addFileLineCountHints(root, tasks) {
|
|
|
324
366
|
file_line_counts: Object.fromEntries(task.file_paths.map((path) => [path, lineIndex[path] ?? 0])),
|
|
325
367
|
}));
|
|
326
368
|
}
|
|
369
|
+
function activeReviewRunFromTask(artifactsDir, task) {
|
|
370
|
+
if (task.preferred_executor !== "agent" || !task.audit_results_path) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const paths = getRunPaths(artifactsDir, task.run_id);
|
|
374
|
+
return {
|
|
375
|
+
run_id: task.run_id,
|
|
376
|
+
task_path: paths.taskPath,
|
|
377
|
+
prompt_path: paths.promptPath,
|
|
378
|
+
pending_audit_tasks_path: task.pending_audit_tasks_path,
|
|
379
|
+
audit_results_path: task.audit_results_path,
|
|
380
|
+
worker_command: task.worker_command,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
async function loadCurrentActiveReviewRun(artifactsDir) {
|
|
384
|
+
try {
|
|
385
|
+
const task = await readJsonFile(join(artifactsDir, "dispatch", "current-task.json"));
|
|
386
|
+
return activeReviewRunFromTask(artifactsDir, task);
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
if (isFileMissingError(error)) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function writeHandoffOnly(params) {
|
|
396
|
+
const handoff = buildAuditCodeHandoff({
|
|
397
|
+
root: params.root,
|
|
398
|
+
artifactsDir: params.artifactsDir,
|
|
399
|
+
state: params.audit_state,
|
|
400
|
+
bundle: params.bundle,
|
|
401
|
+
providerName: params.providerName,
|
|
402
|
+
progressSummary: params.progress_summary,
|
|
403
|
+
isConfigError: params.isConfigError,
|
|
404
|
+
activeReviewRun: params.activeReviewRun,
|
|
405
|
+
});
|
|
406
|
+
await writeAuditCodeHandoffArtifacts(handoff);
|
|
407
|
+
}
|
|
408
|
+
async function ensureSemanticReviewRun(params) {
|
|
409
|
+
const existingRun = await loadCurrentActiveReviewRun(params.artifactsDir);
|
|
410
|
+
if (existingRun) {
|
|
411
|
+
const blockedState = params.bundle.audit_state?.status === "blocked"
|
|
412
|
+
? params.bundle.audit_state
|
|
413
|
+
: buildBlockedAuditState({
|
|
414
|
+
state: params.state,
|
|
415
|
+
obligationId: params.obligationId,
|
|
416
|
+
executor: "agent",
|
|
417
|
+
blocker: buildManualReviewBlocker(LOCAL_SUBPROCESS_PROVIDER_NAME),
|
|
418
|
+
});
|
|
419
|
+
const blockedBundle = { ...params.bundle, audit_state: blockedState };
|
|
420
|
+
await writeCoreArtifacts(params.artifactsDir, blockedBundle);
|
|
421
|
+
await writeHandoffOnly({
|
|
422
|
+
root: params.root,
|
|
423
|
+
artifactsDir: params.artifactsDir,
|
|
424
|
+
bundle: blockedBundle,
|
|
425
|
+
audit_state: blockedState,
|
|
426
|
+
progress_summary: buildManualReviewBlocker(LOCAL_SUBPROCESS_PROVIDER_NAME),
|
|
427
|
+
providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
|
|
428
|
+
activeReviewRun: existingRun,
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
state: blockedState,
|
|
432
|
+
bundle: blockedBundle,
|
|
433
|
+
activeReviewRun: existingRun,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const blockedState = buildBlockedAuditState({
|
|
437
|
+
state: params.state,
|
|
438
|
+
obligationId: params.obligationId,
|
|
439
|
+
executor: "agent",
|
|
440
|
+
blocker: buildManualReviewBlocker(LOCAL_SUBPROCESS_PROVIDER_NAME),
|
|
441
|
+
});
|
|
442
|
+
await writeCoreArtifacts(params.artifactsDir, {
|
|
443
|
+
...params.bundle,
|
|
444
|
+
audit_state: blockedState,
|
|
445
|
+
});
|
|
446
|
+
const runId = buildRunId(params.obligationId, 1);
|
|
447
|
+
const paths = getRunPaths(params.artifactsDir, runId);
|
|
448
|
+
const pendingTasks = await addFileLineCountHints(params.root, buildPendingAuditTasks(params.bundle));
|
|
449
|
+
const pendingTasksPath = join(paths.runDir, "pending-audit-tasks.json");
|
|
450
|
+
const auditResultsPath = join(paths.runDir, "audit-results.json");
|
|
451
|
+
const task = {
|
|
452
|
+
contract_version: "audit-code-worker/v1alpha1",
|
|
453
|
+
run_id: runId,
|
|
454
|
+
repo_root: params.root,
|
|
455
|
+
artifacts_dir: params.artifactsDir,
|
|
456
|
+
obligation_id: params.obligationId,
|
|
457
|
+
preferred_executor: "agent",
|
|
458
|
+
result_path: paths.resultPath,
|
|
459
|
+
worker_command: [
|
|
460
|
+
process.execPath,
|
|
461
|
+
params.selfCliPath,
|
|
462
|
+
"worker-run",
|
|
463
|
+
"--task",
|
|
464
|
+
paths.taskPath,
|
|
465
|
+
],
|
|
466
|
+
audit_results_path: auditResultsPath,
|
|
467
|
+
pending_audit_tasks_path: pendingTasksPath,
|
|
468
|
+
timeout_ms: params.timeoutMs,
|
|
469
|
+
max_retries: 0,
|
|
470
|
+
};
|
|
471
|
+
const prompt = renderWorkerPrompt(task);
|
|
472
|
+
await writeWorkerTaskFiles(task, prompt, paths, params.artifactsDir, pendingTasks);
|
|
473
|
+
await writeJsonFile(pendingTasksPath, pendingTasks);
|
|
474
|
+
const activeReviewRun = activeReviewRunFromTask(params.artifactsDir, task);
|
|
475
|
+
if (!activeReviewRun) {
|
|
476
|
+
throw new Error("Internal error: failed to materialize active review run.");
|
|
477
|
+
}
|
|
478
|
+
const blockedBundle = {
|
|
479
|
+
...params.bundle,
|
|
480
|
+
audit_state: blockedState,
|
|
481
|
+
};
|
|
482
|
+
await writeHandoffOnly({
|
|
483
|
+
root: params.root,
|
|
484
|
+
artifactsDir: params.artifactsDir,
|
|
485
|
+
bundle: blockedBundle,
|
|
486
|
+
audit_state: blockedState,
|
|
487
|
+
progress_summary: buildManualReviewBlocker(LOCAL_SUBPROCESS_PROVIDER_NAME),
|
|
488
|
+
providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
|
|
489
|
+
activeReviewRun,
|
|
490
|
+
});
|
|
491
|
+
return { state: blockedState, bundle: blockedBundle, activeReviewRun };
|
|
492
|
+
}
|
|
493
|
+
function nextStepCommand(root, artifactsDir, extraArgs = []) {
|
|
494
|
+
return renderCommand([
|
|
495
|
+
"audit-code",
|
|
496
|
+
"next-step",
|
|
497
|
+
"--root",
|
|
498
|
+
root,
|
|
499
|
+
"--artifacts-dir",
|
|
500
|
+
artifactsDir,
|
|
501
|
+
...extraArgs,
|
|
502
|
+
]);
|
|
503
|
+
}
|
|
504
|
+
function mergeAndIngestCommand(artifactsDir, runId) {
|
|
505
|
+
return renderCommand([
|
|
506
|
+
"audit-code",
|
|
507
|
+
"merge-and-ingest",
|
|
508
|
+
"--run-id",
|
|
509
|
+
runId,
|
|
510
|
+
"--artifacts-dir",
|
|
511
|
+
artifactsDir,
|
|
512
|
+
]);
|
|
513
|
+
}
|
|
514
|
+
function renderCapabilityCheckPrompt(params) {
|
|
515
|
+
const yesCommand = nextStepCommand(params.root, params.artifactsDir, [
|
|
516
|
+
"--host-can-dispatch-subagents",
|
|
517
|
+
"true",
|
|
518
|
+
]);
|
|
519
|
+
const noCommand = nextStepCommand(params.root, params.artifactsDir, [
|
|
520
|
+
"--host-can-dispatch-subagents",
|
|
521
|
+
"false",
|
|
522
|
+
]);
|
|
523
|
+
return [
|
|
524
|
+
"# audit-code capability check",
|
|
525
|
+
"",
|
|
526
|
+
"Decide one thing from the active toolset: does this host expose a callable subagent/delegation tool for source-code review, such as Agent, Task, or an equivalent built-in subagent call?",
|
|
527
|
+
"",
|
|
528
|
+
"Do not run shell commands to answer this. Do not inspect packet prompts, schemas, or backend command catalogs.",
|
|
529
|
+
"",
|
|
530
|
+
"If callable subagents are available, run:",
|
|
531
|
+
"",
|
|
532
|
+
` ${yesCommand}`,
|
|
533
|
+
"",
|
|
534
|
+
"If callable subagents are not available, run:",
|
|
535
|
+
"",
|
|
536
|
+
` ${noCommand}`,
|
|
537
|
+
"",
|
|
538
|
+
"If the host can also restrict tools per subagent or select models per subagent, add the matching `--host-can-restrict-subagent-tools true` or `--host-can-select-subagent-model true` flags to the same command. Omit those flags when unsure.",
|
|
539
|
+
"",
|
|
540
|
+
"After the command writes the next step, read and follow only its `prompt_path`.",
|
|
541
|
+
"",
|
|
542
|
+
].join("\n");
|
|
543
|
+
}
|
|
544
|
+
function renderDispatchReviewPrompt(params) {
|
|
545
|
+
const mergeCommand = mergeAndIngestCommand(params.artifactsDir, params.activeReviewRun.run_id);
|
|
546
|
+
const continueCommand = nextStepCommand(params.root, params.artifactsDir);
|
|
547
|
+
const modelLine = params.hostCanSelectSubagentModel
|
|
548
|
+
? "When launching each subagent, map `entry.model_hint.tier` (`small`, `standard`, `deep`) to an available host model without asking the user for model names."
|
|
549
|
+
: "Ignore `entry.model_hint`; this host did not report per-subagent model selection.";
|
|
550
|
+
const toolsLine = params.hostCanRestrictSubagentTools
|
|
551
|
+
? "Restrict review subagents to read/search plus the packet submit command named in their prompt. Do not give them source edit/write tools."
|
|
552
|
+
: "Do not ask the user about per-subagent tool restrictions; this host did not report a callable restriction facility.";
|
|
553
|
+
const fileLines = params.dispatchQuotaPath
|
|
554
|
+
? [
|
|
555
|
+
"Dispatch is prepared. Read both of these files:",
|
|
556
|
+
"",
|
|
557
|
+
` Dispatch plan: ${params.dispatchPlanPath}`,
|
|
558
|
+
` Dispatch quota: ${params.dispatchQuotaPath}`,
|
|
559
|
+
"",
|
|
560
|
+
"The quota file contains a `wave_size` field. Dispatch at most `wave_size` subagents at a time. If `cooldown_until` is non-null, wait until that timestamp before starting the first wave.",
|
|
561
|
+
"",
|
|
562
|
+
"For each wave: launch up to `wave_size` subagents in parallel (one per plan entry), wait for all of them to finish, then start the next wave. Repeat until all entries are dispatched.",
|
|
563
|
+
]
|
|
564
|
+
: [
|
|
565
|
+
"Dispatch is prepared. Read only this dispatch plan JSON:",
|
|
566
|
+
"",
|
|
567
|
+
` ${params.dispatchPlanPath}`,
|
|
568
|
+
"",
|
|
569
|
+
"Launch one host subagent for each entry in the plan.",
|
|
570
|
+
];
|
|
571
|
+
return [
|
|
572
|
+
"# audit-code dispatch review",
|
|
573
|
+
"",
|
|
574
|
+
...fileLines,
|
|
575
|
+
"",
|
|
576
|
+
"Pass each packet prompt path literally to its subagent; do not load packet prompt files into this orchestrator context.",
|
|
577
|
+
"",
|
|
578
|
+
"Subagent prompt shape:",
|
|
579
|
+
"",
|
|
580
|
+
' Read and follow the audit instructions in: <entry.prompt_path>',
|
|
581
|
+
"",
|
|
582
|
+
modelLine,
|
|
583
|
+
toolsLine,
|
|
584
|
+
"",
|
|
585
|
+
"Each subagent must submit its packet through the submit command printed in its packet prompt and stop after successful submission.",
|
|
586
|
+
"",
|
|
587
|
+
"After all waves complete, run exactly:",
|
|
588
|
+
"",
|
|
589
|
+
` ${mergeCommand}`,
|
|
590
|
+
"",
|
|
591
|
+
"If merge-and-ingest fails, stop and report the exact command and error output. Do not manually merge results or edit audit state.",
|
|
592
|
+
"",
|
|
593
|
+
"If merge-and-ingest succeeds, run:",
|
|
594
|
+
"",
|
|
595
|
+
` ${continueCommand}`,
|
|
596
|
+
"",
|
|
597
|
+
"Read and follow only the new step prompt path returned by that command.",
|
|
598
|
+
"",
|
|
599
|
+
].join("\n");
|
|
600
|
+
}
|
|
601
|
+
function renderSingleTaskFallbackStepPrompt(params) {
|
|
602
|
+
return [
|
|
603
|
+
"# audit-code single-task fallback step",
|
|
604
|
+
"",
|
|
605
|
+
"Use this step only because the host reported no callable subagent facility.",
|
|
606
|
+
"",
|
|
607
|
+
"Read and follow exactly this generated single-task prompt:",
|
|
608
|
+
"",
|
|
609
|
+
` ${params.singleTaskPromptPath}`,
|
|
610
|
+
"",
|
|
611
|
+
"Complete exactly one AuditResult for the task named there, write the JSON array to the prompt's audit_results_path, run the exact worker_command from that prompt, then stop.",
|
|
612
|
+
"",
|
|
613
|
+
"Do not run dispatch commands, do not prepare packets, do not run next-step again in this turn, and do not read a report after the worker command.",
|
|
614
|
+
"",
|
|
615
|
+
"The only backend command allowed after writing the result is:",
|
|
616
|
+
"",
|
|
617
|
+
` ${renderCommand(params.activeReviewRun.worker_command)}`,
|
|
618
|
+
"",
|
|
619
|
+
].join("\n");
|
|
620
|
+
}
|
|
621
|
+
function renderPresentReportPrompt(finalReportPath) {
|
|
622
|
+
return [
|
|
623
|
+
"# audit-code present report",
|
|
624
|
+
"",
|
|
625
|
+
"The deterministic audit is complete.",
|
|
626
|
+
"",
|
|
627
|
+
"Read this report and present the completed audit with work blocks first:",
|
|
628
|
+
"",
|
|
629
|
+
` ${finalReportPath}`,
|
|
630
|
+
"",
|
|
631
|
+
"Do not run the orchestrator again for this completed audit.",
|
|
632
|
+
"",
|
|
633
|
+
].join("\n");
|
|
634
|
+
}
|
|
635
|
+
function renderBlockedStepPrompt(reason) {
|
|
636
|
+
return [
|
|
637
|
+
"# audit-code blocked",
|
|
638
|
+
"",
|
|
639
|
+
"The audit cannot continue automatically from this step.",
|
|
640
|
+
"",
|
|
641
|
+
"Report this blocker verbatim and stop:",
|
|
642
|
+
"",
|
|
643
|
+
reason,
|
|
644
|
+
"",
|
|
645
|
+
].join("\n");
|
|
646
|
+
}
|
|
647
|
+
async function writeCurrentStep(params) {
|
|
648
|
+
const stepsDir = join(params.artifactsDir, "steps");
|
|
649
|
+
await mkdir(stepsDir, { recursive: true });
|
|
650
|
+
const promptPath = join(stepsDir, "current-prompt.md");
|
|
651
|
+
const stepPath = join(stepsDir, "current-step.json");
|
|
652
|
+
await writeFile(promptPath, params.prompt, "utf8");
|
|
653
|
+
const step = {
|
|
654
|
+
contract_version: STEP_CONTRACT_VERSION,
|
|
655
|
+
step_kind: params.stepKind,
|
|
656
|
+
prompt_path: promptPath,
|
|
657
|
+
status: params.status,
|
|
658
|
+
run_id: params.runId,
|
|
659
|
+
allowed_commands: params.allowedCommands,
|
|
660
|
+
stop_condition: params.stopCondition,
|
|
661
|
+
repo_root: params.repoRoot,
|
|
662
|
+
artifacts_dir: params.artifactsDir,
|
|
663
|
+
artifact_paths: {
|
|
664
|
+
current_step: stepPath,
|
|
665
|
+
current_prompt: promptPath,
|
|
666
|
+
...params.artifactPaths,
|
|
667
|
+
},
|
|
668
|
+
};
|
|
669
|
+
await writeJsonFile(stepPath, step);
|
|
670
|
+
return step;
|
|
671
|
+
}
|
|
327
672
|
function formatAuditResultValidationError(issues) {
|
|
328
673
|
return (`audit-results validation failed with ${issues.length} error(s):\n` +
|
|
329
674
|
formatAuditResultIssues(issues));
|
|
@@ -659,6 +1004,255 @@ async function cmdAdvanceAudit(argv) {
|
|
|
659
1004
|
await promoteFinalAuditReport({ artifactsDir, repoRoot: root });
|
|
660
1005
|
}
|
|
661
1006
|
}
|
|
1007
|
+
async function runDeterministicForNextStep(params) {
|
|
1008
|
+
let lastSummary = "";
|
|
1009
|
+
for (let index = 0; index < params.maxRuns; index++) {
|
|
1010
|
+
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
1011
|
+
const decision = decideNextStep(bundle);
|
|
1012
|
+
const state = decision.state;
|
|
1013
|
+
if (state.status === "complete") {
|
|
1014
|
+
await writeHandoffOnly({
|
|
1015
|
+
root: params.root,
|
|
1016
|
+
artifactsDir: params.artifactsDir,
|
|
1017
|
+
bundle,
|
|
1018
|
+
audit_state: state,
|
|
1019
|
+
progress_summary: decision.reason,
|
|
1020
|
+
providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
|
|
1021
|
+
});
|
|
1022
|
+
const promoted = await promoteFinalAuditReport({
|
|
1023
|
+
artifactsDir: params.artifactsDir,
|
|
1024
|
+
repoRoot: params.root,
|
|
1025
|
+
});
|
|
1026
|
+
return {
|
|
1027
|
+
kind: "complete",
|
|
1028
|
+
state,
|
|
1029
|
+
bundle,
|
|
1030
|
+
finalReportPath: promoted.promoted
|
|
1031
|
+
? join(params.root, "audit-report.md")
|
|
1032
|
+
: join(params.artifactsDir, "audit-report.md"),
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
if (decision.selected_executor === "agent") {
|
|
1036
|
+
return {
|
|
1037
|
+
kind: "semantic_review",
|
|
1038
|
+
...(await ensureSemanticReviewRun({
|
|
1039
|
+
root: params.root,
|
|
1040
|
+
artifactsDir: params.artifactsDir,
|
|
1041
|
+
bundle,
|
|
1042
|
+
state,
|
|
1043
|
+
obligationId: decision.selected_obligation,
|
|
1044
|
+
selfCliPath: params.selfCliPath,
|
|
1045
|
+
timeoutMs: params.timeoutMs,
|
|
1046
|
+
})),
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (!decision.selected_executor) {
|
|
1050
|
+
await writeHandoffOnly({
|
|
1051
|
+
root: params.root,
|
|
1052
|
+
artifactsDir: params.artifactsDir,
|
|
1053
|
+
bundle,
|
|
1054
|
+
audit_state: state,
|
|
1055
|
+
progress_summary: lastSummary || decision.reason,
|
|
1056
|
+
providerName: LOCAL_SUBPROCESS_PROVIDER_NAME,
|
|
1057
|
+
});
|
|
1058
|
+
return {
|
|
1059
|
+
kind: "blocked",
|
|
1060
|
+
state,
|
|
1061
|
+
bundle,
|
|
1062
|
+
reason: lastSummary || decision.reason,
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
const result = await runAuditStep({
|
|
1066
|
+
root: params.root,
|
|
1067
|
+
artifactsDir: params.artifactsDir,
|
|
1068
|
+
});
|
|
1069
|
+
lastSummary = result.progress_summary;
|
|
1070
|
+
if (result.selected_executor !== "agent") {
|
|
1071
|
+
await clearDispatchFiles(params.artifactsDir);
|
|
1072
|
+
}
|
|
1073
|
+
if (!result.progress_made) {
|
|
1074
|
+
return {
|
|
1075
|
+
kind: "blocked",
|
|
1076
|
+
state: result.audit_state,
|
|
1077
|
+
bundle: result.updated_bundle,
|
|
1078
|
+
reason: result.progress_summary,
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const bundle = await loadArtifactBundle(params.artifactsDir);
|
|
1083
|
+
const state = deriveAuditState(bundle);
|
|
1084
|
+
return {
|
|
1085
|
+
kind: "blocked",
|
|
1086
|
+
state,
|
|
1087
|
+
bundle,
|
|
1088
|
+
reason: `Reached max run limit (${params.maxRuns}) before a review, report, or blocker step was ready.`,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
async function cmdNextStep(argv) {
|
|
1092
|
+
const root = getRootDir(argv);
|
|
1093
|
+
const artifactsDir = getArtifactsDir(argv);
|
|
1094
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
1095
|
+
await ensureSupervisorDirs(artifactsDir);
|
|
1096
|
+
const hostCanDispatchSubagents = getOptionalBooleanFlag(argv, "--host-can-dispatch-subagents");
|
|
1097
|
+
const hostCanRestrictSubagentTools = getOptionalBooleanFlag(argv, "--host-can-restrict-subagent-tools") ??
|
|
1098
|
+
false;
|
|
1099
|
+
const hostCanSelectSubagentModel = getOptionalBooleanFlag(argv, "--host-can-select-subagent-model") ?? false;
|
|
1100
|
+
let sessionConfig;
|
|
1101
|
+
try {
|
|
1102
|
+
sessionConfig = await loadSessionConfig(artifactsDir);
|
|
1103
|
+
}
|
|
1104
|
+
catch (error) {
|
|
1105
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1106
|
+
await persistConfigErrorHandoff({
|
|
1107
|
+
root,
|
|
1108
|
+
artifactsDir,
|
|
1109
|
+
progressSummary: reason,
|
|
1110
|
+
});
|
|
1111
|
+
const step = await writeCurrentStep({
|
|
1112
|
+
artifactsDir,
|
|
1113
|
+
stepKind: "blocked",
|
|
1114
|
+
status: "blocked",
|
|
1115
|
+
runId: null,
|
|
1116
|
+
allowedCommands: [],
|
|
1117
|
+
stopCondition: "Report the configuration blocker and stop.",
|
|
1118
|
+
repoRoot: root,
|
|
1119
|
+
artifactPaths: {
|
|
1120
|
+
operator_handoff: join(artifactsDir, "operator-handoff.json"),
|
|
1121
|
+
},
|
|
1122
|
+
prompt: renderBlockedStepPrompt(reason),
|
|
1123
|
+
});
|
|
1124
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const result = await runDeterministicForNextStep({
|
|
1128
|
+
root,
|
|
1129
|
+
artifactsDir,
|
|
1130
|
+
selfCliPath: resolve(argv[1] ?? process.argv[1] ?? ""),
|
|
1131
|
+
timeoutMs: getTimeoutMs(argv, sessionConfig),
|
|
1132
|
+
maxRuns: getMaxRuns(argv),
|
|
1133
|
+
});
|
|
1134
|
+
if (result.kind === "complete") {
|
|
1135
|
+
const step = await writeCurrentStep({
|
|
1136
|
+
artifactsDir,
|
|
1137
|
+
stepKind: "present_report",
|
|
1138
|
+
status: "complete",
|
|
1139
|
+
runId: null,
|
|
1140
|
+
allowedCommands: [],
|
|
1141
|
+
stopCondition: "Present the final report and stop.",
|
|
1142
|
+
repoRoot: root,
|
|
1143
|
+
artifactPaths: {
|
|
1144
|
+
final_report: result.finalReportPath,
|
|
1145
|
+
},
|
|
1146
|
+
prompt: renderPresentReportPrompt(result.finalReportPath),
|
|
1147
|
+
});
|
|
1148
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
if (result.kind === "blocked") {
|
|
1152
|
+
const step = await writeCurrentStep({
|
|
1153
|
+
artifactsDir,
|
|
1154
|
+
stepKind: "blocked",
|
|
1155
|
+
status: "blocked",
|
|
1156
|
+
runId: null,
|
|
1157
|
+
allowedCommands: [],
|
|
1158
|
+
stopCondition: "Report the blocker and stop.",
|
|
1159
|
+
repoRoot: root,
|
|
1160
|
+
artifactPaths: {
|
|
1161
|
+
operator_handoff: join(artifactsDir, "operator-handoff.json"),
|
|
1162
|
+
},
|
|
1163
|
+
prompt: renderBlockedStepPrompt(result.reason),
|
|
1164
|
+
});
|
|
1165
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (hostCanDispatchSubagents === undefined) {
|
|
1169
|
+
const yesCommand = nextStepCommand(root, artifactsDir, [
|
|
1170
|
+
"--host-can-dispatch-subagents",
|
|
1171
|
+
"true",
|
|
1172
|
+
]);
|
|
1173
|
+
const noCommand = nextStepCommand(root, artifactsDir, [
|
|
1174
|
+
"--host-can-dispatch-subagents",
|
|
1175
|
+
"false",
|
|
1176
|
+
]);
|
|
1177
|
+
const step = await writeCurrentStep({
|
|
1178
|
+
artifactsDir,
|
|
1179
|
+
stepKind: "capability_check",
|
|
1180
|
+
status: "ready",
|
|
1181
|
+
runId: result.activeReviewRun.run_id,
|
|
1182
|
+
allowedCommands: [yesCommand, noCommand],
|
|
1183
|
+
stopCondition: "Run exactly one next-step command with an explicit host dispatch capability.",
|
|
1184
|
+
repoRoot: root,
|
|
1185
|
+
artifactPaths: {
|
|
1186
|
+
active_review_task: result.activeReviewRun.task_path,
|
|
1187
|
+
active_review_prompt: result.activeReviewRun.prompt_path,
|
|
1188
|
+
pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
|
|
1189
|
+
single_task_prompt: join(artifactsDir, "dispatch", "current-single-task-prompt.md"),
|
|
1190
|
+
},
|
|
1191
|
+
prompt: renderCapabilityCheckPrompt({ root, artifactsDir }),
|
|
1192
|
+
});
|
|
1193
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (!hostCanDispatchSubagents) {
|
|
1197
|
+
const singleTaskPromptPath = join(artifactsDir, "dispatch", "current-single-task-prompt.md");
|
|
1198
|
+
const workerCommand = renderCommand(result.activeReviewRun.worker_command);
|
|
1199
|
+
const step = await writeCurrentStep({
|
|
1200
|
+
artifactsDir,
|
|
1201
|
+
stepKind: "single_task_fallback",
|
|
1202
|
+
status: "ready",
|
|
1203
|
+
runId: result.activeReviewRun.run_id,
|
|
1204
|
+
allowedCommands: [workerCommand],
|
|
1205
|
+
stopCondition: "Run the exact worker_command after one result, then stop without looping.",
|
|
1206
|
+
repoRoot: root,
|
|
1207
|
+
artifactPaths: {
|
|
1208
|
+
active_review_task: result.activeReviewRun.task_path,
|
|
1209
|
+
active_review_prompt: result.activeReviewRun.prompt_path,
|
|
1210
|
+
pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
|
|
1211
|
+
audit_results: result.activeReviewRun.audit_results_path,
|
|
1212
|
+
single_task_prompt: singleTaskPromptPath,
|
|
1213
|
+
},
|
|
1214
|
+
prompt: renderSingleTaskFallbackStepPrompt({
|
|
1215
|
+
singleTaskPromptPath,
|
|
1216
|
+
activeReviewRun: result.activeReviewRun,
|
|
1217
|
+
}),
|
|
1218
|
+
});
|
|
1219
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const dispatch = await prepareDispatchArtifacts({
|
|
1223
|
+
runId: result.activeReviewRun.run_id,
|
|
1224
|
+
artifactsDir,
|
|
1225
|
+
root,
|
|
1226
|
+
});
|
|
1227
|
+
const mergeCommand = mergeAndIngestCommand(artifactsDir, result.activeReviewRun.run_id);
|
|
1228
|
+
const continueCommand = nextStepCommand(root, artifactsDir);
|
|
1229
|
+
const step = await writeCurrentStep({
|
|
1230
|
+
artifactsDir,
|
|
1231
|
+
stepKind: "dispatch_review",
|
|
1232
|
+
status: "ready",
|
|
1233
|
+
runId: result.activeReviewRun.run_id,
|
|
1234
|
+
allowedCommands: [mergeCommand, continueCommand],
|
|
1235
|
+
stopCondition: "Dispatch every packet, merge-and-ingest once, then run next-step again.",
|
|
1236
|
+
repoRoot: root,
|
|
1237
|
+
artifactPaths: {
|
|
1238
|
+
dispatch_plan: dispatch.dispatch_plan_path,
|
|
1239
|
+
dispatch_quota: dispatch.dispatch_quota_path,
|
|
1240
|
+
dispatch_warnings: dispatch.dispatch_warnings_path,
|
|
1241
|
+
active_review_task: result.activeReviewRun.task_path,
|
|
1242
|
+
pending_audit_tasks: result.activeReviewRun.pending_audit_tasks_path ?? null,
|
|
1243
|
+
},
|
|
1244
|
+
prompt: renderDispatchReviewPrompt({
|
|
1245
|
+
root,
|
|
1246
|
+
artifactsDir,
|
|
1247
|
+
activeReviewRun: result.activeReviewRun,
|
|
1248
|
+
dispatchPlanPath: dispatch.dispatch_plan_path,
|
|
1249
|
+
dispatchQuotaPath: dispatch.dispatch_quota_path,
|
|
1250
|
+
hostCanRestrictSubagentTools,
|
|
1251
|
+
hostCanSelectSubagentModel,
|
|
1252
|
+
}),
|
|
1253
|
+
});
|
|
1254
|
+
console.log(JSON.stringify(step, null, 2));
|
|
1255
|
+
}
|
|
662
1256
|
async function cmdRunToCompletion(argv) {
|
|
663
1257
|
const root = getRootDir(argv);
|
|
664
1258
|
const artifactsDir = getArtifactsDir(argv);
|
|
@@ -684,6 +1278,7 @@ async function cmdRunToCompletion(argv) {
|
|
|
684
1278
|
const agentBatchSize = getAgentBatchSize(argv, sessionConfig);
|
|
685
1279
|
const parallelWorkers = getParallelWorkers(argv, sessionConfig);
|
|
686
1280
|
const timeoutMs = getTimeoutMs(argv, sessionConfig);
|
|
1281
|
+
const hostModel = getHostModel(argv);
|
|
687
1282
|
const selfCliPath = resolve(argv[1] ?? process.argv[1] ?? "");
|
|
688
1283
|
const batchResultsDir = getBatchResultsDir(argv);
|
|
689
1284
|
if (batchResultsDir && getFlag(argv, "--results")) {
|
|
@@ -821,8 +1416,27 @@ async function cmdRunToCompletion(argv) {
|
|
|
821
1416
|
return;
|
|
822
1417
|
}
|
|
823
1418
|
if (preferredExecutor === "agent" && parallelWorkers > 1) {
|
|
1419
|
+
const quotaState = await readQuotaState();
|
|
1420
|
+
const providerModelKey = buildProviderModelKey(provider.name, hostModel);
|
|
1421
|
+
const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
|
|
1422
|
+
const waveSchedule = scheduleWave({
|
|
1423
|
+
providerName: resolveFreshSessionProviderName(getExplicitProvider(argv), sessionConfig),
|
|
1424
|
+
sessionConfig,
|
|
1425
|
+
hostModel,
|
|
1426
|
+
requestedConcurrency: parallelWorkers,
|
|
1427
|
+
quotaStateEntry,
|
|
1428
|
+
});
|
|
1429
|
+
const waveSize = waveSchedule.wave_size;
|
|
1430
|
+
if (waveSchedule.cooldown_until) {
|
|
1431
|
+
const waitMs = new Date(waveSchedule.cooldown_until).getTime() - Date.now();
|
|
1432
|
+
if (waitMs > 0) {
|
|
1433
|
+
const cappedWait = Math.min(waitMs, 120_000);
|
|
1434
|
+
process.stderr.write(`[quota] Cooldown active — waiting ${Math.ceil(cappedWait / 1000)}s before next wave.\n`);
|
|
1435
|
+
await new Promise((r) => setTimeout(r, cappedWait));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
824
1438
|
const allPendingTasks = buildPendingAuditTasks(bundle);
|
|
825
|
-
const taskGroups = chunkArray(allPendingTasks.slice(0,
|
|
1439
|
+
const taskGroups = chunkArray(allPendingTasks.slice(0, waveSize * agentBatchSize), agentBatchSize);
|
|
826
1440
|
const workerSlots = [];
|
|
827
1441
|
for (const rawGroup of taskGroups) {
|
|
828
1442
|
const group = await addFileLineCountHints(root, rawGroup);
|
|
@@ -976,6 +1590,16 @@ async function cmdRunToCompletion(argv) {
|
|
|
976
1590
|
});
|
|
977
1591
|
artifactsWritten.add("run-ledger.json");
|
|
978
1592
|
}
|
|
1593
|
+
// Record outcome for adaptive learning (best-effort — never blocks dispatch)
|
|
1594
|
+
{
|
|
1595
|
+
const hasRateLimit = batchErrors.some(detectRateLimitError);
|
|
1596
|
+
await recordWaveOutcome(providerModelKey, {
|
|
1597
|
+
concurrency: workerSlots.length,
|
|
1598
|
+
estimated_tokens: waveSize * agentBatchSize * 900,
|
|
1599
|
+
outcome: hasRateLimit ? "rate_limited" : batchErrors.length > 0 ? "timeout" : "success",
|
|
1600
|
+
cooldown_until: hasRateLimit ? defaultCooldownUntil(null) : null,
|
|
1601
|
+
}, sessionConfig.quota?.empirical_half_life_hours ?? 24).catch(() => undefined);
|
|
1602
|
+
}
|
|
979
1603
|
if (batchErrors.length > 0) {
|
|
980
1604
|
const bundleAfter = await loadArtifactBundle(artifactsDir);
|
|
981
1605
|
const blockedState = buildBlockedAuditState({
|
|
@@ -1544,17 +2168,14 @@ function renderPacketGraphContext(packet) {
|
|
|
1544
2168
|
lines.push("");
|
|
1545
2169
|
return lines;
|
|
1546
2170
|
}
|
|
1547
|
-
async function
|
|
1548
|
-
const runId =
|
|
1549
|
-
|
|
1550
|
-
throw new Error("prepare-dispatch requires --run-id <run_id>");
|
|
1551
|
-
const artifactsDir = getArtifactsDir(argv);
|
|
2171
|
+
async function prepareDispatchArtifacts(params) {
|
|
2172
|
+
const runId = params.runId;
|
|
2173
|
+
const artifactsDir = params.artifactsDir;
|
|
1552
2174
|
const runDir = join(artifactsDir, "runs", runId);
|
|
1553
2175
|
const tasksPath = join(runDir, "pending-audit-tasks.json");
|
|
1554
2176
|
const taskResultsDir = join(runDir, "task-results");
|
|
1555
2177
|
const dispatchPlanPath = join(runDir, "dispatch-plan.json");
|
|
1556
|
-
|
|
1557
|
-
let reviewRoot = explicitRoot;
|
|
2178
|
+
let reviewRoot = params.root;
|
|
1558
2179
|
try {
|
|
1559
2180
|
const workerTask = await readJsonFile(join(runDir, "task.json"));
|
|
1560
2181
|
reviewRoot ??= workerTask.repo_root;
|
|
@@ -1566,6 +2187,7 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1566
2187
|
}
|
|
1567
2188
|
const tasks = await readJsonFile(tasksPath);
|
|
1568
2189
|
const bundle = await loadArtifactBundle(artifactsDir);
|
|
2190
|
+
const sessionConfig = params.sessionConfig ?? (await loadSessionConfig(artifactsDir).catch(() => ({})));
|
|
1569
2191
|
const lensDefsPath = join(packageRoot, "dispatch", "lens-definitions.json");
|
|
1570
2192
|
const lensDefs = await readJsonFile(lensDefsPath);
|
|
1571
2193
|
await mkdir(taskResultsDir, { recursive: true });
|
|
@@ -1721,6 +2343,7 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1721
2343
|
largeFileMode
|
|
1722
2344
|
? "Use targeted Read/Grep calls. Paths are repo-relative from the current working directory."
|
|
1723
2345
|
: "Use your Read tool. Paths are repo-relative from the current working directory.",
|
|
2346
|
+
"Prefer host Read/Grep tools. On native Windows, do not use Unix pipelines like `grep ... | head`; if shell search is unavoidable, use `Select-String` as a fallback.",
|
|
1724
2347
|
fileList,
|
|
1725
2348
|
"",
|
|
1726
2349
|
...renderPacketGraphContext(packet),
|
|
@@ -1790,15 +2413,62 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1790
2413
|
run_id: runId,
|
|
1791
2414
|
entries: resultMapEntries,
|
|
1792
2415
|
});
|
|
2416
|
+
// Compute and write dispatch-quota.json
|
|
2417
|
+
const hostModel = params.hostModel ?? null;
|
|
2418
|
+
const avgPacketTokens = plan.length > 0
|
|
2419
|
+
? Math.floor(plan.reduce((s, p) => s + p.complexity.estimated_tokens, 0) / plan.length)
|
|
2420
|
+
: 0;
|
|
2421
|
+
const quotaProviderName = resolveFreshSessionProviderName(undefined, sessionConfig);
|
|
2422
|
+
const quotaProviderKey = buildProviderModelKey(quotaProviderName, hostModel);
|
|
2423
|
+
const quotaState = await readQuotaState().catch(() => ({ version: 1, entries: {} }));
|
|
2424
|
+
const quotaStateEntry = quotaState.entries[quotaProviderKey] ?? null;
|
|
2425
|
+
const waveSchedule = scheduleWave({
|
|
2426
|
+
providerName: quotaProviderName,
|
|
2427
|
+
sessionConfig,
|
|
2428
|
+
hostModel,
|
|
2429
|
+
requestedConcurrency: sessionConfig.parallel_workers ?? 1,
|
|
2430
|
+
estimatedPacketTokens: avgPacketTokens,
|
|
2431
|
+
quotaStateEntry,
|
|
2432
|
+
});
|
|
2433
|
+
const dispatchQuota = {
|
|
2434
|
+
contract_version: "audit-code-dispatch-quota/v1alpha1",
|
|
2435
|
+
run_id: runId,
|
|
2436
|
+
model: hostModel,
|
|
2437
|
+
resolved_limits: waveSchedule.resolved_limits,
|
|
2438
|
+
confidence: waveSchedule.confidence,
|
|
2439
|
+
source: waveSchedule.source,
|
|
2440
|
+
wave_size: waveSchedule.wave_size,
|
|
2441
|
+
estimated_wave_tokens: waveSchedule.estimated_wave_tokens,
|
|
2442
|
+
cooldown_until: waveSchedule.cooldown_until,
|
|
2443
|
+
};
|
|
2444
|
+
const dispatchQuotaPath = join(runDir, "dispatch-quota.json");
|
|
2445
|
+
await writeJsonFile(dispatchQuotaPath, dispatchQuota);
|
|
2446
|
+
// Warn about packets that exceed the context budget only when we have reliable limit
|
|
2447
|
+
// information (confidence medium/high). Low-confidence limits are conservative defaults
|
|
2448
|
+
// and would produce misleading warnings since the real context window is unknown.
|
|
2449
|
+
if (waveSchedule.confidence !== "low") {
|
|
2450
|
+
const contextBudget = waveSchedule.resolved_limits.context_tokens - waveSchedule.resolved_limits.output_tokens;
|
|
2451
|
+
for (const p of plan) {
|
|
2452
|
+
if (p.complexity.estimated_tokens > contextBudget) {
|
|
2453
|
+
warnings.push({
|
|
2454
|
+
code: "oversized_packet",
|
|
2455
|
+
message: `Packet ${p.packet_id} estimated tokens (${p.complexity.estimated_tokens}) exceed ` +
|
|
2456
|
+
`context budget (${contextBudget}). This packet may fail at dispatch. ` +
|
|
2457
|
+
`Set quota.default_context_tokens or quota.models in session-config.json to override.`,
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
1793
2462
|
const warningsPath = warnings.length > 0
|
|
1794
2463
|
? join(runDir, "dispatch-warnings.json")
|
|
1795
2464
|
: null;
|
|
1796
2465
|
if (warningsPath) {
|
|
1797
2466
|
await writeJsonFile(warningsPath, warnings);
|
|
1798
2467
|
}
|
|
1799
|
-
|
|
2468
|
+
return {
|
|
1800
2469
|
run_id: runId,
|
|
1801
2470
|
dispatch_plan_path: dispatchPlanPath,
|
|
2471
|
+
dispatch_quota_path: dispatchQuotaPath,
|
|
1802
2472
|
packet_count: plan.length,
|
|
1803
2473
|
task_count: orderedTasks.length,
|
|
1804
2474
|
largest_packet: largestPacketId
|
|
@@ -1810,7 +2480,19 @@ async function cmdPrepareDispatch(argv) {
|
|
|
1810
2480
|
: null,
|
|
1811
2481
|
warning_count: warnings.length,
|
|
1812
2482
|
dispatch_warnings_path: warningsPath,
|
|
1813
|
-
}
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
async function cmdPrepareDispatch(argv) {
|
|
2486
|
+
const runId = getFlag(argv, "--run-id");
|
|
2487
|
+
if (!runId)
|
|
2488
|
+
throw new Error("prepare-dispatch requires --run-id <run_id>");
|
|
2489
|
+
const result = await prepareDispatchArtifacts({
|
|
2490
|
+
runId,
|
|
2491
|
+
artifactsDir: getArtifactsDir(argv),
|
|
2492
|
+
root: getFlag(argv, "--root") ? getRootDir(argv) : undefined,
|
|
2493
|
+
hostModel: getHostModel(argv),
|
|
2494
|
+
});
|
|
2495
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1814
2496
|
}
|
|
1815
2497
|
async function cmdSubmitPacket(argv) {
|
|
1816
2498
|
const runId = resolveRunScopedArg(argv, "--run-id", "--run-id-b64");
|
|
@@ -2360,6 +3042,45 @@ async function cmdCleanup(argv) {
|
|
|
2360
3042
|
async function cmdMcp(argv) {
|
|
2361
3043
|
await runAuditCodeMcpServer(argv.slice(3));
|
|
2362
3044
|
}
|
|
3045
|
+
async function cmdQuota(argv) {
|
|
3046
|
+
const artifactsDir = getArtifactsDir(argv);
|
|
3047
|
+
const sessionConfig = await loadSessionConfig(artifactsDir).catch(() => ({}));
|
|
3048
|
+
const explicitProvider = getExplicitProvider(argv);
|
|
3049
|
+
const hostModel = getHostModel(argv);
|
|
3050
|
+
const probeMode = getQuotaProbeMode(argv, sessionConfig);
|
|
3051
|
+
const providerName = resolveFreshSessionProviderName(explicitProvider, sessionConfig);
|
|
3052
|
+
const providerModelKey = buildProviderModelKey(providerName, hostModel);
|
|
3053
|
+
const { limits, source, confidence } = resolveLimits({ providerName, sessionConfig, hostModel });
|
|
3054
|
+
const probeResult = await probeProvider(providerName, probeMode);
|
|
3055
|
+
const quotaState = await readQuotaState().catch(() => ({ version: 1, entries: {} }));
|
|
3056
|
+
const quotaStateEntry = quotaState.entries[providerModelKey] ?? null;
|
|
3057
|
+
const halfLifeHours = sessionConfig.quota?.empirical_half_life_hours ?? 24;
|
|
3058
|
+
const waveSchedule = scheduleWave({
|
|
3059
|
+
providerName,
|
|
3060
|
+
sessionConfig,
|
|
3061
|
+
hostModel,
|
|
3062
|
+
requestedConcurrency: sessionConfig.parallel_workers ?? 1,
|
|
3063
|
+
quotaStateEntry,
|
|
3064
|
+
});
|
|
3065
|
+
console.log(JSON.stringify({
|
|
3066
|
+
provider: providerName,
|
|
3067
|
+
model: hostModel,
|
|
3068
|
+
provider_model_key: providerModelKey,
|
|
3069
|
+
resolved_limits: limits,
|
|
3070
|
+
confidence,
|
|
3071
|
+
source,
|
|
3072
|
+
probe: probeResult,
|
|
3073
|
+
learned_caps: quotaStateEntry
|
|
3074
|
+
? {
|
|
3075
|
+
max_safe_concurrency: computeMaxSafeConcurrency(quotaStateEntry, halfLifeHours),
|
|
3076
|
+
cooldown_until: quotaStateEntry.cooldown_until,
|
|
3077
|
+
last_429_at: quotaStateEntry.last_429_at,
|
|
3078
|
+
}
|
|
3079
|
+
: null,
|
|
3080
|
+
wave_schedule: waveSchedule,
|
|
3081
|
+
quota_state_path: getQuotaStatePath(),
|
|
3082
|
+
}, null, 2));
|
|
3083
|
+
}
|
|
2363
3084
|
async function main(argv) {
|
|
2364
3085
|
const command = argv[2] ?? "sample-run";
|
|
2365
3086
|
switch (command) {
|
|
@@ -2369,6 +3090,9 @@ async function main(argv) {
|
|
|
2369
3090
|
case "advance-audit":
|
|
2370
3091
|
await cmdAdvanceAudit(argv);
|
|
2371
3092
|
return;
|
|
3093
|
+
case "next-step":
|
|
3094
|
+
await cmdNextStep(argv);
|
|
3095
|
+
return;
|
|
2372
3096
|
case "run-to-completion":
|
|
2373
3097
|
await cmdRunToCompletion(argv);
|
|
2374
3098
|
return;
|
|
@@ -2423,9 +3147,12 @@ async function main(argv) {
|
|
|
2423
3147
|
case "validate-result":
|
|
2424
3148
|
await cmdValidateResult(argv);
|
|
2425
3149
|
return;
|
|
3150
|
+
case "quota":
|
|
3151
|
+
await cmdQuota(argv);
|
|
3152
|
+
return;
|
|
2426
3153
|
default:
|
|
2427
3154
|
console.error(`Unknown command: ${command}`);
|
|
2428
|
-
console.error("Available commands: sample-run, advance-audit, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, cleanup, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result");
|
|
3155
|
+
console.error("Available commands: sample-run, advance-audit, next-step, run-to-completion, worker-run, import-external-analyzer, intake, plan, ingest-results, explain-task, update-runtime-validation, validate, validate-results, requeue, synthesize, cleanup, mcp, prepare-dispatch, merge-and-ingest, submit-packet, validate-result, quota");
|
|
2429
3156
|
process.exitCode = 1;
|
|
2430
3157
|
}
|
|
2431
3158
|
}
|