sequant 2.7.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +18 -2
- package/dist/bin/cli.d.ts +1 -1
- package/dist/bin/cli.js +12 -2
- package/dist/bin/preflight.d.ts +21 -0
- package/dist/bin/preflight.js +45 -0
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +2 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +18 -3
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +330 -57
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/force-push.md +34 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +117 -19
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +9 -6
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +29 -0
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +100 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +24 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +285 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +202 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +287 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/test-quality-checklist.md +272 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +40 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +95 -11
- package/dist/marketplace/external_plugins/sequant/skills/references/shared/framework-gotchas.md +186 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +669 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/references/browser-testing-patterns.md +423 -0
- package/dist/marketplace/external_plugins/sequant/skills/upstream/SKILL.md +419 -0
- package/dist/src/commands/logs.js +6 -1
- package/dist/src/commands/run-display.d.ts +20 -0
- package/dist/src/commands/run-display.js +80 -1
- package/dist/src/commands/stats.js +47 -0
- package/dist/src/lib/assess-collision-detect.d.ts +19 -2
- package/dist/src/lib/assess-collision-detect.js +68 -4
- package/dist/src/lib/cli-ui/run-renderer.js +17 -9
- package/dist/src/lib/errors.d.ts +91 -0
- package/dist/src/lib/errors.js +118 -0
- package/dist/src/lib/manifest.js +1 -17
- package/dist/src/lib/version-check.d.ts +19 -0
- package/dist/src/lib/version-check.js +45 -5
- package/dist/src/lib/workflow/batch-executor.d.ts +13 -0
- package/dist/src/lib/workflow/batch-executor.js +142 -24
- package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/chain-preflight.js +199 -0
- package/dist/src/lib/workflow/chain-resume.d.ts +116 -0
- package/dist/src/lib/workflow/chain-resume.js +166 -0
- package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
- package/dist/src/lib/workflow/dependency-markers.js +79 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +17 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +29 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +136 -8
- package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
- package/dist/src/lib/workflow/error-classifier.js +14 -1
- package/dist/src/lib/workflow/log-writer.d.ts +1 -1
- package/dist/src/lib/workflow/log-writer.js +6 -8
- package/dist/src/lib/workflow/metrics-schema.d.ts +39 -0
- package/dist/src/lib/workflow/metrics-schema.js +16 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +50 -0
- package/dist/src/lib/workflow/phase-executor.js +151 -17
- package/dist/src/lib/workflow/run-log-schema.d.ts +26 -0
- package/dist/src/lib/workflow/run-log-schema.js +52 -1
- package/dist/src/lib/workflow/run-orchestrator.d.ts +14 -0
- package/dist/src/lib/workflow/run-orchestrator.js +291 -30
- package/dist/src/lib/workflow/state-manager.d.ts +1 -0
- package/dist/src/lib/workflow/state-manager.js +6 -0
- package/dist/src/lib/workflow/state-schema.d.ts +3 -0
- package/dist/src/lib/workflow/state-schema.js +7 -0
- package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
- package/dist/src/lib/workflow/status-derivation.js +27 -0
- package/dist/src/lib/workflow/types.d.ts +40 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +43 -1
- package/dist/src/lib/workflow/worktree-manager.js +103 -33
- package/dist/src/mcp/tools/run.d.ts +2 -0
- package/dist/src/mcp/tools/run.js +2 -0
- package/dist/src/ui/tui/theme.d.ts +18 -4
- package/dist/src/ui/tui/theme.js +18 -4
- package/package.json +5 -6
- package/templates/hooks/post-tool.sh +18 -3
- package/templates/hooks/pre-tool.sh +330 -57
- package/templates/scripts/cleanup-worktree.sh +103 -14
- package/templates/skills/_shared/references/force-push.md +34 -0
- package/templates/skills/assess/SKILL.md +117 -19
- package/templates/skills/assess/references/predicted-collision-detection.md +9 -6
- package/templates/skills/exec/SKILL.md +29 -0
- package/templates/skills/fullsolve/SKILL.md +1 -1
- package/templates/skills/loop/SKILL.md +100 -2
- package/templates/skills/qa/SKILL.md +24 -0
- package/templates/skills/qa/references/anti-pattern-detection.md +285 -0
- package/templates/skills/qa/references/call-site-review.md +202 -0
- package/templates/skills/qa/references/quality-gates.md +287 -0
- package/templates/skills/qa/references/test-quality-checklist.md +272 -0
- package/templates/skills/qa/references/testing-requirements.md +40 -0
- package/templates/skills/qa/scripts/quality-checks.sh +95 -11
- package/templates/skills/references/shared/framework-gotchas.md +186 -0
- package/templates/skills/reflect/SKILL.md +27 -13
- package/templates/skills/reflect/references/documentation-tiers.md +80 -68
- package/templates/skills/reflect/references/phase-reflection.md +31 -15
- package/templates/skills/release/SKILL.md +669 -0
- package/templates/skills/spec/references/verification-criteria.md +1 -1
- package/templates/skills/test/references/browser-testing-patterns.md +423 -0
- package/templates/skills/upstream/SKILL.md +419 -0
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
8
|
import { getMcpServersConfig } from "../../system.js";
|
|
9
|
+
import { RateLimitError, BillingError, createRateLimitError, isRateLimitFailureInfo, } from "../../errors.js";
|
|
9
10
|
import { RingBuffer } from "../ring-buffer.js";
|
|
10
11
|
export class ClaudeCodeDriver {
|
|
11
12
|
name = "claude-code";
|
|
@@ -47,6 +48,16 @@ export class ClaudeCodeDriver {
|
|
|
47
48
|
let resultMessage;
|
|
48
49
|
let capturedOutput = "";
|
|
49
50
|
let capturedStderr = "";
|
|
51
|
+
// Structured rate-limit / billing signals captured from the SDK stream
|
|
52
|
+
// (#732). The SDK emits these but sequant previously dropped them on the
|
|
53
|
+
// floor, falling back to regex-on-stderr classification. We keep only the
|
|
54
|
+
// latest *failure-grade* rate-limit info (rejection or billing) so an
|
|
55
|
+
// informational `allowed_warning` event isn't mis-attributed to an
|
|
56
|
+
// unrelated phase failure.
|
|
57
|
+
let rateLimitInfo;
|
|
58
|
+
let assistantError;
|
|
59
|
+
// Last api_retry signal, captured opportunistically for diagnostics.
|
|
60
|
+
let apiRetryError;
|
|
50
61
|
const stderrBuffer = new RingBuffer(50);
|
|
51
62
|
const stdoutBuffer = new RingBuffer(50);
|
|
52
63
|
// Resolve resume token with cwd-safety check.
|
|
@@ -99,7 +110,26 @@ export class ClaudeCodeDriver {
|
|
|
99
110
|
if (message.type === "system" && message.subtype === "init") {
|
|
100
111
|
resultSessionId = message.session_id;
|
|
101
112
|
}
|
|
113
|
+
// Capture structured rate-limit info (#732). Only retain
|
|
114
|
+
// failure-grade events (rejection / billing) so a benign warning
|
|
115
|
+
// doesn't poison the failure path.
|
|
116
|
+
if (message.type === "rate_limit_event" &&
|
|
117
|
+
isRateLimitFailureInfo(message.rate_limit_info)) {
|
|
118
|
+
rateLimitInfo = message.rate_limit_info;
|
|
119
|
+
}
|
|
120
|
+
// Capture api_retry diagnostics (#732, optional). These are transient
|
|
121
|
+
// retries the SDK performs internally; recorded for the structured
|
|
122
|
+
// error fallback when no rate_limit_event/assistant error is present.
|
|
123
|
+
if (message.type === "system" && message.subtype === "api_retry") {
|
|
124
|
+
apiRetryError = message.error;
|
|
125
|
+
}
|
|
102
126
|
if (message.type === "assistant") {
|
|
127
|
+
// Capture the assistant-level error field (#732) — `rate_limit`,
|
|
128
|
+
// `billing_error`, `overloaded`, etc. Previously discarded by the
|
|
129
|
+
// text-only content filter below.
|
|
130
|
+
if (message.error) {
|
|
131
|
+
assistantError = message.error;
|
|
132
|
+
}
|
|
103
133
|
const content = message.message.content;
|
|
104
134
|
const textContent = content
|
|
105
135
|
.filter((c) => c.type === "text" && c.text)
|
|
@@ -124,6 +154,10 @@ export class ClaudeCodeDriver {
|
|
|
124
154
|
// `config.cwd`. `sessionId` is mirrored for one release (#674) so
|
|
125
155
|
// upgraded callers can still drive resume off the deprecated field.
|
|
126
156
|
const resumeHandle = this.buildResumeHandle(resultSessionId, config.cwd);
|
|
157
|
+
// Build a typed error from structured SDK signals (#732). Present only
|
|
158
|
+
// when the stream surfaced a rate-limit/billing failure; otherwise
|
|
159
|
+
// undefined and the executor falls back to stderr-regex classification.
|
|
160
|
+
const structuredError = this.buildStructuredError(rateLimitInfo, assistantError, apiRetryError);
|
|
127
161
|
if (resultMessage) {
|
|
128
162
|
if (resultMessage.subtype === "success") {
|
|
129
163
|
return {
|
|
@@ -135,13 +169,29 @@ export class ClaudeCodeDriver {
|
|
|
135
169
|
stdoutTail: stdoutBuffer.getLines(),
|
|
136
170
|
};
|
|
137
171
|
}
|
|
172
|
+
// Turn-cap is a soft, recoverable outcome, not a hard failure: the
|
|
173
|
+
// agent produced partial work before hitting its `maxTurns` ceiling
|
|
174
|
+
// (turn caps are live on every agent since #484). Warn (not error)
|
|
175
|
+
// and return the partial output flagged `capped` so consumers — the
|
|
176
|
+
// /qa and /exec skills — can treat it as inconclusive/incomplete
|
|
177
|
+
// rather than discarding the work. See #733. Branched out of the
|
|
178
|
+
// error switch below so it never carries a hard `error` string.
|
|
179
|
+
if (resultMessage.subtype === "error_max_turns") {
|
|
180
|
+
config.onStderr?.("⚠️ Agent hit its turn cap (error_max_turns). Returning partial results.\n");
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
capped: true,
|
|
184
|
+
output: capturedOutput,
|
|
185
|
+
sessionId: resultSessionId,
|
|
186
|
+
resumeHandle,
|
|
187
|
+
stderrTail: stderrBuffer.getLines(),
|
|
188
|
+
stdoutTail: stdoutBuffer.getLines(),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
138
191
|
// Handle error subtypes
|
|
139
192
|
let error;
|
|
140
193
|
const errorSubtype = resultMessage.subtype;
|
|
141
|
-
if (errorSubtype === "
|
|
142
|
-
error = "Max turns reached";
|
|
143
|
-
}
|
|
144
|
-
else if (errorSubtype === "error_during_execution") {
|
|
194
|
+
if (errorSubtype === "error_during_execution") {
|
|
145
195
|
error = resultMessage.errors?.join(", ") || "Error during execution";
|
|
146
196
|
}
|
|
147
197
|
else if (errorSubtype === "error_max_budget_usd") {
|
|
@@ -155,7 +205,10 @@ export class ClaudeCodeDriver {
|
|
|
155
205
|
output: capturedOutput,
|
|
156
206
|
sessionId: resultSessionId,
|
|
157
207
|
resumeHandle,
|
|
158
|
-
|
|
208
|
+
// Prefer the structured cause (e.g. "Out of credits") over the
|
|
209
|
+
// generic subtype text when available (#732).
|
|
210
|
+
error: structuredError?.message ?? error,
|
|
211
|
+
structuredError,
|
|
159
212
|
stderrTail: stderrBuffer.getLines(),
|
|
160
213
|
stdoutTail: stdoutBuffer.getLines(),
|
|
161
214
|
};
|
|
@@ -165,7 +218,8 @@ export class ClaudeCodeDriver {
|
|
|
165
218
|
output: capturedOutput,
|
|
166
219
|
sessionId: resultSessionId,
|
|
167
220
|
resumeHandle,
|
|
168
|
-
error: "No result received from Claude",
|
|
221
|
+
error: structuredError?.message ?? "No result received from Claude",
|
|
222
|
+
structuredError,
|
|
169
223
|
stderrTail: stderrBuffer.getLines(),
|
|
170
224
|
stdoutTail: stdoutBuffer.getLines(),
|
|
171
225
|
};
|
|
@@ -174,14 +228,31 @@ export class ClaudeCodeDriver {
|
|
|
174
228
|
clearTimeout(timeoutId);
|
|
175
229
|
const error = err instanceof Error ? err.message : String(err);
|
|
176
230
|
if (error.includes("abort") || error.includes("AbortError")) {
|
|
231
|
+
// A rate limit can manifest as a hang rather than a stream event
|
|
232
|
+
// reaching the result: the SDK stalls, the phase timeout fires, and
|
|
233
|
+
// the abort lands here. Only failure-grade signals are ever captured
|
|
234
|
+
// (see the stream loop), so attaching them cannot mask a genuine
|
|
235
|
+
// timeout — with no signal, `abortStructuredError` is undefined and
|
|
236
|
+
// the bare timeout message survives unchanged (#761 AC-1). Without
|
|
237
|
+
// this, the captured `rateLimitInfo` was discarded and downstream
|
|
238
|
+
// classification saw only `Timeout after Ns`, sending a closed
|
|
239
|
+
// rate-limit window into the full retry + MCP-fallback ladder.
|
|
240
|
+
const abortStructuredError = this.buildStructuredError(rateLimitInfo, assistantError, apiRetryError);
|
|
177
241
|
return {
|
|
178
242
|
success: false,
|
|
179
243
|
output: capturedOutput,
|
|
180
|
-
error:
|
|
244
|
+
error: abortStructuredError?.message ??
|
|
245
|
+
`Timeout after ${config.phaseTimeout}s`,
|
|
246
|
+
structuredError: abortStructuredError,
|
|
181
247
|
stderrTail: stderrBuffer.getLines(),
|
|
182
248
|
stdoutTail: stdoutBuffer.getLines(),
|
|
183
249
|
};
|
|
184
250
|
}
|
|
251
|
+
// If the stream surfaced a failure-grade rate-limit/billing signal before
|
|
252
|
+
// throwing, prefer that typed cause (#732) over the raw thrown message — a
|
|
253
|
+
// mid-stream throw after a *rejected* rate_limit_event is very likely the
|
|
254
|
+
// proximate cause.
|
|
255
|
+
const structuredError = this.buildStructuredError(rateLimitInfo, assistantError, apiRetryError);
|
|
185
256
|
const stderrSuffix = capturedStderr
|
|
186
257
|
? `\nStderr: ${capturedStderr.slice(0, 500)}`
|
|
187
258
|
: "";
|
|
@@ -190,12 +261,69 @@ export class ClaudeCodeDriver {
|
|
|
190
261
|
output: capturedOutput,
|
|
191
262
|
sessionId: resultSessionId,
|
|
192
263
|
resumeHandle: this.buildResumeHandle(resultSessionId, config.cwd),
|
|
193
|
-
error: error + stderrSuffix,
|
|
264
|
+
error: structuredError?.message ?? error + stderrSuffix,
|
|
265
|
+
structuredError,
|
|
194
266
|
stderrTail: stderrBuffer.getLines(),
|
|
195
267
|
stdoutTail: stdoutBuffer.getLines(),
|
|
196
268
|
};
|
|
197
269
|
}
|
|
198
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Derive a typed {@link SequantError} from structured SDK failure signals
|
|
273
|
+
* (#732). Precedence: a captured `rate_limit_event` (richest signal) wins;
|
|
274
|
+
* otherwise the assistant-level `error`; otherwise the last `api_retry`
|
|
275
|
+
* error. Returns undefined when no rate-limit/billing signal was seen, so
|
|
276
|
+
* the executor falls back to stderr-regex classification.
|
|
277
|
+
*
|
|
278
|
+
* Exception: a non-retryable billing failure must never be downgraded to a
|
|
279
|
+
* retryable {@link RateLimitError}. If the `rate_limit_event` was only a
|
|
280
|
+
* transient throttle but the assistant separately reported `billing_error`,
|
|
281
|
+
* the billing cause wins — a retry cannot refill credits, and a
|
|
282
|
+
* RateLimitError would wrongly re-enable the retry / MCP-fallback path. When
|
|
283
|
+
* the `rate_limit_event` is itself a billing failure its richer metadata
|
|
284
|
+
* (`canUserPurchaseCredits`, etc.) is preserved.
|
|
285
|
+
*/
|
|
286
|
+
buildStructuredError(rateLimitInfo, assistantError, apiRetryError) {
|
|
287
|
+
if (rateLimitInfo) {
|
|
288
|
+
const err = createRateLimitError(rateLimitInfo);
|
|
289
|
+
if (err instanceof RateLimitError && assistantError === "billing_error") {
|
|
290
|
+
return new BillingError("Billing error");
|
|
291
|
+
}
|
|
292
|
+
return err;
|
|
293
|
+
}
|
|
294
|
+
return (this.errorFromAssistantError(assistantError) ??
|
|
295
|
+
this.errorFromAssistantError(apiRetryError));
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Map the SDK's assistant/api-retry error enum to a typed error. Only
|
|
299
|
+
* rate-limit / billing variants are mapped; other variants (auth, etc.)
|
|
300
|
+
* return undefined and defer to the existing classification path.
|
|
301
|
+
*
|
|
302
|
+
* The assistant-error channel carries no `resetsAt`/`rateLimitType` (the
|
|
303
|
+
* enum is the whole signal — `SDKAssistantMessageError` is a bare string),
|
|
304
|
+
* so the originating variant is recorded in metadata to keep `rate_limit`
|
|
305
|
+
* and `overloaded` distinguishable downstream (#761 AC-9). With no
|
|
306
|
+
* `resetsAt`, these errors are treated as transient by the executor's
|
|
307
|
+
* window-exhaustion check — the mandatory metadata-absent fallback.
|
|
308
|
+
*/
|
|
309
|
+
errorFromAssistantError(error) {
|
|
310
|
+
switch (error) {
|
|
311
|
+
case "billing_error":
|
|
312
|
+
return new BillingError("Billing error", {
|
|
313
|
+
assistantError: "billing_error",
|
|
314
|
+
});
|
|
315
|
+
case "rate_limit":
|
|
316
|
+
return new RateLimitError("Rate limited", {
|
|
317
|
+
assistantError: "rate_limit",
|
|
318
|
+
});
|
|
319
|
+
case "overloaded":
|
|
320
|
+
return new RateLimitError("API overloaded", {
|
|
321
|
+
assistantError: "overloaded",
|
|
322
|
+
});
|
|
323
|
+
default:
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
199
327
|
buildResumeHandle(token, originCwd) {
|
|
200
328
|
if (!token)
|
|
201
329
|
return undefined;
|
|
@@ -5,8 +5,15 @@
|
|
|
5
5
|
* categories. Exit codes are the primary signal; stderr patterns are secondary.
|
|
6
6
|
*/
|
|
7
7
|
import { SequantError } from "../errors.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* All recognized error categories (kept for backwards compatibility).
|
|
10
|
+
*
|
|
11
|
+
* `rate_limit` / `billing` (#761 AC-6) only arise from the driver's structured
|
|
12
|
+
* errors — `classifyError` never produces them, since stderr text cannot
|
|
13
|
+
* distinguish a window-exhausted limit from a transient 429 (`api_error`).
|
|
14
|
+
* Keep in sync with the inline category enum in `run-log-schema.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const ERROR_CATEGORIES: readonly ["context_overflow", "api_error", "hook_failure", "build_error", "timeout", "rate_limit", "billing", "unknown"];
|
|
10
17
|
export type ErrorCategory = (typeof ERROR_CATEGORIES)[number];
|
|
11
18
|
/**
|
|
12
19
|
* Map from error type name to legacy category string.
|
|
@@ -5,13 +5,22 @@
|
|
|
5
5
|
* categories. Exit codes are the primary signal; stderr patterns are secondary.
|
|
6
6
|
*/
|
|
7
7
|
import { ContextOverflowError, ApiError, HookFailureError, BuildError, TimeoutError, SubprocessError, } from "../errors.js";
|
|
8
|
-
/**
|
|
8
|
+
/**
|
|
9
|
+
* All recognized error categories (kept for backwards compatibility).
|
|
10
|
+
*
|
|
11
|
+
* `rate_limit` / `billing` (#761 AC-6) only arise from the driver's structured
|
|
12
|
+
* errors — `classifyError` never produces them, since stderr text cannot
|
|
13
|
+
* distinguish a window-exhausted limit from a transient 429 (`api_error`).
|
|
14
|
+
* Keep in sync with the inline category enum in `run-log-schema.ts`.
|
|
15
|
+
*/
|
|
9
16
|
export const ERROR_CATEGORIES = [
|
|
10
17
|
"context_overflow",
|
|
11
18
|
"api_error",
|
|
12
19
|
"hook_failure",
|
|
13
20
|
"build_error",
|
|
14
21
|
"timeout",
|
|
22
|
+
"rate_limit",
|
|
23
|
+
"billing",
|
|
15
24
|
"unknown",
|
|
16
25
|
];
|
|
17
26
|
/**
|
|
@@ -30,6 +39,10 @@ export function errorTypeToCategory(error) {
|
|
|
30
39
|
return "build_error";
|
|
31
40
|
case "TimeoutError":
|
|
32
41
|
return "timeout";
|
|
42
|
+
case "RateLimitError":
|
|
43
|
+
return "rate_limit";
|
|
44
|
+
case "BillingError":
|
|
45
|
+
return "billing";
|
|
33
46
|
default:
|
|
34
47
|
return "unknown";
|
|
35
48
|
}
|
|
@@ -99,4 +99,4 @@ export declare class LogWriter {
|
|
|
99
99
|
*
|
|
100
100
|
* Utility function for creating phase logs when you have start/end times.
|
|
101
101
|
*/
|
|
102
|
-
export declare function createPhaseLogFromTiming(phase: Phase, issueNumber: number, startTime: Date, endTime: Date, status: PhaseLog["status"], options?: Partial<Pick<PhaseLog, "error" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "summary" | "commitHash" | "fileDiffStats" | "cacheMetrics" | "errorContext">>): PhaseLog;
|
|
102
|
+
export declare function createPhaseLogFromTiming(phase: Phase, issueNumber: number, startTime: Date, endTime: Date, status: PhaseLog["status"], options?: Partial<Pick<PhaseLog, "error" | "capped" | "iterations" | "filesModified" | "testsRun" | "testsPassed" | "verdict" | "summary" | "commitHash" | "fileDiffStats" | "cacheMetrics" | "errorContext">>): PhaseLog;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import * as fs from "fs";
|
|
17
17
|
import * as path from "path";
|
|
18
18
|
import * as os from "os";
|
|
19
|
-
import { createEmptyRunLog, finalizeRunLog, generateLogFilename, LOG_PATHS, } from "./run-log-schema.js";
|
|
19
|
+
import { createEmptyRunLog, finalizeRunLog, deriveIssueLogStatus, generateLogFilename, LOG_PATHS, } from "./run-log-schema.js";
|
|
20
20
|
import { rotateIfNeeded, DEFAULT_ROTATION_SETTINGS, } from "./log-rotation.js";
|
|
21
21
|
/**
|
|
22
22
|
* Manages writing structured run logs to disk
|
|
@@ -94,13 +94,11 @@ export class LogWriter {
|
|
|
94
94
|
throw new Error(`No active issue #${phaseLog.issueNumber}. Call startIssue() first.`);
|
|
95
95
|
}
|
|
96
96
|
issue.phases = [...(issue.phases ?? []), phaseLog];
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
issue.status = "partial";
|
|
103
|
-
}
|
|
97
|
+
// #766: derive from the latest attempt of each phase (loop excluded) rather
|
|
98
|
+
// than pinning failure/partial forever. A timeout or failure that a later
|
|
99
|
+
// quality-loop iteration recovers from no longer sticks, so the JSON log
|
|
100
|
+
// agrees with the live card and summary table (AC-3/AC-5).
|
|
101
|
+
issue.status = deriveIssueLogStatus(issue.phases);
|
|
104
102
|
if (this.verbose) {
|
|
105
103
|
console.log(`Log phase: ${phaseLog.phase} (${phaseLog.status}) - ${phaseLog.durationSeconds.toFixed(1)}s`);
|
|
106
104
|
}
|
|
@@ -25,6 +25,24 @@ export declare const RunOutcomeSchema: z.ZodEnum<{
|
|
|
25
25
|
failed: "failed";
|
|
26
26
|
}>;
|
|
27
27
|
export type RunOutcome = z.infer<typeof RunOutcomeSchema>;
|
|
28
|
+
/**
|
|
29
|
+
* Bounded-enum classification of the failure that ended a run (#761 AC-7).
|
|
30
|
+
*
|
|
31
|
+
* Sourced from `ERROR_CATEGORIES` so the metric can never carry free text —
|
|
32
|
+
* error *messages* stay excluded per the privacy contract above MetricRunSchema
|
|
33
|
+
* (they could contain sensitive info); a closed enum cannot.
|
|
34
|
+
*/
|
|
35
|
+
export declare const FailureCategorySchema: z.ZodEnum<{
|
|
36
|
+
unknown: "unknown";
|
|
37
|
+
timeout: "timeout";
|
|
38
|
+
context_overflow: "context_overflow";
|
|
39
|
+
api_error: "api_error";
|
|
40
|
+
hook_failure: "hook_failure";
|
|
41
|
+
build_error: "build_error";
|
|
42
|
+
rate_limit: "rate_limit";
|
|
43
|
+
billing: "billing";
|
|
44
|
+
}>;
|
|
45
|
+
export type FailureCategory = z.infer<typeof FailureCategorySchema>;
|
|
28
46
|
/**
|
|
29
47
|
* Available phases (aligned with run-log-schema.ts)
|
|
30
48
|
*/
|
|
@@ -83,6 +101,16 @@ export declare const MetricRunSchema: z.ZodObject<{
|
|
|
83
101
|
duration: z.ZodNumber;
|
|
84
102
|
model: z.ZodString;
|
|
85
103
|
flags: z.ZodArray<z.ZodString>;
|
|
104
|
+
failureCategory: z.ZodOptional<z.ZodEnum<{
|
|
105
|
+
unknown: "unknown";
|
|
106
|
+
timeout: "timeout";
|
|
107
|
+
context_overflow: "context_overflow";
|
|
108
|
+
api_error: "api_error";
|
|
109
|
+
hook_failure: "hook_failure";
|
|
110
|
+
build_error: "build_error";
|
|
111
|
+
rate_limit: "rate_limit";
|
|
112
|
+
billing: "billing";
|
|
113
|
+
}>>;
|
|
86
114
|
metrics: z.ZodObject<{
|
|
87
115
|
tokensUsed: z.ZodNumber;
|
|
88
116
|
filesChanged: z.ZodNumber;
|
|
@@ -123,6 +151,16 @@ export declare const MetricsSchema: z.ZodObject<{
|
|
|
123
151
|
duration: z.ZodNumber;
|
|
124
152
|
model: z.ZodString;
|
|
125
153
|
flags: z.ZodArray<z.ZodString>;
|
|
154
|
+
failureCategory: z.ZodOptional<z.ZodEnum<{
|
|
155
|
+
unknown: "unknown";
|
|
156
|
+
timeout: "timeout";
|
|
157
|
+
context_overflow: "context_overflow";
|
|
158
|
+
api_error: "api_error";
|
|
159
|
+
hook_failure: "hook_failure";
|
|
160
|
+
build_error: "build_error";
|
|
161
|
+
rate_limit: "rate_limit";
|
|
162
|
+
billing: "billing";
|
|
163
|
+
}>>;
|
|
126
164
|
metrics: z.ZodObject<{
|
|
127
165
|
tokensUsed: z.ZodNumber;
|
|
128
166
|
filesChanged: z.ZodNumber;
|
|
@@ -154,6 +192,7 @@ export declare function createMetricRun(options: {
|
|
|
154
192
|
duration: number;
|
|
155
193
|
model?: string;
|
|
156
194
|
flags?: string[];
|
|
195
|
+
failureCategory?: FailureCategory;
|
|
157
196
|
metrics?: Partial<RunMetrics>;
|
|
158
197
|
}): MetricRun;
|
|
159
198
|
/**
|
|
@@ -17,10 +17,19 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import { z } from "zod";
|
|
20
|
+
import { ERROR_CATEGORIES } from "./error-classifier.js";
|
|
20
21
|
/**
|
|
21
22
|
* Outcome of a workflow run
|
|
22
23
|
*/
|
|
23
24
|
export const RunOutcomeSchema = z.enum(["success", "partial", "failed"]);
|
|
25
|
+
/**
|
|
26
|
+
* Bounded-enum classification of the failure that ended a run (#761 AC-7).
|
|
27
|
+
*
|
|
28
|
+
* Sourced from `ERROR_CATEGORIES` so the metric can never carry free text —
|
|
29
|
+
* error *messages* stay excluded per the privacy contract above MetricRunSchema
|
|
30
|
+
* (they could contain sensitive info); a closed enum cannot.
|
|
31
|
+
*/
|
|
32
|
+
export const FailureCategorySchema = z.enum(ERROR_CATEGORIES);
|
|
24
33
|
/**
|
|
25
34
|
* Available phases (aligned with run-log-schema.ts)
|
|
26
35
|
*/
|
|
@@ -81,6 +90,12 @@ export const MetricRunSchema = z.object({
|
|
|
81
90
|
model: z.string(),
|
|
82
91
|
/** CLI flags used (e.g., ["--chain", "--sequential"]) */
|
|
83
92
|
flags: z.array(z.string()),
|
|
93
|
+
/**
|
|
94
|
+
* Category of the failure that ended the run (#761 AC-7). Optional and
|
|
95
|
+
* enum-only; absent on success and on records written before this field
|
|
96
|
+
* existed (additive — no `version` bump required).
|
|
97
|
+
*/
|
|
98
|
+
failureCategory: FailureCategorySchema.optional(),
|
|
84
99
|
/** Aggregate metrics */
|
|
85
100
|
metrics: RunMetricsSchema,
|
|
86
101
|
});
|
|
@@ -121,6 +136,7 @@ export function createMetricRun(options) {
|
|
|
121
136
|
duration: options.duration,
|
|
122
137
|
model: options.model ?? "unknown",
|
|
123
138
|
flags: options.flags ?? [],
|
|
139
|
+
failureCategory: options.failureCategory,
|
|
124
140
|
metrics: {
|
|
125
141
|
tokensUsed: options.metrics?.tokensUsed ?? 0,
|
|
126
142
|
filesChanged: options.metrics?.filesChanged ?? 0,
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* });
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
|
-
import { type Metrics, type MetricRun, type MetricPhase, type RunOutcome, type RunMetrics } from "./metrics-schema.js";
|
|
23
|
+
import { type Metrics, type MetricRun, type MetricPhase, type RunOutcome, type RunMetrics, type FailureCategory } from "./metrics-schema.js";
|
|
24
24
|
export interface MetricsWriterOptions {
|
|
25
25
|
/** Path to metrics file (default: .sequant/metrics.json) */
|
|
26
26
|
metricsPath?: string;
|
|
@@ -69,6 +69,7 @@ export declare class MetricsWriter {
|
|
|
69
69
|
duration: number;
|
|
70
70
|
model?: string;
|
|
71
71
|
flags?: string[];
|
|
72
|
+
failureCategory?: FailureCategory;
|
|
72
73
|
metrics?: Partial<RunMetrics>;
|
|
73
74
|
}): Promise<MetricRun>;
|
|
74
75
|
/**
|
|
@@ -11,6 +11,7 @@ import { ShutdownManager } from "../shutdown.js";
|
|
|
11
11
|
import { Phase, ExecutionConfig, PhaseResult, QaVerdict, PhasePauseHandle } from "./types.js";
|
|
12
12
|
import type { QaSummary } from "./run-log-schema.js";
|
|
13
13
|
import type { AgentPhaseResult, ResumeHandle } from "./drivers/index.js";
|
|
14
|
+
import { SequantError } from "../errors.js";
|
|
14
15
|
/**
|
|
15
16
|
* Leading + trailing throttle. Fires the wrapped callback immediately on the
|
|
16
17
|
* first call, drops subsequent calls that arrive inside `intervalMs` but
|
|
@@ -35,6 +36,37 @@ export declare function createThrottledReporter(fn: (text: string) => void, inte
|
|
|
35
36
|
export declare const SPEC_RETRY_BACKOFF_MS: number;
|
|
36
37
|
/** @internal Exported for testing only */
|
|
37
38
|
export declare const SPEC_EXTRA_RETRIES: number;
|
|
39
|
+
/**
|
|
40
|
+
* A rate limit whose window resets further out than this is treated as
|
|
41
|
+
* exhausted rather than transient (#761 AC-2): no retry can succeed inside a
|
|
42
|
+
* closed window, so consuming cold-start retries (each burning up to a full
|
|
43
|
+
* `phaseTimeout`) only delays the labeled halt. Five minutes comfortably
|
|
44
|
+
* exceeds any backoff this executor performs while staying far below the
|
|
45
|
+
* five-hour/seven-day windows the check exists to catch.
|
|
46
|
+
*
|
|
47
|
+
* @internal Exported for testing only
|
|
48
|
+
*/
|
|
49
|
+
export declare const RATE_LIMIT_WINDOW_SKIP_THRESHOLD_MS: number;
|
|
50
|
+
/**
|
|
51
|
+
* Base backoff for transient rate-limit retries (#761 AC-4), doubled per
|
|
52
|
+
* attempt (5s, 10s). Same scale as `SPEC_RETRY_BACKOFF_MS` — long enough to
|
|
53
|
+
* outlive a momentary throttle, short enough to be negligible next to a
|
|
54
|
+
* phase's runtime.
|
|
55
|
+
*
|
|
56
|
+
* @internal Exported for testing only
|
|
57
|
+
*/
|
|
58
|
+
export declare const RATE_LIMIT_RETRY_BACKOFF_MS = 5000;
|
|
59
|
+
/**
|
|
60
|
+
* True when a failure is a rate limit whose reset lies beyond
|
|
61
|
+
* {@link RATE_LIMIT_WINDOW_SKIP_THRESHOLD_MS} — i.e. window exhaustion, not a
|
|
62
|
+
* transient throttle. Metadata-absent rate limits (the assistant-error channel
|
|
63
|
+
* carries no `resetsAt`, see #761 AC-9) return false and fall through to the
|
|
64
|
+
* transient path: with no timing signal, retry-with-backoff is the safe
|
|
65
|
+
* default, skipping all retries is not.
|
|
66
|
+
*
|
|
67
|
+
* @internal Exported for testing only
|
|
68
|
+
*/
|
|
69
|
+
export declare function isWindowExhaustedRateLimit(error: SequantError | undefined, now?: number): boolean;
|
|
38
70
|
export declare function parseQaVerdict(output: string): QaVerdict | null;
|
|
39
71
|
/**
|
|
40
72
|
* Parse condensed QA summary from QA phase output (#434).
|
|
@@ -111,6 +143,24 @@ export declare function mapAgentSuccessToPhaseResult(phase: Phase, agentResult:
|
|
|
111
143
|
sessionId?: string;
|
|
112
144
|
resumeHandle?: ResumeHandle;
|
|
113
145
|
};
|
|
146
|
+
/**
|
|
147
|
+
* Map a failed driver result to a `PhaseResult`.
|
|
148
|
+
*
|
|
149
|
+
* Symmetric to {@link mapAgentSuccessToPhaseResult}; extracted so the
|
|
150
|
+
* failure-path mapping (notably the #739 capped/output gating) is unit-testable
|
|
151
|
+
* without spawning a driver.
|
|
152
|
+
*
|
|
153
|
+
* `output` is propagated **only** for a capped phase (#739): a capped result is
|
|
154
|
+
* incomplete-but-not-hard-failed, so its partial work must survive downstream.
|
|
155
|
+
* A genuine (non-capped) failure keeps the historical behaviour of dropping
|
|
156
|
+
* `output`, leaving the `/loop` fix-context (`formatFailureContext`) unchanged.
|
|
157
|
+
*
|
|
158
|
+
* @internal Exported for testing only
|
|
159
|
+
*/
|
|
160
|
+
export declare function mapAgentFailureToPhaseResult(phase: Phase, agentResult: AgentPhaseResult, durationSeconds: number): PhaseResult & {
|
|
161
|
+
sessionId?: string;
|
|
162
|
+
resumeHandle?: ResumeHandle;
|
|
163
|
+
};
|
|
114
164
|
/**
|
|
115
165
|
* Get the prompt for a phase with the issue number substituted.
|
|
116
166
|
* Selects self-contained prompts for non-Claude agents.
|