auditor-lambda 0.3.32 → 0.3.34
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 +2 -1
- package/audit-code-wrapper-lib.mjs +30 -28
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +55 -123
- package/dist/mcp/server.js +11 -11
- package/dist/orchestrator/reviewPackets.d.ts +3 -0
- package/dist/orchestrator/reviewPackets.js +13 -2
- package/dist/quota/compositeQuotaSource.d.ts +7 -0
- package/dist/quota/compositeQuotaSource.js +20 -0
- package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +6 -0
- package/dist/quota/errorParsers/claudeCodeErrorParser.js +39 -0
- package/dist/quota/errorParsers/genericErrorParser.d.ts +9 -0
- package/dist/quota/errorParsers/genericErrorParser.js +7 -0
- package/dist/quota/errorParsers/index.d.ts +5 -0
- package/dist/quota/errorParsers/index.js +12 -0
- package/dist/quota/errorParsing.d.ts +7 -0
- package/dist/quota/errorParsing.js +69 -0
- package/dist/quota/fileLock.d.ts +6 -0
- package/dist/quota/fileLock.js +64 -0
- package/dist/quota/index.d.ts +11 -1
- package/dist/quota/index.js +7 -1
- package/dist/quota/learnedQuotaSource.d.ts +7 -0
- package/dist/quota/learnedQuotaSource.js +25 -0
- package/dist/quota/probe.d.ts +1 -4
- package/dist/quota/probe.js +1 -4
- package/dist/quota/quotaSource.d.ts +12 -0
- package/dist/quota/quotaSource.js +1 -0
- package/dist/quota/scheduler.d.ts +5 -1
- package/dist/quota/scheduler.js +51 -9
- package/dist/quota/slidingWindow.d.ts +4 -0
- package/dist/quota/slidingWindow.js +28 -0
- package/dist/quota/state.d.ts +3 -0
- package/dist/quota/state.js +57 -14
- package/dist/quota/types.d.ts +11 -2
- package/dist/supervisor/operatorHandoff.js +1 -1
- package/dist/types/sessionConfig.d.ts +3 -0
- package/dist/validation/sessionConfig.js +4 -0
- package/package.json +1 -1
- package/schemas/dispatch_quota.schema.json +23 -2
- package/skills/audit-code/audit-code.prompt.md +5 -0
package/dist/quota/state.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { withFileLock } from "./fileLock.js";
|
|
4
5
|
const STATE_DIR = join(homedir(), ".audit-code");
|
|
5
6
|
const STATE_PATH = join(STATE_DIR, "quota-state.json");
|
|
6
7
|
// A bucket needs at least this much success weight before we trust it.
|
|
@@ -27,31 +28,38 @@ export function applyDecayToEntry(entry, halfLifeHours) {
|
|
|
27
28
|
return { ...entry, buckets: decayed };
|
|
28
29
|
}
|
|
29
30
|
function isQuotaState(value) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
32
|
+
return false;
|
|
33
|
+
const obj = value;
|
|
34
|
+
const version = obj["version"];
|
|
35
|
+
return (version === 1 || version === 2) && typeof obj["entries"] === "object";
|
|
35
36
|
}
|
|
36
37
|
export async function readQuotaState() {
|
|
37
38
|
try {
|
|
38
39
|
const raw = await readFile(STATE_PATH, "utf8");
|
|
39
40
|
const parsed = JSON.parse(raw);
|
|
40
|
-
if (isQuotaState(parsed))
|
|
41
|
+
if (isQuotaState(parsed)) {
|
|
42
|
+
if (parsed.version === 1) {
|
|
43
|
+
for (const entry of Object.values(parsed.entries)) {
|
|
44
|
+
entry.consecutive_429_count ??= 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
41
47
|
return parsed;
|
|
42
|
-
|
|
48
|
+
}
|
|
49
|
+
process.stderr.write(`[quota] ignoring invalid quota state at ${STATE_PATH}: expected { version: 1|2, entries: object }\n`);
|
|
43
50
|
}
|
|
44
51
|
catch (error) {
|
|
45
52
|
if (error.code === "ENOENT") {
|
|
46
|
-
return { version:
|
|
53
|
+
return { version: 2, entries: {} };
|
|
47
54
|
}
|
|
48
55
|
process.stderr.write(`[quota] ignoring unreadable quota state at ${STATE_PATH}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
49
56
|
}
|
|
50
|
-
return { version:
|
|
57
|
+
return { version: 2, entries: {} };
|
|
51
58
|
}
|
|
52
59
|
export async function writeQuotaState(state) {
|
|
53
60
|
await mkdir(STATE_DIR, { recursive: true });
|
|
54
|
-
|
|
61
|
+
const normalized = { ...state, version: 2 };
|
|
62
|
+
await writeFile(STATE_PATH, JSON.stringify(normalized, null, 2) + "\n", "utf8");
|
|
55
63
|
}
|
|
56
64
|
/**
|
|
57
65
|
* Returns the highest concurrency level for which decayed success evidence
|
|
@@ -74,14 +82,39 @@ export function computeMaxSafeConcurrency(entry, halfLifeHours, maxToCheck = 32)
|
|
|
74
82
|
}
|
|
75
83
|
return maxSafe;
|
|
76
84
|
}
|
|
85
|
+
const RAMP_UP_MIN_SUCCESSES = 2;
|
|
86
|
+
export function computeRampUpConcurrency(entry, halfLifeHours, maxToCheck = 32) {
|
|
87
|
+
const maxSafe = computeMaxSafeConcurrency(entry, halfLifeHours, maxToCheck);
|
|
88
|
+
const decayed = applyDecayToEntry(entry, halfLifeHours);
|
|
89
|
+
const bucket = decayed.buckets[String(maxSafe)];
|
|
90
|
+
if (bucket &&
|
|
91
|
+
bucket.success_weight >= RAMP_UP_MIN_SUCCESSES &&
|
|
92
|
+
bucket.failure_weight === 0) {
|
|
93
|
+
return maxSafe + 1;
|
|
94
|
+
}
|
|
95
|
+
return maxSafe;
|
|
96
|
+
}
|
|
77
97
|
function blankEntry() {
|
|
78
98
|
return { updated_at: new Date().toISOString(), buckets: {}, cooldown_until: null, last_429_at: null };
|
|
79
99
|
}
|
|
100
|
+
const BASE_COOLDOWN_MS = 60_000;
|
|
101
|
+
const MAX_COOLDOWN_MS = 15 * 60_000;
|
|
102
|
+
export function computeBackoffCooldownMs(consecutive429Count) {
|
|
103
|
+
const ms = BASE_COOLDOWN_MS * Math.pow(2, Math.max(0, consecutive429Count - 1));
|
|
104
|
+
return Math.min(ms, MAX_COOLDOWN_MS);
|
|
105
|
+
}
|
|
106
|
+
export function computeBackoffFailureWeight(consecutive429Count) {
|
|
107
|
+
return 1.0 + 0.5 * Math.max(0, consecutive429Count - 1);
|
|
108
|
+
}
|
|
109
|
+
const LOCK_PATH = STATE_PATH + ".lock";
|
|
80
110
|
export async function recordWaveOutcome(providerModelKey, outcome, halfLifeHours) {
|
|
111
|
+
await withFileLock(LOCK_PATH, () => recordWaveOutcomeUnsafe(providerModelKey, outcome, halfLifeHours));
|
|
112
|
+
}
|
|
113
|
+
async function recordWaveOutcomeUnsafe(providerModelKey, outcome, halfLifeHours) {
|
|
81
114
|
const state = await readQuotaState();
|
|
82
115
|
const entry = applyDecayToEntry(state.entries[providerModelKey] ?? blankEntry(), halfLifeHours);
|
|
83
116
|
if (outcome.outcome === "success") {
|
|
84
|
-
|
|
117
|
+
entry.consecutive_429_count = 0;
|
|
85
118
|
for (let n = 1; n <= outcome.concurrency; n++) {
|
|
86
119
|
const bucket = entry.buckets[String(n)] ?? { success_weight: 0, failure_weight: 0 };
|
|
87
120
|
bucket.success_weight += 1.0;
|
|
@@ -89,13 +122,23 @@ export async function recordWaveOutcome(providerModelKey, outcome, halfLifeHours
|
|
|
89
122
|
}
|
|
90
123
|
}
|
|
91
124
|
else {
|
|
125
|
+
const prev429Count = entry.consecutive_429_count ?? 0;
|
|
126
|
+
const new429Count = outcome.outcome === "rate_limited" ? prev429Count + 1 : prev429Count;
|
|
127
|
+
entry.consecutive_429_count = new429Count;
|
|
92
128
|
entry.last_429_at = new Date().toISOString();
|
|
93
|
-
if (outcome.
|
|
129
|
+
if (outcome.outcome === "rate_limited" && new429Count > 0) {
|
|
130
|
+
const backoffMs = computeBackoffCooldownMs(new429Count);
|
|
131
|
+
entry.cooldown_until = new Date(Date.now() + backoffMs).toISOString();
|
|
132
|
+
}
|
|
133
|
+
else if (outcome.cooldown_until) {
|
|
94
134
|
entry.cooldown_until = outcome.cooldown_until;
|
|
95
|
-
|
|
135
|
+
}
|
|
136
|
+
const failureWeight = outcome.outcome === "rate_limited"
|
|
137
|
+
? computeBackoffFailureWeight(new429Count)
|
|
138
|
+
: 1.0;
|
|
96
139
|
for (let n = outcome.concurrency; n <= outcome.concurrency + 4; n++) {
|
|
97
140
|
const bucket = entry.buckets[String(n)] ?? { success_weight: 0, failure_weight: 0 };
|
|
98
|
-
bucket.failure_weight +=
|
|
141
|
+
bucket.failure_weight += failureWeight;
|
|
99
142
|
entry.buckets[String(n)] = bucket;
|
|
100
143
|
}
|
|
101
144
|
}
|
package/dist/quota/types.d.ts
CHANGED
|
@@ -22,9 +22,10 @@ export interface QuotaStateEntry {
|
|
|
22
22
|
buckets: Record<string, ConcurrencyBucket>;
|
|
23
23
|
cooldown_until: string | null;
|
|
24
24
|
last_429_at: string | null;
|
|
25
|
+
consecutive_429_count?: number;
|
|
25
26
|
}
|
|
26
27
|
export interface QuotaState {
|
|
27
|
-
version: 1;
|
|
28
|
+
version: 1 | 2;
|
|
28
29
|
entries: Record<string, QuotaStateEntry>;
|
|
29
30
|
}
|
|
30
31
|
export interface WaveSchedule {
|
|
@@ -36,9 +37,15 @@ export interface WaveSchedule {
|
|
|
36
37
|
resolved_limits: ResolvedLimits;
|
|
37
38
|
host_concurrency_limit: HostConcurrencyLimit | null;
|
|
38
39
|
model: string | null;
|
|
40
|
+
quota_source_snapshot?: import("./quotaSource.js").QuotaUsageSnapshot | null;
|
|
41
|
+
}
|
|
42
|
+
export interface BackoffState {
|
|
43
|
+
consecutive_429_count: number;
|
|
44
|
+
current_cooldown_ms: number;
|
|
45
|
+
current_failure_weight: number;
|
|
39
46
|
}
|
|
40
47
|
export interface DispatchQuota {
|
|
41
|
-
contract_version: "audit-code-dispatch-quota/v1alpha1";
|
|
48
|
+
contract_version: "audit-code-dispatch-quota/v1alpha1" | "audit-code-dispatch-quota/v1alpha2";
|
|
42
49
|
run_id: string;
|
|
43
50
|
model: string | null;
|
|
44
51
|
resolved_limits: ResolvedLimits;
|
|
@@ -48,6 +55,8 @@ export interface DispatchQuota {
|
|
|
48
55
|
wave_size: number;
|
|
49
56
|
estimated_wave_tokens: number;
|
|
50
57
|
cooldown_until: string | null;
|
|
58
|
+
quota_source_snapshot?: import("./quotaSource.js").QuotaUsageSnapshot | null;
|
|
59
|
+
backoff_state?: BackoffState | null;
|
|
51
60
|
}
|
|
52
61
|
export interface ObservedWaveOutcome {
|
|
53
62
|
concurrency: number;
|
|
@@ -168,7 +168,7 @@ function renderMarkdown(handoff) {
|
|
|
168
168
|
lines.push(`- ${command}`);
|
|
169
169
|
}
|
|
170
170
|
if (handoff.active_review_run) {
|
|
171
|
-
lines.push("- Use next-step so the backend renders either packet dispatch or single-task fallback
|
|
171
|
+
lines.push("- Use next-step so the backend renders either packet dispatch or single-task fallback from CLI flags, session config, environment, or the default single-task path.");
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
if (handoff.active_review_run) {
|
|
@@ -44,6 +44,8 @@ export interface QuotaConfig {
|
|
|
44
44
|
reserved_output_tokens?: number;
|
|
45
45
|
/** Half-life of empirical success/failure evidence in hours (default: 24). */
|
|
46
46
|
empirical_half_life_hours?: number;
|
|
47
|
+
/** Allow the scheduler to try concurrency maxSafe+1 after consecutive successes (default: true). */
|
|
48
|
+
ramp_up_enabled?: boolean;
|
|
47
49
|
/** Hard host ceiling for simultaneously active conversation subagents. */
|
|
48
50
|
host_active_subagent_limit?: number;
|
|
49
51
|
/** Per-model overrides keyed by "provider/model". */
|
|
@@ -63,6 +65,7 @@ export interface SessionConfig {
|
|
|
63
65
|
provider?: ProviderName;
|
|
64
66
|
timeout_ms?: number;
|
|
65
67
|
ui_mode?: SessionUiMode;
|
|
68
|
+
host_can_dispatch_subagents?: boolean;
|
|
66
69
|
subprocess_template?: SubprocessTemplateConfig;
|
|
67
70
|
claude_code?: ClaudeCodeConfig;
|
|
68
71
|
opencode?: OpenCodeConfig;
|
|
@@ -151,6 +151,10 @@ export function validateSessionConfig(value) {
|
|
|
151
151
|
pushIssue(issues, "ui_mode", `ui_mode must be one of: ${Array.from(VALID_UI_MODES).join(", ")}.`);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
if (value.host_can_dispatch_subagents !== undefined &&
|
|
155
|
+
typeof value.host_can_dispatch_subagents !== "boolean") {
|
|
156
|
+
pushIssue(issues, "host_can_dispatch_subagents", "host_can_dispatch_subagents must be a boolean when provided.");
|
|
157
|
+
}
|
|
154
158
|
validateTemplateProviderSection(value.subprocess_template, "subprocess_template", issues, provider === "subprocess-template");
|
|
155
159
|
validateTemplateProviderSection(value.vscode_task, "vscode_task", issues, provider === "vscode-task");
|
|
156
160
|
validateAgentProviderSection(value.claude_code, "claude_code", issues);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "audit-code-dispatch-quota/
|
|
3
|
+
"$id": "audit-code-dispatch-quota/v1alpha2",
|
|
4
4
|
"title": "DispatchQuota",
|
|
5
5
|
"description": "Quota schedule for a prepare-dispatch run. Written beside dispatch-plan.json. Hosts must launch at most wave_size packets per wave, then re-read this file before the next wave to pick up any updated limits.",
|
|
6
6
|
"type": "object",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"properties": {
|
|
21
21
|
"contract_version": {
|
|
22
22
|
"type": "string",
|
|
23
|
-
"
|
|
23
|
+
"enum": ["audit-code-dispatch-quota/v1alpha1", "audit-code-dispatch-quota/v1alpha2"]
|
|
24
24
|
},
|
|
25
25
|
"run_id": {
|
|
26
26
|
"type": "string",
|
|
@@ -97,6 +97,27 @@
|
|
|
97
97
|
"type": ["string", "null"],
|
|
98
98
|
"format": "date-time",
|
|
99
99
|
"description": "If non-null, the host should wait until this timestamp before launching the next wave."
|
|
100
|
+
},
|
|
101
|
+
"quota_source_snapshot": {
|
|
102
|
+
"type": ["object", "null"],
|
|
103
|
+
"description": "Real-time usage snapshot from a QuotaSource, if available.",
|
|
104
|
+
"properties": {
|
|
105
|
+
"remaining_pct": { "type": ["number", "null"] },
|
|
106
|
+
"reset_at": { "type": ["string", "null"], "format": "date-time" },
|
|
107
|
+
"requests_remaining": { "type": ["integer", "null"] },
|
|
108
|
+
"tokens_remaining": { "type": ["integer", "null"] },
|
|
109
|
+
"captured_at": { "type": "string", "format": "date-time" },
|
|
110
|
+
"source": { "type": "string" }
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"backoff_state": {
|
|
114
|
+
"type": ["object", "null"],
|
|
115
|
+
"description": "Exponential backoff state for repeated rate-limit errors.",
|
|
116
|
+
"properties": {
|
|
117
|
+
"consecutive_429_count": { "type": "integer", "minimum": 0 },
|
|
118
|
+
"current_cooldown_ms": { "type": "integer", "minimum": 0 },
|
|
119
|
+
"current_failure_weight": { "type": "number", "minimum": 0 }
|
|
120
|
+
}
|
|
100
121
|
}
|
|
101
122
|
}
|
|
102
123
|
}
|
|
@@ -40,6 +40,11 @@ follow only that prompt. Do not read packet prompts, schemas, command catalogs,
|
|
|
40
40
|
or handoff files unless the current step prompt explicitly instructs you to do
|
|
41
41
|
so.
|
|
42
42
|
|
|
43
|
+
Use MCP tools only as a compatibility adapter when direct shell access to
|
|
44
|
+
`audit-code next-step` is unavailable. The MCP `start_audit` and
|
|
45
|
+
`continue_audit` tools return the same one-step contract; they are not a
|
|
46
|
+
separate orchestration path.
|
|
47
|
+
|
|
43
48
|
When a step prompt tells you to continue, run `audit-code next-step` again and
|
|
44
49
|
follow only the newly returned `prompt_path`.
|
|
45
50
|
|