@hegemonart/get-design-done 1.20.0 → 1.22.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 +9 -12
- package/.claude-plugin/plugin.json +8 -31
- package/CHANGELOG.md +200 -0
- package/README.md +48 -7
- package/bin/gdd-sdk +55 -0
- package/hooks/_hook-emit.js +81 -0
- package/hooks/gdd-bash-guard.js +8 -0
- package/hooks/gdd-decision-injector.js +2 -0
- package/hooks/gdd-protected-paths.js +8 -0
- package/hooks/gdd-trajectory-capture.js +64 -0
- package/hooks/hooks.json +9 -0
- package/package.json +19 -47
- package/reference/codex-tools.md +53 -0
- package/reference/gemini-tools.md +53 -0
- package/reference/registry.json +14 -0
- package/scripts/cli/gdd-events.mjs +283 -0
- package/scripts/e2e/run-headless.ts +514 -0
- package/scripts/lib/cli/commands/audit.ts +382 -0
- package/scripts/lib/cli/commands/init.ts +217 -0
- package/scripts/lib/cli/commands/query.ts +329 -0
- package/scripts/lib/cli/commands/run.ts +656 -0
- package/scripts/lib/cli/commands/stage.ts +468 -0
- package/scripts/lib/cli/index.ts +167 -0
- package/scripts/lib/cli/parse-args.ts +336 -0
- package/scripts/lib/connection-probe/index.cjs +263 -0
- package/scripts/lib/context-engine/index.ts +116 -0
- package/scripts/lib/context-engine/manifest.ts +69 -0
- package/scripts/lib/context-engine/truncate.ts +282 -0
- package/scripts/lib/context-engine/types.ts +59 -0
- package/scripts/lib/discuss-parallel-runner/aggregator.ts +448 -0
- package/scripts/lib/discuss-parallel-runner/discussants.ts +430 -0
- package/scripts/lib/discuss-parallel-runner/index.ts +223 -0
- package/scripts/lib/discuss-parallel-runner/types.ts +184 -0
- package/scripts/lib/event-chain.cjs +177 -0
- package/scripts/lib/event-stream/index.ts +31 -1
- package/scripts/lib/event-stream/reader.ts +139 -0
- package/scripts/lib/event-stream/types.ts +155 -1
- package/scripts/lib/event-stream/writer.ts +65 -8
- package/scripts/lib/explore-parallel-runner/index.ts +294 -0
- package/scripts/lib/explore-parallel-runner/mappers.ts +290 -0
- package/scripts/lib/explore-parallel-runner/synthesizer.ts +295 -0
- package/scripts/lib/explore-parallel-runner/types.ts +139 -0
- package/scripts/lib/harness/detect.ts +90 -0
- package/scripts/lib/harness/index.ts +64 -0
- package/scripts/lib/harness/tool-map.ts +142 -0
- package/scripts/lib/init-runner/index.ts +396 -0
- package/scripts/lib/init-runner/researchers.ts +245 -0
- package/scripts/lib/init-runner/scaffold.ts +224 -0
- package/scripts/lib/init-runner/synthesizer.ts +224 -0
- package/scripts/lib/init-runner/types.ts +143 -0
- package/scripts/lib/logger/index.ts +251 -0
- package/scripts/lib/logger/sinks.ts +269 -0
- package/scripts/lib/logger/types.ts +110 -0
- package/scripts/lib/pipeline-runner/human-gate.ts +134 -0
- package/scripts/lib/pipeline-runner/index.ts +527 -0
- package/scripts/lib/pipeline-runner/stage-handlers.ts +339 -0
- package/scripts/lib/pipeline-runner/state-machine.ts +144 -0
- package/scripts/lib/pipeline-runner/types.ts +183 -0
- package/scripts/lib/redact.cjs +122 -0
- package/scripts/lib/session-runner/errors.ts +406 -0
- package/scripts/lib/session-runner/index.ts +715 -0
- package/scripts/lib/session-runner/transcript.ts +189 -0
- package/scripts/lib/session-runner/types.ts +144 -0
- package/scripts/lib/tool-scoping/index.ts +219 -0
- package/scripts/lib/tool-scoping/parse-agent-tools.ts +207 -0
- package/scripts/lib/tool-scoping/stage-scopes.ts +139 -0
- package/scripts/lib/tool-scoping/types.ts +77 -0
- package/scripts/lib/trajectory/index.cjs +126 -0
- package/scripts/lib/transports/ws.cjs +179 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* redact.cjs — secret scrubbing for event-stream payloads (Plan 22-02).
|
|
3
|
+
*
|
|
4
|
+
* Deep-walks a value, replacing any string that matches a known secret
|
|
5
|
+
* pattern with a `[REDACTED:<type>]` placeholder. Non-mutating — returns
|
|
6
|
+
* a new structure; the input is not modified.
|
|
7
|
+
*
|
|
8
|
+
* Called from `event-stream/writer.ts` at serialize time so every event
|
|
9
|
+
* that hits disk (and every bus subscriber) sees the redacted form.
|
|
10
|
+
*
|
|
11
|
+
* Patterns are intentionally conservative: false-positives on redaction
|
|
12
|
+
* are low-cost (logged telemetry becomes slightly harder to read); false-
|
|
13
|
+
* negatives (real secrets leaking) are high-cost. When in doubt, match.
|
|
14
|
+
*
|
|
15
|
+
* Adding a pattern: append an entry to `PATTERNS` with a stable `type`
|
|
16
|
+
* key. The type string is the label emitted into the placeholder.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
/** @type {Array<{type: string, re: RegExp}>} */
|
|
22
|
+
const PATTERNS = [
|
|
23
|
+
// PEM first — must redact before generic base64 patterns would hit.
|
|
24
|
+
{
|
|
25
|
+
type: 'pem',
|
|
26
|
+
re: /-----BEGIN [A-Z ]+-----[\s\S]+?-----END [A-Z ]+-----/g,
|
|
27
|
+
},
|
|
28
|
+
// JWT — 3 dot-separated base64url segments, beginning with eyJ.
|
|
29
|
+
{
|
|
30
|
+
type: 'jwt',
|
|
31
|
+
re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
32
|
+
},
|
|
33
|
+
// Anthropic API keys (sk-ant-…) — matched before generic sk- to win.
|
|
34
|
+
{
|
|
35
|
+
type: 'anthropic',
|
|
36
|
+
re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g,
|
|
37
|
+
},
|
|
38
|
+
// Stripe live secret key.
|
|
39
|
+
{
|
|
40
|
+
type: 'stripe',
|
|
41
|
+
re: /\bsk_live_[A-Za-z0-9]{20,}\b/g,
|
|
42
|
+
},
|
|
43
|
+
// Slack tokens — xoxb/xoxp/xoxa/xoxr/xoxs.
|
|
44
|
+
{
|
|
45
|
+
type: 'slack',
|
|
46
|
+
re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
|
|
47
|
+
},
|
|
48
|
+
// GitHub personal access token.
|
|
49
|
+
{
|
|
50
|
+
type: 'github_pat',
|
|
51
|
+
re: /\bghp_[A-Za-z0-9]{36,}\b/g,
|
|
52
|
+
},
|
|
53
|
+
// AWS access key ID.
|
|
54
|
+
{
|
|
55
|
+
type: 'aws',
|
|
56
|
+
re: /\bAKIA[0-9A-Z]{16}\b/g,
|
|
57
|
+
},
|
|
58
|
+
// Generic OpenAI-style sk-… (last in the sk-* family — lower priority
|
|
59
|
+
// than anthropic/stripe which start with `sk_live_`/`sk-ant-`).
|
|
60
|
+
{
|
|
61
|
+
type: 'sk',
|
|
62
|
+
re: /\bsk-[A-Za-z0-9_-]{20,}\b/g,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Redact every secret-shaped substring in `s`, returning the scrubbed
|
|
68
|
+
* string. Patterns are applied in `PATTERNS` order — more-specific
|
|
69
|
+
* patterns (anthropic, stripe) first so they win over the generic
|
|
70
|
+
* `sk-` catch-all.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} s
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
function redactString(s) {
|
|
76
|
+
if (typeof s !== 'string' || s.length < 10) return s;
|
|
77
|
+
let out = s;
|
|
78
|
+
for (const { type, re } of PATTERNS) {
|
|
79
|
+
re.lastIndex = 0; // safety: `g` flag carries state across calls
|
|
80
|
+
out = out.replace(re, `[REDACTED:${type}]`);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Deep-walk `value`, redacting every string encountered. Arrays and
|
|
87
|
+
* plain objects recurse; everything else returns unchanged.
|
|
88
|
+
*
|
|
89
|
+
* Cycle-safe via a WeakSet.
|
|
90
|
+
*
|
|
91
|
+
* @param {unknown} value
|
|
92
|
+
* @param {WeakSet<object>} [seen]
|
|
93
|
+
* @returns {unknown}
|
|
94
|
+
*/
|
|
95
|
+
function redact(value, seen) {
|
|
96
|
+
if (value === null || value === undefined) return value;
|
|
97
|
+
if (typeof value === 'string') return redactString(value);
|
|
98
|
+
if (typeof value !== 'object') return value;
|
|
99
|
+
|
|
100
|
+
const visited = seen ?? new WeakSet();
|
|
101
|
+
if (visited.has(/** @type {object} */ (value))) return value;
|
|
102
|
+
visited.add(/** @type {object} */ (value));
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
return value.map((v) => redact(v, visited));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Plain object. Don't try to preserve class instances — event payloads
|
|
109
|
+
// are expected to be JSON-shaped bags.
|
|
110
|
+
/** @type {Record<string, unknown>} */
|
|
111
|
+
const out = {};
|
|
112
|
+
for (const key of Object.keys(/** @type {object} */ (value))) {
|
|
113
|
+
out[key] = redact(/** @type {Record<string, unknown>} */ (value)[key], visited);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
redact,
|
|
120
|
+
redactString,
|
|
121
|
+
PATTERNS,
|
|
122
|
+
};
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
// scripts/lib/session-runner/errors.ts — map Anthropic Agent SDK
|
|
2
|
+
// errors onto the gdd-errors taxonomy (Plan 21-01 Task 3).
|
|
3
|
+
//
|
|
4
|
+
// The SDK throws a variety of shapes — typed APIError subclasses,
|
|
5
|
+
// plain Error objects, AbortError from node:dom, and occasionally
|
|
6
|
+
// unwrapped provider payloads. This module normalizes any of those
|
|
7
|
+
// into:
|
|
8
|
+
//
|
|
9
|
+
// * a GDDError subclass instance (ValidationError / StateConflictError /
|
|
10
|
+
// OperationFailedError) for the caller to surface in SessionResult.error
|
|
11
|
+
// * a `retryable` flag the run-loop uses to decide whether to re-invoke
|
|
12
|
+
// `query()`
|
|
13
|
+
// * a `backoff_hint_ms` the run-loop may honor instead of the baseline
|
|
14
|
+
// jittered backoff (server-provided retry-after wins when larger)
|
|
15
|
+
//
|
|
16
|
+
// Classification rules (evaluated top to bottom; first match wins):
|
|
17
|
+
// 1. rate_limit_error / message /rate.?limit/i → StateConflictError / retryable
|
|
18
|
+
// 2. overloaded_error / message /overloaded/i → StateConflictError / retryable
|
|
19
|
+
// 3. authentication_error → ValidationError / NOT retryable
|
|
20
|
+
// 4. permission_error → ValidationError / NOT retryable
|
|
21
|
+
// 5. context length / overflow / token-limit message → OperationFailedError / NOT retryable
|
|
22
|
+
// 6. invalid_request_error with schema shape → ValidationError / NOT retryable
|
|
23
|
+
// 7. api_error (5xx surface) → OperationFailedError / retryable
|
|
24
|
+
// 8. AbortError → OperationFailedError / NOT retryable
|
|
25
|
+
// 9. Transport errno (ECONNRESET etc. via error-classifier) → OperationFailedError / retryable
|
|
26
|
+
// 10. Fallthrough → OperationFailedError / NOT retryable
|
|
27
|
+
//
|
|
28
|
+
// Rule 5 is before rule 6 because context-overflow is shaped as
|
|
29
|
+
// invalid_request_error in some SDK surfaces; we want the more specific
|
|
30
|
+
// OperationFailedError classification rather than ValidationError.
|
|
31
|
+
|
|
32
|
+
import { createRequire } from 'node:module';
|
|
33
|
+
import { existsSync } from 'node:fs';
|
|
34
|
+
import { isAbsolute, join, resolve, dirname } from 'node:path';
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
ValidationError,
|
|
38
|
+
OperationFailedError,
|
|
39
|
+
StateConflictError,
|
|
40
|
+
type GDDError,
|
|
41
|
+
} from '../gdd-errors/index.ts';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build an absolute path to a repo-root-relative file. We can't use
|
|
45
|
+
* `import.meta.url` here because tsc's Node16 module mode classifies
|
|
46
|
+
* this .ts file as CommonJS output for typecheck purposes even though
|
|
47
|
+
* it actually runs as ESM under `--experimental-strip-types`. Instead
|
|
48
|
+
* we locate the repo root by walking up from `process.cwd()` until we
|
|
49
|
+
* find `package.json`, memoize the result, and resolve relative paths
|
|
50
|
+
* against that anchor. This survives cwd changes during test runs
|
|
51
|
+
* (sandbox chdir) because we resolve once at module load time.
|
|
52
|
+
*/
|
|
53
|
+
function findRepoRoot(): string {
|
|
54
|
+
let dir = process.cwd();
|
|
55
|
+
for (let i = 0; i < 8; i++) {
|
|
56
|
+
if (existsSync(join(dir, 'package.json'))) return dir;
|
|
57
|
+
const parent = dirname(dir);
|
|
58
|
+
if (parent === dir) break;
|
|
59
|
+
dir = parent;
|
|
60
|
+
}
|
|
61
|
+
return process.cwd();
|
|
62
|
+
}
|
|
63
|
+
const REPO_ROOT = findRepoRoot();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build a `createRequire` loader anchored to the repo root. From this
|
|
67
|
+
* loader we resolve our `.cjs` siblings via absolute path strings.
|
|
68
|
+
*/
|
|
69
|
+
const nodeRequire = createRequire(join(REPO_ROOT, 'package.json'));
|
|
70
|
+
const transportClassifier = nodeRequire(
|
|
71
|
+
resolve(REPO_ROOT, 'scripts/lib/error-classifier.cjs'),
|
|
72
|
+
) as {
|
|
73
|
+
classify: (err: unknown) => {
|
|
74
|
+
reason: string;
|
|
75
|
+
retryable: boolean;
|
|
76
|
+
suggestedAction: string;
|
|
77
|
+
raw: unknown;
|
|
78
|
+
};
|
|
79
|
+
FailoverReason: {
|
|
80
|
+
readonly RATE_LIMITED: 'rate_limited';
|
|
81
|
+
readonly CONTEXT_OVERFLOW: 'context_overflow';
|
|
82
|
+
readonly AUTH_ERROR: 'auth_error';
|
|
83
|
+
readonly NETWORK_TRANSIENT: 'network_transient';
|
|
84
|
+
readonly NETWORK_PERMANENT: 'network_permanent';
|
|
85
|
+
readonly TOOL_NOT_FOUND: 'tool_not_found';
|
|
86
|
+
readonly VALIDATION: 'validation';
|
|
87
|
+
readonly UNKNOWN: 'unknown';
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Return type of {@link mapSdkError}. */
|
|
92
|
+
export interface MappedSdkError {
|
|
93
|
+
/** A GDDError subclass instance (ValidationError / StateConflictError / OperationFailedError). */
|
|
94
|
+
gddError: GDDError;
|
|
95
|
+
/** Caller may retry with backoff iff `true`. */
|
|
96
|
+
retryable: boolean;
|
|
97
|
+
/** Server-provided hint in ms (retry-after header or similar); 0 when unknown. */
|
|
98
|
+
backoff_hint_ms: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Extract `error.type` from Anthropic-shaped errors. Robust to missing keys. */
|
|
102
|
+
function sdkType(err: unknown): string {
|
|
103
|
+
if (err === null || err === undefined || typeof err !== 'object') return '';
|
|
104
|
+
// Direct property (SDK's APIError class exposes `type`).
|
|
105
|
+
const direct = (err as { type?: unknown }).type;
|
|
106
|
+
if (typeof direct === 'string') return direct;
|
|
107
|
+
// Nested `error.type` (raw API response shape).
|
|
108
|
+
const nested = (err as { error?: unknown }).error;
|
|
109
|
+
if (nested !== null && typeof nested === 'object') {
|
|
110
|
+
const t = (nested as { type?: unknown }).type;
|
|
111
|
+
if (typeof t === 'string') return t;
|
|
112
|
+
}
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Extract a message string. Handles strings / Error / plain objects. */
|
|
117
|
+
function sdkMessage(err: unknown): string {
|
|
118
|
+
if (err === null || err === undefined) return '';
|
|
119
|
+
if (typeof err === 'string') return err;
|
|
120
|
+
if (err instanceof Error) return err.message;
|
|
121
|
+
if (typeof err === 'object') {
|
|
122
|
+
const m = (err as { message?: unknown }).message;
|
|
123
|
+
if (typeof m === 'string') return m;
|
|
124
|
+
// Anthropic raw shape: { error: { message } }.
|
|
125
|
+
const nested = (err as { error?: unknown }).error;
|
|
126
|
+
if (nested !== null && typeof nested === 'object') {
|
|
127
|
+
const em = (nested as { message?: unknown }).message;
|
|
128
|
+
if (typeof em === 'string') return em;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Extract numeric HTTP status. */
|
|
135
|
+
function sdkStatus(err: unknown): number | null {
|
|
136
|
+
if (err === null || err === undefined || typeof err !== 'object') return null;
|
|
137
|
+
const direct = (err as { status?: unknown }).status;
|
|
138
|
+
if (typeof direct === 'number' && Number.isFinite(direct)) return direct;
|
|
139
|
+
const code = (err as { statusCode?: unknown }).statusCode;
|
|
140
|
+
if (typeof code === 'number' && Number.isFinite(code)) return code;
|
|
141
|
+
const resp = (err as { response?: unknown }).response;
|
|
142
|
+
if (resp !== null && typeof resp === 'object') {
|
|
143
|
+
const s = (resp as { status?: unknown }).status;
|
|
144
|
+
if (typeof s === 'number' && Number.isFinite(s)) return s;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Extract a retry-after hint in ms. Checks the common locations:
|
|
151
|
+
* 1. `err.retryAfter` (typed SDK field, seconds)
|
|
152
|
+
* 2. `err.headers['retry-after']` (seconds or HTTP-date)
|
|
153
|
+
* 3. `err.response.headers['retry-after']`
|
|
154
|
+
*
|
|
155
|
+
* Returns 0 when nothing usable is present.
|
|
156
|
+
*/
|
|
157
|
+
function retryAfterMs(err: unknown): number {
|
|
158
|
+
if (err === null || err === undefined || typeof err !== 'object') return 0;
|
|
159
|
+
|
|
160
|
+
// 1. Direct numeric field.
|
|
161
|
+
const direct = (err as { retryAfter?: unknown }).retryAfter;
|
|
162
|
+
if (typeof direct === 'number' && Number.isFinite(direct) && direct >= 0) {
|
|
163
|
+
return Math.floor(direct * 1000);
|
|
164
|
+
}
|
|
165
|
+
if (typeof direct === 'string') {
|
|
166
|
+
const parsed = parseRetryAfter(direct);
|
|
167
|
+
if (parsed !== null) return parsed;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 2. Header bag at top level.
|
|
171
|
+
const headers = (err as { headers?: unknown }).headers;
|
|
172
|
+
const fromHeaders = retryAfterFromHeaders(headers);
|
|
173
|
+
if (fromHeaders > 0) return fromHeaders;
|
|
174
|
+
|
|
175
|
+
// 3. Header bag under .response.
|
|
176
|
+
const resp = (err as { response?: unknown }).response;
|
|
177
|
+
if (resp !== null && typeof resp === 'object') {
|
|
178
|
+
const respHeaders = (resp as { headers?: unknown }).headers;
|
|
179
|
+
const fromResp = retryAfterFromHeaders(respHeaders);
|
|
180
|
+
if (fromResp > 0) return fromResp;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function retryAfterFromHeaders(headers: unknown): number {
|
|
187
|
+
if (headers === null || headers === undefined) return 0;
|
|
188
|
+
let raw: unknown;
|
|
189
|
+
if (typeof (headers as { get?: unknown }).get === 'function') {
|
|
190
|
+
raw = (headers as { get: (name: string) => unknown }).get('retry-after');
|
|
191
|
+
} else if (headers instanceof Map) {
|
|
192
|
+
for (const [k, v] of headers.entries()) {
|
|
193
|
+
if (typeof k === 'string' && k.toLowerCase() === 'retry-after') {
|
|
194
|
+
raw = v;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} else if (typeof headers === 'object') {
|
|
199
|
+
for (const k of Object.keys(headers as Record<string, unknown>)) {
|
|
200
|
+
if (k.toLowerCase() === 'retry-after') {
|
|
201
|
+
raw = (headers as Record<string, unknown>)[k];
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (raw === undefined || raw === null) return 0;
|
|
207
|
+
const parsed = parseRetryAfter(String(raw));
|
|
208
|
+
return parsed ?? 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function parseRetryAfter(v: string): number | null {
|
|
212
|
+
const trimmed = v.trim();
|
|
213
|
+
if (trimmed === '') return null;
|
|
214
|
+
if (/^\d+$/.test(trimmed)) {
|
|
215
|
+
const secs = Number(trimmed);
|
|
216
|
+
if (Number.isFinite(secs) && secs >= 0) return Math.floor(secs * 1000);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
const t = Date.parse(trimmed);
|
|
220
|
+
if (Number.isFinite(t)) {
|
|
221
|
+
const delta = t - Date.now();
|
|
222
|
+
return delta > 0 ? delta : 0;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Detect schema-shaped invalid_request_error (has `.type === 'invalid_request_error'`). */
|
|
228
|
+
function isInvalidRequestShape(err: unknown): boolean {
|
|
229
|
+
return sdkType(err) === 'invalid_request_error';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Detect context-length / overflow / token-limit messages. */
|
|
233
|
+
function isContextOverflow(err: unknown): boolean {
|
|
234
|
+
const msg = sdkMessage(err).toLowerCase();
|
|
235
|
+
if (msg === '') return false;
|
|
236
|
+
return (
|
|
237
|
+
/context.?length/.test(msg) ||
|
|
238
|
+
/context.?overflow/.test(msg) ||
|
|
239
|
+
/context.?window/.test(msg) ||
|
|
240
|
+
/token.?limit/.test(msg) ||
|
|
241
|
+
/prompt is too long/.test(msg) ||
|
|
242
|
+
/maximum context length/.test(msg) ||
|
|
243
|
+
/context_length_exceeded/.test(msg)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Detect AbortError from node:events or DOM abort signals. */
|
|
248
|
+
function isAbortError(err: unknown): boolean {
|
|
249
|
+
if (err === null || err === undefined || typeof err !== 'object') return false;
|
|
250
|
+
const name = (err as { name?: unknown }).name;
|
|
251
|
+
return name === 'AbortError';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Map an error thrown/returned by @anthropic-ai/claude-agent-sdk into the
|
|
256
|
+
* gdd-errors taxonomy.
|
|
257
|
+
*
|
|
258
|
+
* See the rules table at the top of this file for the decision tree.
|
|
259
|
+
*/
|
|
260
|
+
export function mapSdkError(err: unknown): MappedSdkError {
|
|
261
|
+
const type = sdkType(err);
|
|
262
|
+
const msg = sdkMessage(err);
|
|
263
|
+
const status = sdkStatus(err);
|
|
264
|
+
const hint = retryAfterMs(err);
|
|
265
|
+
|
|
266
|
+
const context: Record<string, unknown> = { sdkType: type || null };
|
|
267
|
+
if (msg !== '' && err instanceof Error && err.name !== 'Error') {
|
|
268
|
+
context['errorName'] = err.name;
|
|
269
|
+
}
|
|
270
|
+
if (status !== null) context['status'] = status;
|
|
271
|
+
|
|
272
|
+
// 1. rate_limit_error.
|
|
273
|
+
if (type === 'rate_limit_error' || /rate.?limit/i.test(msg) || status === 429) {
|
|
274
|
+
return {
|
|
275
|
+
gddError: new StateConflictError(
|
|
276
|
+
msg || 'rate limited by provider',
|
|
277
|
+
'RATE_LIMITED',
|
|
278
|
+
context,
|
|
279
|
+
),
|
|
280
|
+
retryable: true,
|
|
281
|
+
backoff_hint_ms: hint,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 2. overloaded_error.
|
|
286
|
+
if (type === 'overloaded_error' || /overloaded/i.test(msg)) {
|
|
287
|
+
return {
|
|
288
|
+
gddError: new StateConflictError(
|
|
289
|
+
msg || 'provider overloaded',
|
|
290
|
+
'OVERLOADED',
|
|
291
|
+
context,
|
|
292
|
+
),
|
|
293
|
+
retryable: true,
|
|
294
|
+
backoff_hint_ms: hint,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 3. authentication_error.
|
|
299
|
+
if (type === 'authentication_error' || status === 401) {
|
|
300
|
+
return {
|
|
301
|
+
gddError: new ValidationError(
|
|
302
|
+
msg || 'authentication failed; check ANTHROPIC_API_KEY',
|
|
303
|
+
'AUTH_ERROR',
|
|
304
|
+
context,
|
|
305
|
+
),
|
|
306
|
+
retryable: false,
|
|
307
|
+
backoff_hint_ms: 0,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 4. permission_error.
|
|
312
|
+
if (type === 'permission_error' || status === 403) {
|
|
313
|
+
return {
|
|
314
|
+
gddError: new ValidationError(
|
|
315
|
+
msg || 'permission denied; tool not allowed by scope',
|
|
316
|
+
'PERMISSION_DENIED',
|
|
317
|
+
context,
|
|
318
|
+
),
|
|
319
|
+
retryable: false,
|
|
320
|
+
backoff_hint_ms: 0,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// 5. context overflow (checked BEFORE invalid_request_error because
|
|
325
|
+
// it's the more specific classification when both apply).
|
|
326
|
+
if (isContextOverflow(err)) {
|
|
327
|
+
return {
|
|
328
|
+
gddError: new OperationFailedError(
|
|
329
|
+
msg || 'context length exceeded',
|
|
330
|
+
'CONTEXT_OVERFLOW',
|
|
331
|
+
context,
|
|
332
|
+
),
|
|
333
|
+
retryable: false,
|
|
334
|
+
backoff_hint_ms: 0,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 6. invalid_request_error / schema-shape.
|
|
339
|
+
if (isInvalidRequestShape(err) || type === 'not_found_error') {
|
|
340
|
+
return {
|
|
341
|
+
gddError: new ValidationError(
|
|
342
|
+
msg || 'invalid request to Agent SDK',
|
|
343
|
+
'INVALID_REQUEST',
|
|
344
|
+
context,
|
|
345
|
+
),
|
|
346
|
+
retryable: false,
|
|
347
|
+
backoff_hint_ms: 0,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 7. api_error / 5xx.
|
|
352
|
+
if (type === 'api_error' || (status !== null && status >= 500 && status < 600)) {
|
|
353
|
+
return {
|
|
354
|
+
gddError: new OperationFailedError(
|
|
355
|
+
msg || 'provider API error',
|
|
356
|
+
'API_ERROR',
|
|
357
|
+
context,
|
|
358
|
+
),
|
|
359
|
+
retryable: true,
|
|
360
|
+
backoff_hint_ms: hint,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 8. AbortError (caller cancelled).
|
|
365
|
+
if (isAbortError(err)) {
|
|
366
|
+
return {
|
|
367
|
+
gddError: new OperationFailedError(
|
|
368
|
+
msg || 'session aborted',
|
|
369
|
+
'ABORTED',
|
|
370
|
+
context,
|
|
371
|
+
),
|
|
372
|
+
retryable: false,
|
|
373
|
+
backoff_hint_ms: 0,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 9. Transport-layer classification (ECONNRESET, ETIMEDOUT, etc.).
|
|
378
|
+
// Delegate to scripts/lib/error-classifier.cjs which knows the errno
|
|
379
|
+
// vocabulary. Only trust its `retryable` flag for transient network
|
|
380
|
+
// classes — other classes were already handled above.
|
|
381
|
+
const classified = transportClassifier.classify(err);
|
|
382
|
+
if (classified.reason === transportClassifier.FailoverReason.NETWORK_TRANSIENT) {
|
|
383
|
+
return {
|
|
384
|
+
gddError: new OperationFailedError(
|
|
385
|
+
msg || 'transport error (transient)',
|
|
386
|
+
'NETWORK_TRANSIENT',
|
|
387
|
+
{ ...context, classifier: classified.reason },
|
|
388
|
+
),
|
|
389
|
+
retryable: true,
|
|
390
|
+
backoff_hint_ms: hint,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 10. Fallthrough — plain Error, non-Error throw, anything unclassified.
|
|
395
|
+
const fallbackMsg =
|
|
396
|
+
msg || (err === null || err === undefined ? 'unknown SDK error' : String(err));
|
|
397
|
+
return {
|
|
398
|
+
gddError: new OperationFailedError(
|
|
399
|
+
fallbackMsg,
|
|
400
|
+
'SDK_UNKNOWN',
|
|
401
|
+
{ ...context, classifier: classified.reason },
|
|
402
|
+
),
|
|
403
|
+
retryable: false,
|
|
404
|
+
backoff_hint_ms: 0,
|
|
405
|
+
};
|
|
406
|
+
}
|