@wrongstack/core 0.9.20 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/dist/{agent-bridge-Dti3KXGk.d.ts → agent-bridge-DBBGP5O4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-U-rs7kk7.d.ts → agent-subagent-runner-KwufTWwn.d.ts} +3 -3
- package/dist/{compactor-D7X96RLZ.d.ts → compactor-D1RHFRmF.d.ts} +1 -1
- package/dist/{config-CLXMDOSs.d.ts → config-CJXBka2r.d.ts} +38 -2
- package/dist/{context-zkZeILpr.d.ts → context-7u93AcGD.d.ts} +94 -0
- package/dist/coordination/index.d.ts +12 -12
- package/dist/coordination/index.js +130 -58
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-Uzla85H-.d.ts → default-config-DEXI4jsl.d.ts} +10 -1
- package/dist/defaults/index.d.ts +24 -24
- package/dist/defaults/index.js +611 -95
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-DH-9r-_C.d.ts → events-Bw1K5E6K.d.ts} +8 -1
- package/dist/execution/index.d.ts +18 -15
- package/dist/execution/index.js +309 -61
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/goal-store-BeRsj7YX.d.ts +188 -0
- package/dist/{index-CFO9QmJo.d.ts → index-DIt5TIaF.d.ts} +8 -8
- package/dist/{index-BIHJ4uII.d.ts → index-DgQWnZxP.d.ts} +5 -5
- package/dist/index.d.ts +56 -35
- package/dist/index.js +742 -203
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +57 -8
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DkESgh0G.d.ts → mcp-servers-BgINZzuo.d.ts} +3 -3
- package/dist/models/index.d.ts +3 -3
- package/dist/models/index.js +163 -10
- package/dist/models/index.js.map +1 -1
- package/dist/models-registry-gwMAo6E3.d.ts +86 -0
- package/dist/{multi-agent-DNp6lAzg.d.ts → multi-agent-C1O8EFzb.d.ts} +45 -4
- package/dist/{multi-agent-coordinator-CAhsegPz.d.ts → multi-agent-coordinator-DoVSBFNx.d.ts} +2 -2
- package/dist/{null-fleet-bus-Dnl19vmf.d.ts → null-fleet-bus-T-s3JaOt.d.ts} +8 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-CHiBL0DD.d.ts → path-resolver-BfyK-I7w.d.ts} +2 -2
- package/dist/{permission-H35s9Evv.d.ts → permission-B6sldrSp.d.ts} +1 -1
- package/dist/{permission-policy-CT-nRmTn.d.ts → permission-policy-CtNscWOA.d.ts} +10 -9
- package/dist/{plan-templates-Bs8iRwi6.d.ts → plan-templates-BWs6Je7j.d.ts} +8 -8
- package/dist/{provider-runner-BZdDrWrS.d.ts → provider-runner-BmPe2RuU.d.ts} +3 -3
- package/dist/{retry-policy-J9N_PM40.d.ts → retry-policy-KF18W4dg.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +130 -58
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +65 -4
- package/dist/security/index.js +67 -19
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-CFTh3Z6p.d.ts → selector-DmXxpFyM.d.ts} +1 -1
- package/dist/{session-reader-C7JJlxOw.d.ts → session-reader-CCSBfQmd.d.ts} +1 -1
- package/dist/storage/index.d.ts +7 -7
- package/dist/storage/index.js +125 -6
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-CneIxVip.d.ts → system-prompt-CM6zOhd2.d.ts} +19 -1
- package/dist/{tool-executor-flTuxsqO.d.ts → tool-executor-D7RxLtg4.d.ts} +4 -4
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +344 -14
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +23 -3
- package/dist/utils/index.js +66 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-BCgmTNlG.d.ts → wstack-paths-eMXnY1_X.d.ts} +8 -0
- package/package.json +1 -1
- package/dist/goal-store-C7jcumEh.d.ts +0 -96
- package/dist/models-registry-OG_30xqZ.d.ts +0 -46
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as Usage, d as Context, X as ToolProgressEvent, Q as Tool } from './context-
|
|
1
|
+
import { $ as Usage, d as Context, X as ToolProgressEvent, Q as Tool } from './context-7u93AcGD.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* EventBus — observe-only typed event bus.
|
|
@@ -612,6 +612,13 @@ declare class ScopedEventBus extends EventBus {
|
|
|
612
612
|
* Uses EventBus's public API directly to avoid triggering our own `on()`
|
|
613
613
|
* override (which would consume a key slot for the wrapper, then orphan
|
|
614
614
|
* our registration entry under a different key).
|
|
615
|
+
*
|
|
616
|
+
* When the wrapper fires, it cleans up BOTH the underlying EventBus
|
|
617
|
+
* listener AND the tracking entry — so `scopedListenerCount` returns to
|
|
618
|
+
* its pre-`once()` value without requiring the caller to invoke the
|
|
619
|
+
* returned unsubscribe. The returned `unsub` is still safe to call
|
|
620
|
+
* after auto-removal (its delete is a no-op and its off() finds
|
|
621
|
+
* nothing to remove).
|
|
615
622
|
*/
|
|
616
623
|
once<E extends EventName>(event: E, fn: Listener<E>): () => void;
|
|
617
624
|
/**
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
export { C as CompactorOptions, a as DefaultErrorHandler, b as DefaultRetryPolicy, H as HybridCompactor, T as ToolExecutor } from '../tool-executor-
|
|
2
|
-
import { m as Provider, d as Context } from '../context-
|
|
3
|
-
import { a as Compactor, C as CompactReport } from '../compactor-
|
|
4
|
-
import { M as MessageSelector } from '../selector-
|
|
5
|
-
import { E as EventBus } from '../events-
|
|
6
|
-
import { b as MiddlewareHandler } from '../system-prompt-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { A as AgentFactory } from '../agent-subagent-runner-
|
|
12
|
-
import { f as DispatchClassifier, d as DefaultMultiAgentCoordinator } from '../multi-agent-coordinator-
|
|
1
|
+
export { C as CompactorOptions, a as DefaultErrorHandler, b as DefaultRetryPolicy, H as HybridCompactor, T as ToolExecutor } from '../tool-executor-D7RxLtg4.js';
|
|
2
|
+
import { m as Provider, d as Context } from '../context-7u93AcGD.js';
|
|
3
|
+
import { a as Compactor, C as CompactReport } from '../compactor-D1RHFRmF.js';
|
|
4
|
+
import { M as MessageSelector } from '../selector-DmXxpFyM.js';
|
|
5
|
+
import { E as EventBus } from '../events-Bw1K5E6K.js';
|
|
6
|
+
import { b as MiddlewareHandler } from '../system-prompt-CM6zOhd2.js';
|
|
7
|
+
import { a as SessionEventBridge, J as JournalEntry } from '../goal-store-BeRsj7YX.js';
|
|
8
|
+
import { e as ContextWindowAggressiveOn, i as ContextWindowPolicy } from '../config-CJXBka2r.js';
|
|
9
|
+
import { c as Agent, R as RunResult, w as SystemPromptContributor } from '../index-DgQWnZxP.js';
|
|
10
|
+
import { D as DoneCondition } from '../multi-agent-C1O8EFzb.js';
|
|
11
|
+
import { A as AgentFactory } from '../agent-subagent-runner-KwufTWwn.js';
|
|
12
|
+
import { f as DispatchClassifier, d as DefaultMultiAgentCoordinator } from '../multi-agent-coordinator-DoVSBFNx.js';
|
|
13
13
|
import { a as SkillLoader, b as SkillManifest, S as SkillEntry } from '../skill-CxuWrsKK.js';
|
|
14
|
-
import { a as WstackPaths } from '../wstack-paths-
|
|
15
|
-
import '../retry-policy-
|
|
14
|
+
import { a as WstackPaths } from '../wstack-paths-eMXnY1_X.js';
|
|
15
|
+
import '../retry-policy-KF18W4dg.js';
|
|
16
16
|
import '../models-registry-BcYJDKLm.js';
|
|
17
17
|
import '../logger-DDd5C--Z.js';
|
|
18
18
|
import '../observability-BhnVLBLS.js';
|
|
19
19
|
import '../secret-scrubber-3MHDDAtm.js';
|
|
20
|
-
import '../permission-
|
|
20
|
+
import '../permission-B6sldrSp.js';
|
|
21
21
|
import 'node:events';
|
|
22
22
|
|
|
23
23
|
interface SkillLoaderOptions {
|
|
@@ -195,6 +195,8 @@ interface AutoCompactionOptions {
|
|
|
195
195
|
events?: EventBus;
|
|
196
196
|
failureMode?: CompactionFailureMode;
|
|
197
197
|
policyProvider?: (ctx: Context) => Pick<ContextWindowPolicy, 'thresholds' | 'aggressiveOn'> | null | undefined;
|
|
198
|
+
/** Optional bridge for writing compaction events into the persistent session log. */
|
|
199
|
+
sessionBridge?: SessionEventBridge;
|
|
198
200
|
}
|
|
199
201
|
/**
|
|
200
202
|
* Pipeline middleware that monitors context token load and automatically
|
|
@@ -219,6 +221,7 @@ declare class AutoCompactionMiddleware {
|
|
|
219
221
|
private readonly events?;
|
|
220
222
|
private readonly failureMode;
|
|
221
223
|
private readonly policyProvider?;
|
|
224
|
+
private readonly sessionBridge?;
|
|
222
225
|
/**
|
|
223
226
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
224
227
|
* no oversized tool_results remain to elide), retrying on every iteration
|
package/dist/execution/index.js
CHANGED
|
@@ -1113,6 +1113,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1113
1113
|
events;
|
|
1114
1114
|
failureMode;
|
|
1115
1115
|
policyProvider;
|
|
1116
|
+
sessionBridge;
|
|
1116
1117
|
/**
|
|
1117
1118
|
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
1118
1119
|
* no oversized tool_results remain to elide), retrying on every iteration
|
|
@@ -1149,6 +1150,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1149
1150
|
this.events = opts.events;
|
|
1150
1151
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
1151
1152
|
this.policyProvider = opts.policyProvider;
|
|
1153
|
+
this.sessionBridge = opts.sessionBridge;
|
|
1152
1154
|
}
|
|
1153
1155
|
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
1154
1156
|
* denominator when the active model changes. */
|
|
@@ -1212,6 +1214,15 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1212
1214
|
report,
|
|
1213
1215
|
aggressive
|
|
1214
1216
|
});
|
|
1217
|
+
await this.sessionBridge?.append({
|
|
1218
|
+
type: "compaction",
|
|
1219
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1220
|
+
before: report.before,
|
|
1221
|
+
after: report.after,
|
|
1222
|
+
level: pressure.level,
|
|
1223
|
+
aggressive,
|
|
1224
|
+
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
|
|
1225
|
+
});
|
|
1215
1226
|
ctx.clearFileTracking();
|
|
1216
1227
|
} catch (err) {
|
|
1217
1228
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -1242,6 +1253,141 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
1242
1253
|
}
|
|
1243
1254
|
};
|
|
1244
1255
|
|
|
1256
|
+
// src/security/capabilities.ts
|
|
1257
|
+
var ToolCapabilities = {
|
|
1258
|
+
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
1259
|
+
SHELL_ARBITRARY: "shell.arbitrary",
|
|
1260
|
+
/** Can write / modify / delete files inside the project. */
|
|
1261
|
+
FS_WRITE: "fs.write",
|
|
1262
|
+
/** Can write files outside the current project root (very high risk). */
|
|
1263
|
+
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
1264
|
+
/** Proxies tools from external MCP servers (unknown capability). */
|
|
1265
|
+
MCP_PROXY: "mcp.proxy",
|
|
1266
|
+
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
1267
|
+
SUBAGENT_SPAWN: "subagent.spawn",
|
|
1268
|
+
/** Can mutate global or session configuration / trust state. */
|
|
1269
|
+
CONFIG_MUTATE: "config.mutate",
|
|
1270
|
+
/** Can install packages or run package managers with side effects. */
|
|
1271
|
+
PACKAGE_INSTALL: "package.install"
|
|
1272
|
+
};
|
|
1273
|
+
var DANGEROUS_FOR_SUBAGENTS = [
|
|
1274
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
1275
|
+
ToolCapabilities.FS_WRITE,
|
|
1276
|
+
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
1277
|
+
ToolCapabilities.MCP_PROXY,
|
|
1278
|
+
ToolCapabilities.SUBAGENT_SPAWN,
|
|
1279
|
+
ToolCapabilities.CONFIG_MUTATE,
|
|
1280
|
+
ToolCapabilities.PACKAGE_INSTALL
|
|
1281
|
+
];
|
|
1282
|
+
function hasDangerousCapabilityForSubagents(toolOrCaps) {
|
|
1283
|
+
if (!toolOrCaps) return false;
|
|
1284
|
+
const input = toolOrCaps;
|
|
1285
|
+
const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
|
|
1286
|
+
return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
|
|
1287
|
+
}
|
|
1288
|
+
function getDangerousCapabilities(toolOrCaps) {
|
|
1289
|
+
if (!toolOrCaps) return [];
|
|
1290
|
+
const input = toolOrCaps;
|
|
1291
|
+
const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
|
|
1292
|
+
return caps.filter(
|
|
1293
|
+
(c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/utils/json-schema-validate.ts
|
|
1298
|
+
function validateAgainstSchema(value, schema) {
|
|
1299
|
+
const errors = [];
|
|
1300
|
+
walk(value, schema, "", errors);
|
|
1301
|
+
return { ok: errors.length === 0, errors };
|
|
1302
|
+
}
|
|
1303
|
+
function walk(value, schema, path4, errors) {
|
|
1304
|
+
if (schema.enum !== void 0) {
|
|
1305
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1306
|
+
errors.push({
|
|
1307
|
+
path: path4 || "<root>",
|
|
1308
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1309
|
+
});
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (typeof schema.type === "string") {
|
|
1314
|
+
if (!checkType(value, schema.type)) {
|
|
1315
|
+
errors.push({
|
|
1316
|
+
path: path4 || "<root>",
|
|
1317
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1318
|
+
});
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
1323
|
+
const obj = value;
|
|
1324
|
+
for (const req of schema.required ?? []) {
|
|
1325
|
+
if (!(req in obj)) {
|
|
1326
|
+
errors.push({ path: joinPath(path4, req), message: "required property missing" });
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
if (schema.properties) {
|
|
1330
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1331
|
+
if (key in obj) {
|
|
1332
|
+
walk(obj[key], subSchema, joinPath(path4, key), errors);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1338
|
+
value.forEach((item, i) => walk(item, schema.items, `${path4}[${i}]`, errors));
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function checkType(value, type) {
|
|
1342
|
+
switch (type) {
|
|
1343
|
+
case "string":
|
|
1344
|
+
return typeof value === "string";
|
|
1345
|
+
case "number":
|
|
1346
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
1347
|
+
case "integer":
|
|
1348
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
1349
|
+
case "boolean":
|
|
1350
|
+
return typeof value === "boolean";
|
|
1351
|
+
case "null":
|
|
1352
|
+
return value === null;
|
|
1353
|
+
case "array":
|
|
1354
|
+
return Array.isArray(value);
|
|
1355
|
+
case "object":
|
|
1356
|
+
return isPlainObject(value);
|
|
1357
|
+
default:
|
|
1358
|
+
return true;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function isPlainObject(v) {
|
|
1362
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1363
|
+
}
|
|
1364
|
+
function describeType(v) {
|
|
1365
|
+
if (v === null) return "null";
|
|
1366
|
+
if (Array.isArray(v)) return "array";
|
|
1367
|
+
return typeof v;
|
|
1368
|
+
}
|
|
1369
|
+
function joinPath(parent, key) {
|
|
1370
|
+
if (!parent) return key;
|
|
1371
|
+
return `${parent}.${key}`;
|
|
1372
|
+
}
|
|
1373
|
+
function deepEqual(a, b) {
|
|
1374
|
+
if (a === b) return true;
|
|
1375
|
+
if (typeof a !== typeof b) return false;
|
|
1376
|
+
if (a === null || b === null) return a === b;
|
|
1377
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1378
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
1379
|
+
}
|
|
1380
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1381
|
+
const ak = Object.keys(a);
|
|
1382
|
+
const bk = Object.keys(b);
|
|
1383
|
+
if (ak.length !== bk.length) return false;
|
|
1384
|
+
return ak.every(
|
|
1385
|
+
(k) => deepEqual(a[k], b[k])
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1245
1391
|
// src/utils/tool-output-serializer.ts
|
|
1246
1392
|
function createToolOutputSerializer(opts = {}) {
|
|
1247
1393
|
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
@@ -1323,18 +1469,45 @@ var ToolExecutor = class {
|
|
|
1323
1469
|
budget = this.decrementBudget(result, budget);
|
|
1324
1470
|
return { result, tool, durationMs: Date.now() - start };
|
|
1325
1471
|
}
|
|
1472
|
+
const validation = validateAgainstSchema(use.input, tool.inputSchema);
|
|
1473
|
+
if (!validation.ok) {
|
|
1474
|
+
const errorDetails = validation.errors.map((e) => ` - ${e.path || "input"}: ${e.message}`).join("\n");
|
|
1475
|
+
const result = {
|
|
1476
|
+
type: "tool_result",
|
|
1477
|
+
tool_use_id: use.id,
|
|
1478
|
+
content: `Invalid arguments for tool "${tool.name}".
|
|
1479
|
+
|
|
1480
|
+
Validation errors:
|
|
1481
|
+
${errorDetails}
|
|
1482
|
+
|
|
1483
|
+
Please call the tool again with arguments that match its inputSchema. You can use the "tool-help" tool with name="${tool.name}" to see the exact expected schema.`,
|
|
1484
|
+
is_error: true
|
|
1485
|
+
};
|
|
1486
|
+
budget = this.decrementBudget(result, budget);
|
|
1487
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1488
|
+
}
|
|
1489
|
+
const toolDangerousCaps = getDangerousCapabilities(tool);
|
|
1490
|
+
if (toolDangerousCaps.length > 0) {
|
|
1491
|
+
if (this.opts.events) ;
|
|
1492
|
+
}
|
|
1326
1493
|
if (hasMalformedArguments(use.input)) {
|
|
1327
1494
|
const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
|
|
1328
1495
|
budget = this.decrementBudget(result, budget);
|
|
1329
1496
|
return { result, tool, durationMs: Date.now() - start };
|
|
1330
1497
|
}
|
|
1331
1498
|
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
1332
|
-
|
|
1499
|
+
let effectivePermission = decision.permission;
|
|
1500
|
+
const policy = this.opts.permissionPolicy;
|
|
1501
|
+
const yolo = policy.getYolo?.() === true || policy.getForceAllYolo?.() === true;
|
|
1502
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
|
|
1503
|
+
effectivePermission = "confirm";
|
|
1504
|
+
}
|
|
1505
|
+
if (effectivePermission === "deny") {
|
|
1333
1506
|
const result = this.deniedResult(use, decision.reason);
|
|
1334
1507
|
budget = this.decrementBudget(result, budget);
|
|
1335
1508
|
return { result, tool, durationMs: Date.now() - start };
|
|
1336
1509
|
}
|
|
1337
|
-
if (
|
|
1510
|
+
if (effectivePermission === "confirm") {
|
|
1338
1511
|
if (this.opts.confirmAwaiter) {
|
|
1339
1512
|
const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
|
|
1340
1513
|
if (choice !== "yes" && choice !== "always") {
|
|
@@ -1359,10 +1532,13 @@ var ToolExecutor = class {
|
|
|
1359
1532
|
return { result: pending, tool, durationMs: Date.now() - start };
|
|
1360
1533
|
}
|
|
1361
1534
|
}
|
|
1535
|
+
const toolCapsForAudit = hasDangerousCapabilityForSubagents(tool) ? tool.capabilities ?? [] : [];
|
|
1362
1536
|
const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
|
|
1363
1537
|
"tool.name": tool.name,
|
|
1364
1538
|
"tool.mutating": tool.mutating,
|
|
1365
|
-
"tool.permission": tool.permission
|
|
1539
|
+
"tool.permission": tool.permission,
|
|
1540
|
+
"tool.capabilities": JSON.stringify(tool.capabilities ?? []),
|
|
1541
|
+
"tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
|
|
1366
1542
|
});
|
|
1367
1543
|
try {
|
|
1368
1544
|
const result = await this.executeTool(tool, use, ctx, budget);
|
|
@@ -2531,6 +2707,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2531
2707
|
tokenOutput = 0;
|
|
2532
2708
|
costUsd = 0;
|
|
2533
2709
|
startTime = null;
|
|
2710
|
+
/**
|
|
2711
|
+
* Timestamp of the most recent activity (iteration / tool call / token
|
|
2712
|
+
* usage / streamed progress). Drives the idle timeout — reset by
|
|
2713
|
+
* `markActivity()`. Initialised to `start()` time so a never-active agent
|
|
2714
|
+
* still eventually trips its idle window.
|
|
2715
|
+
*/
|
|
2716
|
+
lastActivityTime = null;
|
|
2534
2717
|
_onThreshold;
|
|
2535
2718
|
/**
|
|
2536
2719
|
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
@@ -2574,6 +2757,24 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2574
2757
|
}
|
|
2575
2758
|
start() {
|
|
2576
2759
|
this.startTime = Date.now();
|
|
2760
|
+
this.lastActivityTime = this.startTime;
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Reset the idle clock. Called on any sign of forward progress —
|
|
2764
|
+
* iterations, tool calls, token usage, and streamed tool/text progress —
|
|
2765
|
+
* so a long-but-productive subagent never trips its `idleTimeoutMs`.
|
|
2766
|
+
*/
|
|
2767
|
+
markActivity() {
|
|
2768
|
+
this.lastActivityTime = Date.now();
|
|
2769
|
+
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Milliseconds since the last activity signal. Returns 0 before `start()`
|
|
2772
|
+
* (nothing to measure yet). Used by the coordinator watchdog to decide
|
|
2773
|
+
* whether to re-arm (still active) or reap (genuinely idle).
|
|
2774
|
+
*/
|
|
2775
|
+
idleMs() {
|
|
2776
|
+
const since = this.lastActivityTime ?? this.startTime;
|
|
2777
|
+
return since === null ? 0 : Date.now() - since;
|
|
2577
2778
|
}
|
|
2578
2779
|
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
2579
2780
|
isNearLimit() {
|
|
@@ -2621,8 +2822,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2621
2822
|
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
2622
2823
|
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
2623
2824
|
}
|
|
2624
|
-
if (elapsedMs !== void 0
|
|
2625
|
-
|
|
2825
|
+
if (elapsedMs !== void 0) {
|
|
2826
|
+
const idle = this.idleMs();
|
|
2827
|
+
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
2828
|
+
exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
2829
|
+
} else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
2830
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
2831
|
+
}
|
|
2626
2832
|
}
|
|
2627
2833
|
if (exceeded.length === 0) return [];
|
|
2628
2834
|
if (!this._onThreshold) {
|
|
@@ -2727,6 +2933,9 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2727
2933
|
if (ext.timeoutMs !== void 0) {
|
|
2728
2934
|
this.limits.timeoutMs = ext.timeoutMs;
|
|
2729
2935
|
}
|
|
2936
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
2937
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
2938
|
+
}
|
|
2730
2939
|
return decision;
|
|
2731
2940
|
} finally {
|
|
2732
2941
|
this._pendingNegotiations.delete(kind);
|
|
@@ -2734,16 +2943,19 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2734
2943
|
}
|
|
2735
2944
|
recordIteration() {
|
|
2736
2945
|
this.iterations++;
|
|
2946
|
+
this.markActivity();
|
|
2737
2947
|
void this.checkLimits();
|
|
2738
2948
|
}
|
|
2739
2949
|
recordToolCall() {
|
|
2740
2950
|
this.toolCalls++;
|
|
2951
|
+
this.markActivity();
|
|
2741
2952
|
void this.checkLimits();
|
|
2742
2953
|
}
|
|
2743
2954
|
recordUsage(usage, costUsd = 0) {
|
|
2744
2955
|
this.tokenInput += usage.input;
|
|
2745
2956
|
this.tokenOutput += usage.output;
|
|
2746
2957
|
this.costUsd += costUsd;
|
|
2958
|
+
this.markActivity();
|
|
2747
2959
|
void this.checkLimits();
|
|
2748
2960
|
}
|
|
2749
2961
|
/**
|
|
@@ -2758,15 +2970,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
2758
2970
|
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
2759
2971
|
*/
|
|
2760
2972
|
checkTimeout() {
|
|
2761
|
-
if (this.startTime === null
|
|
2973
|
+
if (this.startTime === null) return;
|
|
2974
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
2975
|
+
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
2762
2976
|
const elapsed = Date.now() - this.startTime;
|
|
2763
|
-
|
|
2977
|
+
const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
2978
|
+
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
2979
|
+
if (!wallTripped && !idleTripped) return;
|
|
2764
2980
|
void this.checkLimits(elapsed);
|
|
2765
2981
|
}
|
|
2766
|
-
/** Returns true if a timeout has occurred without throwing.
|
|
2982
|
+
/** Returns true if a wall-clock or idle timeout has occurred without throwing. */
|
|
2767
2983
|
isTimedOut() {
|
|
2768
|
-
if (this.startTime === null
|
|
2769
|
-
|
|
2984
|
+
if (this.startTime === null) return false;
|
|
2985
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
2986
|
+
if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
|
|
2987
|
+
if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
|
|
2988
|
+
return false;
|
|
2770
2989
|
}
|
|
2771
2990
|
usage() {
|
|
2772
2991
|
return {
|
|
@@ -2873,6 +3092,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
2873
3092
|
// Cheap: O(1) per progress event, and the budget short-circuits
|
|
2874
3093
|
// when timeoutMs is unset (most subagents have one set anyway).
|
|
2875
3094
|
events.on("tool.progress", () => {
|
|
3095
|
+
ctx.budget.markActivity();
|
|
2876
3096
|
try {
|
|
2877
3097
|
ctx.budget.checkTimeout();
|
|
2878
3098
|
} catch (e) {
|
|
@@ -2889,6 +3109,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
2889
3109
|
currentToolName = e.name;
|
|
2890
3110
|
}),
|
|
2891
3111
|
events.on("provider.text_delta", (e) => {
|
|
3112
|
+
ctx.budget.markActivity();
|
|
2892
3113
|
streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
|
|
2893
3114
|
})
|
|
2894
3115
|
);
|
|
@@ -5282,6 +5503,7 @@ function providerStatusToCode(status, type) {
|
|
|
5282
5503
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
5283
5504
|
)
|
|
5284
5505
|
});
|
|
5506
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
5285
5507
|
var FLEET_ROSTER_BUDGETS = {
|
|
5286
5508
|
"audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
|
|
5287
5509
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
@@ -5293,7 +5515,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
5293
5515
|
)
|
|
5294
5516
|
};
|
|
5295
5517
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
5296
|
-
|
|
5518
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
5297
5519
|
maxIterations: 5e3,
|
|
5298
5520
|
maxToolCalls: 15e3
|
|
5299
5521
|
};
|
|
@@ -5303,7 +5525,13 @@ function applyRosterBudget(cfg) {
|
|
|
5303
5525
|
if (!defaultBudget) return cfg;
|
|
5304
5526
|
return {
|
|
5305
5527
|
...cfg,
|
|
5306
|
-
|
|
5528
|
+
// Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
|
|
5529
|
+
// do NOT impose the roster's historical multi-hour wall-clock default — it
|
|
5530
|
+
// killed agents that were still actively working. Reaping is idle-based.
|
|
5531
|
+
timeoutMs: cfg.timeoutMs,
|
|
5532
|
+
// Idle window is the default reaper. Resets on activity, so a long-but-
|
|
5533
|
+
// productive run is never killed; only a genuine stall is reaped.
|
|
5534
|
+
idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
|
|
5307
5535
|
maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
|
|
5308
5536
|
maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
|
|
5309
5537
|
maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
|
|
@@ -5863,13 +6091,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5863
6091
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
5864
6092
|
const rawMaxCostUsd = subagent.config.maxCostUsd;
|
|
5865
6093
|
const rawTimeoutMs = subagent.config.timeoutMs;
|
|
6094
|
+
const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
|
|
5866
6095
|
const configWithRosterDefaults = applyRosterBudget(subagent.config);
|
|
5867
6096
|
const budget = new SubagentBudget({
|
|
5868
6097
|
maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
|
|
5869
6098
|
maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
|
|
5870
6099
|
maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
|
|
5871
6100
|
maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
|
|
5872
|
-
|
|
6101
|
+
// Wall-clock cap is opt-in (explicit config / defaultBudget only); the
|
|
6102
|
+
// roster no longer supplies one. Idle is the default reaper.
|
|
6103
|
+
timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
|
|
6104
|
+
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
5873
6105
|
});
|
|
5874
6106
|
subagent.activeBudget = budget;
|
|
5875
6107
|
if (!this.runner) {
|
|
@@ -5916,63 +6148,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
5916
6148
|
}
|
|
5917
6149
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
5918
6150
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
5919
|
-
|
|
6151
|
+
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
6152
|
+
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
6153
|
+
return runner(task, ctx);
|
|
6154
|
+
}
|
|
5920
6155
|
const start = Date.now();
|
|
5921
6156
|
let timer = null;
|
|
5922
6157
|
const timeoutPromise = new Promise((_, reject) => {
|
|
5923
6158
|
const armFor = (ms) => {
|
|
5924
6159
|
if (timer) clearTimeout(timer);
|
|
5925
|
-
timer = setTimeout(
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
6160
|
+
timer = setTimeout(onTick, Math.max(0, ms));
|
|
6161
|
+
};
|
|
6162
|
+
const scheduleNext = () => {
|
|
6163
|
+
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
|
|
6164
|
+
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
6165
|
+
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
|
|
6166
|
+
};
|
|
6167
|
+
const onTick = async () => {
|
|
6168
|
+
const elapsed = Date.now() - start;
|
|
6169
|
+
const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
6170
|
+
const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
|
|
6171
|
+
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
6172
|
+
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
6173
|
+
if (idleExceeded && !wallExceeded) {
|
|
6174
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6175
|
+
reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
if (!wallExceeded) {
|
|
6179
|
+
scheduleNext();
|
|
6180
|
+
return;
|
|
6181
|
+
}
|
|
6182
|
+
const limit = wallLimit;
|
|
6183
|
+
if (!budget.onThreshold) {
|
|
6184
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6185
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
6186
|
+
return;
|
|
6187
|
+
}
|
|
6188
|
+
try {
|
|
6189
|
+
const result = budget.onThreshold({
|
|
6190
|
+
kind: "timeout",
|
|
6191
|
+
used: elapsed,
|
|
6192
|
+
limit,
|
|
6193
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
6194
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
6195
|
+
kind: "timeout",
|
|
6196
|
+
used: elapsed,
|
|
6197
|
+
limit,
|
|
6198
|
+
timeoutMs: 6e4,
|
|
6199
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
6200
|
+
deny: () => resolveDecision("stop")
|
|
6201
|
+
});
|
|
6202
|
+
})
|
|
6203
|
+
});
|
|
6204
|
+
const decision = typeof result === "string" ? result : await result;
|
|
6205
|
+
if (decision === "continue" || decision === "throw" || decision === "stop") {
|
|
6206
|
+
armFor(Math.max(1e3, limit));
|
|
5931
6207
|
return;
|
|
5932
6208
|
}
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
limit,
|
|
5938
|
-
requestDecision: () => new Promise((resolveDecision) => {
|
|
5939
|
-
budget._events?.emit("budget.threshold_reached", {
|
|
5940
|
-
kind: "timeout",
|
|
5941
|
-
used: elapsed,
|
|
5942
|
-
limit,
|
|
5943
|
-
timeoutMs: 6e4,
|
|
5944
|
-
extend: (extra) => resolveDecision({ extend: extra }),
|
|
5945
|
-
deny: () => resolveDecision("stop")
|
|
5946
|
-
});
|
|
5947
|
-
})
|
|
5948
|
-
});
|
|
5949
|
-
const decision = typeof result === "string" ? result : await result;
|
|
5950
|
-
if (decision === "continue") {
|
|
5951
|
-
armFor(Math.max(1e3, limit));
|
|
5952
|
-
return;
|
|
5953
|
-
}
|
|
5954
|
-
if (decision === "throw" || decision === "stop") {
|
|
5955
|
-
armFor(Math.max(1e3, limit));
|
|
5956
|
-
return;
|
|
5957
|
-
}
|
|
5958
|
-
if (decision.extend.timeoutMs !== void 0) {
|
|
5959
|
-
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
5960
|
-
const newLimit = decision.extend.timeoutMs;
|
|
5961
|
-
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
5962
|
-
armFor(remaining);
|
|
5963
|
-
return;
|
|
5964
|
-
}
|
|
5965
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
5966
|
-
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
5967
|
-
} catch (err) {
|
|
5968
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
5969
|
-
reject(
|
|
5970
|
-
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
5971
|
-
);
|
|
6209
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
6210
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
6211
|
+
scheduleNext();
|
|
6212
|
+
return;
|
|
5972
6213
|
}
|
|
5973
|
-
|
|
6214
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6215
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
6216
|
+
} catch (err) {
|
|
6217
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
6218
|
+
reject(
|
|
6219
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
6220
|
+
);
|
|
6221
|
+
}
|
|
5974
6222
|
};
|
|
5975
|
-
|
|
6223
|
+
scheduleNext();
|
|
5976
6224
|
});
|
|
5977
6225
|
try {
|
|
5978
6226
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|