@wrongstack/core 0.1.2 → 0.1.3
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/dist/defaults/index.d.ts +738 -138
- package/dist/defaults/index.js +2507 -975
- package/dist/defaults/index.js.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.js +3271 -1057
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +5 -3
- package/dist/kernel/index.js +89 -11
- package/dist/kernel/index.js.map +1 -1
- package/dist/provider-DovtyuM8.d.ts +813 -0
- package/dist/{secret-scrubber-Dax_Ou_o.d.ts → secret-scrubber-qU3AwEiI.d.ts} +126 -457
- package/dist/{tool-executor-DjnMELMV.d.ts → session-reader-DR4u3bu9.d.ts} +445 -59
- package/dist/{system-prompt-BG3nks8P.d.ts → system-prompt--mzZnenv.d.ts} +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.js +153 -5
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +42 -1
- package/dist/utils/index.js +122 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,17 +1,155 @@
|
|
|
1
1
|
import * as fsp from 'fs/promises';
|
|
2
2
|
import * as path2 from 'path';
|
|
3
|
+
import * as crypto2 from 'crypto';
|
|
3
4
|
import { randomBytes, createHash, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
|
|
4
5
|
import * as os3 from 'os';
|
|
5
|
-
import * as
|
|
6
|
+
import * as fs5 from 'fs';
|
|
6
7
|
import { EventEmitter } from 'events';
|
|
7
8
|
import { spawn } from 'child_process';
|
|
8
9
|
|
|
10
|
+
// src/types/errors.ts
|
|
11
|
+
var WrongStackError = class extends Error {
|
|
12
|
+
code;
|
|
13
|
+
subsystem;
|
|
14
|
+
severity;
|
|
15
|
+
recoverable;
|
|
16
|
+
context;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
super(opts.message, { cause: opts.cause });
|
|
19
|
+
this.name = "WrongStackError";
|
|
20
|
+
this.code = opts.code;
|
|
21
|
+
this.subsystem = opts.subsystem;
|
|
22
|
+
this.severity = opts.severity ?? "error";
|
|
23
|
+
this.recoverable = opts.recoverable ?? false;
|
|
24
|
+
this.context = opts.context;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Render a one-line user-facing description.
|
|
28
|
+
* Subclasses should override for domain-specific formatting.
|
|
29
|
+
*/
|
|
30
|
+
describe() {
|
|
31
|
+
const ctx = this.context ? ` ${formatContext(this.context)}` : "";
|
|
32
|
+
return `${this.code}: ${this.message}${ctx}`;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function formatContext(ctx) {
|
|
36
|
+
const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
|
|
37
|
+
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
38
|
+
}
|
|
39
|
+
var ToolError = class extends WrongStackError {
|
|
40
|
+
toolName;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
super({
|
|
43
|
+
message: opts.message,
|
|
44
|
+
code: opts.code,
|
|
45
|
+
subsystem: "tool",
|
|
46
|
+
recoverable: opts.recoverable,
|
|
47
|
+
context: { tool: opts.toolName, ...opts.context },
|
|
48
|
+
cause: opts.cause
|
|
49
|
+
});
|
|
50
|
+
this.name = "ToolError";
|
|
51
|
+
this.toolName = opts.toolName;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var ConfigError = class extends WrongStackError {
|
|
55
|
+
constructor(opts) {
|
|
56
|
+
super({
|
|
57
|
+
message: opts.message,
|
|
58
|
+
code: opts.code,
|
|
59
|
+
subsystem: "config",
|
|
60
|
+
severity: "fatal",
|
|
61
|
+
recoverable: false,
|
|
62
|
+
context: opts.context,
|
|
63
|
+
cause: opts.cause
|
|
64
|
+
});
|
|
65
|
+
this.name = "ConfigError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var PluginError = class extends WrongStackError {
|
|
69
|
+
pluginName;
|
|
70
|
+
constructor(opts) {
|
|
71
|
+
super({
|
|
72
|
+
message: opts.message,
|
|
73
|
+
code: opts.code,
|
|
74
|
+
subsystem: "plugin",
|
|
75
|
+
severity: "error",
|
|
76
|
+
recoverable: opts.code === "PLUGIN_MISSING_DEPENDENCY",
|
|
77
|
+
context: { plugin: opts.pluginName, ...opts.context },
|
|
78
|
+
cause: opts.cause
|
|
79
|
+
});
|
|
80
|
+
this.name = "PluginError";
|
|
81
|
+
this.pluginName = opts.pluginName;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var AgentError = class extends WrongStackError {
|
|
85
|
+
constructor(opts) {
|
|
86
|
+
super({
|
|
87
|
+
message: opts.message,
|
|
88
|
+
code: opts.code,
|
|
89
|
+
subsystem: "agent",
|
|
90
|
+
severity: opts.code === "AGENT_ABORTED" ? "warning" : "error",
|
|
91
|
+
recoverable: opts.recoverable ?? opts.code === "AGENT_ITERATION_LIMIT",
|
|
92
|
+
context: opts.context,
|
|
93
|
+
cause: opts.cause
|
|
94
|
+
});
|
|
95
|
+
this.name = "AgentError";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function toWrongStackError(err, code = "AGENT_RUN_FAILED") {
|
|
99
|
+
if (err instanceof WrongStackError) return err;
|
|
100
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
101
|
+
return new AgentError({
|
|
102
|
+
message,
|
|
103
|
+
code: code === "UNKNOWN" ? "AGENT_RUN_FAILED" : code,
|
|
104
|
+
cause: err
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
var SessionError = class extends WrongStackError {
|
|
108
|
+
sessionId;
|
|
109
|
+
constructor(opts) {
|
|
110
|
+
super({
|
|
111
|
+
message: opts.message,
|
|
112
|
+
code: opts.code,
|
|
113
|
+
subsystem: "session",
|
|
114
|
+
severity: opts.code === "SESSION_WRITE_FAILED" ? "error" : "warning",
|
|
115
|
+
recoverable: opts.code !== "SESSION_CORRUPTED",
|
|
116
|
+
context: { sessionId: opts.sessionId, ...opts.context },
|
|
117
|
+
cause: opts.cause
|
|
118
|
+
});
|
|
119
|
+
this.name = "SessionError";
|
|
120
|
+
this.sessionId = opts.sessionId;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function isWrongStackError(err) {
|
|
124
|
+
return err instanceof WrongStackError;
|
|
125
|
+
}
|
|
126
|
+
function isToolError(err) {
|
|
127
|
+
return err instanceof ToolError;
|
|
128
|
+
}
|
|
129
|
+
function isConfigError(err) {
|
|
130
|
+
return err instanceof ConfigError;
|
|
131
|
+
}
|
|
132
|
+
function isPluginError(err) {
|
|
133
|
+
return err instanceof PluginError;
|
|
134
|
+
}
|
|
135
|
+
function isSessionError(err) {
|
|
136
|
+
return err instanceof SessionError;
|
|
137
|
+
}
|
|
138
|
+
function isAgentError(err) {
|
|
139
|
+
return err instanceof AgentError;
|
|
140
|
+
}
|
|
141
|
+
|
|
9
142
|
// src/kernel/container.ts
|
|
10
143
|
var Container = class {
|
|
11
144
|
entries = /* @__PURE__ */ new Map();
|
|
12
145
|
bind(token, factory, opts = {}) {
|
|
13
146
|
if (this.entries.has(token)) {
|
|
14
|
-
throw new
|
|
147
|
+
throw new WrongStackError({
|
|
148
|
+
message: `Container: token "${token.description ?? "unknown"}" already bound`,
|
|
149
|
+
code: "CONTAINER_TOKEN_ALREADY_BOUND",
|
|
150
|
+
subsystem: "container",
|
|
151
|
+
context: { token: token.description }
|
|
152
|
+
});
|
|
15
153
|
}
|
|
16
154
|
this.entries.set(token, {
|
|
17
155
|
factory,
|
|
@@ -23,9 +161,12 @@ var Container = class {
|
|
|
23
161
|
override(token, factory, opts = {}) {
|
|
24
162
|
const existing = this.entries.get(token);
|
|
25
163
|
if (!existing) {
|
|
26
|
-
throw new
|
|
27
|
-
`Container: cannot override "${token.description ?? "unknown"}" \u2014 not bound
|
|
28
|
-
|
|
164
|
+
throw new WrongStackError({
|
|
165
|
+
message: `Container: cannot override "${token.description ?? "unknown"}" \u2014 not bound`,
|
|
166
|
+
code: "CONTAINER_TOKEN_NOT_BOUND",
|
|
167
|
+
subsystem: "container",
|
|
168
|
+
context: { token: token.description }
|
|
169
|
+
});
|
|
29
170
|
}
|
|
30
171
|
this.entries.set(token, {
|
|
31
172
|
factory,
|
|
@@ -37,9 +178,12 @@ var Container = class {
|
|
|
37
178
|
decorate(token, decorator, owner = "core") {
|
|
38
179
|
const existing = this.entries.get(token);
|
|
39
180
|
if (!existing) {
|
|
40
|
-
throw new
|
|
41
|
-
`Container: cannot decorate "${token.description ?? "unknown"}" \u2014 not bound
|
|
42
|
-
|
|
181
|
+
throw new WrongStackError({
|
|
182
|
+
message: `Container: cannot decorate "${token.description ?? "unknown"}" \u2014 not bound`,
|
|
183
|
+
code: "CONTAINER_TOKEN_NOT_BOUND",
|
|
184
|
+
subsystem: "container",
|
|
185
|
+
context: { token: token.description }
|
|
186
|
+
});
|
|
43
187
|
}
|
|
44
188
|
existing.decorators.push(decorator);
|
|
45
189
|
existing.cache = void 0;
|
|
@@ -48,9 +192,12 @@ var Container = class {
|
|
|
48
192
|
resolve(token) {
|
|
49
193
|
const entry = this.entries.get(token);
|
|
50
194
|
if (!entry) {
|
|
51
|
-
throw new
|
|
52
|
-
`Container: token "${token.description ?? "unknown"}" not bound
|
|
53
|
-
|
|
195
|
+
throw new WrongStackError({
|
|
196
|
+
message: `Container: token "${token.description ?? "unknown"}" not bound`,
|
|
197
|
+
code: "CONTAINER_TOKEN_NOT_BOUND",
|
|
198
|
+
subsystem: "container",
|
|
199
|
+
context: { token: token.description }
|
|
200
|
+
});
|
|
54
201
|
}
|
|
55
202
|
if (entry.singleton && entry.cache !== void 0) {
|
|
56
203
|
return entry.cache;
|
|
@@ -114,6 +261,20 @@ var Container = class {
|
|
|
114
261
|
// src/kernel/pipeline.ts
|
|
115
262
|
var Pipeline = class {
|
|
116
263
|
chain = [];
|
|
264
|
+
errorHandler;
|
|
265
|
+
/**
|
|
266
|
+
* Install an error boundary. When a middleware throws or rejects, the
|
|
267
|
+
* handler is called and decides whether to swallow (continue with the
|
|
268
|
+
* pre-handler value) or rethrow. Without a handler, errors propagate.
|
|
269
|
+
*
|
|
270
|
+
* Wire one per pipeline at boot — the host CLI typically installs a
|
|
271
|
+
* single boundary that logs to the operational log and emits a
|
|
272
|
+
* `pipeline.error` event for /diag.
|
|
273
|
+
*/
|
|
274
|
+
setErrorHandler(handler) {
|
|
275
|
+
this.errorHandler = handler;
|
|
276
|
+
return this;
|
|
277
|
+
}
|
|
117
278
|
use(mw) {
|
|
118
279
|
this.ensureUnique(mw.name);
|
|
119
280
|
this.chain.push(mw);
|
|
@@ -198,6 +359,7 @@ var Pipeline = class {
|
|
|
198
359
|
async run(input) {
|
|
199
360
|
let index = -1;
|
|
200
361
|
const chain = this.chain;
|
|
362
|
+
const errorHandler = this.errorHandler;
|
|
201
363
|
const dispatch = async (i, value) => {
|
|
202
364
|
if (i <= index) {
|
|
203
365
|
throw new Error(`Pipeline: next() called multiple times in "${chain[index]?.name}"`);
|
|
@@ -205,7 +367,14 @@ var Pipeline = class {
|
|
|
205
367
|
index = i;
|
|
206
368
|
const mw = chain[i];
|
|
207
369
|
if (!mw) return value;
|
|
208
|
-
|
|
370
|
+
try {
|
|
371
|
+
return await mw.handler(value, (v) => dispatch(i + 1, v));
|
|
372
|
+
} catch (err) {
|
|
373
|
+
if (!errorHandler) throw err;
|
|
374
|
+
const policy = await errorHandler({ middleware: mw.name, owner: mw.owner, err });
|
|
375
|
+
if (policy === "rethrow") throw err;
|
|
376
|
+
return value;
|
|
377
|
+
}
|
|
209
378
|
};
|
|
210
379
|
return dispatch(0, input);
|
|
211
380
|
}
|
|
@@ -266,6 +435,17 @@ var EventBus = class {
|
|
|
266
435
|
clear() {
|
|
267
436
|
this.listeners.clear();
|
|
268
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* V2-D: introspection helper. Pass an `event` to count handlers for a
|
|
440
|
+
* single key, or omit to get the total across every event. Used by the
|
|
441
|
+
* leak-detection smoke test to flag handler accumulation across runs.
|
|
442
|
+
*/
|
|
443
|
+
listenerCount(event) {
|
|
444
|
+
if (event !== void 0) return this.listeners.get(event)?.size ?? 0;
|
|
445
|
+
let total = 0;
|
|
446
|
+
for (const set of this.listeners.values()) total += set.size;
|
|
447
|
+
return total;
|
|
448
|
+
}
|
|
269
449
|
};
|
|
270
450
|
|
|
271
451
|
// src/kernel/tokens.ts
|
|
@@ -279,6 +459,7 @@ var TOKENS = {
|
|
|
279
459
|
Compactor: t("Compactor"),
|
|
280
460
|
PathResolver: t("PathResolver"),
|
|
281
461
|
ConfigLoader: t("ConfigLoader"),
|
|
462
|
+
ConfigStore: t("ConfigStore"),
|
|
282
463
|
Renderer: t("Renderer"),
|
|
283
464
|
InputReader: t("InputReader"),
|
|
284
465
|
ErrorHandler: t("ErrorHandler"),
|
|
@@ -388,20 +569,26 @@ function asText(content) {
|
|
|
388
569
|
}
|
|
389
570
|
|
|
390
571
|
// src/types/provider.ts
|
|
391
|
-
var ProviderError = class extends
|
|
572
|
+
var ProviderError = class extends WrongStackError {
|
|
392
573
|
status;
|
|
393
574
|
retryable;
|
|
394
575
|
providerId;
|
|
395
576
|
body;
|
|
396
|
-
cause;
|
|
397
577
|
constructor(message, status, retryable, providerId, opts = {}) {
|
|
398
|
-
super(
|
|
578
|
+
super({
|
|
579
|
+
message,
|
|
580
|
+
code: providerStatusToCode(status, opts.body?.type),
|
|
581
|
+
subsystem: "provider",
|
|
582
|
+
severity: status >= 500 ? "error" : "warning",
|
|
583
|
+
recoverable: retryable,
|
|
584
|
+
context: { providerId, status },
|
|
585
|
+
cause: opts.cause
|
|
586
|
+
});
|
|
399
587
|
this.name = "ProviderError";
|
|
400
588
|
this.status = status;
|
|
401
589
|
this.retryable = retryable;
|
|
402
590
|
this.providerId = providerId;
|
|
403
591
|
this.body = opts.body;
|
|
404
|
-
this.cause = opts.cause;
|
|
405
592
|
}
|
|
406
593
|
/**
|
|
407
594
|
* Render a one-line, user-facing description. Designed for the CLI/TUI
|
|
@@ -442,6 +629,16 @@ function describeStatus(status, type) {
|
|
|
442
629
|
function truncate(s, n) {
|
|
443
630
|
return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
|
|
444
631
|
}
|
|
632
|
+
function providerStatusToCode(status, type) {
|
|
633
|
+
if (status === 0) return "PROVIDER_NETWORK_ERROR";
|
|
634
|
+
if (type === "rate_limit_error" || status === 429) return "PROVIDER_RATE_LIMITED";
|
|
635
|
+
if (type === "authentication_error" || status === 401) return "PROVIDER_AUTH_FAILED";
|
|
636
|
+
if (type === "overloaded_error" || status === 529) return "PROVIDER_OVERLOADED";
|
|
637
|
+
if (type === "invalid_request_error" || status === 400) return "PROVIDER_INVALID_REQUEST";
|
|
638
|
+
if (status === 408) return "PROVIDER_NETWORK_ERROR";
|
|
639
|
+
if (status >= 500) return "PROVIDER_SERVER_ERROR";
|
|
640
|
+
return "PROVIDER_INVALID_REQUEST";
|
|
641
|
+
}
|
|
445
642
|
|
|
446
643
|
// src/types/secret-vault.ts
|
|
447
644
|
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
@@ -668,8 +865,11 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
668
865
|
}
|
|
669
866
|
try {
|
|
670
867
|
const fh = await fsp.open(tmp, "r+");
|
|
671
|
-
|
|
672
|
-
|
|
868
|
+
try {
|
|
869
|
+
await fh.sync();
|
|
870
|
+
} finally {
|
|
871
|
+
await fh.close();
|
|
872
|
+
}
|
|
673
873
|
} catch {
|
|
674
874
|
}
|
|
675
875
|
let mode;
|
|
@@ -1107,6 +1307,122 @@ function createToolOutputSerializer(opts = {}) {
|
|
|
1107
1307
|
}
|
|
1108
1308
|
return { serialize, enforceCap, capBytes };
|
|
1109
1309
|
}
|
|
1310
|
+
|
|
1311
|
+
// src/utils/token-estimate.ts
|
|
1312
|
+
var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
1313
|
+
function estimateToolInputTokens(input) {
|
|
1314
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
1315
|
+
if (input !== null && typeof input === "object" && "__tokenEstimate" in input) {
|
|
1316
|
+
return input.__tokenEstimate;
|
|
1317
|
+
}
|
|
1318
|
+
const str = typeof input === "object" ? JSON.stringify(input) : String(input);
|
|
1319
|
+
const estimate = RoughTokenEstimate(str);
|
|
1320
|
+
if (input !== null && typeof input === "object" && !Array.isArray(input)) {
|
|
1321
|
+
input.__tokenEstimate = estimate;
|
|
1322
|
+
}
|
|
1323
|
+
return estimate;
|
|
1324
|
+
}
|
|
1325
|
+
function estimateToolResultTokens(content) {
|
|
1326
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
1327
|
+
return RoughTokenEstimate(JSON.stringify(content));
|
|
1328
|
+
}
|
|
1329
|
+
function estimateTextTokens(text) {
|
|
1330
|
+
return RoughTokenEstimate(text);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/utils/json-schema-validate.ts
|
|
1334
|
+
function validateAgainstSchema(value, schema) {
|
|
1335
|
+
const errors = [];
|
|
1336
|
+
walk(value, schema, "", errors);
|
|
1337
|
+
return { ok: errors.length === 0, errors };
|
|
1338
|
+
}
|
|
1339
|
+
function walk(value, schema, path15, errors) {
|
|
1340
|
+
if (schema.enum !== void 0) {
|
|
1341
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1342
|
+
errors.push({
|
|
1343
|
+
path: path15 || "<root>",
|
|
1344
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1345
|
+
});
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (typeof schema.type === "string") {
|
|
1350
|
+
if (!checkType(value, schema.type)) {
|
|
1351
|
+
errors.push({
|
|
1352
|
+
path: path15 || "<root>",
|
|
1353
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1354
|
+
});
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
1359
|
+
const obj = value;
|
|
1360
|
+
for (const req of schema.required ?? []) {
|
|
1361
|
+
if (!(req in obj)) {
|
|
1362
|
+
errors.push({ path: joinPath(path15, req), message: "required property missing" });
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (schema.properties) {
|
|
1366
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1367
|
+
if (key in obj) {
|
|
1368
|
+
walk(obj[key], subSchema, joinPath(path15, key), errors);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1374
|
+
value.forEach((item, i) => walk(item, schema.items, `${path15}[${i}]`, errors));
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
function checkType(value, type) {
|
|
1378
|
+
switch (type) {
|
|
1379
|
+
case "string":
|
|
1380
|
+
return typeof value === "string";
|
|
1381
|
+
case "number":
|
|
1382
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
1383
|
+
case "integer":
|
|
1384
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
1385
|
+
case "boolean":
|
|
1386
|
+
return typeof value === "boolean";
|
|
1387
|
+
case "null":
|
|
1388
|
+
return value === null;
|
|
1389
|
+
case "array":
|
|
1390
|
+
return Array.isArray(value);
|
|
1391
|
+
case "object":
|
|
1392
|
+
return isPlainObject(value);
|
|
1393
|
+
default:
|
|
1394
|
+
return true;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function isPlainObject(v) {
|
|
1398
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1399
|
+
}
|
|
1400
|
+
function describeType(v) {
|
|
1401
|
+
if (v === null) return "null";
|
|
1402
|
+
if (Array.isArray(v)) return "array";
|
|
1403
|
+
return typeof v;
|
|
1404
|
+
}
|
|
1405
|
+
function joinPath(parent, key) {
|
|
1406
|
+
if (!parent) return key;
|
|
1407
|
+
return `${parent}.${key}`;
|
|
1408
|
+
}
|
|
1409
|
+
function deepEqual(a, b) {
|
|
1410
|
+
if (a === b) return true;
|
|
1411
|
+
if (typeof a !== typeof b) return false;
|
|
1412
|
+
if (a === null || b === null) return a === b;
|
|
1413
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1414
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
1415
|
+
}
|
|
1416
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1417
|
+
const ak = Object.keys(a);
|
|
1418
|
+
const bk = Object.keys(b);
|
|
1419
|
+
if (ak.length !== bk.length) return false;
|
|
1420
|
+
return ak.every(
|
|
1421
|
+
(k) => deepEqual(a[k], b[k])
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
return false;
|
|
1425
|
+
}
|
|
1110
1426
|
var LEVEL_RANK = {
|
|
1111
1427
|
error: 0,
|
|
1112
1428
|
warn: 1,
|
|
@@ -1133,7 +1449,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1133
1449
|
this.pretty = opts.pretty ?? true;
|
|
1134
1450
|
if (this.file) {
|
|
1135
1451
|
try {
|
|
1136
|
-
|
|
1452
|
+
fs5.mkdirSync(path2.dirname(this.file), { recursive: true });
|
|
1137
1453
|
} catch {
|
|
1138
1454
|
}
|
|
1139
1455
|
}
|
|
@@ -1172,7 +1488,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1172
1488
|
}
|
|
1173
1489
|
if (this.file) {
|
|
1174
1490
|
try {
|
|
1175
|
-
|
|
1491
|
+
fs5.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
1176
1492
|
`);
|
|
1177
1493
|
} catch {
|
|
1178
1494
|
}
|
|
@@ -1220,7 +1536,7 @@ var DefaultPathResolver = class {
|
|
|
1220
1536
|
while (dir !== root) {
|
|
1221
1537
|
for (const marker of PROJECT_MARKERS) {
|
|
1222
1538
|
try {
|
|
1223
|
-
|
|
1539
|
+
fs5.accessSync(path2.join(dir, marker));
|
|
1224
1540
|
return dir;
|
|
1225
1541
|
} catch {
|
|
1226
1542
|
}
|
|
@@ -1235,7 +1551,7 @@ var DefaultPathResolver = class {
|
|
|
1235
1551
|
const abs = path2.isAbsolute(input) ? input : path2.resolve(this.cwd, input);
|
|
1236
1552
|
let real;
|
|
1237
1553
|
try {
|
|
1238
|
-
real =
|
|
1554
|
+
real = fs5.realpathSync(abs);
|
|
1239
1555
|
} catch {
|
|
1240
1556
|
real = path2.normalize(abs);
|
|
1241
1557
|
}
|
|
@@ -1259,267 +1575,81 @@ var DefaultPathResolver = class {
|
|
|
1259
1575
|
}
|
|
1260
1576
|
};
|
|
1261
1577
|
|
|
1262
|
-
// src/defaults/
|
|
1263
|
-
var
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
1279
|
-
},
|
|
1280
|
-
{
|
|
1281
|
-
type: "private_key",
|
|
1282
|
-
// Anchored: start must be BEGIN, end must be END with no extra dashes after END
|
|
1283
|
-
regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END[^-]*-----(?:\n|$)/g
|
|
1284
|
-
},
|
|
1285
|
-
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
|
|
1286
|
-
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
1287
|
-
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
1288
|
-
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
1289
|
-
{ type: "bearer_token", regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,}=*(?![A-Za-z0-9_.~+/-])/g },
|
|
1290
|
-
{
|
|
1291
|
-
type: "high_entropy_env",
|
|
1292
|
-
// Value-side word boundary + length gate to avoid matching short random strings
|
|
1293
|
-
regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,})['"]?(?!\s*[A-Za-z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))/g
|
|
1578
|
+
// src/defaults/token-counter.ts
|
|
1579
|
+
var DefaultTokenCounter = class {
|
|
1580
|
+
input = 0;
|
|
1581
|
+
output = 0;
|
|
1582
|
+
cacheRead = 0;
|
|
1583
|
+
cacheWrite = 0;
|
|
1584
|
+
costInput = 0;
|
|
1585
|
+
costOutput = 0;
|
|
1586
|
+
registry;
|
|
1587
|
+
providerId;
|
|
1588
|
+
events;
|
|
1589
|
+
priceCache = /* @__PURE__ */ new Map();
|
|
1590
|
+
constructor(opts = {}) {
|
|
1591
|
+
this.registry = opts.registry;
|
|
1592
|
+
this.providerId = opts.providerId;
|
|
1593
|
+
this.events = opts.events;
|
|
1294
1594
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1595
|
+
account(usage, model) {
|
|
1596
|
+
this.input += usage.input;
|
|
1597
|
+
this.output += usage.output;
|
|
1598
|
+
this.cacheRead += usage.cacheRead ?? 0;
|
|
1599
|
+
this.cacheWrite += usage.cacheWrite ?? 0;
|
|
1600
|
+
const price = model ? this.priceCache.get(model) : void 0;
|
|
1601
|
+
if (price) {
|
|
1602
|
+
this.applyPrice(usage, price);
|
|
1603
|
+
} else if (this.registry && this.providerId && model) {
|
|
1604
|
+
void this.registry.getModel(this.providerId, model).then((m) => {
|
|
1605
|
+
if (m) {
|
|
1606
|
+
const p = priceFromModel(m);
|
|
1607
|
+
this.priceCache.set(model, p);
|
|
1608
|
+
this.applyPrice(usage, p);
|
|
1304
1609
|
}
|
|
1305
|
-
|
|
1610
|
+
}).catch(() => {
|
|
1611
|
+
this.events?.emit("token.cost_estimate_unavailable", { model: model ?? "<unknown>" });
|
|
1612
|
+
return void 0;
|
|
1306
1613
|
});
|
|
1307
1614
|
}
|
|
1308
|
-
return out;
|
|
1309
1615
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1616
|
+
/** Synchronous variant for code paths that have already resolved the model. */
|
|
1617
|
+
accountWithModel(usage, resolved) {
|
|
1618
|
+
this.input += usage.input;
|
|
1619
|
+
this.output += usage.output;
|
|
1620
|
+
this.cacheRead += usage.cacheRead ?? 0;
|
|
1621
|
+
this.cacheWrite += usage.cacheWrite ?? 0;
|
|
1622
|
+
const price = priceFromModel(resolved);
|
|
1623
|
+
this.priceCache.set(resolved.modelId, price);
|
|
1624
|
+
this.applyPrice(usage, price);
|
|
1625
|
+
}
|
|
1626
|
+
total() {
|
|
1627
|
+
return {
|
|
1628
|
+
input: this.input,
|
|
1629
|
+
output: this.output,
|
|
1630
|
+
cacheRead: this.cacheRead,
|
|
1631
|
+
cacheWrite: this.cacheWrite
|
|
1323
1632
|
};
|
|
1324
|
-
return visit(obj);
|
|
1325
1633
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
return attempt < this.maxAttempts(err);
|
|
1334
|
-
}
|
|
1335
|
-
const msg = err.message ?? "";
|
|
1336
|
-
const isNetwork = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(msg);
|
|
1337
|
-
if (isNetwork) return attempt < 2;
|
|
1338
|
-
return false;
|
|
1634
|
+
estimateCost() {
|
|
1635
|
+
return {
|
|
1636
|
+
input: round4(this.costInput),
|
|
1637
|
+
output: round4(this.costOutput),
|
|
1638
|
+
total: round4(this.costInput + this.costOutput),
|
|
1639
|
+
currency: "USD"
|
|
1640
|
+
};
|
|
1339
1641
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
}
|
|
1347
|
-
return 2;
|
|
1642
|
+
cacheStats() {
|
|
1643
|
+
const denom = this.cacheRead + this.input;
|
|
1644
|
+
return {
|
|
1645
|
+
readTokens: this.cacheRead,
|
|
1646
|
+
writeTokens: this.cacheWrite,
|
|
1647
|
+
hitRatio: denom === 0 ? 0 : this.cacheRead / denom
|
|
1648
|
+
};
|
|
1348
1649
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const jitter = Math.random() * base;
|
|
1353
|
-
return Math.min(3e4, exp + jitter);
|
|
1354
|
-
}
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
|
-
// src/defaults/error-handler.ts
|
|
1358
|
-
function buildRecoveryStrategies(opts) {
|
|
1359
|
-
return [
|
|
1360
|
-
{
|
|
1361
|
-
label: "context_overflow_reduce",
|
|
1362
|
-
compactor: opts?.compactor,
|
|
1363
|
-
async attempt(err, ctx) {
|
|
1364
|
-
if (err instanceof ProviderError && (err.status === 413 || /context|too long|tokens/i.test(err.message))) {
|
|
1365
|
-
if (this.compactor) {
|
|
1366
|
-
try {
|
|
1367
|
-
const report = await this.compactor.compact(ctx, { aggressive: true });
|
|
1368
|
-
if (report.after < report.before) {
|
|
1369
|
-
return {
|
|
1370
|
-
content: [{ type: "text", text: "[context compacted automatically \u2014 please retry]" }],
|
|
1371
|
-
stopReason: "end_turn",
|
|
1372
|
-
usage: { input: 0, output: 0 },
|
|
1373
|
-
model: ctx.model
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
} catch {
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
return null;
|
|
1380
|
-
}
|
|
1381
|
-
return null;
|
|
1382
|
-
}
|
|
1383
|
-
},
|
|
1384
|
-
{
|
|
1385
|
-
label: "rate_limit_backoff",
|
|
1386
|
-
async attempt(err, ctx) {
|
|
1387
|
-
if (err instanceof ProviderError && err.status === 429) {
|
|
1388
|
-
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
1389
|
-
const delay = Math.max(1e3, Math.min(delayMs, 6e4));
|
|
1390
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
1391
|
-
return {
|
|
1392
|
-
content: [{ type: "text", text: "[rate limit backoff applied \u2014 please retry]" }],
|
|
1393
|
-
stopReason: "end_turn",
|
|
1394
|
-
usage: { input: 0, output: 0 },
|
|
1395
|
-
model: ctx.model
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
|
-
return null;
|
|
1399
|
-
}
|
|
1400
|
-
},
|
|
1401
|
-
{
|
|
1402
|
-
label: "downgrade_model",
|
|
1403
|
-
async attempt(err, ctx) {
|
|
1404
|
-
if (err instanceof ProviderError && (err.status === 429 || err.status === 529 || err.status >= 500)) {
|
|
1405
|
-
return null;
|
|
1406
|
-
}
|
|
1407
|
-
return null;
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
];
|
|
1411
|
-
}
|
|
1412
|
-
var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
|
|
1413
|
-
var DefaultErrorHandler = class {
|
|
1414
|
-
strategies;
|
|
1415
|
-
constructor(strategies = DEFAULT_RECOVERY_STRATEGIES) {
|
|
1416
|
-
this.strategies = strategies;
|
|
1417
|
-
}
|
|
1418
|
-
classify(err) {
|
|
1419
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
1420
|
-
return { kind: "abort", retryable: false };
|
|
1421
|
-
}
|
|
1422
|
-
if (err instanceof Error && err.name === "AbortError") {
|
|
1423
|
-
return { kind: "abort", retryable: false };
|
|
1424
|
-
}
|
|
1425
|
-
if (err instanceof ProviderError) {
|
|
1426
|
-
if (err.status === 429) return { kind: "rate_limit", retryable: true };
|
|
1427
|
-
if (err.status === 529) return { kind: "overloaded", retryable: true };
|
|
1428
|
-
if (err.status >= 500) return { kind: "server", retryable: true };
|
|
1429
|
-
if (err.status === 413 || /context|too long|tokens/i.test(err.message)) {
|
|
1430
|
-
return { kind: "context_overflow", retryable: false };
|
|
1431
|
-
}
|
|
1432
|
-
if (err.status >= 400) return { kind: "client", retryable: false };
|
|
1433
|
-
}
|
|
1434
|
-
if (err instanceof Error && /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(err.message)) {
|
|
1435
|
-
return { kind: "network", retryable: true };
|
|
1436
|
-
}
|
|
1437
|
-
return { kind: "unknown", retryable: false };
|
|
1438
|
-
}
|
|
1439
|
-
async recover(err, ctx) {
|
|
1440
|
-
for (const strategy of this.strategies) {
|
|
1441
|
-
const result = await strategy.attempt(err, ctx);
|
|
1442
|
-
if (result !== null) return result;
|
|
1443
|
-
}
|
|
1444
|
-
return null;
|
|
1445
|
-
}
|
|
1446
|
-
};
|
|
1447
|
-
|
|
1448
|
-
// src/defaults/token-counter.ts
|
|
1449
|
-
var DefaultTokenCounter = class {
|
|
1450
|
-
input = 0;
|
|
1451
|
-
output = 0;
|
|
1452
|
-
cacheRead = 0;
|
|
1453
|
-
cacheWrite = 0;
|
|
1454
|
-
costInput = 0;
|
|
1455
|
-
costOutput = 0;
|
|
1456
|
-
registry;
|
|
1457
|
-
providerId;
|
|
1458
|
-
events;
|
|
1459
|
-
priceCache = /* @__PURE__ */ new Map();
|
|
1460
|
-
constructor(opts = {}) {
|
|
1461
|
-
this.registry = opts.registry;
|
|
1462
|
-
this.providerId = opts.providerId;
|
|
1463
|
-
this.events = opts.events;
|
|
1464
|
-
}
|
|
1465
|
-
account(usage, model) {
|
|
1466
|
-
this.input += usage.input;
|
|
1467
|
-
this.output += usage.output;
|
|
1468
|
-
this.cacheRead += usage.cacheRead ?? 0;
|
|
1469
|
-
this.cacheWrite += usage.cacheWrite ?? 0;
|
|
1470
|
-
const price = model ? this.priceCache.get(model) : void 0;
|
|
1471
|
-
if (price) {
|
|
1472
|
-
this.applyPrice(usage, price);
|
|
1473
|
-
} else if (this.registry && this.providerId && model) {
|
|
1474
|
-
void this.registry.getModel(this.providerId, model).then((m) => {
|
|
1475
|
-
if (m) {
|
|
1476
|
-
const p = priceFromModel(m);
|
|
1477
|
-
this.priceCache.set(model, p);
|
|
1478
|
-
this.applyPrice(usage, p);
|
|
1479
|
-
}
|
|
1480
|
-
}).catch(() => {
|
|
1481
|
-
this.events?.emit("token.cost_estimate_unavailable", { model: model ?? "<unknown>" });
|
|
1482
|
-
return void 0;
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
/** Synchronous variant for code paths that have already resolved the model. */
|
|
1487
|
-
accountWithModel(usage, resolved) {
|
|
1488
|
-
this.input += usage.input;
|
|
1489
|
-
this.output += usage.output;
|
|
1490
|
-
this.cacheRead += usage.cacheRead ?? 0;
|
|
1491
|
-
this.cacheWrite += usage.cacheWrite ?? 0;
|
|
1492
|
-
const price = priceFromModel(resolved);
|
|
1493
|
-
this.priceCache.set(resolved.modelId, price);
|
|
1494
|
-
this.applyPrice(usage, price);
|
|
1495
|
-
}
|
|
1496
|
-
total() {
|
|
1497
|
-
return {
|
|
1498
|
-
input: this.input,
|
|
1499
|
-
output: this.output,
|
|
1500
|
-
cacheRead: this.cacheRead,
|
|
1501
|
-
cacheWrite: this.cacheWrite
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
|
-
estimateCost() {
|
|
1505
|
-
return {
|
|
1506
|
-
input: round4(this.costInput),
|
|
1507
|
-
output: round4(this.costOutput),
|
|
1508
|
-
total: round4(this.costInput + this.costOutput),
|
|
1509
|
-
currency: "USD"
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
cacheStats() {
|
|
1513
|
-
const denom = this.cacheRead + this.input;
|
|
1514
|
-
return {
|
|
1515
|
-
readTokens: this.cacheRead,
|
|
1516
|
-
writeTokens: this.cacheWrite,
|
|
1517
|
-
hitRatio: denom === 0 ? 0 : this.cacheRead / denom
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
/** Invalidate cached prices so the next account() call fetches fresh data. */
|
|
1521
|
-
invalidateCache() {
|
|
1522
|
-
this.priceCache.clear();
|
|
1650
|
+
/** Invalidate cached prices so the next account() call fetches fresh data. */
|
|
1651
|
+
invalidateCache() {
|
|
1652
|
+
this.priceCache.clear();
|
|
1523
1653
|
}
|
|
1524
1654
|
reset() {
|
|
1525
1655
|
this.input = 0;
|
|
@@ -1840,151 +1970,28 @@ function userInputTitle(content) {
|
|
|
1840
1970
|
const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
1841
1971
|
return (text || "(non-text input)").slice(0, 60);
|
|
1842
1972
|
}
|
|
1843
|
-
var
|
|
1844
|
-
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1845
|
-
var RecoveryLock = class {
|
|
1973
|
+
var QueueStore = class {
|
|
1846
1974
|
file;
|
|
1847
|
-
pid;
|
|
1848
|
-
hostname;
|
|
1849
|
-
maxAgeMs;
|
|
1850
|
-
sessionStore;
|
|
1851
|
-
probe;
|
|
1852
1975
|
constructor(opts) {
|
|
1853
|
-
this.file = path2.join(opts.dir,
|
|
1854
|
-
this.pid = opts.pid ?? process.pid;
|
|
1855
|
-
this.hostname = opts.hostname ?? os3.hostname();
|
|
1856
|
-
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
1857
|
-
this.sessionStore = opts.sessionStore;
|
|
1858
|
-
this.probe = opts.isPidAlive ?? defaultIsPidAlive;
|
|
1859
|
-
}
|
|
1860
|
-
/**
|
|
1861
|
-
* Examine the lockfile and decide whether it represents an abandoned
|
|
1862
|
-
* session. Returns `null` if the file is missing, points to a live
|
|
1863
|
-
* instance, references a clean-closed session, is too old, or is
|
|
1864
|
-
* malformed. Otherwise returns enough detail to prompt the user.
|
|
1865
|
-
*
|
|
1866
|
-
* Important: this is a read-only check. We never delete an active
|
|
1867
|
-
* lock from here — if another wstack instance is alive, the caller
|
|
1868
|
-
* should bail or run with a fresh session instead.
|
|
1869
|
-
*/
|
|
1870
|
-
async checkAbandoned() {
|
|
1871
|
-
const lock = await this.readLock();
|
|
1872
|
-
if (!lock) return null;
|
|
1873
|
-
const ageMs = Date.now() - new Date(lock.startedAt).getTime();
|
|
1874
|
-
if (Number.isNaN(ageMs) || ageMs < 0) {
|
|
1875
|
-
return null;
|
|
1876
|
-
}
|
|
1877
|
-
if (ageMs > this.maxAgeMs) return null;
|
|
1878
|
-
if (lock.hostname === this.hostname && this.probe(lock.pid)) {
|
|
1879
|
-
return null;
|
|
1880
|
-
}
|
|
1881
|
-
let messageCount = 0;
|
|
1882
|
-
if (this.sessionStore) {
|
|
1883
|
-
try {
|
|
1884
|
-
const data = await this.sessionStore.load(lock.sessionId);
|
|
1885
|
-
const closed = data.events.some((e) => e.type === "session_end");
|
|
1886
|
-
if (closed) return null;
|
|
1887
|
-
messageCount = data.messages.length;
|
|
1888
|
-
} catch {
|
|
1889
|
-
return null;
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
return {
|
|
1893
|
-
sessionId: lock.sessionId,
|
|
1894
|
-
pid: lock.pid,
|
|
1895
|
-
startedAt: lock.startedAt,
|
|
1896
|
-
ageMs,
|
|
1897
|
-
messageCount
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
|
-
/**
|
|
1901
|
-
* Claim the lock for the given session. Overwrites any existing lock
|
|
1902
|
-
* — the caller should have already handled abandonment (via
|
|
1903
|
-
* `checkAbandoned`) before calling this.
|
|
1904
|
-
*/
|
|
1905
|
-
async write(sessionId) {
|
|
1906
|
-
await ensureDir(path2.dirname(this.file));
|
|
1907
|
-
const lock = {
|
|
1908
|
-
v: 1,
|
|
1909
|
-
sessionId,
|
|
1910
|
-
pid: this.pid,
|
|
1911
|
-
hostname: this.hostname,
|
|
1912
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1913
|
-
};
|
|
1914
|
-
const tmp = `${this.file}.tmp`;
|
|
1915
|
-
await fsp.writeFile(tmp, JSON.stringify(lock), { mode: 384 });
|
|
1916
|
-
await fsp.rename(tmp, this.file);
|
|
1976
|
+
this.file = path2.join(opts.dir, "queue.json");
|
|
1917
1977
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
*/
|
|
1923
|
-
async clear() {
|
|
1924
|
-
try {
|
|
1925
|
-
await fsp.unlink(this.file);
|
|
1926
|
-
} catch (err) {
|
|
1927
|
-
const code = err.code;
|
|
1928
|
-
if (code === "ENOENT") return;
|
|
1929
|
-
throw err;
|
|
1978
|
+
async write(items) {
|
|
1979
|
+
if (items.length === 0) {
|
|
1980
|
+
await this.clear();
|
|
1981
|
+
return;
|
|
1930
1982
|
}
|
|
1983
|
+
await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
|
|
1931
1984
|
}
|
|
1932
|
-
async
|
|
1985
|
+
async read() {
|
|
1933
1986
|
let raw;
|
|
1934
1987
|
try {
|
|
1935
1988
|
raw = await fsp.readFile(this.file, "utf8");
|
|
1936
1989
|
} catch (err) {
|
|
1937
1990
|
const code = err.code;
|
|
1938
|
-
if (code === "ENOENT") return
|
|
1939
|
-
return
|
|
1991
|
+
if (code === "ENOENT") return [];
|
|
1992
|
+
return [];
|
|
1940
1993
|
}
|
|
1941
|
-
|
|
1942
|
-
const parsed = JSON.parse(raw);
|
|
1943
|
-
if (!isLockFile(parsed)) return null;
|
|
1944
|
-
return parsed;
|
|
1945
|
-
} catch {
|
|
1946
|
-
return null;
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
};
|
|
1950
|
-
function isLockFile(v) {
|
|
1951
|
-
if (typeof v !== "object" || v === null) return false;
|
|
1952
|
-
const o = v;
|
|
1953
|
-
return o["v"] === 1 && typeof o["sessionId"] === "string" && typeof o["pid"] === "number" && typeof o["hostname"] === "string" && typeof o["startedAt"] === "string";
|
|
1954
|
-
}
|
|
1955
|
-
function defaultIsPidAlive(pid) {
|
|
1956
|
-
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
1957
|
-
try {
|
|
1958
|
-
process.kill(pid, 0);
|
|
1959
|
-
return true;
|
|
1960
|
-
} catch (err) {
|
|
1961
|
-
const code = err.code;
|
|
1962
|
-
if (code === "EPERM") return true;
|
|
1963
|
-
return false;
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
var QueueStore = class {
|
|
1967
|
-
file;
|
|
1968
|
-
constructor(opts) {
|
|
1969
|
-
this.file = path2.join(opts.dir, "queue.json");
|
|
1970
|
-
}
|
|
1971
|
-
async write(items) {
|
|
1972
|
-
if (items.length === 0) {
|
|
1973
|
-
await this.clear();
|
|
1974
|
-
return;
|
|
1975
|
-
}
|
|
1976
|
-
await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
|
|
1977
|
-
}
|
|
1978
|
-
async read() {
|
|
1979
|
-
let raw;
|
|
1980
|
-
try {
|
|
1981
|
-
raw = await fsp.readFile(this.file, "utf8");
|
|
1982
|
-
} catch (err) {
|
|
1983
|
-
const code = err.code;
|
|
1984
|
-
if (code === "ENOENT") return [];
|
|
1985
|
-
return [];
|
|
1986
|
-
}
|
|
1987
|
-
let parsed;
|
|
1994
|
+
let parsed;
|
|
1988
1995
|
try {
|
|
1989
1996
|
parsed = JSON.parse(raw);
|
|
1990
1997
|
} catch {
|
|
@@ -2125,6 +2132,205 @@ function mergeAdjacentText(blocks) {
|
|
|
2125
2132
|
}
|
|
2126
2133
|
return out;
|
|
2127
2134
|
}
|
|
2135
|
+
var MAX_BYTES_TOTAL = 32e3;
|
|
2136
|
+
var DefaultMemoryStore = class {
|
|
2137
|
+
files;
|
|
2138
|
+
constructor(opts) {
|
|
2139
|
+
this.files = {
|
|
2140
|
+
"project-agents": opts.paths.inProjectAgentsFile,
|
|
2141
|
+
"project-memory": opts.paths.projectMemory,
|
|
2142
|
+
"user-memory": opts.paths.globalMemory
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
async readAll() {
|
|
2146
|
+
const parts = [];
|
|
2147
|
+
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
2148
|
+
const body = await this.read(scope);
|
|
2149
|
+
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
2150
|
+
|
|
2151
|
+
${body.trim()}`);
|
|
2152
|
+
}
|
|
2153
|
+
return parts.join("\n\n");
|
|
2154
|
+
}
|
|
2155
|
+
async read(scope) {
|
|
2156
|
+
try {
|
|
2157
|
+
return await fsp.readFile(this.files[scope], "utf8");
|
|
2158
|
+
} catch {
|
|
2159
|
+
return "";
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
async remember(text, scope = "project-memory") {
|
|
2163
|
+
const file = this.files[scope];
|
|
2164
|
+
await ensureDir(path2.dirname(file));
|
|
2165
|
+
let existing = "";
|
|
2166
|
+
try {
|
|
2167
|
+
existing = await fsp.readFile(file, "utf8");
|
|
2168
|
+
} catch {
|
|
2169
|
+
}
|
|
2170
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2171
|
+
const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
2172
|
+
const entry = `
|
|
2173
|
+
- [${ts}] ${id} ${text.replace(/\n/g, " ")}
|
|
2174
|
+
`;
|
|
2175
|
+
const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
|
|
2176
|
+
${entry}`;
|
|
2177
|
+
await atomicWrite(file, next);
|
|
2178
|
+
const buf = Buffer.byteLength(next, "utf8");
|
|
2179
|
+
if (buf > MAX_BYTES_TOTAL) {
|
|
2180
|
+
await this.consolidate(scope);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
async forget(query, scope = "project-memory") {
|
|
2184
|
+
const file = this.files[scope];
|
|
2185
|
+
let existing;
|
|
2186
|
+
try {
|
|
2187
|
+
existing = await fsp.readFile(file, "utf8");
|
|
2188
|
+
} catch {
|
|
2189
|
+
return 0;
|
|
2190
|
+
}
|
|
2191
|
+
const needle = query.toLowerCase();
|
|
2192
|
+
const idMatcher = /mem_\d+_\w+/;
|
|
2193
|
+
let removed = 0;
|
|
2194
|
+
const lines = existing.split("\n").filter((line) => {
|
|
2195
|
+
const trimmed = line.trim();
|
|
2196
|
+
if (!trimmed.startsWith("- ")) return true;
|
|
2197
|
+
if (idMatcher.test(query)) {
|
|
2198
|
+
const afterBracket = trimmed.indexOf("] ");
|
|
2199
|
+
if (afterBracket !== -1) {
|
|
2200
|
+
const afterTs = trimmed.slice(afterBracket + 2);
|
|
2201
|
+
const entryIdMatch = /^mem_\d+_\w+/.exec(afterTs);
|
|
2202
|
+
if (entryIdMatch && entryIdMatch[0] === query) {
|
|
2203
|
+
removed++;
|
|
2204
|
+
return false;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
if (trimmed.toLowerCase().includes(needle)) {
|
|
2209
|
+
removed++;
|
|
2210
|
+
return false;
|
|
2211
|
+
}
|
|
2212
|
+
return true;
|
|
2213
|
+
});
|
|
2214
|
+
if (removed > 0) {
|
|
2215
|
+
await atomicWrite(file, lines.join("\n"));
|
|
2216
|
+
}
|
|
2217
|
+
return removed;
|
|
2218
|
+
}
|
|
2219
|
+
async consolidate(scope) {
|
|
2220
|
+
const file = this.files[scope];
|
|
2221
|
+
let existing;
|
|
2222
|
+
try {
|
|
2223
|
+
existing = await fsp.readFile(file, "utf8");
|
|
2224
|
+
} catch {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2228
|
+
const lines = existing.split("\n").filter((line) => {
|
|
2229
|
+
const trimmed = line.trim();
|
|
2230
|
+
if (!trimmed.startsWith("- ")) return true;
|
|
2231
|
+
const norm = trimmed.replace(/\[[^\]]+\]/, "").replace(/\bmem_\d+_\w+\s*/, "").trim().toLowerCase();
|
|
2232
|
+
if (seen.has(norm)) return false;
|
|
2233
|
+
seen.add(norm);
|
|
2234
|
+
return true;
|
|
2235
|
+
});
|
|
2236
|
+
const next = lines.join("\n");
|
|
2237
|
+
try {
|
|
2238
|
+
await atomicWrite(file, next);
|
|
2239
|
+
} catch {
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
const backup = `${file}.bak.${Date.now()}`;
|
|
2243
|
+
try {
|
|
2244
|
+
await fsp.copyFile(file, backup);
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
async clear(scope) {
|
|
2249
|
+
if (scope) {
|
|
2250
|
+
await atomicWrite(this.files[scope], "");
|
|
2251
|
+
} else {
|
|
2252
|
+
for (const s of ["project-agents", "project-memory", "user-memory"]) {
|
|
2253
|
+
await atomicWrite(this.files[s], "");
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
function labelOf(scope) {
|
|
2259
|
+
switch (scope) {
|
|
2260
|
+
case "project-agents":
|
|
2261
|
+
return "Project AGENTS.md";
|
|
2262
|
+
case "project-memory":
|
|
2263
|
+
return "Project memory";
|
|
2264
|
+
case "user-memory":
|
|
2265
|
+
return "User memory";
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// src/defaults/secret-scrubber.ts
|
|
2270
|
+
var PATTERNS = [
|
|
2271
|
+
// Anchored at the start where possible so partial matches inside larger
|
|
2272
|
+
// strings don't trigger false positives.
|
|
2273
|
+
{ type: "anthropic_key", regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
2274
|
+
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
2275
|
+
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
2276
|
+
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
|
|
2277
|
+
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
|
|
2278
|
+
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
|
|
2279
|
+
{ type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
2280
|
+
{ type: "stripe_key", regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g },
|
|
2281
|
+
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
2282
|
+
{
|
|
2283
|
+
type: "jwt",
|
|
2284
|
+
// Anchored: look for literal "eyJ" which is unambiguous for JWT header
|
|
2285
|
+
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
2286
|
+
},
|
|
2287
|
+
{
|
|
2288
|
+
type: "private_key",
|
|
2289
|
+
// Anchored: start must be BEGIN, end must be END with no extra dashes after END
|
|
2290
|
+
regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END[^-]*-----(?:\n|$)/g
|
|
2291
|
+
},
|
|
2292
|
+
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
|
|
2293
|
+
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
2294
|
+
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
2295
|
+
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
2296
|
+
{ type: "bearer_token", regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,}=*(?![A-Za-z0-9_.~+/-])/g },
|
|
2297
|
+
{
|
|
2298
|
+
type: "high_entropy_env",
|
|
2299
|
+
// Value-side word boundary + length gate to avoid matching short random strings
|
|
2300
|
+
regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,})['"]?(?!\s*[A-Za-z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))/g
|
|
2301
|
+
}
|
|
2302
|
+
];
|
|
2303
|
+
var DefaultSecretScrubber = class {
|
|
2304
|
+
scrub(text) {
|
|
2305
|
+
if (!text) return text;
|
|
2306
|
+
let out = text;
|
|
2307
|
+
for (const p of PATTERNS) {
|
|
2308
|
+
out = out.replace(p.regex, (_match, group1, group2) => {
|
|
2309
|
+
if (p.type === "high_entropy_env" && group1 && group2) {
|
|
2310
|
+
return `${group1}=[REDACTED:${p.type}]`;
|
|
2311
|
+
}
|
|
2312
|
+
return `[REDACTED:${p.type}]`;
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
return out;
|
|
2316
|
+
}
|
|
2317
|
+
scrubObject(obj) {
|
|
2318
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2319
|
+
const visit = (v) => {
|
|
2320
|
+
if (typeof v === "string") return this.scrub(v);
|
|
2321
|
+
if (v === null || typeof v !== "object") return v;
|
|
2322
|
+
if (seen.has(v)) return v;
|
|
2323
|
+
seen.add(v);
|
|
2324
|
+
if (Array.isArray(v)) return v.map(visit);
|
|
2325
|
+
const out = {};
|
|
2326
|
+
for (const [k, val] of Object.entries(v)) {
|
|
2327
|
+
out[k] = visit(val);
|
|
2328
|
+
}
|
|
2329
|
+
return out;
|
|
2330
|
+
};
|
|
2331
|
+
return visit(obj);
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2128
2334
|
var KEY_BYTES = 32;
|
|
2129
2335
|
var IV_BYTES = 12;
|
|
2130
2336
|
var TAG_BYTES = 16;
|
|
@@ -2169,7 +2375,7 @@ var DefaultSecretVault = class {
|
|
|
2169
2375
|
loadOrCreateKey() {
|
|
2170
2376
|
if (this.key) return this.key;
|
|
2171
2377
|
try {
|
|
2172
|
-
const buf =
|
|
2378
|
+
const buf = fs5.readFileSync(this.keyFile);
|
|
2173
2379
|
if (buf.length !== KEY_BYTES) {
|
|
2174
2380
|
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
2175
2381
|
}
|
|
@@ -2178,13 +2384,13 @@ var DefaultSecretVault = class {
|
|
|
2178
2384
|
} catch (err) {
|
|
2179
2385
|
if (err.code !== "ENOENT") throw err;
|
|
2180
2386
|
}
|
|
2181
|
-
|
|
2387
|
+
fs5.mkdirSync(path2.dirname(this.keyFile), { recursive: true });
|
|
2182
2388
|
const key = randomBytes(KEY_BYTES);
|
|
2183
2389
|
try {
|
|
2184
|
-
|
|
2390
|
+
fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
2185
2391
|
} catch (err) {
|
|
2186
2392
|
if (err.code !== "EEXIST") throw err;
|
|
2187
|
-
const buf =
|
|
2393
|
+
const buf = fs5.readFileSync(this.keyFile);
|
|
2188
2394
|
if (buf.length !== KEY_BYTES) {
|
|
2189
2395
|
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
2190
2396
|
}
|
|
@@ -2196,23 +2402,23 @@ var DefaultSecretVault = class {
|
|
|
2196
2402
|
}
|
|
2197
2403
|
};
|
|
2198
2404
|
function decryptConfigSecrets(cfg, vault) {
|
|
2199
|
-
return
|
|
2405
|
+
return walk2(cfg, vault, (v) => vault.decrypt(v));
|
|
2200
2406
|
}
|
|
2201
2407
|
function encryptConfigSecrets(cfg, vault) {
|
|
2202
|
-
return
|
|
2408
|
+
return walk2(cfg, vault, (v) => vault.encrypt(v));
|
|
2203
2409
|
}
|
|
2204
|
-
function
|
|
2410
|
+
function walk2(node, vault, transform) {
|
|
2205
2411
|
if (node === null || node === void 0) return node;
|
|
2206
2412
|
if (typeof node !== "object") return node;
|
|
2207
2413
|
if (Array.isArray(node)) {
|
|
2208
|
-
return node.map((item) =>
|
|
2414
|
+
return node.map((item) => walk2(item, vault, transform));
|
|
2209
2415
|
}
|
|
2210
2416
|
const out = {};
|
|
2211
2417
|
for (const [k, v] of Object.entries(node)) {
|
|
2212
2418
|
if (typeof v === "string" && isSecretField(k)) {
|
|
2213
2419
|
out[k] = transform(v);
|
|
2214
2420
|
} else if (typeof v === "object" && v !== null) {
|
|
2215
|
-
out[k] =
|
|
2421
|
+
out[k] = walk2(v, vault, transform);
|
|
2216
2422
|
} else {
|
|
2217
2423
|
out[k] = v;
|
|
2218
2424
|
}
|
|
@@ -2296,145 +2502,12 @@ function deepMerge(a, b) {
|
|
|
2296
2502
|
}
|
|
2297
2503
|
return out;
|
|
2298
2504
|
}
|
|
2299
|
-
var
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
"project-memory": opts.paths.projectMemory,
|
|
2306
|
-
"user-memory": opts.paths.globalMemory
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
async readAll() {
|
|
2310
|
-
const parts = [];
|
|
2311
|
-
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
2312
|
-
const body = await this.read(scope);
|
|
2313
|
-
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
2314
|
-
|
|
2315
|
-
${body.trim()}`);
|
|
2316
|
-
}
|
|
2317
|
-
return parts.join("\n\n");
|
|
2318
|
-
}
|
|
2319
|
-
async read(scope) {
|
|
2320
|
-
try {
|
|
2321
|
-
return await fsp.readFile(this.files[scope], "utf8");
|
|
2322
|
-
} catch {
|
|
2323
|
-
return "";
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
async remember(text, scope = "project-memory") {
|
|
2327
|
-
const file = this.files[scope];
|
|
2328
|
-
await ensureDir(path2.dirname(file));
|
|
2329
|
-
let existing = "";
|
|
2330
|
-
try {
|
|
2331
|
-
existing = await fsp.readFile(file, "utf8");
|
|
2332
|
-
} catch {
|
|
2333
|
-
}
|
|
2334
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2335
|
-
const id = `mem_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
2336
|
-
const entry = `
|
|
2337
|
-
- [${ts}] ${id} ${text.replace(/\n/g, " ")}
|
|
2338
|
-
`;
|
|
2339
|
-
const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
|
|
2340
|
-
${entry}`;
|
|
2341
|
-
await atomicWrite(file, next);
|
|
2342
|
-
const buf = Buffer.byteLength(next, "utf8");
|
|
2343
|
-
if (buf > MAX_BYTES_TOTAL) {
|
|
2344
|
-
await this.consolidate(scope);
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
async forget(query, scope = "project-memory") {
|
|
2348
|
-
const file = this.files[scope];
|
|
2349
|
-
let existing;
|
|
2350
|
-
try {
|
|
2351
|
-
existing = await fsp.readFile(file, "utf8");
|
|
2352
|
-
} catch {
|
|
2353
|
-
return 0;
|
|
2354
|
-
}
|
|
2355
|
-
const needle = query.toLowerCase();
|
|
2356
|
-
const idMatcher = /mem_\d+_\w+/;
|
|
2357
|
-
let removed = 0;
|
|
2358
|
-
const lines = existing.split("\n").filter((line) => {
|
|
2359
|
-
const trimmed = line.trim();
|
|
2360
|
-
if (!trimmed.startsWith("- ")) return true;
|
|
2361
|
-
if (idMatcher.test(query)) {
|
|
2362
|
-
const afterBracket = trimmed.indexOf("] ");
|
|
2363
|
-
if (afterBracket !== -1) {
|
|
2364
|
-
const afterTs = trimmed.slice(afterBracket + 2);
|
|
2365
|
-
const entryIdMatch = /^mem_\d+_\w+/.exec(afterTs);
|
|
2366
|
-
if (entryIdMatch && entryIdMatch[0] === query) {
|
|
2367
|
-
removed++;
|
|
2368
|
-
return false;
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
if (trimmed.toLowerCase().includes(needle)) {
|
|
2373
|
-
removed++;
|
|
2374
|
-
return false;
|
|
2375
|
-
}
|
|
2376
|
-
return true;
|
|
2377
|
-
});
|
|
2378
|
-
if (removed > 0) {
|
|
2379
|
-
await atomicWrite(file, lines.join("\n"));
|
|
2380
|
-
}
|
|
2381
|
-
return removed;
|
|
2382
|
-
}
|
|
2383
|
-
async consolidate(scope) {
|
|
2384
|
-
const file = this.files[scope];
|
|
2385
|
-
let existing;
|
|
2386
|
-
try {
|
|
2387
|
-
existing = await fsp.readFile(file, "utf8");
|
|
2388
|
-
} catch {
|
|
2389
|
-
return;
|
|
2390
|
-
}
|
|
2391
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2392
|
-
const lines = existing.split("\n").filter((line) => {
|
|
2393
|
-
const trimmed = line.trim();
|
|
2394
|
-
if (!trimmed.startsWith("- ")) return true;
|
|
2395
|
-
const norm = trimmed.replace(/\[[^\]]+\]/, "").replace(/\bmem_\d+_\w+\s*/, "").trim().toLowerCase();
|
|
2396
|
-
if (seen.has(norm)) return false;
|
|
2397
|
-
seen.add(norm);
|
|
2398
|
-
return true;
|
|
2399
|
-
});
|
|
2400
|
-
const next = lines.join("\n");
|
|
2401
|
-
try {
|
|
2402
|
-
await atomicWrite(file, next);
|
|
2403
|
-
} catch {
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
|
-
const backup = `${file}.bak.${Date.now()}`;
|
|
2407
|
-
try {
|
|
2408
|
-
await fsp.copyFile(file, backup);
|
|
2409
|
-
} catch {
|
|
2410
|
-
}
|
|
2411
|
-
}
|
|
2412
|
-
async clear(scope) {
|
|
2413
|
-
if (scope) {
|
|
2414
|
-
await atomicWrite(this.files[scope], "");
|
|
2415
|
-
} else {
|
|
2416
|
-
for (const s of ["project-agents", "project-memory", "user-memory"]) {
|
|
2417
|
-
await atomicWrite(this.files[s], "");
|
|
2418
|
-
}
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
};
|
|
2422
|
-
function labelOf(scope) {
|
|
2423
|
-
switch (scope) {
|
|
2424
|
-
case "project-agents":
|
|
2425
|
-
return "Project AGENTS.md";
|
|
2426
|
-
case "project-memory":
|
|
2427
|
-
return "Project memory";
|
|
2428
|
-
case "user-memory":
|
|
2429
|
-
return "User memory";
|
|
2430
|
-
}
|
|
2431
|
-
}
|
|
2432
|
-
var DefaultPermissionPolicy = class {
|
|
2433
|
-
policy = {};
|
|
2434
|
-
loaded = false;
|
|
2435
|
-
trustFile;
|
|
2436
|
-
yolo;
|
|
2437
|
-
promptDelegate;
|
|
2505
|
+
var DefaultPermissionPolicy = class {
|
|
2506
|
+
policy = {};
|
|
2507
|
+
loaded = false;
|
|
2508
|
+
trustFile;
|
|
2509
|
+
yolo;
|
|
2510
|
+
promptDelegate;
|
|
2438
2511
|
constructor(opts) {
|
|
2439
2512
|
this.trustFile = opts.trustFile;
|
|
2440
2513
|
this.yolo = opts.yolo ?? false;
|
|
@@ -2530,6 +2603,126 @@ var DefaultPermissionPolicy = class {
|
|
|
2530
2603
|
return void 0;
|
|
2531
2604
|
}
|
|
2532
2605
|
};
|
|
2606
|
+
|
|
2607
|
+
// src/defaults/retry-policy.ts
|
|
2608
|
+
var DefaultRetryPolicy = class {
|
|
2609
|
+
shouldRetry(err, attempt) {
|
|
2610
|
+
if (err instanceof ProviderError) {
|
|
2611
|
+
if (!err.retryable) return false;
|
|
2612
|
+
return attempt < this.maxAttempts(err);
|
|
2613
|
+
}
|
|
2614
|
+
const msg = err.message ?? "";
|
|
2615
|
+
const isNetwork = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(msg);
|
|
2616
|
+
if (isNetwork) return attempt < 2;
|
|
2617
|
+
return false;
|
|
2618
|
+
}
|
|
2619
|
+
maxAttempts(err) {
|
|
2620
|
+
if (err instanceof ProviderError) {
|
|
2621
|
+
if (err.status === 429) return 5;
|
|
2622
|
+
if (err.status === 529) return 3;
|
|
2623
|
+
if (err.status >= 500) return 3;
|
|
2624
|
+
return 0;
|
|
2625
|
+
}
|
|
2626
|
+
return 2;
|
|
2627
|
+
}
|
|
2628
|
+
delayMs(attempt) {
|
|
2629
|
+
const base = 1e3;
|
|
2630
|
+
const exp = base * 2 ** attempt;
|
|
2631
|
+
const jitter = Math.random() * base;
|
|
2632
|
+
return Math.min(3e4, exp + jitter);
|
|
2633
|
+
}
|
|
2634
|
+
};
|
|
2635
|
+
|
|
2636
|
+
// src/defaults/error-handler.ts
|
|
2637
|
+
function buildRecoveryStrategies(opts) {
|
|
2638
|
+
return [
|
|
2639
|
+
{
|
|
2640
|
+
label: "context_overflow_reduce",
|
|
2641
|
+
compactor: opts?.compactor,
|
|
2642
|
+
async attempt(err, ctx) {
|
|
2643
|
+
if (err instanceof ProviderError && (err.status === 413 || /context|too long|tokens/i.test(err.message))) {
|
|
2644
|
+
if (this.compactor) {
|
|
2645
|
+
try {
|
|
2646
|
+
const report = await this.compactor.compact(ctx, { aggressive: true });
|
|
2647
|
+
if (report.after < report.before) {
|
|
2648
|
+
return {
|
|
2649
|
+
content: [{ type: "text", text: "[context compacted automatically \u2014 please retry]" }],
|
|
2650
|
+
stopReason: "end_turn",
|
|
2651
|
+
usage: { input: 0, output: 0 },
|
|
2652
|
+
model: ctx.model
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
} catch {
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return null;
|
|
2659
|
+
}
|
|
2660
|
+
return null;
|
|
2661
|
+
}
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
label: "rate_limit_backoff",
|
|
2665
|
+
async attempt(err, ctx) {
|
|
2666
|
+
if (err instanceof ProviderError && err.status === 429) {
|
|
2667
|
+
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
2668
|
+
const delay = Math.max(1e3, Math.min(delayMs, 6e4));
|
|
2669
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2670
|
+
return {
|
|
2671
|
+
content: [{ type: "text", text: "[rate limit backoff applied \u2014 please retry]" }],
|
|
2672
|
+
stopReason: "end_turn",
|
|
2673
|
+
usage: { input: 0, output: 0 },
|
|
2674
|
+
model: ctx.model
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
label: "downgrade_model",
|
|
2682
|
+
async attempt(err, ctx) {
|
|
2683
|
+
if (err instanceof ProviderError && (err.status === 429 || err.status === 529 || err.status >= 500)) {
|
|
2684
|
+
return null;
|
|
2685
|
+
}
|
|
2686
|
+
return null;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
];
|
|
2690
|
+
}
|
|
2691
|
+
var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
|
|
2692
|
+
var DefaultErrorHandler = class {
|
|
2693
|
+
strategies;
|
|
2694
|
+
constructor(strategies = DEFAULT_RECOVERY_STRATEGIES) {
|
|
2695
|
+
this.strategies = strategies;
|
|
2696
|
+
}
|
|
2697
|
+
classify(err) {
|
|
2698
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
2699
|
+
return { kind: "abort", retryable: false };
|
|
2700
|
+
}
|
|
2701
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
2702
|
+
return { kind: "abort", retryable: false };
|
|
2703
|
+
}
|
|
2704
|
+
if (err instanceof ProviderError) {
|
|
2705
|
+
if (err.status === 429) return { kind: "rate_limit", retryable: true };
|
|
2706
|
+
if (err.status === 529) return { kind: "overloaded", retryable: true };
|
|
2707
|
+
if (err.status >= 500) return { kind: "server", retryable: true };
|
|
2708
|
+
if (err.status === 413 || /context|too long|tokens/i.test(err.message)) {
|
|
2709
|
+
return { kind: "context_overflow", retryable: false };
|
|
2710
|
+
}
|
|
2711
|
+
if (err.status >= 400) return { kind: "client", retryable: false };
|
|
2712
|
+
}
|
|
2713
|
+
if (err instanceof Error && /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i.test(err.message)) {
|
|
2714
|
+
return { kind: "network", retryable: true };
|
|
2715
|
+
}
|
|
2716
|
+
return { kind: "unknown", retryable: false };
|
|
2717
|
+
}
|
|
2718
|
+
async recover(err, ctx) {
|
|
2719
|
+
for (const strategy of this.strategies) {
|
|
2720
|
+
const result = await strategy.attempt(err, ctx);
|
|
2721
|
+
if (result !== null) return result;
|
|
2722
|
+
}
|
|
2723
|
+
return null;
|
|
2724
|
+
}
|
|
2725
|
+
};
|
|
2533
2726
|
var DefaultSkillLoader = class {
|
|
2534
2727
|
dirs;
|
|
2535
2728
|
cache;
|
|
@@ -2747,8 +2940,22 @@ var DefaultConfigLoader = class {
|
|
|
2747
2940
|
if (this.vault) {
|
|
2748
2941
|
cfg = decryptConfigSecrets(cfg, this.vault);
|
|
2749
2942
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2943
|
+
if (cfg.providers) {
|
|
2944
|
+
for (const pcfg of Object.values(cfg.providers)) {
|
|
2945
|
+
if (!pcfg || typeof pcfg !== "object") continue;
|
|
2946
|
+
const keys = pcfg.apiKeys;
|
|
2947
|
+
if (!Array.isArray(keys) || keys.length === 0) continue;
|
|
2948
|
+
const existing = pcfg.apiKey;
|
|
2949
|
+
if (existing && existing.length > 0) continue;
|
|
2950
|
+
const activeLabel = pcfg.activeKey;
|
|
2951
|
+
const chosen = activeLabel ? keys.find((k) => k.label === activeLabel) ?? keys[0] : keys[0];
|
|
2952
|
+
if (chosen?.apiKey) {
|
|
2953
|
+
pcfg.apiKey = chosen.apiKey;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
this.validateBehavior(cfg);
|
|
2958
|
+
if (this.strict) this.validateIdentity(cfg);
|
|
2752
2959
|
return Object.freeze(cfg);
|
|
2753
2960
|
}
|
|
2754
2961
|
async readJson(file) {
|
|
@@ -2783,6 +2990,110 @@ var DefaultConfigLoader = class {
|
|
|
2783
2990
|
}
|
|
2784
2991
|
};
|
|
2785
2992
|
|
|
2993
|
+
// src/defaults/config-store.ts
|
|
2994
|
+
var DefaultConfigStore = class {
|
|
2995
|
+
current;
|
|
2996
|
+
watchers = /* @__PURE__ */ new Set();
|
|
2997
|
+
constructor(initial) {
|
|
2998
|
+
this.current = deepFreeze(structuredClone(initial));
|
|
2999
|
+
}
|
|
3000
|
+
get() {
|
|
3001
|
+
return this.current;
|
|
3002
|
+
}
|
|
3003
|
+
getSection(key) {
|
|
3004
|
+
return this.current[key];
|
|
3005
|
+
}
|
|
3006
|
+
getExtension(pluginName) {
|
|
3007
|
+
const ext = this.current.extensions?.[pluginName];
|
|
3008
|
+
return ext ? ext : FROZEN_EMPTY;
|
|
3009
|
+
}
|
|
3010
|
+
update(partial) {
|
|
3011
|
+
const next = deepFreeze(
|
|
3012
|
+
structuredClone({ ...this.current, ...partial })
|
|
3013
|
+
);
|
|
3014
|
+
if (next.version !== 1) {
|
|
3015
|
+
throw new Error(`ConfigStore.update: version must remain 1, got ${String(next.version)}`);
|
|
3016
|
+
}
|
|
3017
|
+
const prev = this.current;
|
|
3018
|
+
this.current = next;
|
|
3019
|
+
for (const w of this.watchers) {
|
|
3020
|
+
try {
|
|
3021
|
+
w(next, prev);
|
|
3022
|
+
} catch {
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
return next;
|
|
3026
|
+
}
|
|
3027
|
+
watch(cb) {
|
|
3028
|
+
this.watchers.add(cb);
|
|
3029
|
+
return () => this.watchers.delete(cb);
|
|
3030
|
+
}
|
|
3031
|
+
};
|
|
3032
|
+
var FROZEN_EMPTY = Object.freeze({});
|
|
3033
|
+
function deepFreeze(obj) {
|
|
3034
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
3035
|
+
if (Object.isFrozen(obj)) return obj;
|
|
3036
|
+
for (const key of Object.keys(obj)) {
|
|
3037
|
+
const v = obj[key];
|
|
3038
|
+
if (v !== null && typeof v === "object" && !Object.isFrozen(v)) {
|
|
3039
|
+
deepFreeze(v);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
return Object.freeze(obj);
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
// src/defaults/config-migration.ts
|
|
3046
|
+
var ConfigMigrationError = class extends Error {
|
|
3047
|
+
fromVersion;
|
|
3048
|
+
targetVersion;
|
|
3049
|
+
missingStep;
|
|
3050
|
+
constructor(opts) {
|
|
3051
|
+
super(opts.message);
|
|
3052
|
+
this.name = "ConfigMigrationError";
|
|
3053
|
+
this.fromVersion = opts.fromVersion;
|
|
3054
|
+
this.targetVersion = opts.targetVersion;
|
|
3055
|
+
this.missingStep = opts.missingStep;
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
function runConfigMigrations(input, targetVersion, migrations) {
|
|
3059
|
+
const initial = typeof input["version"] === "number" ? input["version"] : 1;
|
|
3060
|
+
let current = { ...input };
|
|
3061
|
+
let currentVersion = initial;
|
|
3062
|
+
const applied = [];
|
|
3063
|
+
let shouldPersist = false;
|
|
3064
|
+
let guard = 0;
|
|
3065
|
+
while (currentVersion !== targetVersion) {
|
|
3066
|
+
if (++guard > 100) {
|
|
3067
|
+
throw new ConfigMigrationError({
|
|
3068
|
+
message: `Config migration looped past 100 steps (from v${initial} toward v${targetVersion})`,
|
|
3069
|
+
fromVersion: initial,
|
|
3070
|
+
targetVersion,
|
|
3071
|
+
missingStep: currentVersion
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
const step = migrations.find((m) => m.from === currentVersion);
|
|
3075
|
+
if (!step) {
|
|
3076
|
+
throw new ConfigMigrationError({
|
|
3077
|
+
message: `No migration registered from config v${currentVersion} (target v${targetVersion}). Update the framework or revert the config file.`,
|
|
3078
|
+
fromVersion: initial,
|
|
3079
|
+
targetVersion,
|
|
3080
|
+
missingStep: currentVersion
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
const ctx = { fromVersion: currentVersion, shouldPersist: false };
|
|
3084
|
+
const next = step.migrate(current, ctx);
|
|
3085
|
+
if (typeof next["version"] !== "number" || next["version"] !== step.to) {
|
|
3086
|
+
next["version"] = step.to;
|
|
3087
|
+
}
|
|
3088
|
+
current = next;
|
|
3089
|
+
currentVersion = step.to;
|
|
3090
|
+
applied.push(`v${step.from}\u2192v${step.to}`);
|
|
3091
|
+
shouldPersist = shouldPersist || ctx.shouldPersist || step.from < step.to;
|
|
3092
|
+
}
|
|
3093
|
+
return { config: current, applied, shouldPersist };
|
|
3094
|
+
}
|
|
3095
|
+
var DEFAULT_CONFIG_MIGRATIONS = [];
|
|
3096
|
+
|
|
2786
3097
|
// src/defaults/compactor.ts
|
|
2787
3098
|
var HybridCompactor = class {
|
|
2788
3099
|
preserveK;
|
|
@@ -2791,7 +3102,7 @@ var HybridCompactor = class {
|
|
|
2791
3102
|
constructor(opts = {}) {
|
|
2792
3103
|
this.preserveK = opts.preserveK ?? 10;
|
|
2793
3104
|
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
2794
|
-
this.estimator = opts.estimator ??
|
|
3105
|
+
this.estimator = opts.estimator ?? estimateTextTokens;
|
|
2795
3106
|
}
|
|
2796
3107
|
async compact(ctx, opts = {}) {
|
|
2797
3108
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
@@ -2823,8 +3134,7 @@ var HybridCompactor = class {
|
|
|
2823
3134
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
2824
3135
|
const newContent = msg.content.map((b) => {
|
|
2825
3136
|
if (b.type !== "tool_result") return b;
|
|
2826
|
-
const
|
|
2827
|
-
const tokens = this.estimator(text);
|
|
3137
|
+
const tokens = estimateToolResultTokens(b.content);
|
|
2828
3138
|
if (tokens < this.eliseThreshold) return b;
|
|
2829
3139
|
saved += tokens;
|
|
2830
3140
|
const elided = {
|
|
@@ -2862,23 +3172,20 @@ var HybridCompactor = class {
|
|
|
2862
3172
|
},
|
|
2863
3173
|
{ role: "assistant", content: "Continuing from compacted context." }
|
|
2864
3174
|
];
|
|
2865
|
-
ctx.messages.
|
|
3175
|
+
const tail = ctx.messages.slice(boundary);
|
|
3176
|
+
ctx.state.replaceMessages([...summary, ...tail]);
|
|
2866
3177
|
return Math.max(0, removedTokens - this.estimateMessages(summary));
|
|
2867
3178
|
}
|
|
2868
3179
|
estimateMessages(messages) {
|
|
2869
3180
|
let total = 0;
|
|
2870
3181
|
for (const m of messages) {
|
|
2871
3182
|
if (typeof m.content === "string") {
|
|
2872
|
-
total +=
|
|
3183
|
+
total += estimateTextTokens(m.content);
|
|
2873
3184
|
} else {
|
|
2874
3185
|
for (const b of m.content) {
|
|
2875
|
-
if (b.type === "text") total +=
|
|
2876
|
-
else if (b.type === "tool_use") total +=
|
|
2877
|
-
else if (b.type === "tool_result")
|
|
2878
|
-
total += this.estimator(
|
|
2879
|
-
typeof b.content === "string" ? b.content : JSON.stringify(b.content)
|
|
2880
|
-
);
|
|
2881
|
-
}
|
|
3186
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
3187
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
3188
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
2882
3189
|
}
|
|
2883
3190
|
}
|
|
2884
3191
|
}
|
|
@@ -2889,9 +3196,6 @@ function hasTextContent(m) {
|
|
|
2889
3196
|
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
2890
3197
|
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
2891
3198
|
}
|
|
2892
|
-
function roughTokenEstimate(text) {
|
|
2893
|
-
return Math.max(1, Math.ceil(text.length / 4));
|
|
2894
|
-
}
|
|
2895
3199
|
|
|
2896
3200
|
// src/defaults/intelligent-compactor.ts
|
|
2897
3201
|
var IntelligentCompactor = class {
|
|
@@ -2951,7 +3255,8 @@ var IntelligentCompactor = class {
|
|
|
2951
3255
|
content: `[prior_turns_summary: ${summaryText}]`
|
|
2952
3256
|
};
|
|
2953
3257
|
const summaryTokens = this.estimateTokens([summaryMsg]);
|
|
2954
|
-
ctx.messages.
|
|
3258
|
+
const tail = ctx.messages.slice(boundary);
|
|
3259
|
+
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
2955
3260
|
return Math.max(0, removedTokens - summaryTokens);
|
|
2956
3261
|
}
|
|
2957
3262
|
findSafeBoundary(messages, from, to) {
|
|
@@ -3032,8 +3337,7 @@ var IntelligentCompactor = class {
|
|
|
3032
3337
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
3033
3338
|
const newContent = msg.content.map((b) => {
|
|
3034
3339
|
if (b.type !== "tool_result") return b;
|
|
3035
|
-
const
|
|
3036
|
-
const tokens = this.roughTokenEstimate(text);
|
|
3340
|
+
const tokens = estimateToolResultTokens(b.content);
|
|
3037
3341
|
if (tokens < this.eliseThreshold) return b;
|
|
3038
3342
|
saved += tokens;
|
|
3039
3343
|
return {
|
|
@@ -3055,24 +3359,17 @@ var IntelligentCompactor = class {
|
|
|
3055
3359
|
let total = 0;
|
|
3056
3360
|
for (const m of messages) {
|
|
3057
3361
|
if (typeof m.content === "string") {
|
|
3058
|
-
total +=
|
|
3362
|
+
total += estimateTextTokens(m.content);
|
|
3059
3363
|
} else {
|
|
3060
3364
|
for (const b of m.content) {
|
|
3061
|
-
if (b.type === "text") total +=
|
|
3062
|
-
else if (b.type === "tool_use") total +=
|
|
3063
|
-
else if (b.type === "tool_result")
|
|
3064
|
-
total += this.roughTokenEstimate(
|
|
3065
|
-
typeof b.content === "string" ? b.content : JSON.stringify(b.content)
|
|
3066
|
-
);
|
|
3067
|
-
}
|
|
3365
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
3366
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
3367
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
3068
3368
|
}
|
|
3069
3369
|
}
|
|
3070
3370
|
}
|
|
3071
3371
|
return total;
|
|
3072
3372
|
}
|
|
3073
|
-
roughTokenEstimate(text) {
|
|
3074
|
-
return Math.max(1, Math.ceil(text.length / 4));
|
|
3075
|
-
}
|
|
3076
3373
|
};
|
|
3077
3374
|
|
|
3078
3375
|
// src/defaults/llm-selector.ts
|
|
@@ -3295,8 +3592,8 @@ var SelectiveCompactor = class {
|
|
|
3295
3592
|
* insert summaries where the selector provided them.
|
|
3296
3593
|
*/
|
|
3297
3594
|
async executePlan(ctx, plan) {
|
|
3298
|
-
|
|
3299
|
-
|
|
3595
|
+
if (ctx.messages.length === 0) return;
|
|
3596
|
+
const messages = [...ctx.messages];
|
|
3300
3597
|
const sortedCollapsed = [...plan.collapsed].sort((a, b) => b.from - a.from);
|
|
3301
3598
|
for (const range of sortedCollapsed) {
|
|
3302
3599
|
if (range.from < 0 || range.to >= messages.length || range.from > range.to) continue;
|
|
@@ -3311,6 +3608,7 @@ var SelectiveCompactor = class {
|
|
|
3311
3608
|
};
|
|
3312
3609
|
messages.splice(range.from, range.to - range.from + 1, summaryMsg);
|
|
3313
3610
|
}
|
|
3611
|
+
ctx.state.replaceMessages(messages);
|
|
3314
3612
|
}
|
|
3315
3613
|
async summarizeRange(messages, ctx) {
|
|
3316
3614
|
const systemText = `${this.summarizerPrompt}
|
|
@@ -3357,7 +3655,8 @@ Summarize the following message range:`;
|
|
|
3357
3655
|
role: "system",
|
|
3358
3656
|
content: `[${removed.length} earlier turns trimmed \u2014 see session log for details]`
|
|
3359
3657
|
};
|
|
3360
|
-
messages.
|
|
3658
|
+
const tail = messages.slice(boundary);
|
|
3659
|
+
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
3361
3660
|
return Math.max(0, removedTokens - this.estimateTokens([summaryMsg]));
|
|
3362
3661
|
}
|
|
3363
3662
|
computeTargetBudget(load, aggressive) {
|
|
@@ -3747,44 +4046,128 @@ async function loadUserModes(modesDir) {
|
|
|
3747
4046
|
}
|
|
3748
4047
|
return modes;
|
|
3749
4048
|
}
|
|
4049
|
+
|
|
4050
|
+
// src/defaults/subagent-budget.ts
|
|
4051
|
+
var BudgetExceededError = class extends Error {
|
|
4052
|
+
kind;
|
|
4053
|
+
limit;
|
|
4054
|
+
observed;
|
|
4055
|
+
constructor(kind, limit, observed) {
|
|
4056
|
+
super(`Budget exceeded: ${kind} (limit=${limit}, observed=${observed})`);
|
|
4057
|
+
this.name = "BudgetExceededError";
|
|
4058
|
+
this.kind = kind;
|
|
4059
|
+
this.limit = limit;
|
|
4060
|
+
this.observed = observed;
|
|
4061
|
+
}
|
|
4062
|
+
};
|
|
4063
|
+
var SubagentBudget = class {
|
|
4064
|
+
limits;
|
|
4065
|
+
iterations = 0;
|
|
4066
|
+
toolCalls = 0;
|
|
4067
|
+
tokenInput = 0;
|
|
4068
|
+
tokenOutput = 0;
|
|
4069
|
+
costUsd = 0;
|
|
4070
|
+
startTime = null;
|
|
4071
|
+
constructor(limits = {}) {
|
|
4072
|
+
this.limits = Object.freeze({ ...limits });
|
|
4073
|
+
}
|
|
4074
|
+
start() {
|
|
4075
|
+
this.startTime = Date.now();
|
|
4076
|
+
}
|
|
4077
|
+
recordIteration() {
|
|
4078
|
+
this.iterations++;
|
|
4079
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
4080
|
+
throw new BudgetExceededError("iterations", this.limits.maxIterations, this.iterations);
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
recordToolCall() {
|
|
4084
|
+
this.toolCalls++;
|
|
4085
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
4086
|
+
throw new BudgetExceededError("tool_calls", this.limits.maxToolCalls, this.toolCalls);
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
recordUsage(usage, costUsd = 0) {
|
|
4090
|
+
this.tokenInput += usage.input;
|
|
4091
|
+
this.tokenOutput += usage.output;
|
|
4092
|
+
this.costUsd += costUsd;
|
|
4093
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
4094
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
4095
|
+
throw new BudgetExceededError("tokens", this.limits.maxTokens, totalTokens);
|
|
4096
|
+
}
|
|
4097
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
4098
|
+
throw new BudgetExceededError("cost", this.limits.maxCostUsd, this.costUsd);
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
/**
|
|
4102
|
+
* Throws if the wall-clock budget is exhausted. Call this from the iteration
|
|
4103
|
+
* loop so a hung tool can't keep a subagent running past its deadline.
|
|
4104
|
+
*/
|
|
4105
|
+
checkTimeout() {
|
|
4106
|
+
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
4107
|
+
const elapsed = Date.now() - this.startTime;
|
|
4108
|
+
if (elapsed > this.limits.timeoutMs) {
|
|
4109
|
+
throw new BudgetExceededError("timeout", this.limits.timeoutMs, elapsed);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
4113
|
+
isTimedOut() {
|
|
4114
|
+
if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
|
|
4115
|
+
return Date.now() - this.startTime > this.limits.timeoutMs;
|
|
4116
|
+
}
|
|
4117
|
+
usage() {
|
|
4118
|
+
return {
|
|
4119
|
+
iterations: this.iterations,
|
|
4120
|
+
toolCalls: this.toolCalls,
|
|
4121
|
+
tokens: {
|
|
4122
|
+
input: this.tokenInput,
|
|
4123
|
+
output: this.tokenOutput,
|
|
4124
|
+
total: this.tokenInput + this.tokenOutput
|
|
4125
|
+
},
|
|
4126
|
+
costUsd: this.costUsd,
|
|
4127
|
+
elapsedMs: this.startTime === null ? 0 : Date.now() - this.startTime
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
};
|
|
4131
|
+
|
|
4132
|
+
// src/defaults/multi-agent-coordinator.ts
|
|
3750
4133
|
var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
3751
4134
|
coordinatorId;
|
|
3752
4135
|
config;
|
|
4136
|
+
runner;
|
|
3753
4137
|
subagents = /* @__PURE__ */ new Map();
|
|
3754
4138
|
pendingTasks = [];
|
|
3755
4139
|
completedResults = [];
|
|
3756
4140
|
totalIterations = 0;
|
|
3757
|
-
|
|
4141
|
+
inFlight = 0;
|
|
4142
|
+
constructor(config, options = {}) {
|
|
3758
4143
|
super();
|
|
3759
4144
|
this.coordinatorId = config.coordinatorId;
|
|
3760
4145
|
this.config = config;
|
|
4146
|
+
this.runner = options.runner;
|
|
3761
4147
|
}
|
|
3762
4148
|
async spawn(subagent) {
|
|
3763
4149
|
const id = subagent.id || randomUUID();
|
|
3764
4150
|
const context = {
|
|
3765
4151
|
subagentId: id,
|
|
3766
4152
|
tasks: [],
|
|
4153
|
+
// parentBridge: wired by the caller via setSubagentBridge() once the
|
|
4154
|
+
// bidirectional bridge is created. Reads gated by hasParentBridge().
|
|
3767
4155
|
parentBridge: null,
|
|
3768
4156
|
doneCondition: this.config.doneCondition,
|
|
3769
4157
|
maxConcurrent: this.config.maxConcurrent ?? 4
|
|
3770
4158
|
};
|
|
3771
4159
|
this.subagents.set(id, {
|
|
3772
|
-
config: subagent,
|
|
4160
|
+
config: { ...subagent, id },
|
|
3773
4161
|
context,
|
|
3774
|
-
status: "idle"
|
|
4162
|
+
status: "idle",
|
|
4163
|
+
abortController: new AbortController()
|
|
3775
4164
|
});
|
|
3776
4165
|
this.emit("subagent.started", { subagent: { ...subagent, id } });
|
|
3777
|
-
return {
|
|
3778
|
-
subagentId: id,
|
|
3779
|
-
agentId: id
|
|
3780
|
-
};
|
|
4166
|
+
return { subagentId: id, agentId: id };
|
|
3781
4167
|
}
|
|
3782
4168
|
async assign(task) {
|
|
3783
4169
|
this.pendingTasks.push(task);
|
|
3784
|
-
|
|
3785
|
-
if (available) {
|
|
3786
|
-
await this.dispatch(available, task);
|
|
3787
|
-
}
|
|
4170
|
+
this.tryDispatchNext();
|
|
3788
4171
|
}
|
|
3789
4172
|
async delegate(to, msg) {
|
|
3790
4173
|
const subagent = this.subagents.get(to);
|
|
@@ -3795,8 +4178,8 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3795
4178
|
await subagent.context.parentBridge.send(msg);
|
|
3796
4179
|
}
|
|
3797
4180
|
/**
|
|
3798
|
-
* Wire up the communication bridge for a subagent. Call
|
|
3799
|
-
*
|
|
4181
|
+
* Wire up the communication bridge for a subagent. Call after spawn() once
|
|
4182
|
+
* the caller has created the bidirectional connection.
|
|
3800
4183
|
*/
|
|
3801
4184
|
setSubagentBridge(subagentId, bridge) {
|
|
3802
4185
|
const subagent = this.subagents.get(subagentId);
|
|
@@ -3806,6 +4189,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3806
4189
|
async stop(subagentId) {
|
|
3807
4190
|
const subagent = this.subagents.get(subagentId);
|
|
3808
4191
|
if (!subagent) return;
|
|
4192
|
+
subagent.abortController.abort();
|
|
3809
4193
|
subagent.status = "stopped";
|
|
3810
4194
|
subagent.currentTask = void 0;
|
|
3811
4195
|
subagent.context.parentBridge = null;
|
|
@@ -3831,71 +4215,234 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3831
4215
|
done: this.isDone()
|
|
3832
4216
|
};
|
|
3833
4217
|
}
|
|
3834
|
-
|
|
4218
|
+
/** Expose snapshot of completed results — useful for callers awaiting all done. */
|
|
4219
|
+
results() {
|
|
4220
|
+
return this.completedResults;
|
|
4221
|
+
}
|
|
4222
|
+
/**
|
|
4223
|
+
* Manual completion — for callers that drive subagents without a runner
|
|
4224
|
+
* (e.g. external orchestrators). When a runner is configured the coordinator
|
|
4225
|
+
* calls this itself.
|
|
4226
|
+
*/
|
|
4227
|
+
completeTask(result) {
|
|
4228
|
+
this.recordCompletion(result);
|
|
4229
|
+
}
|
|
4230
|
+
// --- internal dispatching ---------------------------------------------
|
|
4231
|
+
tryDispatchNext() {
|
|
4232
|
+
while (this.canDispatch()) {
|
|
4233
|
+
const subagentId = this.findIdleSubagent();
|
|
4234
|
+
if (!subagentId) return;
|
|
4235
|
+
const task = this.pendingTasks.shift();
|
|
4236
|
+
if (!task) return;
|
|
4237
|
+
void this.runDispatched(subagentId, task);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
canDispatch() {
|
|
4241
|
+
const max = this.config.maxConcurrent ?? 4;
|
|
4242
|
+
return this.inFlight < max && this.pendingTasks.length > 0;
|
|
4243
|
+
}
|
|
4244
|
+
findIdleSubagent() {
|
|
3835
4245
|
for (const [id, s] of this.subagents) {
|
|
3836
4246
|
if (s.status === "idle") return id;
|
|
3837
4247
|
}
|
|
3838
4248
|
return null;
|
|
3839
4249
|
}
|
|
3840
|
-
async
|
|
4250
|
+
async runDispatched(subagentId, task) {
|
|
3841
4251
|
const subagent = this.subagents.get(subagentId);
|
|
3842
4252
|
if (!subagent) return;
|
|
3843
4253
|
subagent.status = "running";
|
|
3844
4254
|
subagent.currentTask = task.id;
|
|
3845
4255
|
task.subagentId = subagentId;
|
|
3846
4256
|
subagent.context.tasks.push(task);
|
|
3847
|
-
|
|
3848
|
-
|
|
4257
|
+
this.inFlight++;
|
|
4258
|
+
this.emit("task.assigned", { task, subagentId });
|
|
4259
|
+
const budget = new SubagentBudget({
|
|
4260
|
+
maxIterations: subagent.config.maxIterations ?? this.config.defaultBudget?.maxIterations,
|
|
4261
|
+
maxToolCalls: task.maxToolCalls ?? subagent.config.maxToolCalls ?? this.config.defaultBudget?.maxToolCalls,
|
|
4262
|
+
maxTokens: subagent.config.maxTokens ?? this.config.defaultBudget?.maxTokens,
|
|
4263
|
+
maxCostUsd: subagent.config.maxCostUsd ?? this.config.defaultBudget?.maxCostUsd,
|
|
4264
|
+
timeoutMs: task.timeoutMs ?? subagent.config.timeoutMs ?? this.config.defaultBudget?.timeoutMs
|
|
4265
|
+
});
|
|
4266
|
+
subagent.activeBudget = budget;
|
|
4267
|
+
const startTime = Date.now();
|
|
4268
|
+
const runCtx = {
|
|
4269
|
+
subagentId,
|
|
4270
|
+
config: subagent.config,
|
|
4271
|
+
budget,
|
|
4272
|
+
signal: subagent.abortController.signal,
|
|
4273
|
+
bridge: subagent.context.parentBridge || null
|
|
4274
|
+
};
|
|
4275
|
+
let result;
|
|
4276
|
+
if (!this.runner) {
|
|
3849
4277
|
return;
|
|
3850
4278
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
4279
|
+
budget.start();
|
|
4280
|
+
try {
|
|
4281
|
+
const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
|
|
4282
|
+
result = {
|
|
4283
|
+
subagentId,
|
|
4284
|
+
taskId: task.id,
|
|
4285
|
+
status: "success",
|
|
4286
|
+
result: outcome.result,
|
|
4287
|
+
iterations: outcome.iterations,
|
|
4288
|
+
toolCalls: outcome.toolCalls,
|
|
4289
|
+
durationMs: Date.now() - startTime
|
|
4290
|
+
};
|
|
4291
|
+
} catch (err) {
|
|
4292
|
+
const status = err instanceof BudgetExceededError && err.kind === "timeout" ? "timeout" : subagent.abortController.signal.aborted ? "stopped" : "failed";
|
|
4293
|
+
result = {
|
|
4294
|
+
subagentId,
|
|
4295
|
+
taskId: task.id,
|
|
4296
|
+
status,
|
|
4297
|
+
error: err instanceof Error ? err.message : String(err),
|
|
4298
|
+
iterations: budget.usage().iterations,
|
|
4299
|
+
toolCalls: budget.usage().toolCalls,
|
|
4300
|
+
durationMs: Date.now() - startTime
|
|
4301
|
+
};
|
|
3864
4302
|
}
|
|
3865
|
-
|
|
3866
|
-
|
|
4303
|
+
this.recordCompletion(result);
|
|
4304
|
+
}
|
|
4305
|
+
async executeWithTimeout(runner, task, ctx, budget) {
|
|
4306
|
+
const timeoutMs = budget.limits.timeoutMs;
|
|
4307
|
+
if (timeoutMs === void 0) return runner(task, ctx);
|
|
4308
|
+
let timer = null;
|
|
4309
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
4310
|
+
timer = setTimeout(() => {
|
|
4311
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4312
|
+
reject(new BudgetExceededError("timeout", timeoutMs, Date.now()));
|
|
4313
|
+
}, timeoutMs);
|
|
4314
|
+
});
|
|
4315
|
+
try {
|
|
4316
|
+
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
4317
|
+
} finally {
|
|
4318
|
+
if (timer) clearTimeout(timer);
|
|
3867
4319
|
}
|
|
3868
|
-
return false;
|
|
3869
4320
|
}
|
|
3870
|
-
|
|
4321
|
+
recordCompletion(result) {
|
|
3871
4322
|
this.completedResults.push(result);
|
|
3872
4323
|
this.totalIterations += result.iterations;
|
|
4324
|
+
this.inFlight = Math.max(0, this.inFlight - 1);
|
|
3873
4325
|
const subagent = this.subagents.get(result.subagentId);
|
|
3874
|
-
if (subagent) {
|
|
3875
|
-
subagent.status = "idle";
|
|
4326
|
+
if (subagent && subagent.status !== "stopped") {
|
|
4327
|
+
subagent.status = result.status === "failed" || result.status === "timeout" ? "error" : "idle";
|
|
3876
4328
|
subagent.currentTask = void 0;
|
|
4329
|
+
if (subagent.status === "error") {
|
|
4330
|
+
queueMicrotask(() => {
|
|
4331
|
+
if (subagent.status === "error") subagent.status = "idle";
|
|
4332
|
+
this.tryDispatchNext();
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
3877
4335
|
}
|
|
3878
4336
|
this.emit("task.completed", {
|
|
3879
|
-
task:
|
|
4337
|
+
task: subagent?.context.tasks.find((t2) => t2.id === result.taskId) ?? { id: result.taskId },
|
|
3880
4338
|
result
|
|
3881
4339
|
});
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
if (available) {
|
|
3885
|
-
const nextTask = this.pendingTasks.shift();
|
|
3886
|
-
this.dispatch(available, nextTask);
|
|
3887
|
-
}
|
|
3888
|
-
} else if (this.isDone()) {
|
|
4340
|
+
this.tryDispatchNext();
|
|
4341
|
+
if (this.isDone()) {
|
|
3889
4342
|
this.emit("done", {
|
|
3890
4343
|
results: this.completedResults,
|
|
3891
4344
|
totalIterations: this.totalIterations
|
|
3892
4345
|
});
|
|
3893
4346
|
}
|
|
3894
4347
|
}
|
|
4348
|
+
isDone() {
|
|
4349
|
+
if (this.config.doneCondition.type === "all_tasks_done") {
|
|
4350
|
+
return this.pendingTasks.length === 0 && this.inFlight === 0;
|
|
4351
|
+
}
|
|
4352
|
+
if (this.config.doneCondition.maxIterations !== void 0 && this.totalIterations >= this.config.doneCondition.maxIterations) {
|
|
4353
|
+
return true;
|
|
4354
|
+
}
|
|
4355
|
+
return false;
|
|
4356
|
+
}
|
|
3895
4357
|
};
|
|
4358
|
+
|
|
4359
|
+
// src/defaults/agent-subagent-runner.ts
|
|
4360
|
+
function makeAgentSubagentRunner(opts) {
|
|
4361
|
+
const format = opts.formatTaskInput ?? defaultFormatTaskInput;
|
|
4362
|
+
return async (task, ctx) => {
|
|
4363
|
+
const { agent, events } = await opts.factory(ctx.config);
|
|
4364
|
+
const aborter = new AbortController();
|
|
4365
|
+
const onBudgetError = (err) => {
|
|
4366
|
+
if (err instanceof BudgetExceededError) {
|
|
4367
|
+
aborter.abort();
|
|
4368
|
+
budgetError = err;
|
|
4369
|
+
} else {
|
|
4370
|
+
throw err;
|
|
4371
|
+
}
|
|
4372
|
+
};
|
|
4373
|
+
let budgetError = null;
|
|
4374
|
+
const unsub = [];
|
|
4375
|
+
unsub.push(
|
|
4376
|
+
events.on("tool.started", () => {
|
|
4377
|
+
try {
|
|
4378
|
+
ctx.budget.recordToolCall();
|
|
4379
|
+
} catch (e) {
|
|
4380
|
+
onBudgetError(e);
|
|
4381
|
+
}
|
|
4382
|
+
}),
|
|
4383
|
+
events.on("provider.response", (e) => {
|
|
4384
|
+
try {
|
|
4385
|
+
ctx.budget.recordUsage(e.usage);
|
|
4386
|
+
} catch (e2) {
|
|
4387
|
+
onBudgetError(e2);
|
|
4388
|
+
}
|
|
4389
|
+
}),
|
|
4390
|
+
events.on("iteration.started", () => {
|
|
4391
|
+
try {
|
|
4392
|
+
ctx.budget.recordIteration();
|
|
4393
|
+
ctx.budget.checkTimeout();
|
|
4394
|
+
} catch (e) {
|
|
4395
|
+
onBudgetError(e);
|
|
4396
|
+
}
|
|
4397
|
+
})
|
|
4398
|
+
);
|
|
4399
|
+
const onParentAbort = () => aborter.abort();
|
|
4400
|
+
ctx.signal.addEventListener("abort", onParentAbort);
|
|
4401
|
+
let result;
|
|
4402
|
+
try {
|
|
4403
|
+
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
4404
|
+
} finally {
|
|
4405
|
+
ctx.signal.removeEventListener("abort", onParentAbort);
|
|
4406
|
+
for (const u of unsub) u();
|
|
4407
|
+
}
|
|
4408
|
+
if (budgetError) throw budgetError;
|
|
4409
|
+
if (result.status === "failed") {
|
|
4410
|
+
throw result.error instanceof Error ? result.error : new Error(String(result.error ?? "agent failed"));
|
|
4411
|
+
}
|
|
4412
|
+
if (result.status === "aborted") {
|
|
4413
|
+
throw new Error("agent aborted");
|
|
4414
|
+
}
|
|
4415
|
+
if (result.status === "max_iterations") {
|
|
4416
|
+
throw new Error("agent exhausted iteration limit");
|
|
4417
|
+
}
|
|
4418
|
+
const usage = ctx.budget.usage();
|
|
4419
|
+
return {
|
|
4420
|
+
result: result.finalText,
|
|
4421
|
+
iterations: result.iterations,
|
|
4422
|
+
toolCalls: usage.toolCalls
|
|
4423
|
+
};
|
|
4424
|
+
};
|
|
4425
|
+
}
|
|
4426
|
+
function defaultFormatTaskInput(task) {
|
|
4427
|
+
return task.description ?? "";
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
// src/defaults/transport/in-memory-transport.ts
|
|
3896
4431
|
var InMemoryBridgeTransport = class {
|
|
3897
4432
|
subs = /* @__PURE__ */ new Map();
|
|
3898
4433
|
send(msg, to) {
|
|
4434
|
+
if (to === "*") {
|
|
4435
|
+
for (const [id, handlers2] of this.subs) {
|
|
4436
|
+
if (id === msg.from) continue;
|
|
4437
|
+
for (const h of handlers2) {
|
|
4438
|
+
try {
|
|
4439
|
+
h(msg);
|
|
4440
|
+
} catch {
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
return Promise.resolve();
|
|
4445
|
+
}
|
|
3899
4446
|
const handlers = this.subs.get(to);
|
|
3900
4447
|
if (handlers) {
|
|
3901
4448
|
for (const h of handlers) {
|
|
@@ -3917,6 +4464,8 @@ var InMemoryBridgeTransport = class {
|
|
|
3917
4464
|
return Promise.resolve();
|
|
3918
4465
|
}
|
|
3919
4466
|
};
|
|
4467
|
+
|
|
4468
|
+
// src/defaults/agent-bridge.ts
|
|
3920
4469
|
var InMemoryAgentBridge = class {
|
|
3921
4470
|
agentId;
|
|
3922
4471
|
coordinatorId;
|
|
@@ -4083,7 +4632,7 @@ var AutonomousRunner = class {
|
|
|
4083
4632
|
if (e.message.includes("timeout")) {
|
|
4084
4633
|
const timeoutResult = {
|
|
4085
4634
|
status: "failed",
|
|
4086
|
-
error: e,
|
|
4635
|
+
error: toWrongStackError(e),
|
|
4087
4636
|
iterations: this.iterations,
|
|
4088
4637
|
toolCalls: this.toolCalls,
|
|
4089
4638
|
reason: "iteration timeout"
|
|
@@ -4897,95 +5446,239 @@ var SpecDrivenDev = class {
|
|
|
4897
5446
|
}));
|
|
4898
5447
|
}
|
|
4899
5448
|
};
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
var
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
5449
|
+
var LOCK_FILE = "active.json";
|
|
5450
|
+
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
5451
|
+
var RecoveryLock = class {
|
|
5452
|
+
file;
|
|
5453
|
+
pid;
|
|
5454
|
+
hostname;
|
|
5455
|
+
maxAgeMs;
|
|
5456
|
+
sessionStore;
|
|
5457
|
+
probe;
|
|
5458
|
+
constructor(opts) {
|
|
5459
|
+
this.file = path2.join(opts.dir, LOCK_FILE);
|
|
5460
|
+
this.pid = opts.pid ?? process.pid;
|
|
5461
|
+
this.hostname = opts.hostname ?? os3.hostname();
|
|
5462
|
+
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
5463
|
+
this.sessionStore = opts.sessionStore;
|
|
5464
|
+
this.probe = opts.isPidAlive ?? defaultIsPidAlive;
|
|
4910
5465
|
}
|
|
4911
|
-
registry;
|
|
4912
|
-
opts;
|
|
4913
|
-
serializer;
|
|
4914
|
-
iterationTimeoutMs;
|
|
4915
5466
|
/**
|
|
4916
|
-
*
|
|
4917
|
-
* Returns the
|
|
5467
|
+
* Examine the lockfile and decide whether it represents an abandoned
|
|
5468
|
+
* session. Returns `null` if the file is missing, points to a live
|
|
5469
|
+
* instance, references a clean-closed session, is too old, or is
|
|
5470
|
+
* malformed. Otherwise returns enough detail to prompt the user.
|
|
5471
|
+
*
|
|
5472
|
+
* Important: this is a read-only check. We never delete an active
|
|
5473
|
+
* lock from here — if another wstack instance is alive, the caller
|
|
5474
|
+
* should bail or run with a fresh session instead.
|
|
4918
5475
|
*/
|
|
4919
|
-
async
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
if (!tool) {
|
|
4926
|
-
result = this.unknownToolResult(use, () => this.registry.list().map((t2) => t2.name));
|
|
4927
|
-
} else {
|
|
4928
|
-
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
4929
|
-
if (decision.permission === "deny") {
|
|
4930
|
-
result = this.deniedResult(use, decision.reason);
|
|
4931
|
-
} else if (decision.permission === "confirm") {
|
|
4932
|
-
result = this.confirmResult(use);
|
|
4933
|
-
} else {
|
|
4934
|
-
try {
|
|
4935
|
-
result = await this.executeTool(tool, use, ctx, budget);
|
|
4936
|
-
} catch (err) {
|
|
4937
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
4938
|
-
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
4939
|
-
this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
|
|
4940
|
-
result = {
|
|
4941
|
-
type: "tool_result",
|
|
4942
|
-
tool_use_id: use.id,
|
|
4943
|
-
content: `Tool "${use.name}" threw: ${scrubbed}`,
|
|
4944
|
-
is_error: true
|
|
4945
|
-
};
|
|
4946
|
-
}
|
|
4947
|
-
}
|
|
4948
|
-
}
|
|
4949
|
-
const contentBytes = typeof result.content === "string" ? Buffer.byteLength(result.content, "utf8") : Buffer.byteLength(JSON.stringify(result.content), "utf8");
|
|
4950
|
-
budget = Math.max(0, budget - contentBytes);
|
|
4951
|
-
return { result, tool, durationMs: Date.now() - start };
|
|
4952
|
-
};
|
|
4953
|
-
if (strategy === "sequential") {
|
|
4954
|
-
const outputs = [];
|
|
4955
|
-
for (let i = 0; i < toolUses.length; i++) {
|
|
4956
|
-
const use = toolUses[i];
|
|
4957
|
-
if (use) outputs.push(await runOne(use));
|
|
4958
|
-
}
|
|
4959
|
-
return { outputs, remainingBudget: budget };
|
|
4960
|
-
}
|
|
4961
|
-
if (strategy === "parallel") {
|
|
4962
|
-
const outputs = await Promise.all(toolUses.map((use, i) => runOne(use)));
|
|
4963
|
-
return { outputs, remainingBudget: budget };
|
|
5476
|
+
async checkAbandoned() {
|
|
5477
|
+
const lock = await this.readLock();
|
|
5478
|
+
if (!lock) return null;
|
|
5479
|
+
const ageMs = Date.now() - new Date(lock.startedAt).getTime();
|
|
5480
|
+
if (Number.isNaN(ageMs) || ageMs < 0) {
|
|
5481
|
+
return null;
|
|
4964
5482
|
}
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
const use = toolUses[i];
|
|
4969
|
-
if (!use) continue;
|
|
4970
|
-
const tool = this.registry.get(use.name);
|
|
4971
|
-
if (tool?.mutating) mutating.push({ use, index: i });
|
|
4972
|
-
else nonMutating.push({ use, index: i });
|
|
5483
|
+
if (ageMs > this.maxAgeMs) return null;
|
|
5484
|
+
if (lock.hostname === this.hostname && this.probe(lock.pid)) {
|
|
5485
|
+
return null;
|
|
4973
5486
|
}
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
5487
|
+
let messageCount = 0;
|
|
5488
|
+
if (this.sessionStore) {
|
|
5489
|
+
try {
|
|
5490
|
+
const data = await this.sessionStore.load(lock.sessionId);
|
|
5491
|
+
const closed = data.events.some((e) => e.type === "session_end");
|
|
5492
|
+
if (closed) return null;
|
|
5493
|
+
messageCount = data.messages.length;
|
|
5494
|
+
} catch {
|
|
5495
|
+
return null;
|
|
5496
|
+
}
|
|
4978
5497
|
}
|
|
4979
5498
|
return {
|
|
4980
|
-
|
|
4981
|
-
|
|
5499
|
+
sessionId: lock.sessionId,
|
|
5500
|
+
pid: lock.pid,
|
|
5501
|
+
startedAt: lock.startedAt,
|
|
5502
|
+
ageMs,
|
|
5503
|
+
messageCount
|
|
4982
5504
|
};
|
|
4983
5505
|
}
|
|
4984
5506
|
/**
|
|
4985
|
-
*
|
|
4986
|
-
*
|
|
4987
|
-
*
|
|
4988
|
-
|
|
5507
|
+
* Claim the lock for the given session. Overwrites any existing lock
|
|
5508
|
+
* — the caller should have already handled abandonment (via
|
|
5509
|
+
* `checkAbandoned`) before calling this.
|
|
5510
|
+
*/
|
|
5511
|
+
async write(sessionId) {
|
|
5512
|
+
await ensureDir(path2.dirname(this.file));
|
|
5513
|
+
const lock = {
|
|
5514
|
+
v: 1,
|
|
5515
|
+
sessionId,
|
|
5516
|
+
pid: this.pid,
|
|
5517
|
+
hostname: this.hostname,
|
|
5518
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5519
|
+
};
|
|
5520
|
+
const tmp = `${this.file}.tmp`;
|
|
5521
|
+
await fsp.writeFile(tmp, JSON.stringify(lock), { mode: 384 });
|
|
5522
|
+
await fsp.rename(tmp, this.file);
|
|
5523
|
+
}
|
|
5524
|
+
/**
|
|
5525
|
+
* Release the lock. Idempotent — silently succeeds if the file is
|
|
5526
|
+
* already gone (e.g. someone else cleared it, or the directory was
|
|
5527
|
+
* wiped).
|
|
5528
|
+
*/
|
|
5529
|
+
async clear() {
|
|
5530
|
+
try {
|
|
5531
|
+
await fsp.unlink(this.file);
|
|
5532
|
+
} catch (err) {
|
|
5533
|
+
const code = err.code;
|
|
5534
|
+
if (code === "ENOENT") return;
|
|
5535
|
+
throw err;
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
async readLock() {
|
|
5539
|
+
let raw;
|
|
5540
|
+
try {
|
|
5541
|
+
raw = await fsp.readFile(this.file, "utf8");
|
|
5542
|
+
} catch (err) {
|
|
5543
|
+
const code = err.code;
|
|
5544
|
+
if (code === "ENOENT") return null;
|
|
5545
|
+
return null;
|
|
5546
|
+
}
|
|
5547
|
+
try {
|
|
5548
|
+
const parsed = JSON.parse(raw);
|
|
5549
|
+
if (!isLockFile(parsed)) return null;
|
|
5550
|
+
return parsed;
|
|
5551
|
+
} catch {
|
|
5552
|
+
return null;
|
|
5553
|
+
}
|
|
5554
|
+
}
|
|
5555
|
+
};
|
|
5556
|
+
function isLockFile(v) {
|
|
5557
|
+
if (typeof v !== "object" || v === null) return false;
|
|
5558
|
+
const o = v;
|
|
5559
|
+
return o["v"] === 1 && typeof o["sessionId"] === "string" && typeof o["pid"] === "number" && typeof o["hostname"] === "string" && typeof o["startedAt"] === "string";
|
|
5560
|
+
}
|
|
5561
|
+
function defaultIsPidAlive(pid) {
|
|
5562
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
5563
|
+
try {
|
|
5564
|
+
process.kill(pid, 0);
|
|
5565
|
+
return true;
|
|
5566
|
+
} catch (err) {
|
|
5567
|
+
const code = err.code;
|
|
5568
|
+
if (code === "EPERM") return true;
|
|
5569
|
+
return false;
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
|
|
5573
|
+
// src/defaults/tool-executor.ts
|
|
5574
|
+
var ToolExecutor = class {
|
|
5575
|
+
constructor(registry, opts) {
|
|
5576
|
+
this.registry = registry;
|
|
5577
|
+
this.opts = opts;
|
|
5578
|
+
this.iterationTimeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
5579
|
+
this.serializer = createToolOutputSerializer({
|
|
5580
|
+
perIterationOutputCapBytes: opts.perIterationOutputCapBytes ?? 1e5
|
|
5581
|
+
});
|
|
5582
|
+
}
|
|
5583
|
+
registry;
|
|
5584
|
+
opts;
|
|
5585
|
+
serializer;
|
|
5586
|
+
iterationTimeoutMs;
|
|
5587
|
+
/**
|
|
5588
|
+
* Execute a batch of tool uses using the configured strategy.
|
|
5589
|
+
* Returns the execution results and the remaining output budget.
|
|
5590
|
+
*/
|
|
5591
|
+
async executeBatch(toolUses, ctx, strategy) {
|
|
5592
|
+
let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
|
|
5593
|
+
const runOne = async (use) => {
|
|
5594
|
+
const start = Date.now();
|
|
5595
|
+
const tool = this.registry.get(use.name);
|
|
5596
|
+
if (!tool) {
|
|
5597
|
+
const result = this.unknownToolResult(use, () => this.registry.list().map((t2) => t2.name));
|
|
5598
|
+
budget = this.decrementBudget(result, budget);
|
|
5599
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
5600
|
+
}
|
|
5601
|
+
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
5602
|
+
if (decision.permission === "deny") {
|
|
5603
|
+
const result = this.deniedResult(use, decision.reason);
|
|
5604
|
+
budget = this.decrementBudget(result, budget);
|
|
5605
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
5606
|
+
}
|
|
5607
|
+
if (decision.permission === "confirm") {
|
|
5608
|
+
if (this.opts.confirmAwaiter) {
|
|
5609
|
+
const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
|
|
5610
|
+
if (choice !== "yes" && choice !== "always") {
|
|
5611
|
+
const result = { type: "tool_result", tool_use_id: use.id, content: `Tool "${tool.name}" denied by user.`, is_error: true };
|
|
5612
|
+
budget = this.decrementBudget(result, budget);
|
|
5613
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
5614
|
+
}
|
|
5615
|
+
} else {
|
|
5616
|
+
const suggestedPattern = this.subjectFor(tool.name, use.input) ?? tool.name;
|
|
5617
|
+
const pending = { type: "tool_confirm_pending", toolUseId: use.id, toolName: tool.name, input: use.input, suggestedPattern };
|
|
5618
|
+
return { result: pending, tool, durationMs: Date.now() - start };
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
|
|
5622
|
+
"tool.name": tool.name,
|
|
5623
|
+
"tool.mutating": tool.mutating,
|
|
5624
|
+
"tool.permission": tool.permission
|
|
5625
|
+
});
|
|
5626
|
+
try {
|
|
5627
|
+
const result = await this.executeTool(tool, use, ctx, budget);
|
|
5628
|
+
budget = this.decrementBudget(result, budget);
|
|
5629
|
+
span?.setAttribute("tool.is_error", !!result.is_error);
|
|
5630
|
+
span?.setAttribute(
|
|
5631
|
+
"tool.output_bytes",
|
|
5632
|
+
typeof result.content === "string" ? result.content.length : 0
|
|
5633
|
+
);
|
|
5634
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
5635
|
+
} catch (err) {
|
|
5636
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5637
|
+
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
5638
|
+
this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
|
|
5639
|
+
const result = { type: "tool_result", tool_use_id: use.id, content: `Tool "${tool.name}" threw: ${scrubbed}`, is_error: true };
|
|
5640
|
+
budget = this.decrementBudget(result, budget);
|
|
5641
|
+
if (err instanceof Error) span?.recordError(err);
|
|
5642
|
+
span?.setAttribute("tool.is_error", true);
|
|
5643
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
5644
|
+
} finally {
|
|
5645
|
+
span?.end();
|
|
5646
|
+
}
|
|
5647
|
+
};
|
|
5648
|
+
if (strategy === "sequential") {
|
|
5649
|
+
const outputs = [];
|
|
5650
|
+
for (const use of toolUses) {
|
|
5651
|
+
if (use) outputs.push(await runOne(use));
|
|
5652
|
+
}
|
|
5653
|
+
return { outputs, remainingBudget: budget };
|
|
5654
|
+
}
|
|
5655
|
+
if (strategy === "parallel") {
|
|
5656
|
+
const outputs = await Promise.all(toolUses.map((use) => runOne(use)));
|
|
5657
|
+
return { outputs, remainingBudget: budget };
|
|
5658
|
+
}
|
|
5659
|
+
const nonMutating = [];
|
|
5660
|
+
const mutating = [];
|
|
5661
|
+
for (const use of toolUses) {
|
|
5662
|
+
if (!use) continue;
|
|
5663
|
+
const tool = this.registry.get(use.name);
|
|
5664
|
+
if (tool?.mutating) mutating.push(use);
|
|
5665
|
+
else nonMutating.push(use);
|
|
5666
|
+
}
|
|
5667
|
+
const firstPass = await Promise.all(nonMutating.map((use) => runOne(use)));
|
|
5668
|
+
const secondPass = [];
|
|
5669
|
+
for (const use of mutating) {
|
|
5670
|
+
secondPass.push(await runOne(use));
|
|
5671
|
+
}
|
|
5672
|
+
return {
|
|
5673
|
+
outputs: [...firstPass, ...secondPass],
|
|
5674
|
+
remainingBudget: budget
|
|
5675
|
+
};
|
|
5676
|
+
}
|
|
5677
|
+
/**
|
|
5678
|
+
* Execute a single tool with timeout, permission check, and output capping.
|
|
5679
|
+
* Emits `tool.started` via the injected EventBus (if any) right before
|
|
5680
|
+
* invoking the tool — closes the observability gap between "model decided
|
|
5681
|
+
* to call a tool" and "tool.executed".
|
|
4989
5682
|
*/
|
|
4990
5683
|
async executeTool(tool, use, ctx, budget) {
|
|
4991
5684
|
this.opts.events?.emit("tool.started", {
|
|
@@ -4994,7 +5687,7 @@ var ToolExecutor = class {
|
|
|
4994
5687
|
input: use.input
|
|
4995
5688
|
});
|
|
4996
5689
|
this.opts.renderer?.writeToolCall(tool.name, use.input);
|
|
4997
|
-
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx);
|
|
5690
|
+
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
|
|
4998
5691
|
const text = this.serializer.serialize(output);
|
|
4999
5692
|
const scrubbed = this.opts.secretScrubber.scrub(text);
|
|
5000
5693
|
const { text: capped } = this.serializer.enforceCap(scrubbed, budget);
|
|
@@ -5007,20 +5700,52 @@ var ToolExecutor = class {
|
|
|
5007
5700
|
is_error: false
|
|
5008
5701
|
};
|
|
5009
5702
|
}
|
|
5010
|
-
async runWithTimeout(tool, input, parentSignal, ctx) {
|
|
5703
|
+
async runWithTimeout(tool, input, parentSignal, ctx, toolUseId) {
|
|
5011
5704
|
if (parentSignal.aborted) {
|
|
5012
5705
|
throw parentSignal.reason instanceof Error ? parentSignal.reason : new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
|
|
5013
5706
|
}
|
|
5014
5707
|
const timeoutMs = tool.timeoutMs ?? this.iterationTimeoutMs;
|
|
5015
5708
|
const ctrl = new AbortController();
|
|
5016
5709
|
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
5017
|
-
const combined =
|
|
5710
|
+
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
5018
5711
|
try {
|
|
5712
|
+
if (typeof tool.executeStream === "function") {
|
|
5713
|
+
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
5714
|
+
}
|
|
5019
5715
|
return await tool.execute(input, ctx, { signal: combined });
|
|
5716
|
+
} catch (err) {
|
|
5717
|
+
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
5718
|
+
try {
|
|
5719
|
+
await tool.cleanup(input, ctx);
|
|
5720
|
+
} catch {
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
throw err;
|
|
5020
5724
|
} finally {
|
|
5021
5725
|
clearTimeout(timer);
|
|
5022
5726
|
}
|
|
5023
5727
|
}
|
|
5728
|
+
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
5729
|
+
let finalOutput;
|
|
5730
|
+
let sawFinal = false;
|
|
5731
|
+
const stream = tool.executeStream(input, ctx, { signal });
|
|
5732
|
+
for await (const ev of stream) {
|
|
5733
|
+
if (ev.type === "final") {
|
|
5734
|
+
finalOutput = ev.output;
|
|
5735
|
+
sawFinal = true;
|
|
5736
|
+
break;
|
|
5737
|
+
}
|
|
5738
|
+
this.opts.events?.emit("tool.progress", {
|
|
5739
|
+
name: tool.name,
|
|
5740
|
+
id: toolUseId ?? "<unknown>",
|
|
5741
|
+
event: ev
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
if (!sawFinal) {
|
|
5745
|
+
throw new Error(`tool "${tool.name}" executeStream completed without a 'final' event`);
|
|
5746
|
+
}
|
|
5747
|
+
return finalOutput;
|
|
5748
|
+
}
|
|
5024
5749
|
unknownToolResult(use, listFns) {
|
|
5025
5750
|
return {
|
|
5026
5751
|
type: "tool_result",
|
|
@@ -5037,32 +5762,959 @@ var ToolExecutor = class {
|
|
|
5037
5762
|
is_error: true
|
|
5038
5763
|
};
|
|
5039
5764
|
}
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5765
|
+
decrementBudget(result, budget) {
|
|
5766
|
+
const contentBytes = typeof result.content === "string" ? Buffer.byteLength(result.content, "utf8") : Buffer.byteLength(JSON.stringify(result.content), "utf8");
|
|
5767
|
+
return Math.max(0, budget - contentBytes);
|
|
5768
|
+
}
|
|
5769
|
+
/**
|
|
5770
|
+
* Compute the suggestedPattern string for a tool+input pair.
|
|
5771
|
+
* Matches the logic in DefaultPermissionPolicy so the TUI shows the
|
|
5772
|
+
* same subject that the trust file would use.
|
|
5773
|
+
*/
|
|
5774
|
+
subjectFor(toolName, input) {
|
|
5775
|
+
if (!input || typeof input !== "object") return void 0;
|
|
5776
|
+
const obj = input;
|
|
5777
|
+
const globChars = /[*?\[\]]/g;
|
|
5778
|
+
const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
|
|
5779
|
+
if (toolName === "bash" && typeof obj.command === "string") {
|
|
5780
|
+
return escapeGlob(obj.command);
|
|
5781
|
+
}
|
|
5782
|
+
if (typeof obj.path === "string") {
|
|
5783
|
+
return escapeGlob(obj.path.replace(/\\/g, "/"));
|
|
5784
|
+
}
|
|
5785
|
+
if (typeof obj.url === "string") {
|
|
5786
|
+
return escapeGlob(obj.url);
|
|
5787
|
+
}
|
|
5788
|
+
if (typeof obj.name === "string") {
|
|
5789
|
+
return escapeGlob(obj.name);
|
|
5790
|
+
}
|
|
5791
|
+
return void 0;
|
|
5792
|
+
}
|
|
5793
|
+
};
|
|
5794
|
+
|
|
5795
|
+
// src/defaults/session-reader.ts
|
|
5796
|
+
var DefaultSessionReader = class {
|
|
5797
|
+
store;
|
|
5798
|
+
constructor(opts) {
|
|
5799
|
+
this.store = opts.store;
|
|
5800
|
+
}
|
|
5801
|
+
async query(q = {}) {
|
|
5802
|
+
const raw = await this.store.list(q.limit ? Math.max(q.limit * 4, 100) : 1e3);
|
|
5803
|
+
const titleNeedle = q.titleContains?.toLowerCase();
|
|
5804
|
+
const filtered = raw.filter((s) => {
|
|
5805
|
+
if (q.since && s.startedAt < q.since) return false;
|
|
5806
|
+
if (q.until && s.startedAt > q.until) return false;
|
|
5807
|
+
if (q.provider && s.provider !== q.provider) return false;
|
|
5808
|
+
if (q.model && s.model !== q.model) return false;
|
|
5809
|
+
if (q.minTokens !== void 0 && s.tokenTotal < q.minTokens) return false;
|
|
5810
|
+
if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;
|
|
5811
|
+
return true;
|
|
5812
|
+
});
|
|
5813
|
+
const out = filtered.map((s) => ({
|
|
5814
|
+
id: s.id,
|
|
5815
|
+
title: s.title,
|
|
5816
|
+
startedAt: s.startedAt,
|
|
5817
|
+
provider: s.provider,
|
|
5818
|
+
model: s.model,
|
|
5819
|
+
tokenTotal: s.tokenTotal
|
|
5820
|
+
}));
|
|
5821
|
+
return q.limit ? out.slice(0, q.limit) : out;
|
|
5822
|
+
}
|
|
5823
|
+
async *replay(sessionId) {
|
|
5824
|
+
const data = await this.store.load(sessionId);
|
|
5825
|
+
for (const e of data.events) yield e;
|
|
5826
|
+
}
|
|
5827
|
+
async search(q, sessionId) {
|
|
5828
|
+
const limit = q.limit ?? 100;
|
|
5829
|
+
const matcher = buildMatcher(q);
|
|
5830
|
+
const allowedTypes = q.types ? new Set(q.types) : null;
|
|
5831
|
+
const ids = sessionId ? [sessionId] : (await this.store.list(1e3)).map((s) => s.id);
|
|
5832
|
+
const hits = [];
|
|
5833
|
+
for (const id of ids) {
|
|
5834
|
+
let data;
|
|
5835
|
+
try {
|
|
5836
|
+
data = await this.store.load(id);
|
|
5837
|
+
} catch {
|
|
5838
|
+
continue;
|
|
5839
|
+
}
|
|
5840
|
+
for (let i = 0; i < data.events.length; i++) {
|
|
5841
|
+
const ev = data.events[i];
|
|
5842
|
+
if (allowedTypes && !allowedTypes.has(ev.type)) continue;
|
|
5843
|
+
const text = eventText(ev);
|
|
5844
|
+
if (text === null) continue;
|
|
5845
|
+
const hit = matcher(text);
|
|
5846
|
+
if (!hit) continue;
|
|
5847
|
+
hits.push({
|
|
5848
|
+
sessionId: id,
|
|
5849
|
+
eventIndex: i,
|
|
5850
|
+
ts: ev.ts,
|
|
5851
|
+
type: ev.type,
|
|
5852
|
+
snippet: snippetOf(text, hit.start, hit.end)
|
|
5853
|
+
});
|
|
5854
|
+
if (hits.length >= limit) return hits;
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
return hits;
|
|
5858
|
+
}
|
|
5859
|
+
async export(sessionId, opts) {
|
|
5860
|
+
const data = await this.store.load(sessionId);
|
|
5861
|
+
const includeTools = opts.includeTools ?? true;
|
|
5862
|
+
const includeDiagnostics = opts.includeDiagnostics ?? true;
|
|
5863
|
+
const filtered = data.events.filter((e) => {
|
|
5864
|
+
if (!includeTools && (e.type === "tool_use" || e.type === "tool_result" || e.type === "tool_call_start" || e.type === "tool_call_end")) {
|
|
5865
|
+
return false;
|
|
5866
|
+
}
|
|
5867
|
+
if (!includeDiagnostics && (e.type === "error" || e.type === "compaction" || e.type === "message_truncated")) {
|
|
5868
|
+
return false;
|
|
5869
|
+
}
|
|
5870
|
+
return true;
|
|
5871
|
+
});
|
|
5872
|
+
if (opts.format === "json") {
|
|
5873
|
+
return JSON.stringify({ metadata: data.metadata, events: filtered }, null, 2);
|
|
5874
|
+
}
|
|
5875
|
+
if (opts.format === "text") {
|
|
5876
|
+
return renderPlainText(data.metadata, filtered);
|
|
5877
|
+
}
|
|
5878
|
+
return renderMarkdown(data.metadata, filtered);
|
|
5879
|
+
}
|
|
5880
|
+
async metadata(sessionId) {
|
|
5881
|
+
const data = await this.store.load(sessionId);
|
|
5882
|
+
return data.metadata;
|
|
5883
|
+
}
|
|
5884
|
+
};
|
|
5885
|
+
function buildMatcher(q) {
|
|
5886
|
+
const ci = q.caseInsensitive ?? true;
|
|
5887
|
+
if (q.regex) {
|
|
5888
|
+
const flags = ci ? "i" : "";
|
|
5889
|
+
const re = new RegExp(q.query, flags);
|
|
5890
|
+
return (text) => {
|
|
5891
|
+
const m = re.exec(text);
|
|
5892
|
+
return m ? { start: m.index, end: m.index + m[0].length } : null;
|
|
5046
5893
|
};
|
|
5047
5894
|
}
|
|
5895
|
+
const needle = ci ? q.query.toLowerCase() : q.query;
|
|
5896
|
+
return (text) => {
|
|
5897
|
+
const hay = ci ? text.toLowerCase() : text;
|
|
5898
|
+
const idx = hay.indexOf(needle);
|
|
5899
|
+
return idx === -1 ? null : { start: idx, end: idx + needle.length };
|
|
5900
|
+
};
|
|
5901
|
+
}
|
|
5902
|
+
function eventText(e) {
|
|
5903
|
+
switch (e.type) {
|
|
5904
|
+
case "user_input":
|
|
5905
|
+
return contentToString(e.content);
|
|
5906
|
+
case "llm_response":
|
|
5907
|
+
return contentToString(e.content);
|
|
5908
|
+
case "tool_use":
|
|
5909
|
+
return `${e.name} ${JSON.stringify(e.input)}`;
|
|
5910
|
+
case "tool_result":
|
|
5911
|
+
return typeof e.content === "string" ? e.content : JSON.stringify(e.content);
|
|
5912
|
+
case "error":
|
|
5913
|
+
return `${e.phase}: ${e.message}`;
|
|
5914
|
+
case "session_start":
|
|
5915
|
+
case "session_resumed":
|
|
5916
|
+
return `${e.model}/${e.provider}`;
|
|
5917
|
+
case "task_created":
|
|
5918
|
+
case "task_completed":
|
|
5919
|
+
return e.title;
|
|
5920
|
+
case "task_failed":
|
|
5921
|
+
return `${e.title}: ${e.error}`;
|
|
5922
|
+
case "skill_activated":
|
|
5923
|
+
case "skill_deactivated":
|
|
5924
|
+
return e.skillName;
|
|
5925
|
+
default:
|
|
5926
|
+
return null;
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
function contentToString(content) {
|
|
5930
|
+
if (typeof content === "string") return content;
|
|
5931
|
+
return content.map((b) => {
|
|
5932
|
+
switch (b.type) {
|
|
5933
|
+
case "text":
|
|
5934
|
+
return b.text;
|
|
5935
|
+
case "tool_use":
|
|
5936
|
+
return `[tool_use:${b.name} ${JSON.stringify(b.input)}]`;
|
|
5937
|
+
case "tool_result":
|
|
5938
|
+
return typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
5939
|
+
default:
|
|
5940
|
+
return "";
|
|
5941
|
+
}
|
|
5942
|
+
}).join("\n");
|
|
5943
|
+
}
|
|
5944
|
+
var SNIPPET_RADIUS = 60;
|
|
5945
|
+
function snippetOf(text, start, end) {
|
|
5946
|
+
const from = Math.max(0, start - SNIPPET_RADIUS);
|
|
5947
|
+
const to = Math.min(text.length, end + SNIPPET_RADIUS);
|
|
5948
|
+
const prefix = from > 0 ? "\u2026" : "";
|
|
5949
|
+
const suffix = to < text.length ? "\u2026" : "";
|
|
5950
|
+
return prefix + text.slice(from, to).replace(/\s+/g, " ").trim() + suffix;
|
|
5951
|
+
}
|
|
5952
|
+
function renderMarkdown(meta, events) {
|
|
5953
|
+
const lines = [];
|
|
5954
|
+
lines.push(`# Session ${meta.id}`);
|
|
5955
|
+
lines.push("");
|
|
5956
|
+
if (meta.model || meta.provider) {
|
|
5957
|
+
lines.push(`- **Model:** ${meta.provider ?? "?"}/${meta.model ?? "?"}`);
|
|
5958
|
+
}
|
|
5959
|
+
lines.push(`- **Started:** ${meta.startedAt}`);
|
|
5960
|
+
if (meta.endedAt) lines.push(`- **Ended:** ${meta.endedAt}`);
|
|
5961
|
+
lines.push("");
|
|
5962
|
+
lines.push("---");
|
|
5963
|
+
lines.push("");
|
|
5964
|
+
for (const e of events) {
|
|
5965
|
+
switch (e.type) {
|
|
5966
|
+
case "user_input": {
|
|
5967
|
+
lines.push(`## User \u2014 ${e.ts}`);
|
|
5968
|
+
lines.push("");
|
|
5969
|
+
lines.push(contentToString(e.content));
|
|
5970
|
+
lines.push("");
|
|
5971
|
+
break;
|
|
5972
|
+
}
|
|
5973
|
+
case "llm_response": {
|
|
5974
|
+
lines.push(`## Assistant \u2014 ${e.ts}`);
|
|
5975
|
+
lines.push("");
|
|
5976
|
+
lines.push(contentToString(e.content));
|
|
5977
|
+
if (e.stopReason && e.stopReason !== "end_turn") {
|
|
5978
|
+
lines.push("");
|
|
5979
|
+
lines.push(`*stop: ${e.stopReason}*`);
|
|
5980
|
+
}
|
|
5981
|
+
lines.push("");
|
|
5982
|
+
break;
|
|
5983
|
+
}
|
|
5984
|
+
case "tool_use": {
|
|
5985
|
+
lines.push(`### Tool call: \`${e.name}\``);
|
|
5986
|
+
lines.push("");
|
|
5987
|
+
lines.push("```json");
|
|
5988
|
+
lines.push(JSON.stringify(e.input, null, 2));
|
|
5989
|
+
lines.push("```");
|
|
5990
|
+
lines.push("");
|
|
5991
|
+
break;
|
|
5992
|
+
}
|
|
5993
|
+
case "tool_result": {
|
|
5994
|
+
const body = typeof e.content === "string" ? e.content : JSON.stringify(e.content, null, 2);
|
|
5995
|
+
lines.push(`### Tool result${e.isError ? " (error)" : ""}`);
|
|
5996
|
+
lines.push("");
|
|
5997
|
+
lines.push("```");
|
|
5998
|
+
lines.push(body);
|
|
5999
|
+
lines.push("```");
|
|
6000
|
+
lines.push("");
|
|
6001
|
+
break;
|
|
6002
|
+
}
|
|
6003
|
+
case "error": {
|
|
6004
|
+
lines.push(`> **Error** (${e.phase}): ${e.message}`);
|
|
6005
|
+
lines.push("");
|
|
6006
|
+
break;
|
|
6007
|
+
}
|
|
6008
|
+
case "compaction": {
|
|
6009
|
+
lines.push(`> **Compaction**: ${e.before} \u2192 ${e.after} tokens`);
|
|
6010
|
+
lines.push("");
|
|
6011
|
+
break;
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
return lines.join("\n");
|
|
6016
|
+
}
|
|
6017
|
+
function renderPlainText(meta, events) {
|
|
6018
|
+
const lines = [];
|
|
6019
|
+
lines.push(`Session ${meta.id} \u2014 ${meta.provider ?? "?"}/${meta.model ?? "?"} \u2014 started ${meta.startedAt}`);
|
|
6020
|
+
lines.push("".padEnd(72, "-"));
|
|
6021
|
+
for (const e of events) {
|
|
6022
|
+
switch (e.type) {
|
|
6023
|
+
case "user_input":
|
|
6024
|
+
lines.push(`[${e.ts}] USER`);
|
|
6025
|
+
lines.push(contentToString(e.content));
|
|
6026
|
+
lines.push("");
|
|
6027
|
+
break;
|
|
6028
|
+
case "llm_response":
|
|
6029
|
+
lines.push(`[${e.ts}] ASSISTANT`);
|
|
6030
|
+
lines.push(contentToString(e.content));
|
|
6031
|
+
lines.push("");
|
|
6032
|
+
break;
|
|
6033
|
+
case "tool_use":
|
|
6034
|
+
lines.push(`[${e.ts}] TOOL_USE ${e.name} ${JSON.stringify(e.input)}`);
|
|
6035
|
+
break;
|
|
6036
|
+
case "tool_result":
|
|
6037
|
+
lines.push(
|
|
6038
|
+
`[${e.ts}] TOOL_RESULT${e.isError ? " (error)" : ""} ${typeof e.content === "string" ? e.content : JSON.stringify(e.content)}`
|
|
6039
|
+
);
|
|
6040
|
+
break;
|
|
6041
|
+
case "error":
|
|
6042
|
+
lines.push(`[${e.ts}] ERROR (${e.phase}): ${e.message}`);
|
|
6043
|
+
break;
|
|
6044
|
+
}
|
|
6045
|
+
}
|
|
6046
|
+
return lines.join("\n");
|
|
6047
|
+
}
|
|
6048
|
+
|
|
6049
|
+
// src/defaults/observability/metrics.ts
|
|
6050
|
+
var RESERVOIR_SIZE = 1024;
|
|
6051
|
+
function labelKey(labels) {
|
|
6052
|
+
if (!labels) return "";
|
|
6053
|
+
const keys = Object.keys(labels).sort();
|
|
6054
|
+
return keys.map((k) => `${k}=${labels[k]}`).join(",");
|
|
6055
|
+
}
|
|
6056
|
+
function quantile(sorted, q) {
|
|
6057
|
+
if (sorted.length === 0) return 0;
|
|
6058
|
+
const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
|
|
6059
|
+
return sorted[idx] ?? 0;
|
|
6060
|
+
}
|
|
6061
|
+
var InMemoryMetricsSink = class {
|
|
6062
|
+
counters = /* @__PURE__ */ new Map();
|
|
6063
|
+
gauges = /* @__PURE__ */ new Map();
|
|
6064
|
+
histograms = /* @__PURE__ */ new Map();
|
|
6065
|
+
counter(name, value = 1, labels) {
|
|
6066
|
+
const series = this.getOrCreate(this.counters, name);
|
|
6067
|
+
const key = labelKey(labels);
|
|
6068
|
+
const state = series.get(key) ?? { value: 0 };
|
|
6069
|
+
state.value += value;
|
|
6070
|
+
series.set(key, state);
|
|
6071
|
+
}
|
|
6072
|
+
gauge(name, value, labels) {
|
|
6073
|
+
const series = this.getOrCreate(this.gauges, name);
|
|
6074
|
+
series.set(labelKey(labels), { value });
|
|
6075
|
+
}
|
|
6076
|
+
histogram(name, value, labels) {
|
|
6077
|
+
const series = this.getOrCreate(this.histograms, name);
|
|
6078
|
+
const key = labelKey(labels);
|
|
6079
|
+
let state = series.get(key);
|
|
6080
|
+
if (!state) {
|
|
6081
|
+
state = { count: 0, sum: 0, min: value, max: value, samples: [] };
|
|
6082
|
+
series.set(key, state);
|
|
6083
|
+
}
|
|
6084
|
+
state.count++;
|
|
6085
|
+
state.sum += value;
|
|
6086
|
+
if (value < state.min) state.min = value;
|
|
6087
|
+
if (value > state.max) state.max = value;
|
|
6088
|
+
if (state.samples.length < RESERVOIR_SIZE) {
|
|
6089
|
+
state.samples.push(value);
|
|
6090
|
+
} else {
|
|
6091
|
+
const r = Math.floor(Math.random() * state.count);
|
|
6092
|
+
if (r < RESERVOIR_SIZE) state.samples[r] = value;
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
snapshot() {
|
|
6096
|
+
const series = [];
|
|
6097
|
+
for (const [name, byLabel] of this.counters) {
|
|
6098
|
+
for (const [key, state] of byLabel) {
|
|
6099
|
+
series.push({
|
|
6100
|
+
name,
|
|
6101
|
+
type: "counter",
|
|
6102
|
+
labels: parseLabelKey(key),
|
|
6103
|
+
values: { value: state.value }
|
|
6104
|
+
});
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
for (const [name, byLabel] of this.gauges) {
|
|
6108
|
+
for (const [key, state] of byLabel) {
|
|
6109
|
+
series.push({
|
|
6110
|
+
name,
|
|
6111
|
+
type: "gauge",
|
|
6112
|
+
labels: parseLabelKey(key),
|
|
6113
|
+
values: { value: state.value }
|
|
6114
|
+
});
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6117
|
+
for (const [name, byLabel] of this.histograms) {
|
|
6118
|
+
for (const [key, state] of byLabel) {
|
|
6119
|
+
const sorted = [...state.samples].sort((a, b) => a - b);
|
|
6120
|
+
series.push({
|
|
6121
|
+
name,
|
|
6122
|
+
type: "histogram",
|
|
6123
|
+
labels: parseLabelKey(key),
|
|
6124
|
+
values: {
|
|
6125
|
+
count: state.count,
|
|
6126
|
+
sum: state.sum,
|
|
6127
|
+
min: state.min,
|
|
6128
|
+
max: state.max,
|
|
6129
|
+
p50: quantile(sorted, 0.5),
|
|
6130
|
+
p95: quantile(sorted, 0.95),
|
|
6131
|
+
p99: quantile(sorted, 0.99)
|
|
6132
|
+
}
|
|
6133
|
+
});
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
return { timestamp: Date.now(), series };
|
|
6137
|
+
}
|
|
6138
|
+
reset() {
|
|
6139
|
+
this.counters.clear();
|
|
6140
|
+
this.gauges.clear();
|
|
6141
|
+
this.histograms.clear();
|
|
6142
|
+
}
|
|
6143
|
+
getOrCreate(bag, name) {
|
|
6144
|
+
let series = bag.get(name);
|
|
6145
|
+
if (!series) {
|
|
6146
|
+
series = /* @__PURE__ */ new Map();
|
|
6147
|
+
bag.set(name, series);
|
|
6148
|
+
}
|
|
6149
|
+
return series;
|
|
6150
|
+
}
|
|
5048
6151
|
};
|
|
5049
|
-
function
|
|
5050
|
-
if (
|
|
5051
|
-
|
|
6152
|
+
function parseLabelKey(key) {
|
|
6153
|
+
if (!key) return {};
|
|
6154
|
+
const labels = {};
|
|
6155
|
+
for (const pair of key.split(",")) {
|
|
6156
|
+
const eq = pair.indexOf("=");
|
|
6157
|
+
if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
6158
|
+
}
|
|
6159
|
+
return labels;
|
|
6160
|
+
}
|
|
6161
|
+
var NoopMetricsSink = class {
|
|
6162
|
+
counter() {
|
|
6163
|
+
}
|
|
6164
|
+
gauge() {
|
|
6165
|
+
}
|
|
6166
|
+
histogram() {
|
|
6167
|
+
}
|
|
6168
|
+
snapshot() {
|
|
6169
|
+
return { timestamp: Date.now(), series: [] };
|
|
6170
|
+
}
|
|
6171
|
+
reset() {
|
|
6172
|
+
}
|
|
6173
|
+
};
|
|
6174
|
+
|
|
6175
|
+
// src/defaults/observability/health.ts
|
|
6176
|
+
var SEVERITY = {
|
|
6177
|
+
healthy: 0,
|
|
6178
|
+
degraded: 1,
|
|
6179
|
+
unhealthy: 2
|
|
6180
|
+
};
|
|
6181
|
+
var DefaultHealthRegistry = class {
|
|
6182
|
+
checks = /* @__PURE__ */ new Map();
|
|
6183
|
+
timeoutMs;
|
|
6184
|
+
constructor(opts = {}) {
|
|
6185
|
+
this.timeoutMs = opts.timeoutMs ?? 5e3;
|
|
6186
|
+
}
|
|
6187
|
+
register(check) {
|
|
6188
|
+
this.checks.set(check.name, check);
|
|
6189
|
+
}
|
|
6190
|
+
unregister(name) {
|
|
6191
|
+
this.checks.delete(name);
|
|
6192
|
+
}
|
|
6193
|
+
async run() {
|
|
6194
|
+
const results = await Promise.all(
|
|
6195
|
+
Array.from(this.checks.values()).map(async (c) => {
|
|
6196
|
+
const result = await this.runOne(c);
|
|
6197
|
+
return { name: c.name, ...result };
|
|
6198
|
+
})
|
|
6199
|
+
);
|
|
6200
|
+
let status = "healthy";
|
|
6201
|
+
for (const r of results) {
|
|
6202
|
+
if (SEVERITY[r.status] > SEVERITY[status]) status = r.status;
|
|
6203
|
+
}
|
|
6204
|
+
return { status, timestamp: Date.now(), checks: results };
|
|
6205
|
+
}
|
|
6206
|
+
async runOne(check) {
|
|
6207
|
+
const timeout = new Promise((resolve4) => {
|
|
6208
|
+
setTimeout(
|
|
6209
|
+
() => resolve4({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
6210
|
+
this.timeoutMs
|
|
6211
|
+
);
|
|
6212
|
+
});
|
|
6213
|
+
try {
|
|
6214
|
+
return await Promise.race([check.check(), timeout]);
|
|
6215
|
+
} catch (err) {
|
|
6216
|
+
return { status: "unhealthy", detail: err instanceof Error ? err.message : String(err) };
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
};
|
|
6220
|
+
|
|
6221
|
+
// src/defaults/observability/tracer.ts
|
|
6222
|
+
var NoopTracer = class {
|
|
6223
|
+
startSpan() {
|
|
6224
|
+
return NOOP_SPAN;
|
|
6225
|
+
}
|
|
6226
|
+
};
|
|
6227
|
+
var NOOP_SPAN = {
|
|
6228
|
+
setAttribute() {
|
|
6229
|
+
},
|
|
6230
|
+
recordError() {
|
|
6231
|
+
},
|
|
6232
|
+
end() {
|
|
6233
|
+
}
|
|
6234
|
+
};
|
|
6235
|
+
|
|
6236
|
+
// src/defaults/observability/otel-tracer.ts
|
|
6237
|
+
var OTEL_STATUS_ERROR = 2;
|
|
6238
|
+
var OTelTracer = class {
|
|
6239
|
+
constructor(upstream) {
|
|
6240
|
+
this.upstream = upstream;
|
|
6241
|
+
}
|
|
6242
|
+
upstream;
|
|
6243
|
+
startSpan(name, attrs) {
|
|
6244
|
+
const otelSpan = this.upstream.startSpan(name, attrs ? { attributes: attrs } : void 0);
|
|
6245
|
+
return new OTelSpan(otelSpan);
|
|
6246
|
+
}
|
|
6247
|
+
};
|
|
6248
|
+
var OTelSpan = class {
|
|
6249
|
+
constructor(span) {
|
|
6250
|
+
this.span = span;
|
|
6251
|
+
}
|
|
6252
|
+
span;
|
|
6253
|
+
setAttribute(key, value) {
|
|
6254
|
+
this.span.setAttribute(key, value);
|
|
6255
|
+
}
|
|
6256
|
+
recordError(err) {
|
|
6257
|
+
this.span.recordException(err);
|
|
6258
|
+
this.span.setStatus?.({ code: OTEL_STATUS_ERROR, message: err.message });
|
|
6259
|
+
}
|
|
6260
|
+
end() {
|
|
6261
|
+
this.span.end();
|
|
6262
|
+
}
|
|
6263
|
+
};
|
|
6264
|
+
|
|
6265
|
+
// src/defaults/observability/event-bridge.ts
|
|
6266
|
+
function wireMetricsToEvents(events, sink) {
|
|
6267
|
+
const unsubs = [];
|
|
6268
|
+
unsubs.push(
|
|
6269
|
+
events.on("session.started", () => sink.counter("agent.sessions.started")),
|
|
6270
|
+
events.on("session.ended", (e) => {
|
|
6271
|
+
sink.counter("agent.sessions.ended");
|
|
6272
|
+
sink.histogram("agent.session.tokens.input", e.usage.input);
|
|
6273
|
+
sink.histogram("agent.session.tokens.output", e.usage.output);
|
|
6274
|
+
}),
|
|
6275
|
+
events.on("session.damaged", () => sink.counter("agent.sessions.damaged")),
|
|
6276
|
+
events.on("iteration.completed", () => sink.counter("agent.iterations.total")),
|
|
6277
|
+
events.on("iteration.limit_reached", () => sink.counter("agent.iteration_limit.hit")),
|
|
6278
|
+
events.on("provider.response", (e) => {
|
|
6279
|
+
sink.counter("provider.responses.total", 1, { stop_reason: e.stopReason });
|
|
6280
|
+
sink.counter("provider.tokens.input", e.usage.input);
|
|
6281
|
+
sink.counter("provider.tokens.output", e.usage.output);
|
|
6282
|
+
if (e.usage.cacheRead) sink.counter("provider.tokens.cache_read", e.usage.cacheRead);
|
|
6283
|
+
if (e.usage.cacheWrite) sink.counter("provider.tokens.cache_write", e.usage.cacheWrite);
|
|
6284
|
+
}),
|
|
6285
|
+
events.on(
|
|
6286
|
+
"provider.retry",
|
|
6287
|
+
(e) => sink.counter("provider.retries.total", 1, {
|
|
6288
|
+
provider: e.providerId,
|
|
6289
|
+
status: String(e.status)
|
|
6290
|
+
})
|
|
6291
|
+
),
|
|
6292
|
+
events.on(
|
|
6293
|
+
"provider.error",
|
|
6294
|
+
(e) => sink.counter("provider.errors.total", 1, {
|
|
6295
|
+
provider: e.providerId,
|
|
6296
|
+
status: String(e.status),
|
|
6297
|
+
retryable: String(e.retryable)
|
|
6298
|
+
})
|
|
6299
|
+
),
|
|
6300
|
+
events.on("tool.started", (e) => sink.counter("tool.starts.total", 1, { tool: e.name })),
|
|
6301
|
+
events.on("tool.executed", (e) => {
|
|
6302
|
+
sink.counter("tool.executions.total", 1, { tool: e.name, ok: String(e.ok) });
|
|
6303
|
+
sink.histogram("tool.duration_ms", e.durationMs, { tool: e.name });
|
|
6304
|
+
}),
|
|
6305
|
+
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
6306
|
+
events.on("compaction.fired", (e) => {
|
|
6307
|
+
sink.counter("compaction.fired.total");
|
|
6308
|
+
sink.histogram("compaction.reduction_tokens", e.before - e.after);
|
|
6309
|
+
}),
|
|
6310
|
+
events.on(
|
|
6311
|
+
"mcp.server.connected",
|
|
6312
|
+
(e) => sink.counter("mcp.connects.total", 1, { server: e.name })
|
|
6313
|
+
),
|
|
6314
|
+
events.on(
|
|
6315
|
+
"mcp.server.reconnected",
|
|
6316
|
+
(e) => sink.counter("mcp.reconnects.total", 1, { server: e.name })
|
|
6317
|
+
),
|
|
6318
|
+
events.on(
|
|
6319
|
+
"mcp.server.disconnected",
|
|
6320
|
+
(e) => sink.counter("mcp.disconnects.total", 1, { server: e.name })
|
|
6321
|
+
),
|
|
6322
|
+
events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
|
|
6323
|
+
);
|
|
6324
|
+
return () => {
|
|
6325
|
+
for (const u of unsubs) u();
|
|
6326
|
+
};
|
|
6327
|
+
}
|
|
6328
|
+
|
|
6329
|
+
// src/defaults/observability/prometheus.ts
|
|
6330
|
+
var NUMBER_FORMAT_INFINITY = "NaN";
|
|
6331
|
+
function escapeLabelValue(v) {
|
|
6332
|
+
return v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/"/g, '\\"');
|
|
6333
|
+
}
|
|
6334
|
+
function formatLabels(labels) {
|
|
6335
|
+
const keys = Object.keys(labels);
|
|
6336
|
+
if (keys.length === 0) return "";
|
|
6337
|
+
const parts = keys.map((k) => `${k}="${escapeLabelValue(labels[k] ?? "")}"`);
|
|
6338
|
+
return `{${parts.join(",")}}`;
|
|
6339
|
+
}
|
|
6340
|
+
function formatNumber(n) {
|
|
6341
|
+
if (!Number.isFinite(n)) return NUMBER_FORMAT_INFINITY;
|
|
6342
|
+
return Number.isInteger(n) ? n.toString() : n.toString();
|
|
6343
|
+
}
|
|
6344
|
+
function joinLabels(base, extra) {
|
|
6345
|
+
return { ...base, ...extra };
|
|
6346
|
+
}
|
|
6347
|
+
function renderPrometheus(snapshot) {
|
|
6348
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6349
|
+
for (const s of snapshot.series) {
|
|
6350
|
+
let g = groups.get(s.name);
|
|
6351
|
+
if (!g) {
|
|
6352
|
+
g = { type: s.type, rows: [] };
|
|
6353
|
+
groups.set(s.name, g);
|
|
6354
|
+
}
|
|
6355
|
+
g.rows.push({ labels: s.labels, values: s.values });
|
|
6356
|
+
}
|
|
6357
|
+
const lines = [];
|
|
6358
|
+
for (const [name, g] of groups) {
|
|
6359
|
+
const promType = g.type === "histogram" ? "summary" : g.type;
|
|
6360
|
+
lines.push(`# HELP ${name} ${name}`);
|
|
6361
|
+
lines.push(`# TYPE ${name} ${promType}`);
|
|
6362
|
+
if (g.type === "counter" || g.type === "gauge") {
|
|
6363
|
+
for (const row of g.rows) {
|
|
6364
|
+
lines.push(`${name}${formatLabels(row.labels)} ${formatNumber(row.values.value ?? 0)}`);
|
|
6365
|
+
}
|
|
6366
|
+
} else {
|
|
6367
|
+
for (const row of g.rows) {
|
|
6368
|
+
const { count = 0, sum = 0, p50 = 0, p95 = 0, p99 = 0 } = row.values;
|
|
6369
|
+
lines.push(
|
|
6370
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.5" }))} ${formatNumber(p50)}`
|
|
6371
|
+
);
|
|
6372
|
+
lines.push(
|
|
6373
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.95" }))} ${formatNumber(p95)}`
|
|
6374
|
+
);
|
|
6375
|
+
lines.push(
|
|
6376
|
+
`${name}${formatLabels(joinLabels(row.labels, { quantile: "0.99" }))} ${formatNumber(p99)}`
|
|
6377
|
+
);
|
|
6378
|
+
lines.push(`${name}_sum${formatLabels(row.labels)} ${formatNumber(sum)}`);
|
|
6379
|
+
lines.push(`${name}_count${formatLabels(row.labels)} ${formatNumber(count)}`);
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
return lines.join("\n") + "\n";
|
|
6384
|
+
}
|
|
6385
|
+
var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
6386
|
+
async function startMetricsServer(opts) {
|
|
6387
|
+
const { createServer } = await import('http');
|
|
6388
|
+
const host = opts.host ?? "127.0.0.1";
|
|
6389
|
+
const path15 = opts.path ?? "/metrics";
|
|
6390
|
+
const healthPath = opts.healthPath ?? "/healthz";
|
|
6391
|
+
const healthRegistry = opts.healthRegistry;
|
|
6392
|
+
const server = createServer((req, res) => {
|
|
6393
|
+
if (!req.url || req.method !== "GET") {
|
|
6394
|
+
res.statusCode = req.url ? 405 : 400;
|
|
6395
|
+
res.end();
|
|
6396
|
+
return;
|
|
6397
|
+
}
|
|
6398
|
+
const url = req.url.split("?")[0];
|
|
6399
|
+
if (url === path15) {
|
|
6400
|
+
let body;
|
|
6401
|
+
try {
|
|
6402
|
+
body = renderPrometheus(opts.sink.snapshot());
|
|
6403
|
+
} catch (err) {
|
|
6404
|
+
res.statusCode = 500;
|
|
6405
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
6406
|
+
res.end(`metrics render failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6407
|
+
return;
|
|
6408
|
+
}
|
|
6409
|
+
res.statusCode = 200;
|
|
6410
|
+
res.setHeader("content-type", PROMETHEUS_CONTENT_TYPE);
|
|
6411
|
+
res.end(body);
|
|
6412
|
+
return;
|
|
6413
|
+
}
|
|
6414
|
+
if (healthRegistry && url === healthPath) {
|
|
6415
|
+
healthRegistry.run().then(
|
|
6416
|
+
(agg) => {
|
|
6417
|
+
res.statusCode = agg.status === "unhealthy" ? 503 : 200;
|
|
6418
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
6419
|
+
res.end(JSON.stringify(agg, null, 2));
|
|
6420
|
+
},
|
|
6421
|
+
(err) => {
|
|
6422
|
+
res.statusCode = 500;
|
|
6423
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
6424
|
+
res.end(`health run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
6425
|
+
}
|
|
6426
|
+
);
|
|
6427
|
+
return;
|
|
6428
|
+
}
|
|
6429
|
+
res.statusCode = 404;
|
|
6430
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
6431
|
+
res.end("Not Found");
|
|
6432
|
+
});
|
|
6433
|
+
await new Promise((resolve4, reject) => {
|
|
6434
|
+
const onError = (err) => {
|
|
6435
|
+
server.off("listening", onListening);
|
|
6436
|
+
reject(err);
|
|
6437
|
+
};
|
|
6438
|
+
const onListening = () => {
|
|
6439
|
+
server.off("error", onError);
|
|
6440
|
+
resolve4();
|
|
6441
|
+
};
|
|
6442
|
+
server.once("error", onError);
|
|
6443
|
+
server.once("listening", onListening);
|
|
6444
|
+
server.listen(opts.port, host);
|
|
6445
|
+
});
|
|
6446
|
+
const addr = server.address();
|
|
6447
|
+
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
6448
|
+
return {
|
|
6449
|
+
port: boundPort,
|
|
6450
|
+
url: `http://${host}:${boundPort}${path15}`,
|
|
6451
|
+
close: () => new Promise((resolve4, reject) => {
|
|
6452
|
+
server.close((err) => err ? reject(err) : resolve4());
|
|
6453
|
+
})
|
|
6454
|
+
};
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
// src/defaults/observability/otlp-metrics.ts
|
|
6458
|
+
var DEFAULT_INTERVAL_MS = 3e4;
|
|
6459
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
6460
|
+
function joinEndpoint(base) {
|
|
6461
|
+
if (/\/v1\/metrics\/?$/.test(base)) return base;
|
|
6462
|
+
return base.replace(/\/$/, "") + "/v1/metrics";
|
|
6463
|
+
}
|
|
6464
|
+
function attributesFor(labels) {
|
|
6465
|
+
return Object.entries(labels).map(([key, value]) => ({
|
|
6466
|
+
key,
|
|
6467
|
+
value: { stringValue: value }
|
|
6468
|
+
}));
|
|
6469
|
+
}
|
|
6470
|
+
function buildExportBody(opts) {
|
|
6471
|
+
const metrics = [];
|
|
6472
|
+
for (const s of opts.series) {
|
|
6473
|
+
const dp = {
|
|
6474
|
+
attributes: attributesFor(s.labels),
|
|
6475
|
+
timeUnixNano: opts.timeUnixNano
|
|
6476
|
+
};
|
|
6477
|
+
if (s.type === "counter") {
|
|
6478
|
+
dp.asDouble = s.values.value ?? 0;
|
|
6479
|
+
metrics.push({
|
|
6480
|
+
name: s.name,
|
|
6481
|
+
sum: { dataPoints: [dp], aggregationTemporality: 2, isMonotonic: true }
|
|
6482
|
+
});
|
|
6483
|
+
} else if (s.type === "gauge") {
|
|
6484
|
+
dp.asDouble = s.values.value ?? 0;
|
|
6485
|
+
metrics.push({ name: s.name, gauge: { dataPoints: [dp] } });
|
|
6486
|
+
} else {
|
|
6487
|
+
dp.count = String(s.values.count ?? 0);
|
|
6488
|
+
dp.sum = s.values.sum ?? 0;
|
|
6489
|
+
dp.quantileValues = [
|
|
6490
|
+
{ quantile: 0.5, value: s.values.p50 ?? 0 },
|
|
6491
|
+
{ quantile: 0.95, value: s.values.p95 ?? 0 },
|
|
6492
|
+
{ quantile: 0.99, value: s.values.p99 ?? 0 }
|
|
6493
|
+
];
|
|
6494
|
+
metrics.push({ name: s.name, summary: { dataPoints: [dp] } });
|
|
6495
|
+
}
|
|
5052
6496
|
}
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
6497
|
+
return {
|
|
6498
|
+
resourceMetrics: [
|
|
6499
|
+
{
|
|
6500
|
+
resource: { attributes: attributesFor(opts.resourceAttributes) },
|
|
6501
|
+
scopeMetrics: [
|
|
6502
|
+
{
|
|
6503
|
+
scope: { name: opts.scopeName },
|
|
6504
|
+
metrics
|
|
6505
|
+
}
|
|
6506
|
+
]
|
|
6507
|
+
}
|
|
6508
|
+
]
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
6511
|
+
function buildOtlpMetricsRequest(sink, opts = {}) {
|
|
6512
|
+
return buildExportBody({
|
|
6513
|
+
series: sink.snapshot().series,
|
|
6514
|
+
resourceAttributes: opts.resourceAttributes ?? { "service.name": "wrongstack" },
|
|
6515
|
+
scopeName: opts.scopeName ?? "wrongstack",
|
|
6516
|
+
timeUnixNano: String(BigInt(Date.now()) * 1000000n)
|
|
6517
|
+
});
|
|
6518
|
+
}
|
|
6519
|
+
function startOtlpMetricsExporter(opts) {
|
|
6520
|
+
const url = joinEndpoint(opts.endpoint);
|
|
6521
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
6522
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
6523
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
6524
|
+
const onError = opts.onError ?? (() => {
|
|
6525
|
+
});
|
|
6526
|
+
const resourceAttributes = opts.resourceAttributes ?? { "service.name": "wrongstack" };
|
|
6527
|
+
const scopeName = opts.scopeName ?? "wrongstack";
|
|
6528
|
+
let stopped = false;
|
|
6529
|
+
const headers = {
|
|
6530
|
+
"content-type": "application/json",
|
|
6531
|
+
...opts.headers ?? {}
|
|
6532
|
+
};
|
|
6533
|
+
if (opts.authorization) headers.authorization = opts.authorization;
|
|
6534
|
+
async function pushOnce() {
|
|
6535
|
+
if (stopped) return;
|
|
6536
|
+
const body = buildExportBody({
|
|
6537
|
+
series: opts.sink.snapshot().series,
|
|
6538
|
+
resourceAttributes,
|
|
6539
|
+
scopeName,
|
|
6540
|
+
timeUnixNano: String(BigInt(Date.now()) * 1000000n)
|
|
6541
|
+
});
|
|
6542
|
+
const controller = new AbortController();
|
|
6543
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6544
|
+
try {
|
|
6545
|
+
const res = await fetchImpl(url, {
|
|
6546
|
+
method: "POST",
|
|
6547
|
+
headers,
|
|
6548
|
+
body: JSON.stringify(body),
|
|
6549
|
+
signal: controller.signal
|
|
6550
|
+
});
|
|
6551
|
+
if (!res.ok) {
|
|
6552
|
+
const text = await res.text().catch(() => "");
|
|
6553
|
+
onError(new Error(`OTLP push failed: ${res.status} ${res.statusText} ${text}`));
|
|
6554
|
+
}
|
|
6555
|
+
} catch (err) {
|
|
6556
|
+
onError(err);
|
|
6557
|
+
} finally {
|
|
6558
|
+
clearTimeout(timer);
|
|
6559
|
+
}
|
|
6560
|
+
}
|
|
6561
|
+
const handle = setInterval(() => {
|
|
6562
|
+
void pushOnce();
|
|
6563
|
+
}, intervalMs);
|
|
6564
|
+
handle.unref?.();
|
|
6565
|
+
return {
|
|
6566
|
+
flush: pushOnce,
|
|
6567
|
+
async stop() {
|
|
6568
|
+
stopped = true;
|
|
6569
|
+
clearInterval(handle);
|
|
6570
|
+
await pushOnce().catch(onError);
|
|
6571
|
+
}
|
|
6572
|
+
};
|
|
6573
|
+
}
|
|
6574
|
+
var SPAN_STATUS_CODE_UNSET = 0;
|
|
6575
|
+
var SPAN_STATUS_CODE_OK = 1;
|
|
6576
|
+
var SPAN_STATUS_CODE_ERROR = 2;
|
|
6577
|
+
function hex(bytes) {
|
|
6578
|
+
return crypto2.randomBytes(bytes).toString("hex");
|
|
6579
|
+
}
|
|
6580
|
+
function nowNs() {
|
|
6581
|
+
return BigInt(Date.now()) * 1000000n;
|
|
6582
|
+
}
|
|
6583
|
+
var CapturingSpan = class {
|
|
6584
|
+
constructor(state, onEnd) {
|
|
6585
|
+
this.state = state;
|
|
6586
|
+
this.onEnd = onEnd;
|
|
6587
|
+
}
|
|
6588
|
+
state;
|
|
6589
|
+
onEnd;
|
|
6590
|
+
setAttribute(key, value) {
|
|
6591
|
+
this.state.attributes[key] = value;
|
|
6592
|
+
}
|
|
6593
|
+
recordError(err) {
|
|
6594
|
+
this.state.status = { code: SPAN_STATUS_CODE_ERROR, message: err.message };
|
|
6595
|
+
this.state.attributes["exception.message"] = err.message;
|
|
6596
|
+
if (err.name) this.state.attributes["exception.type"] = err.name;
|
|
6597
|
+
}
|
|
6598
|
+
end() {
|
|
6599
|
+
if (this.state.endTimeUnixNano !== void 0) return;
|
|
6600
|
+
this.state.endTimeUnixNano = nowNs();
|
|
6601
|
+
if (this.state.status.code === SPAN_STATUS_CODE_UNSET) {
|
|
6602
|
+
this.state.status.code = SPAN_STATUS_CODE_OK;
|
|
5059
6603
|
}
|
|
5060
|
-
|
|
6604
|
+
this.onEnd(this.state);
|
|
5061
6605
|
}
|
|
5062
|
-
|
|
5063
|
-
|
|
6606
|
+
};
|
|
6607
|
+
var DEFAULT_INTERVAL_MS2 = 5e3;
|
|
6608
|
+
var DEFAULT_BUFFER_CAP = 2048;
|
|
6609
|
+
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
6610
|
+
function joinEndpoint2(base) {
|
|
6611
|
+
if (/\/v1\/traces\/?$/.test(base)) return base;
|
|
6612
|
+
return base.replace(/\/$/, "") + "/v1/traces";
|
|
6613
|
+
}
|
|
6614
|
+
function encodeAttr(key, value) {
|
|
6615
|
+
if (typeof value === "boolean") return { key, value: { boolValue: value } };
|
|
6616
|
+
if (typeof value === "number") {
|
|
6617
|
+
return Number.isInteger(value) ? { key, value: { intValue: String(value) } } : { key, value: { doubleValue: value } };
|
|
5064
6618
|
}
|
|
5065
|
-
return
|
|
6619
|
+
return { key, value: { stringValue: value } };
|
|
6620
|
+
}
|
|
6621
|
+
function buildOtlpTracesRequest(spans, opts = {}) {
|
|
6622
|
+
const resourceAttributes = opts.resourceAttributes ?? { "service.name": "wrongstack" };
|
|
6623
|
+
const scopeName = opts.scopeName ?? "wrongstack";
|
|
6624
|
+
const otlpSpans = spans.map((s) => ({
|
|
6625
|
+
traceId: s.traceId,
|
|
6626
|
+
spanId: s.spanId,
|
|
6627
|
+
name: s.name,
|
|
6628
|
+
kind: 1,
|
|
6629
|
+
// SPAN_KIND_INTERNAL
|
|
6630
|
+
startTimeUnixNano: s.startTimeUnixNano.toString(),
|
|
6631
|
+
endTimeUnixNano: (s.endTimeUnixNano ?? s.startTimeUnixNano).toString(),
|
|
6632
|
+
attributes: Object.entries(s.attributes).map(([k, v]) => encodeAttr(k, v)),
|
|
6633
|
+
status: s.status
|
|
6634
|
+
}));
|
|
6635
|
+
return {
|
|
6636
|
+
resourceSpans: [
|
|
6637
|
+
{
|
|
6638
|
+
resource: {
|
|
6639
|
+
attributes: Object.entries(resourceAttributes).map(
|
|
6640
|
+
([k, v]) => encodeAttr(k, v)
|
|
6641
|
+
)
|
|
6642
|
+
},
|
|
6643
|
+
scopeSpans: [{ scope: { name: scopeName }, spans: otlpSpans }]
|
|
6644
|
+
}
|
|
6645
|
+
]
|
|
6646
|
+
};
|
|
6647
|
+
}
|
|
6648
|
+
function startOtlpTraceExporter(opts) {
|
|
6649
|
+
const url = joinEndpoint2(opts.endpoint);
|
|
6650
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
6651
|
+
const maxBuffered = opts.maxBufferedSpans ?? DEFAULT_BUFFER_CAP;
|
|
6652
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
6653
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
6654
|
+
const onError = opts.onError ?? (() => {
|
|
6655
|
+
});
|
|
6656
|
+
const resourceAttributes = opts.resourceAttributes ?? { "service.name": "wrongstack" };
|
|
6657
|
+
const scopeName = opts.scopeName ?? "wrongstack";
|
|
6658
|
+
let stopped = false;
|
|
6659
|
+
const buffer = [];
|
|
6660
|
+
const headers = {
|
|
6661
|
+
"content-type": "application/json",
|
|
6662
|
+
...opts.headers ?? {}
|
|
6663
|
+
};
|
|
6664
|
+
if (opts.authorization) headers.authorization = opts.authorization;
|
|
6665
|
+
const tracer = {
|
|
6666
|
+
startSpan(name, attrs) {
|
|
6667
|
+
const state = {
|
|
6668
|
+
traceId: hex(16),
|
|
6669
|
+
spanId: hex(8),
|
|
6670
|
+
name,
|
|
6671
|
+
startTimeUnixNano: nowNs(),
|
|
6672
|
+
attributes: { ...attrs ?? {} },
|
|
6673
|
+
status: { code: SPAN_STATUS_CODE_UNSET }
|
|
6674
|
+
};
|
|
6675
|
+
return new CapturingSpan(state, (ended) => {
|
|
6676
|
+
if (buffer.length >= maxBuffered) buffer.shift();
|
|
6677
|
+
buffer.push(ended);
|
|
6678
|
+
});
|
|
6679
|
+
}
|
|
6680
|
+
};
|
|
6681
|
+
async function pushOnce() {
|
|
6682
|
+
if (buffer.length === 0) return;
|
|
6683
|
+
const batch = buffer.splice(0, buffer.length);
|
|
6684
|
+
const body = buildOtlpTracesRequest(batch, { resourceAttributes, scopeName });
|
|
6685
|
+
const controller = new AbortController();
|
|
6686
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6687
|
+
try {
|
|
6688
|
+
const res = await fetchImpl(url, {
|
|
6689
|
+
method: "POST",
|
|
6690
|
+
headers,
|
|
6691
|
+
body: JSON.stringify(body),
|
|
6692
|
+
signal: controller.signal
|
|
6693
|
+
});
|
|
6694
|
+
if (!res.ok) {
|
|
6695
|
+
const text = await res.text().catch(() => "");
|
|
6696
|
+
onError(new Error(`OTLP traces push failed: ${res.status} ${res.statusText} ${text}`));
|
|
6697
|
+
}
|
|
6698
|
+
} catch (err) {
|
|
6699
|
+
onError(err);
|
|
6700
|
+
} finally {
|
|
6701
|
+
clearTimeout(timer);
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
const handle = setInterval(() => {
|
|
6705
|
+
if (!stopped) void pushOnce();
|
|
6706
|
+
}, intervalMs);
|
|
6707
|
+
handle.unref?.();
|
|
6708
|
+
return {
|
|
6709
|
+
tracer,
|
|
6710
|
+
flush: pushOnce,
|
|
6711
|
+
async stop() {
|
|
6712
|
+
stopped = true;
|
|
6713
|
+
clearInterval(handle);
|
|
6714
|
+
await pushOnce().catch(onError);
|
|
6715
|
+
},
|
|
6716
|
+
buffered: () => [...buffer]
|
|
6717
|
+
};
|
|
5066
6718
|
}
|
|
5067
6719
|
|
|
5068
6720
|
// src/defaults/context-manager.ts
|
|
@@ -5216,18 +6868,370 @@ function createContextManagerTool(opts = {}) {
|
|
|
5216
6868
|
summary: summaryText
|
|
5217
6869
|
};
|
|
5218
6870
|
}
|
|
5219
|
-
default:
|
|
5220
|
-
return {
|
|
5221
|
-
action: input.action,
|
|
5222
|
-
beforeTokens,
|
|
5223
|
-
messageCount: messages.length,
|
|
5224
|
-
notes: `Unknown action: ${input.action}`
|
|
5225
|
-
};
|
|
6871
|
+
default:
|
|
6872
|
+
return {
|
|
6873
|
+
action: input.action,
|
|
6874
|
+
beforeTokens,
|
|
6875
|
+
messageCount: messages.length,
|
|
6876
|
+
notes: `Unknown action: ${input.action}`
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6879
|
+
}
|
|
6880
|
+
};
|
|
6881
|
+
}
|
|
6882
|
+
var contextManagerTool = createContextManagerTool();
|
|
6883
|
+
|
|
6884
|
+
// src/defaults/mcp-servers.ts
|
|
6885
|
+
var filesystemServer = () => ({
|
|
6886
|
+
name: "filesystem",
|
|
6887
|
+
description: "Read, write, and navigate the local filesystem (read-heavy tools)",
|
|
6888
|
+
transport: "stdio",
|
|
6889
|
+
command: "npx",
|
|
6890
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
|
|
6891
|
+
permission: "confirm"
|
|
6892
|
+
});
|
|
6893
|
+
var githubServer = () => ({
|
|
6894
|
+
name: "github",
|
|
6895
|
+
description: "GitHub API \u2014 issues, PRs, repos, search, file ops (requires GITHUB_PERSONAL_ACCESS_TOKEN)",
|
|
6896
|
+
transport: "stdio",
|
|
6897
|
+
command: "npx",
|
|
6898
|
+
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
6899
|
+
env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_PERSONAL_ACCESS_TOKEN ?? "" },
|
|
6900
|
+
permission: "confirm"
|
|
6901
|
+
});
|
|
6902
|
+
var context7Server = () => ({
|
|
6903
|
+
name: "context7",
|
|
6904
|
+
description: "Codebase-aware documentation and Q&A (context7.ai)",
|
|
6905
|
+
transport: "streamable-http",
|
|
6906
|
+
url: "https://server.context7.ai/mcp",
|
|
6907
|
+
permission: "confirm"
|
|
6908
|
+
});
|
|
6909
|
+
var braveSearchServer = () => ({
|
|
6910
|
+
name: "brave-search",
|
|
6911
|
+
description: "Web search (Brave). Requires BRAVE_SEARCH_API_KEY \u2014 free tier 2k queries/month",
|
|
6912
|
+
transport: "stdio",
|
|
6913
|
+
command: "npx",
|
|
6914
|
+
args: ["-y", "@modelcontextprotocol/server-brave-search"],
|
|
6915
|
+
env: { BRAVE_SEARCH_API_KEY: process.env.BRAVE_SEARCH_API_KEY ?? "" },
|
|
6916
|
+
permission: "confirm"
|
|
6917
|
+
});
|
|
6918
|
+
var blockServer = () => ({
|
|
6919
|
+
name: "block",
|
|
6920
|
+
description: "Postgres database access via SQL (Block MCP server)",
|
|
6921
|
+
transport: "stdio",
|
|
6922
|
+
command: "npx",
|
|
6923
|
+
args: ["-y", "@modelcontextprotocol/server-block"],
|
|
6924
|
+
permission: "confirm"
|
|
6925
|
+
});
|
|
6926
|
+
var everArtServer = () => ({
|
|
6927
|
+
name: "everart",
|
|
6928
|
+
description: "AI image generation (EverArt). Requires EVERART_API_KEY",
|
|
6929
|
+
transport: "stdio",
|
|
6930
|
+
command: "npx",
|
|
6931
|
+
args: ["-y", "@modelcontextprotocol/server-everart"],
|
|
6932
|
+
env: { EVERART_API_KEY: process.env.EVERART_API_KEY ?? "" },
|
|
6933
|
+
permission: "confirm"
|
|
6934
|
+
});
|
|
6935
|
+
var slackServer = () => ({
|
|
6936
|
+
name: "slack",
|
|
6937
|
+
description: "Slack \u2014 messaging, channels, search. Requires SLACK_BOT_TOKEN + SLACK_TEAM_ID",
|
|
6938
|
+
transport: "stdio",
|
|
6939
|
+
command: "npx",
|
|
6940
|
+
args: ["-y", "@modelcontextprotocol/server-slack"],
|
|
6941
|
+
env: {
|
|
6942
|
+
SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN ?? "",
|
|
6943
|
+
SLACK_TEAM_ID: process.env.SLACK_TEAM_ID ?? ""
|
|
6944
|
+
},
|
|
6945
|
+
permission: "confirm"
|
|
6946
|
+
});
|
|
6947
|
+
var awsServer = () => ({
|
|
6948
|
+
name: "aws",
|
|
6949
|
+
description: "AWS \u2014 EC2, S3, Lambda, IAM, CloudFormation, costs. Requires AWS credentials",
|
|
6950
|
+
transport: "stdio",
|
|
6951
|
+
command: "npx",
|
|
6952
|
+
args: ["-y", "@modelcontextprotocol/server-aws"],
|
|
6953
|
+
permission: "confirm"
|
|
6954
|
+
});
|
|
6955
|
+
var googleMapsServer = () => ({
|
|
6956
|
+
name: "google-maps",
|
|
6957
|
+
description: "Google Maps \u2014 directions, geocoding, places. Requires GOOGLE_MAPS_API_KEY",
|
|
6958
|
+
transport: "stdio",
|
|
6959
|
+
command: "npx",
|
|
6960
|
+
args: ["-y", "@modelcontextprotocol/server-google-maps"],
|
|
6961
|
+
env: { GOOGLE_MAPS_API_KEY: process.env.GOOGLE_MAPS_API_KEY ?? "" },
|
|
6962
|
+
permission: "confirm"
|
|
6963
|
+
});
|
|
6964
|
+
var sentinelServer = () => ({
|
|
6965
|
+
name: "sentinel",
|
|
6966
|
+
description: "Security vulnerability scanning (Sentinel)",
|
|
6967
|
+
transport: "streamable-http",
|
|
6968
|
+
url: "https://mcp.sentinel.ai",
|
|
6969
|
+
permission: "deny"
|
|
6970
|
+
// security tool — require explicit confirmation
|
|
6971
|
+
});
|
|
6972
|
+
var allServers = () => ({
|
|
6973
|
+
filesystem: { ...filesystemServer(), enabled: false },
|
|
6974
|
+
github: { ...githubServer(), enabled: false },
|
|
6975
|
+
context7: { ...context7Server(), enabled: false },
|
|
6976
|
+
"brave-search": { ...braveSearchServer(), enabled: false },
|
|
6977
|
+
block: { ...blockServer(), enabled: false },
|
|
6978
|
+
everart: { ...everArtServer(), enabled: false },
|
|
6979
|
+
slack: { ...slackServer(), enabled: false },
|
|
6980
|
+
aws: { ...awsServer(), enabled: false },
|
|
6981
|
+
"google-maps": { ...googleMapsServer(), enabled: false },
|
|
6982
|
+
sentinel: { ...sentinelServer(), enabled: false }
|
|
6983
|
+
});
|
|
6984
|
+
|
|
6985
|
+
// src/core/streaming-response-builder.ts
|
|
6986
|
+
function buildResponse(state) {
|
|
6987
|
+
const content = [];
|
|
6988
|
+
for (const b of state.blockOrder) {
|
|
6989
|
+
if (b.kind === "text") {
|
|
6990
|
+
const txt = state.textBuffers[b.idx] ?? "";
|
|
6991
|
+
if (txt) content.push({ type: "text", text: txt });
|
|
6992
|
+
} else {
|
|
6993
|
+
const tb = state.tools.get(b.id);
|
|
6994
|
+
if (tb) {
|
|
6995
|
+
const block = {
|
|
6996
|
+
type: "tool_use",
|
|
6997
|
+
id: b.id,
|
|
6998
|
+
name: tb.name,
|
|
6999
|
+
input: tb.input ?? {}
|
|
7000
|
+
};
|
|
7001
|
+
if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
|
|
7002
|
+
block.providerMeta = tb.providerMeta;
|
|
7003
|
+
}
|
|
7004
|
+
content.push(block);
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
7007
|
+
}
|
|
7008
|
+
if (content.length === 0) content.push({ type: "text", text: "" });
|
|
7009
|
+
return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
|
|
7010
|
+
}
|
|
7011
|
+
function createStreamingState(model) {
|
|
7012
|
+
return {
|
|
7013
|
+
model,
|
|
7014
|
+
stopReason: "end_turn",
|
|
7015
|
+
usage: { input: 0, output: 0 },
|
|
7016
|
+
textBuffers: [],
|
|
7017
|
+
currentTextIndex: -1,
|
|
7018
|
+
tools: /* @__PURE__ */ new Map(),
|
|
7019
|
+
blockOrder: []
|
|
7020
|
+
};
|
|
7021
|
+
}
|
|
7022
|
+
function handleMessageStart(state, model) {
|
|
7023
|
+
state.model = model;
|
|
7024
|
+
}
|
|
7025
|
+
function handleContentBlockStart(state, ev) {
|
|
7026
|
+
const kind = ev.kind ?? "text";
|
|
7027
|
+
if (kind === "text") {
|
|
7028
|
+
state.currentTextIndex = state.textBuffers.length;
|
|
7029
|
+
state.textBuffers.push("");
|
|
7030
|
+
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
7031
|
+
} else if (kind === "tool_use") {
|
|
7032
|
+
const id = ev.id ?? crypto.randomUUID();
|
|
7033
|
+
state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
|
|
7034
|
+
state.blockOrder.push({ kind: "tool", id });
|
|
7035
|
+
state.currentTextIndex = -1;
|
|
7036
|
+
}
|
|
7037
|
+
}
|
|
7038
|
+
function handleContentBlockStop(state, ev) {
|
|
7039
|
+
}
|
|
7040
|
+
function handleTextDelta(state, text) {
|
|
7041
|
+
if (state.currentTextIndex === -1) {
|
|
7042
|
+
state.currentTextIndex = state.textBuffers.length;
|
|
7043
|
+
state.textBuffers.push("");
|
|
7044
|
+
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
7045
|
+
}
|
|
7046
|
+
state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
|
|
7047
|
+
}
|
|
7048
|
+
function handleToolUseStart(state, ev) {
|
|
7049
|
+
state.currentTextIndex = -1;
|
|
7050
|
+
state.tools.set(ev.id, { name: ev.name, partial: "" });
|
|
7051
|
+
state.blockOrder.push({ kind: "tool", id: ev.id });
|
|
7052
|
+
}
|
|
7053
|
+
function handleToolUseInputDelta(state, ev) {
|
|
7054
|
+
const t2 = state.tools.get(ev.id);
|
|
7055
|
+
if (t2) t2.partial += ev.partial;
|
|
7056
|
+
}
|
|
7057
|
+
function safeJsonOrRaw(s) {
|
|
7058
|
+
if (!s) return {};
|
|
7059
|
+
try {
|
|
7060
|
+
return JSON.parse(s);
|
|
7061
|
+
} catch {
|
|
7062
|
+
return { _raw: s };
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
function handleToolUseStop(state, ev) {
|
|
7066
|
+
const t2 = state.tools.get(ev.id);
|
|
7067
|
+
if (t2) {
|
|
7068
|
+
t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
|
|
7069
|
+
if (ev.providerMeta) t2.providerMeta = ev.providerMeta;
|
|
7070
|
+
}
|
|
7071
|
+
state.currentTextIndex = -1;
|
|
7072
|
+
}
|
|
7073
|
+
function handleMessageStop(state, ev) {
|
|
7074
|
+
state.stopReason = ev.stopReason ?? "end_turn";
|
|
7075
|
+
state.usage = ev.usage ?? { input: 0, output: 0 };
|
|
7076
|
+
}
|
|
7077
|
+
async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
7078
|
+
const state = createStreamingState(req.model);
|
|
7079
|
+
const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
|
|
7080
|
+
try {
|
|
7081
|
+
for (; ; ) {
|
|
7082
|
+
const next = await iter.next();
|
|
7083
|
+
if (next.done) break;
|
|
7084
|
+
const ev = next.value;
|
|
7085
|
+
switch (ev.type) {
|
|
7086
|
+
case "message_start":
|
|
7087
|
+
handleMessageStart(state, ev.model);
|
|
7088
|
+
break;
|
|
7089
|
+
case "content_block_start":
|
|
7090
|
+
handleContentBlockStart(state, ev);
|
|
7091
|
+
break;
|
|
7092
|
+
case "content_block_stop":
|
|
7093
|
+
handleContentBlockStop(state, ev);
|
|
7094
|
+
break;
|
|
7095
|
+
case "text_delta":
|
|
7096
|
+
handleTextDelta(state, ev.text);
|
|
7097
|
+
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
7098
|
+
break;
|
|
7099
|
+
case "tool_use_start":
|
|
7100
|
+
handleToolUseStart(state, ev);
|
|
7101
|
+
events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
|
|
7102
|
+
break;
|
|
7103
|
+
case "tool_use_input_delta":
|
|
7104
|
+
handleToolUseInputDelta(state, ev);
|
|
7105
|
+
break;
|
|
7106
|
+
case "tool_use_stop":
|
|
7107
|
+
handleToolUseStop(state, ev);
|
|
7108
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id });
|
|
7109
|
+
break;
|
|
7110
|
+
case "message_stop":
|
|
7111
|
+
handleMessageStop(state, ev);
|
|
7112
|
+
break;
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
} catch (err) {
|
|
7116
|
+
if (signal.aborted) {
|
|
7117
|
+
state.stopReason = "max_tokens";
|
|
7118
|
+
return buildResponse(state);
|
|
7119
|
+
}
|
|
7120
|
+
throw err;
|
|
7121
|
+
} finally {
|
|
7122
|
+
try {
|
|
7123
|
+
await iter.return?.();
|
|
7124
|
+
} catch {
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
return buildResponse(state);
|
|
7128
|
+
}
|
|
7129
|
+
|
|
7130
|
+
// src/core/provider-runner.ts
|
|
7131
|
+
async function runProviderWithRetry(opts) {
|
|
7132
|
+
const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
|
|
7133
|
+
let attempt = 0;
|
|
7134
|
+
for (; ; ) {
|
|
7135
|
+
const span = tracer?.startSpan("provider.complete", {
|
|
7136
|
+
"provider.id": provider.id,
|
|
7137
|
+
"provider.model": request.model,
|
|
7138
|
+
"provider.streaming": provider.capabilities.streaming,
|
|
7139
|
+
"provider.attempt": attempt
|
|
7140
|
+
});
|
|
7141
|
+
try {
|
|
7142
|
+
const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
|
|
7143
|
+
span?.setAttribute("provider.stopReason", res.stopReason);
|
|
7144
|
+
span?.setAttribute("provider.usage_in", res.usage.input);
|
|
7145
|
+
span?.setAttribute("provider.usage_out", res.usage.output);
|
|
7146
|
+
span?.end();
|
|
7147
|
+
return res;
|
|
7148
|
+
} catch (err) {
|
|
7149
|
+
if (err instanceof Error) span?.recordError(err);
|
|
7150
|
+
span?.end();
|
|
7151
|
+
if (signal.aborted) throw err;
|
|
7152
|
+
const isProviderErr = err instanceof ProviderError;
|
|
7153
|
+
const errAsErr = err instanceof Error ? err : new Error(String(err));
|
|
7154
|
+
const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
|
|
7155
|
+
const description = isProviderErr ? err.describe() : errAsErr.message;
|
|
7156
|
+
if (!canRetry) {
|
|
7157
|
+
if (isProviderErr) {
|
|
7158
|
+
events.emit("provider.error", {
|
|
7159
|
+
providerId: err.providerId,
|
|
7160
|
+
status: err.status,
|
|
7161
|
+
description,
|
|
7162
|
+
retryable: false
|
|
7163
|
+
});
|
|
7164
|
+
}
|
|
7165
|
+
throw err;
|
|
7166
|
+
}
|
|
7167
|
+
const delay = Math.round(retry.delayMs(attempt));
|
|
7168
|
+
const attemptNum = attempt + 1;
|
|
7169
|
+
logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
|
|
7170
|
+
if (isProviderErr) {
|
|
7171
|
+
events.emit("provider.retry", {
|
|
7172
|
+
providerId: err.providerId,
|
|
7173
|
+
attempt: attemptNum,
|
|
7174
|
+
delayMs: delay,
|
|
7175
|
+
status: err.status,
|
|
7176
|
+
description
|
|
7177
|
+
});
|
|
5226
7178
|
}
|
|
7179
|
+
await new Promise((resolve4, reject) => {
|
|
7180
|
+
const t2 = setTimeout(resolve4, delay);
|
|
7181
|
+
const onAbort = () => {
|
|
7182
|
+
clearTimeout(t2);
|
|
7183
|
+
reject(new Error("aborted"));
|
|
7184
|
+
};
|
|
7185
|
+
if (signal.aborted) onAbort();
|
|
7186
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
7187
|
+
});
|
|
7188
|
+
attempt++;
|
|
5227
7189
|
}
|
|
5228
|
-
}
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
|
|
7193
|
+
// src/core/iteration-limit.ts
|
|
7194
|
+
function requestLimitExtension(opts) {
|
|
7195
|
+
const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
|
|
7196
|
+
return new Promise((resolve4) => {
|
|
7197
|
+
let resolved = false;
|
|
7198
|
+
const timer = setTimeout(() => {
|
|
7199
|
+
if (!resolved) {
|
|
7200
|
+
resolved = true;
|
|
7201
|
+
resolve4(0);
|
|
7202
|
+
}
|
|
7203
|
+
}, timeoutMs);
|
|
7204
|
+
const deny = () => {
|
|
7205
|
+
if (!resolved) {
|
|
7206
|
+
resolved = true;
|
|
7207
|
+
clearTimeout(timer);
|
|
7208
|
+
resolve4(0);
|
|
7209
|
+
}
|
|
7210
|
+
};
|
|
7211
|
+
const grant = (extra) => {
|
|
7212
|
+
if (!resolved) {
|
|
7213
|
+
resolved = true;
|
|
7214
|
+
clearTimeout(timer);
|
|
7215
|
+
resolve4(Math.max(0, extra));
|
|
7216
|
+
}
|
|
7217
|
+
};
|
|
7218
|
+
events.emit("iteration.limit_reached", {
|
|
7219
|
+
currentIterations,
|
|
7220
|
+
currentLimit,
|
|
7221
|
+
grant,
|
|
7222
|
+
deny
|
|
7223
|
+
});
|
|
7224
|
+
if (autoExtend) {
|
|
7225
|
+
setImmediate(() => {
|
|
7226
|
+
if (!resolved) {
|
|
7227
|
+
resolved = true;
|
|
7228
|
+
clearTimeout(timer);
|
|
7229
|
+
resolve4(100);
|
|
7230
|
+
}
|
|
7231
|
+
});
|
|
7232
|
+
}
|
|
7233
|
+
});
|
|
5229
7234
|
}
|
|
5230
|
-
var contextManagerTool = createContextManagerTool();
|
|
5231
7235
|
|
|
5232
7236
|
// src/core/agent.ts
|
|
5233
7237
|
var DEFAULT_MAX_ITERATIONS = 100;
|
|
@@ -5262,6 +7266,7 @@ var Agent = class {
|
|
|
5262
7266
|
plugins = [];
|
|
5263
7267
|
toolExecutor;
|
|
5264
7268
|
autoExtendLimit;
|
|
7269
|
+
tracer;
|
|
5265
7270
|
constructor(init) {
|
|
5266
7271
|
this.container = init.container;
|
|
5267
7272
|
this.tools = init.tools;
|
|
@@ -5274,13 +7279,16 @@ var Agent = class {
|
|
|
5274
7279
|
this.executionStrategy = init.executionStrategy ?? "smart";
|
|
5275
7280
|
this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
|
|
5276
7281
|
this.autoExtendLimit = init.autoExtendLimit ?? true;
|
|
7282
|
+
this.tracer = init.tracer;
|
|
5277
7283
|
this.toolExecutor = new ToolExecutor(this.tools, {
|
|
5278
7284
|
permissionPolicy: this.permission,
|
|
5279
7285
|
secretScrubber: this.scrubber,
|
|
5280
7286
|
renderer: this.renderer,
|
|
5281
7287
|
events: this.events,
|
|
7288
|
+
confirmAwaiter: init.confirmAwaiter,
|
|
5282
7289
|
iterationTimeoutMs: this.iterationTimeoutMs,
|
|
5283
|
-
perIterationOutputCapBytes: this.perIterationOutputCapBytes
|
|
7290
|
+
perIterationOutputCapBytes: this.perIterationOutputCapBytes,
|
|
7291
|
+
tracer: this.tracer
|
|
5284
7292
|
});
|
|
5285
7293
|
}
|
|
5286
7294
|
get logger() {
|
|
@@ -5316,334 +7324,190 @@ var Agent = class {
|
|
|
5316
7324
|
const signal = controller.signal;
|
|
5317
7325
|
this.ctx.signal = signal;
|
|
5318
7326
|
controller.onAbort(() => this.ctx.drainAbortHooks());
|
|
7327
|
+
const span = this.tracer?.startSpan("agent.run", {
|
|
7328
|
+
"agent.model": opts.model ?? this.ctx.model,
|
|
7329
|
+
"agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
|
|
7330
|
+
});
|
|
5319
7331
|
try {
|
|
5320
|
-
|
|
7332
|
+
const result = await this.runInner(userInput, opts, controller);
|
|
7333
|
+
span?.setAttribute("agent.status", result.status);
|
|
7334
|
+
span?.setAttribute("agent.iterations", result.iterations);
|
|
7335
|
+
return result;
|
|
7336
|
+
} catch (err) {
|
|
7337
|
+
const wse = err instanceof AgentError ? err : toWrongStackError(err);
|
|
7338
|
+
this.events.emit("error", { err: toError(err), phase: "agent" });
|
|
7339
|
+
if (err instanceof Error) span?.recordError(err);
|
|
7340
|
+
span?.setAttribute("agent.status", "failed");
|
|
7341
|
+
return {
|
|
7342
|
+
status: signal.aborted ? "aborted" : "failed",
|
|
7343
|
+
iterations: 0,
|
|
7344
|
+
error: wse
|
|
7345
|
+
};
|
|
5321
7346
|
} finally {
|
|
7347
|
+
span?.end();
|
|
5322
7348
|
await controller.dispose();
|
|
5323
7349
|
}
|
|
5324
7350
|
}
|
|
5325
7351
|
async runInner(userInput, opts, controller) {
|
|
5326
|
-
|
|
5327
|
-
const { blocks, text } = normalizeInput(userInput);
|
|
5328
|
-
await this.pipelines.userInput.run({ content: blocks, text, ctx: this.ctx });
|
|
5329
|
-
this.ctx.messages.push({ role: "user", content: blocks });
|
|
5330
|
-
await this.ctx.session.append({
|
|
5331
|
-
type: "user_input",
|
|
5332
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5333
|
-
content: blocks
|
|
5334
|
-
});
|
|
7352
|
+
await this.normalizeAndEmitUserInput(userInput);
|
|
5335
7353
|
let finalText = "";
|
|
5336
7354
|
let iterations = 0;
|
|
5337
|
-
let
|
|
5338
|
-
const hasHardLimit =
|
|
7355
|
+
let effectiveLimit = opts.maxIterations ?? this.maxIterations;
|
|
7356
|
+
const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
|
|
5339
7357
|
for (let i = 0; ; i++) {
|
|
5340
7358
|
iterations = i + 1;
|
|
5341
|
-
if (signal.aborted) {
|
|
7359
|
+
if (controller.signal.aborted) {
|
|
5342
7360
|
return { status: "aborted", iterations };
|
|
5343
7361
|
}
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
7362
|
+
const limitCheck = await this.checkIterationLimit(
|
|
7363
|
+
i,
|
|
7364
|
+
effectiveLimit,
|
|
7365
|
+
hasHardLimit,
|
|
7366
|
+
iterations
|
|
7367
|
+
);
|
|
7368
|
+
effectiveLimit = limitCheck.limit;
|
|
7369
|
+
if (limitCheck.exit) {
|
|
7370
|
+
return { ...limitCheck.exit, finalText };
|
|
5352
7371
|
}
|
|
5353
7372
|
this.events.emit("iteration.started", { ctx: this.ctx, index: i });
|
|
5354
|
-
const
|
|
5355
|
-
model: opts.model ?? this.ctx.model,
|
|
5356
|
-
system: this.ctx.systemPrompt,
|
|
5357
|
-
messages: this.ctx.messages,
|
|
5358
|
-
tools: this.tools.list(),
|
|
5359
|
-
maxTokens: 8192
|
|
5360
|
-
};
|
|
5361
|
-
const req = await this.pipelines.request.run(baseReq);
|
|
7373
|
+
const req = await this.buildAndRunRequestPipeline(opts);
|
|
5362
7374
|
let res;
|
|
5363
7375
|
try {
|
|
5364
|
-
res = await
|
|
7376
|
+
res = await runProviderWithRetry({
|
|
7377
|
+
provider: this.ctx.provider,
|
|
7378
|
+
request: req,
|
|
7379
|
+
signal: controller.signal,
|
|
7380
|
+
ctx: this.ctx,
|
|
7381
|
+
events: this.events,
|
|
7382
|
+
retry: this.retry,
|
|
7383
|
+
logger: this.logger,
|
|
7384
|
+
tracer: this.tracer
|
|
7385
|
+
});
|
|
5365
7386
|
} catch (err) {
|
|
5366
|
-
if (signal.aborted) {
|
|
7387
|
+
if (controller.signal.aborted) {
|
|
5367
7388
|
this.events.emit("error", { err: toError(err), phase: "provider" });
|
|
5368
|
-
return { status: "aborted", iterations, error: err };
|
|
7389
|
+
return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
|
|
5369
7390
|
}
|
|
5370
7391
|
const recovered = await this.errorHandler.recover(err, this.ctx);
|
|
5371
7392
|
if (!recovered) {
|
|
5372
7393
|
this.events.emit("error", { err: toError(err), phase: "provider" });
|
|
5373
|
-
return { status: "failed", iterations, error: err };
|
|
7394
|
+
return { status: "failed", iterations, error: toWrongStackError(err) };
|
|
5374
7395
|
}
|
|
5375
7396
|
res = recovered;
|
|
5376
7397
|
}
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
usage: res.usage,
|
|
5381
|
-
stopReason: res.stopReason
|
|
5382
|
-
});
|
|
5383
|
-
this.ctx.tokenCounter.account(res.usage, req.model);
|
|
5384
|
-
this.ctx.messages.push({ role: "assistant", content: res.content });
|
|
5385
|
-
await this.ctx.session.append({
|
|
5386
|
-
type: "llm_response",
|
|
5387
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5388
|
-
content: res.content,
|
|
5389
|
-
stopReason: res.stopReason,
|
|
5390
|
-
usage: res.usage
|
|
5391
|
-
});
|
|
5392
|
-
if (signal.aborted) {
|
|
5393
|
-
for (const block of res.content) {
|
|
5394
|
-
if (isTextBlock(block)) finalText += block.text;
|
|
5395
|
-
}
|
|
5396
|
-
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
5397
|
-
return { status: "aborted", iterations, finalText };
|
|
7398
|
+
const responseResult = await this.processResponse(res, req);
|
|
7399
|
+
if (responseResult.aborted) {
|
|
7400
|
+
return { status: "aborted", iterations, finalText: responseResult.finalText };
|
|
5398
7401
|
}
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
if (isTextBlock(block)) {
|
|
5402
|
-
const rendered = await this.pipelines.assistantOutput.run(block);
|
|
5403
|
-
finalText += rendered.text;
|
|
5404
|
-
if (!streamed) this.renderer?.write(rendered);
|
|
5405
|
-
}
|
|
7402
|
+
if (responseResult.done) {
|
|
7403
|
+
return { status: "done", iterations, finalText: responseResult.finalText };
|
|
5406
7404
|
}
|
|
7405
|
+
finalText = responseResult.finalText;
|
|
5407
7406
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
5408
|
-
if (toolUses.length === 0
|
|
7407
|
+
if (toolUses.length === 0) {
|
|
5409
7408
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
5410
7409
|
return { status: "done", iterations, finalText };
|
|
5411
7410
|
}
|
|
5412
|
-
|
|
5413
|
-
this.ctx.messages.push({ role: "user", content: results });
|
|
7411
|
+
await this.executeTools(toolUses);
|
|
5414
7412
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
5415
|
-
|
|
5416
|
-
await this.pipelines.contextWindow.run(this.ctx);
|
|
5417
|
-
}
|
|
7413
|
+
await this.compactContextIfNeeded();
|
|
5418
7414
|
}
|
|
5419
|
-
return { status: "max_iterations", iterations, finalText };
|
|
5420
7415
|
}
|
|
5421
7416
|
/**
|
|
5422
|
-
*
|
|
5423
|
-
* limit. Returns the number of additional iterations granted. If no listener
|
|
5424
|
-
* responds or the user declines, returns 0.
|
|
7417
|
+
* Normalize user input and emit through userInput pipeline + session append.
|
|
5425
7418
|
*/
|
|
5426
|
-
async
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
}, 3e4);
|
|
5435
|
-
const wrappedDeny = () => {
|
|
5436
|
-
if (!resolved) {
|
|
5437
|
-
resolved = true;
|
|
5438
|
-
clearTimeout(timer);
|
|
5439
|
-
resolve4(0);
|
|
5440
|
-
}
|
|
5441
|
-
};
|
|
5442
|
-
const wrappedGrant = (extra) => {
|
|
5443
|
-
if (!resolved) {
|
|
5444
|
-
resolved = true;
|
|
5445
|
-
clearTimeout(timer);
|
|
5446
|
-
resolve4(Math.max(0, extra));
|
|
5447
|
-
}
|
|
5448
|
-
};
|
|
5449
|
-
if (this.autoExtendLimit) {
|
|
5450
|
-
this.events.emit("iteration.limit_reached", {
|
|
5451
|
-
currentIterations,
|
|
5452
|
-
currentLimit: this.maxIterations,
|
|
5453
|
-
grant: wrappedGrant,
|
|
5454
|
-
deny: wrappedDeny
|
|
5455
|
-
});
|
|
5456
|
-
setImmediate(() => {
|
|
5457
|
-
if (!resolved) {
|
|
5458
|
-
resolved = true;
|
|
5459
|
-
clearTimeout(timer);
|
|
5460
|
-
resolve4(100);
|
|
5461
|
-
}
|
|
5462
|
-
});
|
|
5463
|
-
} else {
|
|
5464
|
-
this.events.emit("iteration.limit_reached", {
|
|
5465
|
-
currentIterations,
|
|
5466
|
-
currentLimit: this.maxIterations,
|
|
5467
|
-
grant: wrappedGrant,
|
|
5468
|
-
deny: wrappedDeny
|
|
5469
|
-
});
|
|
5470
|
-
}
|
|
7419
|
+
async normalizeAndEmitUserInput(userInput) {
|
|
7420
|
+
const { blocks, text } = normalizeInput(userInput);
|
|
7421
|
+
await this.pipelines.userInput.run({ content: blocks, text, ctx: this.ctx });
|
|
7422
|
+
this.ctx.state.appendMessage({ role: "user", content: blocks });
|
|
7423
|
+
await this.ctx.session.append({
|
|
7424
|
+
type: "user_input",
|
|
7425
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7426
|
+
content: blocks
|
|
5471
7427
|
});
|
|
5472
7428
|
}
|
|
5473
7429
|
/**
|
|
5474
|
-
*
|
|
5475
|
-
*
|
|
5476
|
-
*
|
|
5477
|
-
*
|
|
7430
|
+
* Check if iteration limit has been reached and request extension if needed.
|
|
7431
|
+
* Returns the new effective limit (possibly extended) and a RunResult if
|
|
7432
|
+
* the loop should exit. Returns `{ limit }` with no result when the
|
|
7433
|
+
* iteration may proceed.
|
|
5478
7434
|
*/
|
|
5479
|
-
async
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
if (b.kind === "text") {
|
|
5492
|
-
const txt = textBuffers[b.idx] ?? "";
|
|
5493
|
-
if (txt) content.push({ type: "text", text: txt });
|
|
5494
|
-
} else {
|
|
5495
|
-
const tb = tools.get(b.id);
|
|
5496
|
-
if (tb) {
|
|
5497
|
-
content.push({
|
|
5498
|
-
type: "tool_use",
|
|
5499
|
-
id: b.id,
|
|
5500
|
-
name: tb.name,
|
|
5501
|
-
input: tb.input ?? {}
|
|
5502
|
-
});
|
|
5503
|
-
}
|
|
5504
|
-
}
|
|
7435
|
+
async checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations) {
|
|
7436
|
+
if (hasHardLimit && iterationIndex >= limit) {
|
|
7437
|
+
const extendBy = await requestLimitExtension({
|
|
7438
|
+
events: this.events,
|
|
7439
|
+
currentIterations,
|
|
7440
|
+
currentLimit: this.maxIterations,
|
|
7441
|
+
autoExtend: this.autoExtendLimit
|
|
7442
|
+
});
|
|
7443
|
+
if (extendBy > 0) {
|
|
7444
|
+
const newLimit = limit + extendBy;
|
|
7445
|
+
this.logger.info(`Iteration limit extended by ${extendBy} (new limit: ${newLimit})`);
|
|
7446
|
+
return { limit: newLimit };
|
|
5505
7447
|
}
|
|
5506
|
-
|
|
5507
|
-
|
|
7448
|
+
return { limit, exit: { status: "max_iterations", iterations: currentIterations } };
|
|
7449
|
+
}
|
|
7450
|
+
return { limit };
|
|
7451
|
+
}
|
|
7452
|
+
/**
|
|
7453
|
+
* Build request and run through request pipeline.
|
|
7454
|
+
*/
|
|
7455
|
+
async buildAndRunRequestPipeline(opts) {
|
|
7456
|
+
const baseReq = {
|
|
7457
|
+
model: opts.model ?? this.ctx.model,
|
|
7458
|
+
system: this.ctx.systemPrompt,
|
|
7459
|
+
messages: this.ctx.messages,
|
|
7460
|
+
tools: this.tools.list(),
|
|
7461
|
+
maxTokens: 8192
|
|
5508
7462
|
};
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
const blockIndex = ev.index;
|
|
5536
|
-
if (blockIndex !== void 0) {
|
|
5537
|
-
openContentBlocks.delete(String(blockIndex));
|
|
5538
|
-
}
|
|
5539
|
-
break;
|
|
5540
|
-
}
|
|
5541
|
-
case "text_delta":
|
|
5542
|
-
if (currentTextIndex === -1) {
|
|
5543
|
-
currentTextIndex = textBuffers.length;
|
|
5544
|
-
textBuffers.push("");
|
|
5545
|
-
blockOrder.push({ kind: "text", idx: currentTextIndex });
|
|
5546
|
-
}
|
|
5547
|
-
textBuffers[currentTextIndex] = (textBuffers[currentTextIndex] ?? "") + ev.text;
|
|
5548
|
-
this.events.emit("provider.text_delta", { ctx: this.ctx, text: ev.text });
|
|
5549
|
-
break;
|
|
5550
|
-
case "tool_use_start":
|
|
5551
|
-
currentTextIndex = -1;
|
|
5552
|
-
tools.set(ev.id, { name: ev.name, partial: "" });
|
|
5553
|
-
blockOrder.push({ kind: "tool", id: ev.id });
|
|
5554
|
-
this.events.emit("provider.tool_use_start", {
|
|
5555
|
-
ctx: this.ctx,
|
|
5556
|
-
id: ev.id,
|
|
5557
|
-
name: ev.name
|
|
5558
|
-
});
|
|
5559
|
-
break;
|
|
5560
|
-
case "tool_use_input_delta": {
|
|
5561
|
-
const t2 = tools.get(ev.id);
|
|
5562
|
-
if (t2) t2.partial += ev.partial;
|
|
5563
|
-
break;
|
|
5564
|
-
}
|
|
5565
|
-
case "tool_use_stop": {
|
|
5566
|
-
const t2 = tools.get(ev.id);
|
|
5567
|
-
if (t2) {
|
|
5568
|
-
t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
|
|
5569
|
-
}
|
|
5570
|
-
currentTextIndex = -1;
|
|
5571
|
-
this.events.emit("provider.tool_use_stop", { ctx: this.ctx, id: ev.id });
|
|
5572
|
-
break;
|
|
5573
|
-
}
|
|
5574
|
-
case "message_stop":
|
|
5575
|
-
stopReason = ev.stopReason;
|
|
5576
|
-
usage = ev.usage;
|
|
5577
|
-
break;
|
|
5578
|
-
}
|
|
5579
|
-
}
|
|
5580
|
-
} catch (err) {
|
|
5581
|
-
if (signal.aborted) {
|
|
5582
|
-
stopReason = "max_tokens";
|
|
5583
|
-
return buildResponse();
|
|
5584
|
-
}
|
|
5585
|
-
throw err;
|
|
5586
|
-
} finally {
|
|
5587
|
-
try {
|
|
5588
|
-
await iter.return?.();
|
|
5589
|
-
} catch {
|
|
7463
|
+
return this.pipelines.request.run(baseReq);
|
|
7464
|
+
}
|
|
7465
|
+
/**
|
|
7466
|
+
* Process the provider response: run response pipeline, emit events,
|
|
7467
|
+
* update session, render text, handle abort.
|
|
7468
|
+
*/
|
|
7469
|
+
async processResponse(res, req) {
|
|
7470
|
+
await this.pipelines.response.run(res);
|
|
7471
|
+
this.events.emit("provider.response", {
|
|
7472
|
+
ctx: this.ctx,
|
|
7473
|
+
usage: res.usage,
|
|
7474
|
+
stopReason: res.stopReason
|
|
7475
|
+
});
|
|
7476
|
+
this.ctx.tokenCounter.account(res.usage, req.model);
|
|
7477
|
+
this.ctx.state.appendMessage({ role: "assistant", content: res.content });
|
|
7478
|
+
await this.ctx.session.append({
|
|
7479
|
+
type: "llm_response",
|
|
7480
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7481
|
+
content: res.content,
|
|
7482
|
+
stopReason: res.stopReason,
|
|
7483
|
+
usage: res.usage
|
|
7484
|
+
});
|
|
7485
|
+
if (this.ctx.signal.aborted) {
|
|
7486
|
+
let finalText2 = "";
|
|
7487
|
+
for (const block of res.content) {
|
|
7488
|
+
if (isTextBlock(block)) finalText2 += block.text;
|
|
5590
7489
|
}
|
|
7490
|
+
return { finalText: finalText2, aborted: true, done: false };
|
|
5591
7491
|
}
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
if (
|
|
5599
|
-
return await this.streamProviderToResponse(provider, req, signal);
|
|
5600
|
-
}
|
|
5601
|
-
return await provider.complete(req, { signal });
|
|
5602
|
-
} catch (err) {
|
|
5603
|
-
if (signal.aborted) throw err;
|
|
5604
|
-
const isProviderErr = err instanceof ProviderError;
|
|
5605
|
-
const errAsErr = err instanceof Error ? err : new Error(String(err));
|
|
5606
|
-
const canRetry = this.retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
|
|
5607
|
-
const description = isProviderErr ? err.describe() : errAsErr.message;
|
|
5608
|
-
if (!canRetry) {
|
|
5609
|
-
if (isProviderErr) {
|
|
5610
|
-
this.events.emit("provider.error", {
|
|
5611
|
-
providerId: err.providerId,
|
|
5612
|
-
status: err.status,
|
|
5613
|
-
description,
|
|
5614
|
-
retryable: false
|
|
5615
|
-
});
|
|
5616
|
-
}
|
|
5617
|
-
throw err;
|
|
5618
|
-
}
|
|
5619
|
-
const delay = Math.round(this.retry.delayMs(attempt));
|
|
5620
|
-
const attemptNum = attempt + 1;
|
|
5621
|
-
this.logger.warn(
|
|
5622
|
-
`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`
|
|
5623
|
-
);
|
|
5624
|
-
if (isProviderErr) {
|
|
5625
|
-
this.events.emit("provider.retry", {
|
|
5626
|
-
providerId: err.providerId,
|
|
5627
|
-
attempt: attemptNum,
|
|
5628
|
-
delayMs: delay,
|
|
5629
|
-
status: err.status,
|
|
5630
|
-
description
|
|
5631
|
-
});
|
|
5632
|
-
}
|
|
5633
|
-
await new Promise((resolve4, reject) => {
|
|
5634
|
-
const t2 = setTimeout(resolve4, delay);
|
|
5635
|
-
const onAbort = () => {
|
|
5636
|
-
clearTimeout(t2);
|
|
5637
|
-
reject(new Error("aborted"));
|
|
5638
|
-
};
|
|
5639
|
-
if (signal.aborted) onAbort();
|
|
5640
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5641
|
-
});
|
|
5642
|
-
attempt++;
|
|
7492
|
+
let finalText = "";
|
|
7493
|
+
const streamed = this.ctx.provider.capabilities.streaming;
|
|
7494
|
+
for (const block of res.content) {
|
|
7495
|
+
if (isTextBlock(block)) {
|
|
7496
|
+
const rendered = await this.pipelines.assistantOutput.run(block);
|
|
7497
|
+
finalText += rendered.text;
|
|
7498
|
+
if (!streamed) this.renderer?.write(rendered);
|
|
5643
7499
|
}
|
|
5644
7500
|
}
|
|
7501
|
+
return { finalText, aborted: false, done: false };
|
|
5645
7502
|
}
|
|
5646
|
-
|
|
7503
|
+
/**
|
|
7504
|
+
* Execute tools and append tool results to context.
|
|
7505
|
+
* When a tool returns `tool_confirm_pending` (no confirmAwaiter set),
|
|
7506
|
+
* we pause and emit `tool.confirm_needed`. The run is blocked until
|
|
7507
|
+
* the event listener resolves the confirmation, then we re-run the
|
|
7508
|
+
* single tool.
|
|
7509
|
+
*/
|
|
7510
|
+
async executeTools(toolUses) {
|
|
5647
7511
|
const { outputs } = await this.toolExecutor.executeBatch(
|
|
5648
7512
|
toolUses,
|
|
5649
7513
|
this.ctx,
|
|
@@ -5651,6 +7515,39 @@ var Agent = class {
|
|
|
5651
7515
|
);
|
|
5652
7516
|
const useById = new Map(toolUses.map((u) => [u.id, u]));
|
|
5653
7517
|
for (const { result, tool, durationMs } of outputs) {
|
|
7518
|
+
if (result.type === "tool_confirm_pending") {
|
|
7519
|
+
const decision = await this.waitForConfirm({
|
|
7520
|
+
tool,
|
|
7521
|
+
input: result.input,
|
|
7522
|
+
toolUseId: result.toolUseId,
|
|
7523
|
+
suggestedPattern: result.suggestedPattern
|
|
7524
|
+
});
|
|
7525
|
+
const reRunResult = await this.executeSingleWithDecision(
|
|
7526
|
+
tool,
|
|
7527
|
+
{ id: result.toolUseId, name: tool.name, input: result.input },
|
|
7528
|
+
decision
|
|
7529
|
+
);
|
|
7530
|
+
const use2 = useById.get(reRunResult.result.tool_use_id);
|
|
7531
|
+
if (use2) {
|
|
7532
|
+
await this.pipelines.toolCall.run({ toolUse: use2, result: reRunResult.result, ctx: this.ctx, tool });
|
|
7533
|
+
await this.ctx.session.append({
|
|
7534
|
+
type: "tool_result",
|
|
7535
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7536
|
+
id: reRunResult.result.tool_use_id,
|
|
7537
|
+
content: reRunResult.result.content,
|
|
7538
|
+
isError: !!reRunResult.result.is_error
|
|
7539
|
+
});
|
|
7540
|
+
this.events.emit("tool.executed", {
|
|
7541
|
+
name: tool.name,
|
|
7542
|
+
durationMs: reRunResult.durationMs,
|
|
7543
|
+
ok: !reRunResult.result.is_error,
|
|
7544
|
+
input: result.input,
|
|
7545
|
+
output: truncateForEvent(reRunResult.result.content)
|
|
7546
|
+
});
|
|
7547
|
+
}
|
|
7548
|
+
this.ctx.state.appendMessage({ role: "user", content: [reRunResult.result] });
|
|
7549
|
+
continue;
|
|
7550
|
+
}
|
|
5654
7551
|
const use = useById.get(result.tool_use_id);
|
|
5655
7552
|
if (!use) continue;
|
|
5656
7553
|
await this.pipelines.toolCall.run({
|
|
@@ -5674,25 +7571,132 @@ var Agent = class {
|
|
|
5674
7571
|
output: truncateForEvent(result.content)
|
|
5675
7572
|
});
|
|
5676
7573
|
}
|
|
5677
|
-
|
|
7574
|
+
this.ctx.state.appendMessage({ role: "user", content: outputs.map((o) => o.result) });
|
|
7575
|
+
}
|
|
7576
|
+
waitForConfirm(info) {
|
|
7577
|
+
return new Promise((resolve4) => {
|
|
7578
|
+
this.events.emit("tool.confirm_needed", {
|
|
7579
|
+
tool: info.tool,
|
|
7580
|
+
input: info.input,
|
|
7581
|
+
toolUseId: info.toolUseId,
|
|
7582
|
+
suggestedPattern: info.suggestedPattern,
|
|
7583
|
+
resolve: resolve4
|
|
7584
|
+
});
|
|
7585
|
+
});
|
|
7586
|
+
}
|
|
7587
|
+
async executeSingleWithDecision(tool, use, decision) {
|
|
7588
|
+
const start = Date.now();
|
|
7589
|
+
if (decision === "no" || decision === "deny") {
|
|
7590
|
+
return {
|
|
7591
|
+
result: { type: "tool_result", tool_use_id: use.id, content: `Tool "${tool.name}" denied by user.`, is_error: true },
|
|
7592
|
+
durationMs: Date.now() - start
|
|
7593
|
+
};
|
|
7594
|
+
}
|
|
7595
|
+
try {
|
|
7596
|
+
const result = await this.toolExecutor.executeTool(tool, use, this.ctx, this.perIterationOutputCapBytes);
|
|
7597
|
+
return { result, durationMs: Date.now() - start };
|
|
7598
|
+
} catch (err) {
|
|
7599
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7600
|
+
return {
|
|
7601
|
+
result: { type: "tool_result", tool_use_id: use.id, content: `Tool "${tool.name}" threw: ${msg}`, is_error: true },
|
|
7602
|
+
durationMs: Date.now() - start
|
|
7603
|
+
};
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
/**
|
|
7607
|
+
* Run context window pipeline if compactor is present.
|
|
7608
|
+
*/
|
|
7609
|
+
async compactContextIfNeeded() {
|
|
7610
|
+
if (this.compactor) {
|
|
7611
|
+
await this.pipelines.contextWindow.run(this.ctx);
|
|
7612
|
+
}
|
|
5678
7613
|
}
|
|
5679
7614
|
};
|
|
5680
7615
|
function toError(err) {
|
|
5681
7616
|
return err instanceof Error ? err : new Error(String(err));
|
|
5682
7617
|
}
|
|
5683
|
-
function safeJsonOrRaw(s) {
|
|
5684
|
-
if (!s) return {};
|
|
5685
|
-
try {
|
|
5686
|
-
return JSON.parse(s);
|
|
5687
|
-
} catch {
|
|
5688
|
-
return { _raw: s };
|
|
5689
|
-
}
|
|
5690
|
-
}
|
|
5691
7618
|
function truncateForEvent(content, max = 400) {
|
|
5692
7619
|
if (!content) return "";
|
|
5693
7620
|
return content.length <= max ? content : `${content.slice(0, max - 1)}\u2026`;
|
|
5694
7621
|
}
|
|
5695
7622
|
|
|
7623
|
+
// src/core/conversation-state.ts
|
|
7624
|
+
var ConversationState = class {
|
|
7625
|
+
ctx;
|
|
7626
|
+
listeners = /* @__PURE__ */ new Set();
|
|
7627
|
+
constructor(ctx) {
|
|
7628
|
+
this.ctx = ctx;
|
|
7629
|
+
}
|
|
7630
|
+
// ─── Read API ───────────────────────────────────────────────────────
|
|
7631
|
+
get messages() {
|
|
7632
|
+
return this.ctx.messages;
|
|
7633
|
+
}
|
|
7634
|
+
get todos() {
|
|
7635
|
+
return this.ctx.todos;
|
|
7636
|
+
}
|
|
7637
|
+
get meta() {
|
|
7638
|
+
return this.ctx.meta;
|
|
7639
|
+
}
|
|
7640
|
+
/**
|
|
7641
|
+
* Cheap immutable snapshot. Useful for tests and for compaction passes
|
|
7642
|
+
* that need a stable view across an async boundary.
|
|
7643
|
+
*/
|
|
7644
|
+
snapshot() {
|
|
7645
|
+
return {
|
|
7646
|
+
messages: [...this.ctx.messages],
|
|
7647
|
+
todos: [...this.ctx.todos],
|
|
7648
|
+
meta: { ...this.ctx.meta }
|
|
7649
|
+
};
|
|
7650
|
+
}
|
|
7651
|
+
// ─── Write API (preferred — fires onChange) ─────────────────────────
|
|
7652
|
+
appendMessage(message) {
|
|
7653
|
+
this.ctx.messages.push(message);
|
|
7654
|
+
this.emit({ kind: "message_appended", message });
|
|
7655
|
+
}
|
|
7656
|
+
replaceMessages(messages) {
|
|
7657
|
+
this.ctx.messages.length = 0;
|
|
7658
|
+
this.ctx.messages.push(...messages);
|
|
7659
|
+
this.emit({ kind: "messages_replaced", messages: [...messages] });
|
|
7660
|
+
}
|
|
7661
|
+
replaceTodos(todos) {
|
|
7662
|
+
this.ctx.todos.length = 0;
|
|
7663
|
+
this.ctx.todos.push(...todos);
|
|
7664
|
+
this.emit({ kind: "todos_replaced", todos: [...todos] });
|
|
7665
|
+
}
|
|
7666
|
+
setMeta(key, value) {
|
|
7667
|
+
this.ctx.meta[key] = value;
|
|
7668
|
+
this.emit({ kind: "meta_set", key, value });
|
|
7669
|
+
}
|
|
7670
|
+
deleteMeta(key) {
|
|
7671
|
+
if (!(key in this.ctx.meta)) return;
|
|
7672
|
+
delete this.ctx.meta[key];
|
|
7673
|
+
this.emit({ kind: "meta_deleted", key });
|
|
7674
|
+
}
|
|
7675
|
+
// ─── Subscription ───────────────────────────────────────────────────
|
|
7676
|
+
/**
|
|
7677
|
+
* Subscribe to mutations that go through this wrapper. Note: mutations
|
|
7678
|
+
* that bypass the wrapper (e.g. `ctx.messages.push(...)` directly) are
|
|
7679
|
+
* NOT observed — by design during migration, since we don't want to
|
|
7680
|
+
* monkey-patch arrays. Migrating call sites to use this API is the
|
|
7681
|
+
* dev-plan #1 work.
|
|
7682
|
+
*/
|
|
7683
|
+
onChange(listener) {
|
|
7684
|
+
this.listeners.add(listener);
|
|
7685
|
+
return () => this.listeners.delete(listener);
|
|
7686
|
+
}
|
|
7687
|
+
emit(change) {
|
|
7688
|
+
for (const h of this.listeners) {
|
|
7689
|
+
try {
|
|
7690
|
+
h(change, this);
|
|
7691
|
+
} catch {
|
|
7692
|
+
}
|
|
7693
|
+
}
|
|
7694
|
+
}
|
|
7695
|
+
};
|
|
7696
|
+
function wrapAsState(ctx) {
|
|
7697
|
+
return new ConversationState(ctx);
|
|
7698
|
+
}
|
|
7699
|
+
|
|
5696
7700
|
// src/core/context.ts
|
|
5697
7701
|
var Context = class {
|
|
5698
7702
|
messages = [];
|
|
@@ -5720,11 +7724,29 @@ var Context = class {
|
|
|
5720
7724
|
this.model = init.model;
|
|
5721
7725
|
this.tools = init.tools ?? [];
|
|
5722
7726
|
}
|
|
7727
|
+
/**
|
|
7728
|
+
* Observable wrapper over the mutable conversation state. Lazy so
|
|
7729
|
+
* subsystems that don't subscribe pay nothing. Mutations made directly
|
|
7730
|
+
* on `ctx.messages` / `ctx.todos` are still visible through this
|
|
7731
|
+
* wrapper's read API (it holds a reference, not a copy) but only
|
|
7732
|
+
* mutations that go through `state.appendMessage()` etc. fire
|
|
7733
|
+
* `onChange`. New code should prefer the wrapper API.
|
|
7734
|
+
*/
|
|
7735
|
+
_state = null;
|
|
7736
|
+
get state() {
|
|
7737
|
+
if (!this._state) this._state = new ConversationState(this);
|
|
7738
|
+
return this._state;
|
|
7739
|
+
}
|
|
5723
7740
|
/**
|
|
5724
7741
|
* Register a teardown hook tied to the current run's abort signal. The
|
|
5725
7742
|
* hook fires when the run aborts OR ends normally — Agent.run wires
|
|
5726
7743
|
* this through a RunController. When no run is active the hook fires
|
|
5727
7744
|
* immediately so callers don't leak resources.
|
|
7745
|
+
*
|
|
7746
|
+
* **Scope:** these hooks fire on the **whole agent run's** abort, not on
|
|
7747
|
+
* an individual tool call. For per-tool teardown of resources owned by
|
|
7748
|
+
* the tool author (child processes, handles), prefer `Tool.cleanup` —
|
|
7749
|
+
* see its JSDoc for the full rule.
|
|
5728
7750
|
*/
|
|
5729
7751
|
abortHooks = /* @__PURE__ */ new Set();
|
|
5730
7752
|
registerAbortHook(fn) {
|
|
@@ -5756,6 +7778,21 @@ var Context = class {
|
|
|
5756
7778
|
}
|
|
5757
7779
|
};
|
|
5758
7780
|
|
|
7781
|
+
// src/core/run-env.ts
|
|
7782
|
+
function extractRunEnv(ctx) {
|
|
7783
|
+
return Object.freeze({
|
|
7784
|
+
provider: ctx.provider,
|
|
7785
|
+
session: ctx.session,
|
|
7786
|
+
signal: ctx.signal,
|
|
7787
|
+
tokenCounter: ctx.tokenCounter,
|
|
7788
|
+
cwd: ctx.cwd,
|
|
7789
|
+
projectRoot: ctx.projectRoot,
|
|
7790
|
+
model: ctx.model,
|
|
7791
|
+
systemPrompt: ctx.systemPrompt,
|
|
7792
|
+
tools: ctx.tools
|
|
7793
|
+
});
|
|
7794
|
+
}
|
|
7795
|
+
|
|
5759
7796
|
// src/core/input-builder.ts
|
|
5760
7797
|
var InputBuilder = class {
|
|
5761
7798
|
store;
|
|
@@ -6061,10 +8098,25 @@ var ToolRegistry = class {
|
|
|
6061
8098
|
tools = /* @__PURE__ */ new Map();
|
|
6062
8099
|
register(tool, owner = "core") {
|
|
6063
8100
|
if (this.tools.has(tool.name)) {
|
|
6064
|
-
throw new
|
|
8101
|
+
throw new WrongStackError({
|
|
8102
|
+
message: `Tool "${tool.name}" already registered`,
|
|
8103
|
+
code: "REGISTRY_DUPLICATE",
|
|
8104
|
+
subsystem: "container",
|
|
8105
|
+
context: { tool: tool.name }
|
|
8106
|
+
});
|
|
6065
8107
|
}
|
|
6066
8108
|
this.tools.set(tool.name, { tool, owner });
|
|
6067
8109
|
}
|
|
8110
|
+
/**
|
|
8111
|
+
* Attempt to register a tool. Returns true if successful, false if a tool
|
|
8112
|
+
* with the same name is already registered. Useful in multi-agent or plugin
|
|
8113
|
+
* scenarios where duplicate registration may be intentional.
|
|
8114
|
+
*/
|
|
8115
|
+
tryRegister(tool, owner = "core") {
|
|
8116
|
+
if (this.tools.has(tool.name)) return false;
|
|
8117
|
+
this.tools.set(tool.name, { tool, owner });
|
|
8118
|
+
return true;
|
|
8119
|
+
}
|
|
6068
8120
|
/**
|
|
6069
8121
|
* Register a tool as a default. If the tool name is already registered,
|
|
6070
8122
|
* this is a no-op — the existing registration (from core or another
|
|
@@ -6083,7 +8135,12 @@ var ToolRegistry = class {
|
|
|
6083
8135
|
*/
|
|
6084
8136
|
override(name, tool, owner = "core") {
|
|
6085
8137
|
if (!this.tools.has(name)) {
|
|
6086
|
-
throw new
|
|
8138
|
+
throw new WrongStackError({
|
|
8139
|
+
message: `Tool "${name}" not registered; cannot override`,
|
|
8140
|
+
code: "REGISTRY_NOT_FOUND",
|
|
8141
|
+
subsystem: "container",
|
|
8142
|
+
context: { tool: name }
|
|
8143
|
+
});
|
|
6087
8144
|
}
|
|
6088
8145
|
this.tools.set(name, { tool, owner });
|
|
6089
8146
|
}
|
|
@@ -6107,9 +8164,25 @@ var ToolRegistry = class {
|
|
|
6107
8164
|
// src/registry/provider-registry.ts
|
|
6108
8165
|
var ProviderRegistry = class {
|
|
6109
8166
|
factories = /* @__PURE__ */ new Map();
|
|
8167
|
+
/**
|
|
8168
|
+
* Register a provider factory. If a factory with the same type already
|
|
8169
|
+
* exists, it is replaced. Use this for both initial registration and
|
|
8170
|
+
* runtime overrides (e.g. from plugins or CLI flags).
|
|
8171
|
+
*/
|
|
6110
8172
|
register(f) {
|
|
6111
8173
|
this.factories.set(f.type, f);
|
|
6112
8174
|
}
|
|
8175
|
+
/**
|
|
8176
|
+
* Override an existing factory. Throws if no factory is registered
|
|
8177
|
+
* for the given type. Use this to safely replace a provider at runtime
|
|
8178
|
+
* (e.g. in tests or when a plugin provides a custom implementation).
|
|
8179
|
+
*/
|
|
8180
|
+
override(type, f) {
|
|
8181
|
+
if (!this.factories.has(type)) {
|
|
8182
|
+
throw new Error(`Provider type "${type}" not registered; cannot override`);
|
|
8183
|
+
}
|
|
8184
|
+
this.factories.set(type, f);
|
|
8185
|
+
}
|
|
6113
8186
|
has(type) {
|
|
6114
8187
|
return this.factories.has(type);
|
|
6115
8188
|
}
|
|
@@ -6319,20 +8392,23 @@ function parseSemver(v) {
|
|
|
6319
8392
|
const parts = v.replace(/^[^0-9]*/, "").split(".").map((s) => Number.parseInt(s, 10) || 0);
|
|
6320
8393
|
return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
|
|
6321
8394
|
}
|
|
6322
|
-
function satisfies(range,
|
|
6323
|
-
const [
|
|
8395
|
+
function satisfies(range, version) {
|
|
8396
|
+
const [vMaj, vMin, vPatch] = parseSemver(version);
|
|
6324
8397
|
const trimmed = range.trim();
|
|
6325
8398
|
const op = trimmed.startsWith("^") ? "^" : trimmed.startsWith("~") ? "~" : "=";
|
|
6326
8399
|
const ver = trimmed.replace(/^[\^~=]/, "");
|
|
6327
8400
|
const [rMaj, rMin, rPatch] = parseSemver(ver);
|
|
6328
8401
|
if (op === "^") {
|
|
6329
|
-
if (rMaj === 0) return
|
|
6330
|
-
return
|
|
8402
|
+
if (rMaj === 0) return vMaj === 0 && vMin === rMin && vPatch >= rPatch;
|
|
8403
|
+
return vMaj === rMaj && (vMin > rMin || vMin === rMin && vPatch >= rPatch);
|
|
6331
8404
|
}
|
|
6332
8405
|
if (op === "~") {
|
|
6333
|
-
return
|
|
8406
|
+
return vMaj === rMaj && vMin === rMin && vPatch >= rPatch;
|
|
6334
8407
|
}
|
|
6335
|
-
return
|
|
8408
|
+
return vMaj === rMaj && vMin === rMin && vPatch === rPatch;
|
|
8409
|
+
}
|
|
8410
|
+
function normalizeDep(d) {
|
|
8411
|
+
return typeof d === "string" ? { name: d } : d;
|
|
6336
8412
|
}
|
|
6337
8413
|
function topoSort(plugins) {
|
|
6338
8414
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -6343,19 +8419,48 @@ function topoSort(plugins) {
|
|
|
6343
8419
|
const visit = (p, stack) => {
|
|
6344
8420
|
if (visited.has(p.name)) return;
|
|
6345
8421
|
if (visiting.has(p.name)) {
|
|
6346
|
-
throw new
|
|
8422
|
+
throw new PluginError({
|
|
8423
|
+
message: `Plugin dependency cycle: ${[...stack, p.name].join(" -> ")}`,
|
|
8424
|
+
code: "PLUGIN_LOAD_FAILED",
|
|
8425
|
+
pluginName: p.name
|
|
8426
|
+
});
|
|
6347
8427
|
}
|
|
6348
8428
|
visiting.add(p.name);
|
|
6349
|
-
for (const
|
|
6350
|
-
const
|
|
8429
|
+
for (const raw of p.dependsOn ?? []) {
|
|
8430
|
+
const dep = normalizeDep(raw);
|
|
8431
|
+
const d = map.get(dep.name);
|
|
6351
8432
|
if (!d) {
|
|
6352
|
-
throw new
|
|
8433
|
+
throw new PluginError({
|
|
8434
|
+
message: `Plugin "${p.name}" depends on missing plugin "${dep.name}"`,
|
|
8435
|
+
code: "PLUGIN_MISSING_DEPENDENCY",
|
|
8436
|
+
pluginName: p.name,
|
|
8437
|
+
context: { dependency: dep.name }
|
|
8438
|
+
});
|
|
8439
|
+
}
|
|
8440
|
+
if (dep.version && d.version && !satisfies(dep.version, d.version)) {
|
|
8441
|
+
throw new PluginError({
|
|
8442
|
+
message: `Plugin "${p.name}" requires "${dep.name}@${dep.version}", found ${d.version}`,
|
|
8443
|
+
code: "PLUGIN_LOAD_FAILED",
|
|
8444
|
+
pluginName: p.name,
|
|
8445
|
+
context: { dependency: dep.name, required: dep.version, found: d.version }
|
|
8446
|
+
});
|
|
6353
8447
|
}
|
|
6354
8448
|
visit(d, [...stack, p.name]);
|
|
6355
8449
|
}
|
|
6356
|
-
for (const
|
|
6357
|
-
const
|
|
6358
|
-
|
|
8450
|
+
for (const raw of p.optionalDeps ?? []) {
|
|
8451
|
+
const dep = normalizeDep(raw);
|
|
8452
|
+
const d = map.get(dep.name);
|
|
8453
|
+
if (d) {
|
|
8454
|
+
if (dep.version && d.version && !satisfies(dep.version, d.version)) {
|
|
8455
|
+
throw new PluginError({
|
|
8456
|
+
message: `Plugin "${p.name}" optional dep "${dep.name}@${dep.version}" found ${d.version}`,
|
|
8457
|
+
code: "PLUGIN_LOAD_FAILED",
|
|
8458
|
+
pluginName: p.name,
|
|
8459
|
+
context: { dependency: dep.name, required: dep.version, found: d.version }
|
|
8460
|
+
});
|
|
8461
|
+
}
|
|
8462
|
+
visit(d, [...stack, p.name]);
|
|
8463
|
+
}
|
|
6359
8464
|
}
|
|
6360
8465
|
visiting.delete(p.name);
|
|
6361
8466
|
visited.add(p.name);
|
|
@@ -6372,7 +8477,12 @@ async function loadPlugins(plugins, opts) {
|
|
|
6372
8477
|
for (const p of plugins) {
|
|
6373
8478
|
for (const c of p.conflictsWith ?? []) {
|
|
6374
8479
|
if (names.has(c)) {
|
|
6375
|
-
throw new
|
|
8480
|
+
throw new PluginError({
|
|
8481
|
+
message: `Plugin "${p.name}" conflicts with loaded plugin "${c}"`,
|
|
8482
|
+
code: "PLUGIN_LOAD_FAILED",
|
|
8483
|
+
pluginName: p.name,
|
|
8484
|
+
context: { conflictsWith: c }
|
|
8485
|
+
});
|
|
6376
8486
|
}
|
|
6377
8487
|
}
|
|
6378
8488
|
}
|
|
@@ -6385,15 +8495,38 @@ async function loadPlugins(plugins, opts) {
|
|
|
6385
8495
|
}
|
|
6386
8496
|
for (const plugin of sorted) {
|
|
6387
8497
|
if (!satisfies(plugin.apiVersion, kernelVersion)) {
|
|
6388
|
-
const err = new
|
|
6389
|
-
`Plugin "${plugin.name}" requires apiVersion ${plugin.apiVersion}; kernel is ${kernelVersion}
|
|
6390
|
-
|
|
8498
|
+
const err = new PluginError({
|
|
8499
|
+
message: `Plugin "${plugin.name}" requires apiVersion ${plugin.apiVersion}; kernel is ${kernelVersion}`,
|
|
8500
|
+
code: "PLUGIN_API_MISMATCH",
|
|
8501
|
+
pluginName: plugin.name,
|
|
8502
|
+
context: { required: plugin.apiVersion, kernel: kernelVersion }
|
|
8503
|
+
});
|
|
6391
8504
|
opts.log.error(err.message);
|
|
6392
8505
|
failed.push({ plugin, err });
|
|
6393
8506
|
continue;
|
|
6394
8507
|
}
|
|
8508
|
+
if (plugin.configSchema && opts.pluginOptions) {
|
|
8509
|
+
const pluginOpts = opts.pluginOptions[plugin.name];
|
|
8510
|
+
if (pluginOpts !== void 0) {
|
|
8511
|
+
const result = validateAgainstSchema(pluginOpts, plugin.configSchema);
|
|
8512
|
+
if (!result.ok) {
|
|
8513
|
+
const firstErr = result.errors[0];
|
|
8514
|
+
const detail = firstErr ? `${firstErr.path}: ${firstErr.message}` : "config invalid";
|
|
8515
|
+
const err = new PluginError({
|
|
8516
|
+
message: `Plugin "${plugin.name}" config invalid \u2014 ${detail}`,
|
|
8517
|
+
code: "PLUGIN_LOAD_FAILED",
|
|
8518
|
+
pluginName: plugin.name,
|
|
8519
|
+
context: { errors: result.errors }
|
|
8520
|
+
});
|
|
8521
|
+
opts.log.error(err.message);
|
|
8522
|
+
failed.push({ plugin, err });
|
|
8523
|
+
continue;
|
|
8524
|
+
}
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
6395
8527
|
try {
|
|
6396
|
-
const
|
|
8528
|
+
const rawApi = opts.apiFactory(plugin);
|
|
8529
|
+
const api = plugin.capabilities ? wrapApiForCapabilityCheck(plugin, rawApi, opts.log) : rawApi;
|
|
6397
8530
|
await plugin.setup(api);
|
|
6398
8531
|
loaded.push(plugin);
|
|
6399
8532
|
opts.log.info(`Plugin "${plugin.name}" loaded`);
|
|
@@ -6404,7 +8537,88 @@ async function loadPlugins(plugins, opts) {
|
|
|
6404
8537
|
}
|
|
6405
8538
|
return { loaded, failed };
|
|
6406
8539
|
}
|
|
8540
|
+
async function unloadPlugins(loadedPlugins, opts) {
|
|
8541
|
+
const ordered = [...loadedPlugins].reverse();
|
|
8542
|
+
for (const plugin of ordered) {
|
|
8543
|
+
if (typeof plugin.teardown !== "function") continue;
|
|
8544
|
+
try {
|
|
8545
|
+
const api = opts.apiFactory(plugin);
|
|
8546
|
+
await plugin.teardown(api);
|
|
8547
|
+
opts.log.info(`Plugin "${plugin.name}" torn down`);
|
|
8548
|
+
} catch (err) {
|
|
8549
|
+
opts.log.error(`Plugin "${plugin.name}" teardown failed`, err);
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
function wrapApiForCapabilityCheck(plugin, api, log) {
|
|
8554
|
+
const caps = plugin.capabilities ?? {};
|
|
8555
|
+
const warn = (subsystem, detail) => {
|
|
8556
|
+
const msg = `Plugin "${plugin.name}" used ${subsystem} without declaring capabilities.${subsystem} \u2014 ${detail}`;
|
|
8557
|
+
if (typeof log.warn === "function") log.warn(msg);
|
|
8558
|
+
else log.error(msg);
|
|
8559
|
+
};
|
|
8560
|
+
const wrappedTools = caps.tools !== false ? api.tools : new Proxy(api.tools, {
|
|
8561
|
+
get(target, prop, receiver) {
|
|
8562
|
+
if (prop === "register") {
|
|
8563
|
+
return (t2) => {
|
|
8564
|
+
warn("tools", `register(${t2?.name ?? "<unknown>"})`);
|
|
8565
|
+
return target.register(t2);
|
|
8566
|
+
};
|
|
8567
|
+
}
|
|
8568
|
+
return Reflect.get(target, prop, receiver);
|
|
8569
|
+
}
|
|
8570
|
+
});
|
|
8571
|
+
const wrappedProviders = caps.providers !== false ? api.providers : new Proxy(api.providers, {
|
|
8572
|
+
get(target, prop, receiver) {
|
|
8573
|
+
if (prop === "register") {
|
|
8574
|
+
return (f) => {
|
|
8575
|
+
warn("providers", `register(${f?.type ?? "<unknown>"})`);
|
|
8576
|
+
return target.register(f);
|
|
8577
|
+
};
|
|
8578
|
+
}
|
|
8579
|
+
return Reflect.get(target, prop, receiver);
|
|
8580
|
+
}
|
|
8581
|
+
});
|
|
8582
|
+
const wrappedSlash = caps.slashCommands !== false ? api.slashCommands : new Proxy(api.slashCommands, {
|
|
8583
|
+
get(target, prop, receiver) {
|
|
8584
|
+
if (prop === "register") {
|
|
8585
|
+
return (c) => {
|
|
8586
|
+
warn("slashCommands", `register(${c?.name ?? "<unknown>"})`);
|
|
8587
|
+
return target.register(c);
|
|
8588
|
+
};
|
|
8589
|
+
}
|
|
8590
|
+
return Reflect.get(target, prop, receiver);
|
|
8591
|
+
}
|
|
8592
|
+
});
|
|
8593
|
+
const wrappedMcp = caps.mcp !== false ? api.mcp : new Proxy(api.mcp, {
|
|
8594
|
+
get(target, prop, receiver) {
|
|
8595
|
+
if (prop === "start") {
|
|
8596
|
+
return (cfg) => {
|
|
8597
|
+
warn("mcp", `start(${cfg?.name ?? "<unknown>"})`);
|
|
8598
|
+
return target.start(cfg);
|
|
8599
|
+
};
|
|
8600
|
+
}
|
|
8601
|
+
return Reflect.get(target, prop, receiver);
|
|
8602
|
+
}
|
|
8603
|
+
});
|
|
8604
|
+
return new Proxy(api, {
|
|
8605
|
+
get(target, prop, receiver) {
|
|
8606
|
+
switch (prop) {
|
|
8607
|
+
case "tools":
|
|
8608
|
+
return wrappedTools;
|
|
8609
|
+
case "providers":
|
|
8610
|
+
return wrappedProviders;
|
|
8611
|
+
case "slashCommands":
|
|
8612
|
+
return wrappedSlash;
|
|
8613
|
+
case "mcp":
|
|
8614
|
+
return wrappedMcp;
|
|
8615
|
+
default:
|
|
8616
|
+
return Reflect.get(target, prop, receiver);
|
|
8617
|
+
}
|
|
8618
|
+
}
|
|
8619
|
+
});
|
|
8620
|
+
}
|
|
6407
8621
|
|
|
6408
|
-
export { Agent, AutoCompactionMiddleware, AutonomousRunner, Container, Context, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_SPEC_TEMPLATE, DefaultAttachmentStore, DefaultConfigLoader, DefaultErrorHandler, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, DoneConditionChecker, ENCRYPTED_PREFIX, EventBus, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, Pipeline, ProviderError, ProviderRegistry, QueueStore, RecoveryLock, RunController, SelectiveCompactor, SlashCommandRegistry, SpecDrivenDev, SpecParser, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, ToolRegistry, asBlocks, asText, atomicWrite, classifyFamily, color, compileGlob, computeTaskProgress, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, findCriticalPath, isImageBlock, isTextBlock, isToolResultBlock, isToolUseBlock, loadPlugins, loadProjectModes, loadUserModes, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, resolveWstackPaths, rewriteConfigEncrypted, safeParse, safeStringify, sanitizeJsonString, stripAnsi, toStyle, topologicalSort, unifiedDiff };
|
|
8622
|
+
export { Agent, AgentError, AutoCompactionMiddleware, AutonomousRunner, BudgetExceededError, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_SPEC_TEMPLATE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, DoneConditionChecker, ENCRYPTED_PREFIX, EventBus, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, RecoveryLock, RunController, SelectiveCompactor, SessionError, SlashCommandRegistry, SpecDrivenDev, SpecParser, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, allServers, asBlocks, asText, atomicWrite, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, color, compileGlob, computeTaskProgress, context7Server, contextManagerTool, createContextManagerTool, createDefaultPipelines, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, encryptConfigSecrets, ensureDir, estimateTextTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, githubServer, googleMapsServer, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, loadPlugins, loadProjectModes, loadUserModes, makeAgentSubagentRunner, matchAny, matchGlob, migratePlaintextSecrets, normalizeToLf, projectHash, renderPrometheus, resolveWstackPaths, rewriteConfigEncrypted, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState };
|
|
6409
8623
|
//# sourceMappingURL=index.js.map
|
|
6410
8624
|
//# sourceMappingURL=index.js.map
|