@wrongstack/core 0.9.20 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +37 -33
  2. package/dist/{agent-bridge-Dti3KXGk.d.ts → agent-bridge-DBBGP5O4.d.ts} +1 -1
  3. package/dist/{agent-subagent-runner-U-rs7kk7.d.ts → agent-subagent-runner-KwufTWwn.d.ts} +3 -3
  4. package/dist/{compactor-D7X96RLZ.d.ts → compactor-D1RHFRmF.d.ts} +1 -1
  5. package/dist/{config-CLXMDOSs.d.ts → config-CJXBka2r.d.ts} +38 -2
  6. package/dist/{context-zkZeILpr.d.ts → context-7u93AcGD.d.ts} +94 -0
  7. package/dist/coordination/index.d.ts +12 -12
  8. package/dist/coordination/index.js +130 -58
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/{default-config-Uzla85H-.d.ts → default-config-DEXI4jsl.d.ts} +10 -1
  11. package/dist/defaults/index.d.ts +24 -24
  12. package/dist/defaults/index.js +611 -95
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/{events-DH-9r-_C.d.ts → events-Bw1K5E6K.d.ts} +8 -1
  15. package/dist/execution/index.d.ts +18 -15
  16. package/dist/execution/index.js +309 -61
  17. package/dist/execution/index.js.map +1 -1
  18. package/dist/extension/index.d.ts +6 -6
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/goal-store-BeRsj7YX.d.ts +188 -0
  21. package/dist/{index-CFO9QmJo.d.ts → index-DIt5TIaF.d.ts} +8 -8
  22. package/dist/{index-BIHJ4uII.d.ts → index-DgQWnZxP.d.ts} +5 -5
  23. package/dist/index.d.ts +56 -35
  24. package/dist/index.js +742 -203
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +6 -6
  27. package/dist/kernel/index.d.ts +9 -9
  28. package/dist/kernel/index.js +57 -8
  29. package/dist/kernel/index.js.map +1 -1
  30. package/dist/{mcp-servers-DkESgh0G.d.ts → mcp-servers-BgINZzuo.d.ts} +3 -3
  31. package/dist/models/index.d.ts +3 -3
  32. package/dist/models/index.js +163 -10
  33. package/dist/models/index.js.map +1 -1
  34. package/dist/models-registry-gwMAo6E3.d.ts +86 -0
  35. package/dist/{multi-agent-DNp6lAzg.d.ts → multi-agent-C1O8EFzb.d.ts} +45 -4
  36. package/dist/{multi-agent-coordinator-CAhsegPz.d.ts → multi-agent-coordinator-DoVSBFNx.d.ts} +2 -2
  37. package/dist/{null-fleet-bus-Dnl19vmf.d.ts → null-fleet-bus-T-s3JaOt.d.ts} +8 -6
  38. package/dist/observability/index.d.ts +2 -2
  39. package/dist/{path-resolver-CHiBL0DD.d.ts → path-resolver-BfyK-I7w.d.ts} +2 -2
  40. package/dist/{permission-H35s9Evv.d.ts → permission-B6sldrSp.d.ts} +1 -1
  41. package/dist/{permission-policy-CT-nRmTn.d.ts → permission-policy-CtNscWOA.d.ts} +10 -9
  42. package/dist/{plan-templates-Bs8iRwi6.d.ts → plan-templates-BWs6Je7j.d.ts} +8 -8
  43. package/dist/{provider-runner-BZdDrWrS.d.ts → provider-runner-BmPe2RuU.d.ts} +3 -3
  44. package/dist/{retry-policy-J9N_PM40.d.ts → retry-policy-KF18W4dg.d.ts} +1 -1
  45. package/dist/sdd/index.d.ts +8 -8
  46. package/dist/sdd/index.js +130 -58
  47. package/dist/sdd/index.js.map +1 -1
  48. package/dist/security/index.d.ts +65 -4
  49. package/dist/security/index.js +67 -19
  50. package/dist/security/index.js.map +1 -1
  51. package/dist/{selector-CFTh3Z6p.d.ts → selector-DmXxpFyM.d.ts} +1 -1
  52. package/dist/{session-reader-C7JJlxOw.d.ts → session-reader-CCSBfQmd.d.ts} +1 -1
  53. package/dist/storage/index.d.ts +7 -7
  54. package/dist/storage/index.js +125 -6
  55. package/dist/storage/index.js.map +1 -1
  56. package/dist/{system-prompt-CneIxVip.d.ts → system-prompt-CM6zOhd2.d.ts} +19 -1
  57. package/dist/{tool-executor-flTuxsqO.d.ts → tool-executor-D7RxLtg4.d.ts} +4 -4
  58. package/dist/types/index.d.ts +16 -16
  59. package/dist/types/index.js +344 -14
  60. package/dist/types/index.js.map +1 -1
  61. package/dist/utils/index.d.ts +23 -3
  62. package/dist/utils/index.js +66 -1
  63. package/dist/utils/index.js.map +1 -1
  64. package/dist/{wstack-paths-BCgmTNlG.d.ts → wstack-paths-eMXnY1_X.d.ts} +8 -0
  65. package/package.json +1 -1
  66. package/dist/goal-store-C7jcumEh.d.ts +0 -96
  67. package/dist/models-registry-OG_30xqZ.d.ts +0 -46
@@ -919,7 +919,7 @@ function isPersistedQueueItem(v) {
919
919
  // src/storage/attachment-store.ts
920
920
  init_atomic_write();
921
921
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
922
- var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
922
+ var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
923
923
  var DefaultAttachmentStore = class {
924
924
  items = /* @__PURE__ */ new Map();
925
925
  refs = [];
@@ -973,9 +973,15 @@ var DefaultAttachmentStore = class {
973
973
  const idx = m.index ?? 0;
974
974
  const before = text.slice(lastIndex, idx);
975
975
  if (before) blocks.push({ type: "text", text: before });
976
- const kind = prefixToKind(m[1]);
977
- const seq = Number(m[2]);
978
- const ref = this.refs.find((r) => r.kind === kind && r.seq === seq);
976
+ let ref;
977
+ if (m[3] !== void 0) {
978
+ const wantPath = m[3];
979
+ ref = findLast(this.refs, (r) => r.kind === "file" && refPath(r) === wantPath);
980
+ } else {
981
+ const kind = prefixToKind(m[1]);
982
+ const seq = Number(m[2]);
983
+ ref = this.refs.find((r) => r.kind === kind && r.seq === seq);
984
+ }
979
985
  const att = ref ? this.items.get(ref.id) : void 0;
980
986
  if (!att) {
981
987
  blocks.push({ type: "text", text: m[0] });
@@ -1028,6 +1034,15 @@ function prefixToKind(prefix) {
1028
1034
  if (prefix === "image") return "image";
1029
1035
  return "file";
1030
1036
  }
1037
+ function refPath(ref) {
1038
+ return ref.meta.filename ?? ref.meta.label;
1039
+ }
1040
+ function findLast(arr, pred) {
1041
+ for (let i = arr.length - 1; i >= 0; i--) {
1042
+ if (pred(arr[i])) return arr[i];
1043
+ }
1044
+ return void 0;
1045
+ }
1031
1046
  function mergeAdjacentText(blocks) {
1032
1047
  const out = [];
1033
1048
  for (const b of blocks) {
@@ -1412,6 +1427,14 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
1412
1427
  var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
1413
1428
  autoProceedDelayMs: 45e3
1414
1429
  });
1430
+ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
1431
+ auditLevel: "standard",
1432
+ sampling: {
1433
+ toolProgress: {
1434
+ sampleRate: 8
1435
+ }
1436
+ }
1437
+ });
1415
1438
 
1416
1439
  // src/storage/config-loader.ts
1417
1440
  var BEHAVIOR_DEFAULTS = {
@@ -1440,7 +1463,8 @@ var BEHAVIOR_DEFAULTS = {
1440
1463
  memory: true,
1441
1464
  modelsRegistry: true,
1442
1465
  skills: true
1443
- }
1466
+ },
1467
+ session: { ...DEFAULT_SESSION_LOGGING_CONFIG }
1444
1468
  };
1445
1469
  var ENV_MAP = {
1446
1470
  WRONGSTACK_PROVIDER: (c, v) => {
@@ -2244,6 +2268,101 @@ var SessionAnalyzer = class {
2244
2268
  }
2245
2269
  };
2246
2270
 
2271
+ // src/storage/session-event-bridge.ts
2272
+ var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
2273
+ "session_start",
2274
+ "session_resumed",
2275
+ "user_input",
2276
+ "llm_response",
2277
+ "tool_result",
2278
+ "checkpoint",
2279
+ "file_snapshot",
2280
+ "rewound",
2281
+ "in_flight_start",
2282
+ "in_flight_end",
2283
+ "session_end"
2284
+ ]);
2285
+ var STANDARD_AUDIT_EVENTS = /* @__PURE__ */ new Set([
2286
+ "llm_request",
2287
+ "tool_use",
2288
+ "tool_call_start",
2289
+ "tool_call_end",
2290
+ "compaction",
2291
+ "error",
2292
+ "message_truncated",
2293
+ "provider_retry",
2294
+ "provider_error"
2295
+ ]);
2296
+ var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set([
2297
+ "tool_progress"
2298
+ ]);
2299
+ function isAllowed(type, level) {
2300
+ if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;
2301
+ if (level === "minimal") return false;
2302
+ if (STANDARD_AUDIT_EVENTS.has(type)) return true;
2303
+ if (level === "standard") return false;
2304
+ if (FULL_ONLY_EVENTS.has(type)) {
2305
+ return level === "full";
2306
+ }
2307
+ return true;
2308
+ }
2309
+ function createSessionEventBridge(writer, level = "standard", options = {}) {
2310
+ const normalizedLevel = level ?? "standard";
2311
+ const progressCounters = /* @__PURE__ */ new Map();
2312
+ const toolProgressConfig = options.sampling?.toolProgress ?? {};
2313
+ const TOOL_PROGRESS_SAMPLE_RATE = toolProgressConfig.sampleRate ?? 8;
2314
+ function shouldSample(event) {
2315
+ if (event.type !== "tool_progress") return true;
2316
+ const progEvent = event;
2317
+ const innerType = progEvent.event?.type;
2318
+ if (innerType === "warning" || innerType === "metric" || innerType === "file_changed") {
2319
+ return true;
2320
+ }
2321
+ if (innerType === "log" || innerType === "partial_output") {
2322
+ const key = progEvent.id || progEvent.name;
2323
+ const count = (progressCounters.get(key) || 0) + 1;
2324
+ progressCounters.set(key, count);
2325
+ return count === 1 || count % TOOL_PROGRESS_SAMPLE_RATE === 0;
2326
+ }
2327
+ return true;
2328
+ }
2329
+ return {
2330
+ level: normalizedLevel,
2331
+ allows(type) {
2332
+ return isAllowed(type, normalizedLevel);
2333
+ },
2334
+ async append(event) {
2335
+ if (!writer) return;
2336
+ if (!isAllowed(event.type, normalizedLevel)) return;
2337
+ if (!shouldSample(event)) return;
2338
+ try {
2339
+ await writer.append(event);
2340
+ } catch (err) {
2341
+ }
2342
+ }
2343
+ };
2344
+ }
2345
+ function resolveAuditLevel(cfg) {
2346
+ const raw = cfg?.session?.auditLevel;
2347
+ if (raw === "minimal" || raw === "standard" || raw === "full") {
2348
+ return raw;
2349
+ }
2350
+ return "standard";
2351
+ }
2352
+ function resolveSessionLoggingConfig(cfg) {
2353
+ const session = cfg?.session ?? {};
2354
+ const auditLevel = resolveAuditLevel(cfg);
2355
+ const toolProgressSampleRate = session.sampling?.toolProgress?.sampleRate ?? 8;
2356
+ return {
2357
+ auditLevel,
2358
+ sampling: {
2359
+ toolProgress: {
2360
+ sampleRate: Math.max(1, Math.floor(toolProgressSampleRate))
2361
+ }
2362
+ }
2363
+ };
2364
+ }
2365
+
2247
2366
  // src/storage/todos-checkpoint.ts
2248
2367
  init_atomic_write();
2249
2368
  async function loadTodosCheckpoint(filePath) {
@@ -3052,6 +3171,47 @@ function deepMerge2(a, b) {
3052
3171
  return out;
3053
3172
  }
3054
3173
 
3174
+ // src/security/capabilities.ts
3175
+ var ToolCapabilities = {
3176
+ /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
3177
+ SHELL_ARBITRARY: "shell.arbitrary",
3178
+ /** Can write / modify / delete files inside the project. */
3179
+ FS_WRITE: "fs.write",
3180
+ /** Can write files outside the current project root (very high risk). */
3181
+ FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
3182
+ /** Proxies tools from external MCP servers (unknown capability). */
3183
+ MCP_PROXY: "mcp.proxy",
3184
+ /** Can spawn or manage subagents / multi-agent tasks. */
3185
+ SUBAGENT_SPAWN: "subagent.spawn",
3186
+ /** Can mutate global or session configuration / trust state. */
3187
+ CONFIG_MUTATE: "config.mutate",
3188
+ /** Can install packages or run package managers with side effects. */
3189
+ PACKAGE_INSTALL: "package.install"
3190
+ };
3191
+ var DANGEROUS_FOR_SUBAGENTS = [
3192
+ ToolCapabilities.SHELL_ARBITRARY,
3193
+ ToolCapabilities.FS_WRITE,
3194
+ ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
3195
+ ToolCapabilities.MCP_PROXY,
3196
+ ToolCapabilities.SUBAGENT_SPAWN,
3197
+ ToolCapabilities.CONFIG_MUTATE,
3198
+ ToolCapabilities.PACKAGE_INSTALL
3199
+ ];
3200
+ function hasDangerousCapabilityForSubagents(toolOrCaps) {
3201
+ if (!toolOrCaps) return false;
3202
+ const input = toolOrCaps;
3203
+ const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
3204
+ return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
3205
+ }
3206
+ function getDangerousCapabilities(toolOrCaps) {
3207
+ if (!toolOrCaps) return [];
3208
+ const input = toolOrCaps;
3209
+ const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
3210
+ return caps.filter(
3211
+ (c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
3212
+ );
3213
+ }
3214
+
3055
3215
  // src/security/permission-policy.ts
3056
3216
  init_atomic_write();
3057
3217
 
@@ -3371,31 +3531,21 @@ var DefaultPermissionPolicy = class {
3371
3531
  };
3372
3532
  var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
3373
3533
  /**
3374
- * Tools that are too dangerous to auto-approve even in a delegated
3375
- * subagent context. Subagents run non-interactively under a director
3376
- * and cannot answer prompts, but inherited authorization does not
3377
- * imply blanket permission for destructive or privilege-escalating
3378
- * operations. These tools remain at their declared `permission`
3379
- * level so the leader must explicitly allow them per-spawn.
3534
+ * Legacy name-based denylist.
3535
+ * @deprecated Prefer declaring `capabilities` on the Tool and using capability-based checks.
3380
3536
  */
3381
- static DENY = /* @__PURE__ */ new Set([
3537
+ static LEGACY_NAME_DENY = /* @__PURE__ */ new Set([
3382
3538
  "bash",
3383
- // arbitrary shell — use exec for constrained shell
3384
3539
  "write",
3385
- // arbitrary file write
3386
3540
  "edit",
3387
- // arbitrary in-project file modification (equivalent to write)
3388
3541
  "replace",
3389
- // arbitrary multi-file find/replace (equivalent to write)
3390
3542
  "scaffold",
3391
- // arbitrary file generation outside project root
3392
3543
  "patch",
3393
- // arbitrary diff application
3394
3544
  "install",
3395
- // installs from arbitrary package sources
3396
3545
  "exec"
3397
- // restricted shell but with arbitrary command args
3398
3546
  ]);
3547
+ // Note: hasDangerousCapabilityForSubagents is now the shared helper from capabilities.ts
3548
+ // The old private method was removed in favor of the centralized utility.
3399
3549
  /**
3400
3550
  * Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
3401
3551
  * unknown capability — they may wrap a shell or filesystem. They are
@@ -3406,12 +3556,16 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
3406
3556
  return name.startsWith("mcp__");
3407
3557
  }
3408
3558
  async evaluate(tool) {
3409
- const blocked = _AutoApprovePermissionPolicy.DENY.has(tool.name) || _AutoApprovePermissionPolicy.isMcpTool(tool.name);
3410
- if (tool.permission === "deny" || blocked) {
3559
+ const hasDangerousCap = hasDangerousCapabilityForSubagents(tool);
3560
+ const legacyNameBlock = _AutoApprovePermissionPolicy.LEGACY_NAME_DENY.has(tool.name);
3561
+ const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
3562
+ const blocked = tool.permission === "deny" || hasDangerousCap || legacyNameBlock || isMcp;
3563
+ if (blocked) {
3564
+ const reason = hasDangerousCap ? `tool declares dangerous capability (${tool.capabilities?.join(", ")}) \u2014 not auto-approved for subagents` : legacyNameBlock || isMcp ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny";
3411
3565
  return {
3412
3566
  permission: "deny",
3413
3567
  source: "subagent_guard",
3414
- reason: blocked ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
3568
+ reason
3415
3569
  };
3416
3570
  }
3417
3571
  return { permission: "auto", source: "yolo" };
@@ -5152,6 +5306,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5152
5306
  events;
5153
5307
  failureMode;
5154
5308
  policyProvider;
5309
+ sessionBridge;
5155
5310
  /**
5156
5311
  * Once a compaction attempt reduces nothing (preserveK protects everything,
5157
5312
  * no oversized tool_results remain to elide), retrying on every iteration
@@ -5188,6 +5343,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5188
5343
  this.events = opts.events;
5189
5344
  this.failureMode = opts.failureMode ?? "throw_on_hard";
5190
5345
  this.policyProvider = opts.policyProvider;
5346
+ this.sessionBridge = opts.sessionBridge;
5191
5347
  }
5192
5348
  /** Allow callers (e.g. model-switch in WebUI) to update the context window
5193
5349
  * denominator when the active model changes. */
@@ -5251,6 +5407,15 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5251
5407
  report,
5252
5408
  aggressive
5253
5409
  });
5410
+ await this.sessionBridge?.append({
5411
+ type: "compaction",
5412
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5413
+ before: report.before,
5414
+ after: report.after,
5415
+ level: pressure.level,
5416
+ aggressive,
5417
+ reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
5418
+ });
5254
5419
  ctx.clearFileTracking();
5255
5420
  } catch (err) {
5256
5421
  const error = err instanceof Error ? err : new Error(String(err));
@@ -5281,6 +5446,100 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5281
5446
  }
5282
5447
  };
5283
5448
 
5449
+ // src/utils/json-schema-validate.ts
5450
+ function validateAgainstSchema(value, schema) {
5451
+ const errors = [];
5452
+ walk3(value, schema, "", errors);
5453
+ return { ok: errors.length === 0, errors };
5454
+ }
5455
+ function walk3(value, schema, path18, errors) {
5456
+ if (schema.enum !== void 0) {
5457
+ if (!schema.enum.some((e) => deepEqual(e, value))) {
5458
+ errors.push({
5459
+ path: path18 || "<root>",
5460
+ message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
5461
+ });
5462
+ return;
5463
+ }
5464
+ }
5465
+ if (typeof schema.type === "string") {
5466
+ if (!checkType(value, schema.type)) {
5467
+ errors.push({
5468
+ path: path18 || "<root>",
5469
+ message: `expected ${schema.type}, got ${describeType(value)}`
5470
+ });
5471
+ return;
5472
+ }
5473
+ }
5474
+ if (schema.type === "object" && isPlainObject(value)) {
5475
+ const obj = value;
5476
+ for (const req of schema.required ?? []) {
5477
+ if (!(req in obj)) {
5478
+ errors.push({ path: joinPath(path18, req), message: "required property missing" });
5479
+ }
5480
+ }
5481
+ if (schema.properties) {
5482
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
5483
+ if (key in obj) {
5484
+ walk3(obj[key], subSchema, joinPath(path18, key), errors);
5485
+ }
5486
+ }
5487
+ }
5488
+ }
5489
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
5490
+ value.forEach((item, i) => walk3(item, schema.items, `${path18}[${i}]`, errors));
5491
+ }
5492
+ }
5493
+ function checkType(value, type) {
5494
+ switch (type) {
5495
+ case "string":
5496
+ return typeof value === "string";
5497
+ case "number":
5498
+ return typeof value === "number" && !Number.isNaN(value);
5499
+ case "integer":
5500
+ return typeof value === "number" && Number.isInteger(value);
5501
+ case "boolean":
5502
+ return typeof value === "boolean";
5503
+ case "null":
5504
+ return value === null;
5505
+ case "array":
5506
+ return Array.isArray(value);
5507
+ case "object":
5508
+ return isPlainObject(value);
5509
+ default:
5510
+ return true;
5511
+ }
5512
+ }
5513
+ function isPlainObject(v) {
5514
+ return typeof v === "object" && v !== null && !Array.isArray(v);
5515
+ }
5516
+ function describeType(v) {
5517
+ if (v === null) return "null";
5518
+ if (Array.isArray(v)) return "array";
5519
+ return typeof v;
5520
+ }
5521
+ function joinPath(parent, key) {
5522
+ if (!parent) return key;
5523
+ return `${parent}.${key}`;
5524
+ }
5525
+ function deepEqual(a, b) {
5526
+ if (a === b) return true;
5527
+ if (typeof a !== typeof b) return false;
5528
+ if (a === null || b === null) return a === b;
5529
+ if (Array.isArray(a) && Array.isArray(b)) {
5530
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
5531
+ }
5532
+ if (typeof a === "object" && typeof b === "object") {
5533
+ const ak = Object.keys(a);
5534
+ const bk = Object.keys(b);
5535
+ if (ak.length !== bk.length) return false;
5536
+ return ak.every(
5537
+ (k) => deepEqual(a[k], b[k])
5538
+ );
5539
+ }
5540
+ return false;
5541
+ }
5542
+
5284
5543
  // src/utils/tool-output-serializer.ts
5285
5544
  function createToolOutputSerializer(opts = {}) {
5286
5545
  const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
@@ -5362,18 +5621,45 @@ var ToolExecutor = class {
5362
5621
  budget = this.decrementBudget(result, budget);
5363
5622
  return { result, tool, durationMs: Date.now() - start };
5364
5623
  }
5624
+ const validation = validateAgainstSchema(use.input, tool.inputSchema);
5625
+ if (!validation.ok) {
5626
+ const errorDetails = validation.errors.map((e) => ` - ${e.path || "input"}: ${e.message}`).join("\n");
5627
+ const result = {
5628
+ type: "tool_result",
5629
+ tool_use_id: use.id,
5630
+ content: `Invalid arguments for tool "${tool.name}".
5631
+
5632
+ Validation errors:
5633
+ ${errorDetails}
5634
+
5635
+ 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.`,
5636
+ is_error: true
5637
+ };
5638
+ budget = this.decrementBudget(result, budget);
5639
+ return { result, tool, durationMs: Date.now() - start };
5640
+ }
5641
+ const toolDangerousCaps = getDangerousCapabilities(tool);
5642
+ if (toolDangerousCaps.length > 0) {
5643
+ if (this.opts.events) ;
5644
+ }
5365
5645
  if (hasMalformedArguments(use.input)) {
5366
5646
  const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
5367
5647
  budget = this.decrementBudget(result, budget);
5368
5648
  return { result, tool, durationMs: Date.now() - start };
5369
5649
  }
5370
5650
  const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
5371
- if (decision.permission === "deny") {
5651
+ let effectivePermission = decision.permission;
5652
+ const policy = this.opts.permissionPolicy;
5653
+ const yolo = policy.getYolo?.() === true || policy.getForceAllYolo?.() === true;
5654
+ if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
5655
+ effectivePermission = "confirm";
5656
+ }
5657
+ if (effectivePermission === "deny") {
5372
5658
  const result = this.deniedResult(use, decision.reason);
5373
5659
  budget = this.decrementBudget(result, budget);
5374
5660
  return { result, tool, durationMs: Date.now() - start };
5375
5661
  }
5376
- if (decision.permission === "confirm") {
5662
+ if (effectivePermission === "confirm") {
5377
5663
  if (this.opts.confirmAwaiter) {
5378
5664
  const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
5379
5665
  if (choice !== "yes" && choice !== "always") {
@@ -5398,10 +5684,13 @@ var ToolExecutor = class {
5398
5684
  return { result: pending, tool, durationMs: Date.now() - start };
5399
5685
  }
5400
5686
  }
5687
+ const toolCapsForAudit = hasDangerousCapabilityForSubagents(tool) ? tool.capabilities ?? [] : [];
5401
5688
  const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
5402
5689
  "tool.name": tool.name,
5403
5690
  "tool.mutating": tool.mutating,
5404
- "tool.permission": tool.permission
5691
+ "tool.permission": tool.permission,
5692
+ "tool.capabilities": JSON.stringify(tool.capabilities ?? []),
5693
+ "tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
5405
5694
  });
5406
5695
  try {
5407
5696
  const result = await this.executeTool(tool, use, ctx, budget);
@@ -6474,6 +6763,13 @@ var SubagentBudget = class _SubagentBudget {
6474
6763
  tokenOutput = 0;
6475
6764
  costUsd = 0;
6476
6765
  startTime = null;
6766
+ /**
6767
+ * Timestamp of the most recent activity (iteration / tool call / token
6768
+ * usage / streamed progress). Drives the idle timeout — reset by
6769
+ * `markActivity()`. Initialised to `start()` time so a never-active agent
6770
+ * still eventually trips its idle window.
6771
+ */
6772
+ lastActivityTime = null;
6477
6773
  _onThreshold;
6478
6774
  /**
6479
6775
  * Hard cap on how long `_negotiateExtension` waits for the coordinator to
@@ -6517,6 +6813,24 @@ var SubagentBudget = class _SubagentBudget {
6517
6813
  }
6518
6814
  start() {
6519
6815
  this.startTime = Date.now();
6816
+ this.lastActivityTime = this.startTime;
6817
+ }
6818
+ /**
6819
+ * Reset the idle clock. Called on any sign of forward progress —
6820
+ * iterations, tool calls, token usage, and streamed tool/text progress —
6821
+ * so a long-but-productive subagent never trips its `idleTimeoutMs`.
6822
+ */
6823
+ markActivity() {
6824
+ this.lastActivityTime = Date.now();
6825
+ }
6826
+ /**
6827
+ * Milliseconds since the last activity signal. Returns 0 before `start()`
6828
+ * (nothing to measure yet). Used by the coordinator watchdog to decide
6829
+ * whether to re-arm (still active) or reap (genuinely idle).
6830
+ */
6831
+ idleMs() {
6832
+ const since = this.lastActivityTime ?? this.startTime;
6833
+ return since === null ? 0 : Date.now() - since;
6520
6834
  }
6521
6835
  /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
6522
6836
  isNearLimit() {
@@ -6564,8 +6878,13 @@ var SubagentBudget = class _SubagentBudget {
6564
6878
  if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6565
6879
  exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
6566
6880
  }
6567
- if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
6568
- exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
6881
+ if (elapsedMs !== void 0) {
6882
+ const idle = this.idleMs();
6883
+ if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
6884
+ exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
6885
+ } else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
6886
+ exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
6887
+ }
6569
6888
  }
6570
6889
  if (exceeded.length === 0) return [];
6571
6890
  if (!this._onThreshold) {
@@ -6670,6 +6989,9 @@ var SubagentBudget = class _SubagentBudget {
6670
6989
  if (ext.timeoutMs !== void 0) {
6671
6990
  this.limits.timeoutMs = ext.timeoutMs;
6672
6991
  }
6992
+ if (ext.idleTimeoutMs !== void 0) {
6993
+ this.limits.idleTimeoutMs = ext.idleTimeoutMs;
6994
+ }
6673
6995
  return decision;
6674
6996
  } finally {
6675
6997
  this._pendingNegotiations.delete(kind);
@@ -6677,16 +6999,19 @@ var SubagentBudget = class _SubagentBudget {
6677
6999
  }
6678
7000
  recordIteration() {
6679
7001
  this.iterations++;
7002
+ this.markActivity();
6680
7003
  void this.checkLimits();
6681
7004
  }
6682
7005
  recordToolCall() {
6683
7006
  this.toolCalls++;
7007
+ this.markActivity();
6684
7008
  void this.checkLimits();
6685
7009
  }
6686
7010
  recordUsage(usage, costUsd = 0) {
6687
7011
  this.tokenInput += usage.input;
6688
7012
  this.tokenOutput += usage.output;
6689
7013
  this.costUsd += costUsd;
7014
+ this.markActivity();
6690
7015
  void this.checkLimits();
6691
7016
  }
6692
7017
  /**
@@ -6701,15 +7026,22 @@ var SubagentBudget = class _SubagentBudget {
6701
7026
  * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
6702
7027
  */
6703
7028
  checkTimeout() {
6704
- if (this.startTime === null || this.limits.timeoutMs === void 0) return;
7029
+ if (this.startTime === null) return;
7030
+ const { timeoutMs, idleTimeoutMs } = this.limits;
7031
+ if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
6705
7032
  const elapsed = Date.now() - this.startTime;
6706
- if (elapsed <= this.limits.timeoutMs) return;
7033
+ const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
7034
+ const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
7035
+ if (!wallTripped && !idleTripped) return;
6707
7036
  void this.checkLimits(elapsed);
6708
7037
  }
6709
- /** Returns true if a timeout has occurred without throwing. Useful for races. */
7038
+ /** Returns true if a wall-clock or idle timeout has occurred without throwing. */
6710
7039
  isTimedOut() {
6711
- if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
6712
- return Date.now() - this.startTime > this.limits.timeoutMs;
7040
+ if (this.startTime === null) return false;
7041
+ const { timeoutMs, idleTimeoutMs } = this.limits;
7042
+ if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
7043
+ if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
7044
+ return false;
6713
7045
  }
6714
7046
  usage() {
6715
7047
  return {
@@ -6816,6 +7148,7 @@ function makeAgentSubagentRunner(opts) {
6816
7148
  // Cheap: O(1) per progress event, and the budget short-circuits
6817
7149
  // when timeoutMs is unset (most subagents have one set anyway).
6818
7150
  events.on("tool.progress", () => {
7151
+ ctx.budget.markActivity();
6819
7152
  try {
6820
7153
  ctx.budget.checkTimeout();
6821
7154
  } catch (e) {
@@ -6832,6 +7165,7 @@ function makeAgentSubagentRunner(opts) {
6832
7165
  currentToolName = e.name;
6833
7166
  }),
6834
7167
  events.on("provider.text_delta", (e) => {
7168
+ ctx.budget.markActivity();
6835
7169
  streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
6836
7170
  })
6837
7171
  );
@@ -9401,6 +9735,7 @@ var FLEET_ROSTER = {
9401
9735
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
9402
9736
  )
9403
9737
  };
9738
+ var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
9404
9739
  var FLEET_ROSTER_BUDGETS = {
9405
9740
  "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
9406
9741
  "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
@@ -9412,7 +9747,7 @@ var FLEET_ROSTER_BUDGETS = {
9412
9747
  )
9413
9748
  };
9414
9749
  var GENERIC_SUBAGENT_BUDGET = {
9415
- timeoutMs: 3 * 60 * 60 * 1e3,
9750
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
9416
9751
  maxIterations: 5e3,
9417
9752
  maxToolCalls: 15e3
9418
9753
  };
@@ -9422,7 +9757,13 @@ function applyRosterBudget(cfg) {
9422
9757
  if (!defaultBudget) return cfg;
9423
9758
  return {
9424
9759
  ...cfg,
9425
- timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
9760
+ // Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
9761
+ // do NOT impose the roster's historical multi-hour wall-clock default — it
9762
+ // killed agents that were still actively working. Reaping is idle-based.
9763
+ timeoutMs: cfg.timeoutMs,
9764
+ // Idle window is the default reaper. Resets on activity, so a long-but-
9765
+ // productive run is never killed; only a genuine stall is reaped.
9766
+ idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
9426
9767
  maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
9427
9768
  maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
9428
9769
  maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
@@ -9984,13 +10325,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9984
10325
  const rawMaxTokens = subagent.config.maxTokens;
9985
10326
  const rawMaxCostUsd = subagent.config.maxCostUsd;
9986
10327
  const rawTimeoutMs = subagent.config.timeoutMs;
10328
+ const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
9987
10329
  const configWithRosterDefaults = applyRosterBudget(subagent.config);
9988
10330
  const budget = new SubagentBudget({
9989
10331
  maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
9990
10332
  maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
9991
10333
  maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
9992
10334
  maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
9993
- timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs
10335
+ // Wall-clock cap is opt-in (explicit config / defaultBudget only); the
10336
+ // roster no longer supplies one. Idle is the default reaper.
10337
+ timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
10338
+ idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
9994
10339
  });
9995
10340
  subagent.activeBudget = budget;
9996
10341
  if (!this.runner) {
@@ -10037,63 +10382,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10037
10382
  }
10038
10383
  async executeWithTimeout(runner, task, ctx, budget) {
10039
10384
  const initialTimeoutMs = budget.limits.timeoutMs;
10040
- if (initialTimeoutMs === void 0) return runner(task, ctx);
10385
+ const idleLimitMs = budget.limits.idleTimeoutMs;
10386
+ if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
10387
+ return runner(task, ctx);
10388
+ }
10041
10389
  const start = Date.now();
10042
10390
  let timer = null;
10043
10391
  const timeoutPromise = new Promise((_, reject) => {
10044
10392
  const armFor = (ms) => {
10045
10393
  if (timer) clearTimeout(timer);
10046
- timer = setTimeout(async () => {
10047
- const elapsed = Date.now() - start;
10048
- const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
10049
- if (!budget.onThreshold) {
10050
- this.subagents.get(ctx.subagentId)?.abortController.abort();
10051
- reject(new BudgetExceededError("timeout", limit, elapsed));
10394
+ timer = setTimeout(onTick, Math.max(0, ms));
10395
+ };
10396
+ const scheduleNext = () => {
10397
+ const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
10398
+ const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
10399
+ armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
10400
+ };
10401
+ const onTick = async () => {
10402
+ const elapsed = Date.now() - start;
10403
+ const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
10404
+ const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
10405
+ const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
10406
+ const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
10407
+ if (idleExceeded && !wallExceeded) {
10408
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10409
+ reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
10410
+ return;
10411
+ }
10412
+ if (!wallExceeded) {
10413
+ scheduleNext();
10414
+ return;
10415
+ }
10416
+ const limit = wallLimit;
10417
+ if (!budget.onThreshold) {
10418
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10419
+ reject(new BudgetExceededError("timeout", limit, elapsed));
10420
+ return;
10421
+ }
10422
+ try {
10423
+ const result = budget.onThreshold({
10424
+ kind: "timeout",
10425
+ used: elapsed,
10426
+ limit,
10427
+ requestDecision: () => new Promise((resolveDecision) => {
10428
+ budget._events?.emit("budget.threshold_reached", {
10429
+ kind: "timeout",
10430
+ used: elapsed,
10431
+ limit,
10432
+ timeoutMs: 6e4,
10433
+ extend: (extra) => resolveDecision({ extend: extra }),
10434
+ deny: () => resolveDecision("stop")
10435
+ });
10436
+ })
10437
+ });
10438
+ const decision = typeof result === "string" ? result : await result;
10439
+ if (decision === "continue" || decision === "throw" || decision === "stop") {
10440
+ armFor(Math.max(1e3, limit));
10052
10441
  return;
10053
10442
  }
10054
- try {
10055
- const result = budget.onThreshold({
10056
- kind: "timeout",
10057
- used: elapsed,
10058
- limit,
10059
- requestDecision: () => new Promise((resolveDecision) => {
10060
- budget._events?.emit("budget.threshold_reached", {
10061
- kind: "timeout",
10062
- used: elapsed,
10063
- limit,
10064
- timeoutMs: 6e4,
10065
- extend: (extra) => resolveDecision({ extend: extra }),
10066
- deny: () => resolveDecision("stop")
10067
- });
10068
- })
10069
- });
10070
- const decision = typeof result === "string" ? result : await result;
10071
- if (decision === "continue") {
10072
- armFor(Math.max(1e3, limit));
10073
- return;
10074
- }
10075
- if (decision === "throw" || decision === "stop") {
10076
- armFor(Math.max(1e3, limit));
10077
- return;
10078
- }
10079
- if (decision.extend.timeoutMs !== void 0) {
10080
- budget.limits.timeoutMs = decision.extend.timeoutMs;
10081
- const newLimit = decision.extend.timeoutMs;
10082
- const remaining = Math.max(1e3, newLimit - elapsed);
10083
- armFor(remaining);
10084
- return;
10085
- }
10086
- this.subagents.get(ctx.subagentId)?.abortController.abort();
10087
- reject(new BudgetExceededError("timeout", limit, elapsed));
10088
- } catch (err) {
10089
- this.subagents.get(ctx.subagentId)?.abortController.abort();
10090
- reject(
10091
- err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
10092
- );
10443
+ if (decision.extend.timeoutMs !== void 0) {
10444
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
10445
+ scheduleNext();
10446
+ return;
10093
10447
  }
10094
- }, ms);
10448
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10449
+ reject(new BudgetExceededError("timeout", limit, elapsed));
10450
+ } catch (err) {
10451
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10452
+ reject(
10453
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
10454
+ );
10455
+ }
10095
10456
  };
10096
- armFor(initialTimeoutMs);
10457
+ scheduleNext();
10097
10458
  });
10098
10459
  try {
10099
10460
  return await Promise.race([runner(task, ctx), timeoutPromise]);
@@ -13497,6 +13858,71 @@ var NULL_FLEET_BUS = new FleetBus();
13497
13858
 
13498
13859
  // src/models/models-registry.ts
13499
13860
  init_atomic_write();
13861
+
13862
+ // src/utils/merge-models-payload.ts
13863
+ function mergeModelsPayload(base, overlay) {
13864
+ const out = {};
13865
+ for (const [id, provider] of Object.entries(base)) {
13866
+ out[id] = cloneProvider(provider);
13867
+ }
13868
+ for (const [id, ovProvider] of Object.entries(overlay)) {
13869
+ const existing = out[id];
13870
+ out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
13871
+ }
13872
+ return out;
13873
+ }
13874
+ function mergeProvider(base, overlay) {
13875
+ const models = {};
13876
+ for (const [mid, m] of Object.entries(base.models ?? {})) {
13877
+ models[mid] = { ...m };
13878
+ }
13879
+ for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
13880
+ const existing = models[mid];
13881
+ models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
13882
+ }
13883
+ return {
13884
+ ...base,
13885
+ // Overlay scalar fields win when explicitly provided; otherwise keep base.
13886
+ ...stripUndefined({
13887
+ id: overlay.id,
13888
+ name: overlay.name,
13889
+ npm: overlay.npm,
13890
+ api: overlay.api,
13891
+ env: overlay.env,
13892
+ doc: overlay.doc
13893
+ }),
13894
+ models
13895
+ };
13896
+ }
13897
+ function mergeModel(base, overlay) {
13898
+ const merged = { ...base, ...overlay };
13899
+ if (base.limit || overlay.limit) {
13900
+ merged.limit = { ...base.limit, ...overlay.limit };
13901
+ }
13902
+ if (base.cost || overlay.cost) {
13903
+ merged.cost = { ...base.cost, ...overlay.cost };
13904
+ }
13905
+ if (base.modalities || overlay.modalities) {
13906
+ merged.modalities = { ...base.modalities, ...overlay.modalities };
13907
+ }
13908
+ return merged;
13909
+ }
13910
+ function cloneProvider(p) {
13911
+ const models = {};
13912
+ for (const [mid, m] of Object.entries(p.models ?? {})) {
13913
+ models[mid] = { ...m };
13914
+ }
13915
+ return { ...p, models };
13916
+ }
13917
+ function stripUndefined(obj) {
13918
+ const out = {};
13919
+ for (const [k, v] of Object.entries(obj)) {
13920
+ if (v !== void 0) out[k] = v;
13921
+ }
13922
+ return out;
13923
+ }
13924
+
13925
+ // src/models/models-registry.ts
13500
13926
  var DEFAULT_URL = "https://models.dev/api.json";
13501
13927
  var DEFAULT_TTL_SECONDS = 24 * 3600;
13502
13928
  var FAMILY_BY_NPM = {
@@ -13524,7 +13950,10 @@ function classifyFamily(npm) {
13524
13950
  return FAMILY_BY_NPM[npm] ?? "unsupported";
13525
13951
  }
13526
13952
  var DefaultModelsRegistry = class {
13953
+ /** Merged (base + overlay) payload — what every reader sees. */
13527
13954
  payload;
13955
+ /** Memoised overlay payload (in-memory / fetched / file). */
13956
+ overlayPayload;
13528
13957
  fetchedAt;
13529
13958
  cacheFile;
13530
13959
  url;
@@ -13532,6 +13961,10 @@ var DefaultModelsRegistry = class {
13532
13961
  fetchImpl;
13533
13962
  seed;
13534
13963
  maxStaleAgeMs;
13964
+ overlay;
13965
+ overlayUrl;
13966
+ overlayFile;
13967
+ overlayCacheFile;
13535
13968
  constructor(opts) {
13536
13969
  this.cacheFile = opts.cacheFile;
13537
13970
  this.url = opts.url ?? DEFAULT_URL;
@@ -13540,6 +13973,10 @@ var DefaultModelsRegistry = class {
13540
13973
  this.seed = opts.seed;
13541
13974
  const maxStaleSeconds = opts.maxStaleAgeSeconds ?? 7 * 24 * 3600;
13542
13975
  this.maxStaleAgeMs = maxStaleSeconds * 1e3;
13976
+ this.overlay = opts.overlay;
13977
+ this.overlayUrl = opts.overlayUrl;
13978
+ this.overlayFile = opts.overlayFile;
13979
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path15.join(path15.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
13543
13980
  }
13544
13981
  async load(opts = {}) {
13545
13982
  if (this.payload && !opts.force) return this.payload;
@@ -13548,27 +13985,45 @@ var DefaultModelsRegistry = class {
13548
13985
  this.fetchedAt = /* @__PURE__ */ new Date();
13549
13986
  return this.payload;
13550
13987
  }
13988
+ const overlay = await this.loadOverlay(opts);
13989
+ const base = await this.loadBase(opts, Object.keys(overlay).length > 0);
13990
+ this.payload = mergeModelsPayload(base, overlay);
13991
+ return this.payload;
13992
+ }
13993
+ /**
13994
+ * Load the models.dev base payload: fresh cache → network → stale cache.
13995
+ * On total failure, degrade to `{}` (so a non-empty overlay still drives
13996
+ * the catalog) rather than throwing — unless there's no curated overlay to
13997
+ * fall back on, in which case the original error propagates so pure-
13998
+ * models.dev setups still surface the problem.
13999
+ */
14000
+ async loadBase(opts = {}, overlayAvailable = false) {
13551
14001
  if (!opts.force) {
13552
- const cached = await this.readCache();
14002
+ const cached = await this.readCacheAt(this.cacheFile);
13553
14003
  if (cached && this.isFresh(cached.fetchedAt)) {
13554
- this.payload = cached.payload;
13555
14004
  this.fetchedAt = new Date(cached.fetchedAt);
13556
14005
  return cached.payload;
13557
14006
  }
13558
14007
  }
13559
14008
  try {
13560
- return await this.refresh();
14009
+ return await this.refreshBase();
13561
14010
  } catch (err) {
13562
- const cached = await this.readCache();
14011
+ const cached = await this.readCacheAt(this.cacheFile);
13563
14012
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
13564
- this.payload = cached.payload;
13565
14013
  this.fetchedAt = new Date(cached.fetchedAt);
13566
14014
  return cached.payload;
13567
14015
  }
14016
+ if (overlayAvailable) {
14017
+ console.warn(
14018
+ `ModelsRegistry: models.dev unavailable (${err instanceof Error ? err.message : String(err)}); serving curated overlay only.`
14019
+ );
14020
+ return {};
14021
+ }
13568
14022
  throw err;
13569
14023
  }
13570
14024
  }
13571
- async refresh() {
14025
+ /** Fetch + cache the models.dev base. Throws on failure (used by `refresh`). */
14026
+ async refreshBase() {
13572
14027
  const res = await this.fetchImpl(this.url, {
13573
14028
  method: "GET",
13574
14029
  headers: { accept: "application/json" }
@@ -13577,7 +14032,6 @@ var DefaultModelsRegistry = class {
13577
14032
  throw new Error(`ModelsRegistry: HTTP ${res.status} fetching ${this.url}`);
13578
14033
  }
13579
14034
  const json = await res.json();
13580
- this.payload = json;
13581
14035
  this.fetchedAt = /* @__PURE__ */ new Date();
13582
14036
  const envelope = {
13583
14037
  fetchedAt: this.fetchedAt.toISOString(),
@@ -13587,6 +14041,68 @@ var DefaultModelsRegistry = class {
13587
14041
  await atomicWrite(this.cacheFile, JSON.stringify(envelope));
13588
14042
  return json;
13589
14043
  }
14044
+ /**
14045
+ * Resolve the curated overlay, memoised. Order: in-memory `overlay` →
14046
+ * fetched `overlayUrl` (cached, same TTL/stale rules) → `overlayFile` on
14047
+ * disk. Never throws — a missing/broken overlay yields `{}`.
14048
+ */
14049
+ async loadOverlay(opts = {}) {
14050
+ if (this.overlayPayload && !opts.force) return this.overlayPayload;
14051
+ if (this.overlay) {
14052
+ this.overlayPayload = this.overlay;
14053
+ return this.overlayPayload;
14054
+ }
14055
+ const fetched = await this.loadOverlayFromUrl(opts);
14056
+ if (fetched) {
14057
+ this.overlayPayload = fetched;
14058
+ return fetched;
14059
+ }
14060
+ const fromFile = await this.readOverlayFile();
14061
+ this.overlayPayload = fromFile ?? {};
14062
+ return this.overlayPayload;
14063
+ }
14064
+ async loadOverlayFromUrl(opts) {
14065
+ if (!this.overlayUrl || !this.overlayCacheFile) return void 0;
14066
+ if (!opts.force) {
14067
+ const cached = await this.readCacheAt(this.overlayCacheFile);
14068
+ if (cached && this.isFresh(cached.fetchedAt)) return cached.payload;
14069
+ }
14070
+ try {
14071
+ const res = await this.fetchImpl(this.overlayUrl, {
14072
+ method: "GET",
14073
+ headers: { accept: "application/json" }
14074
+ });
14075
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
14076
+ const json = await res.json();
14077
+ const envelope = {
14078
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
14079
+ url: this.overlayUrl,
14080
+ payload: json
14081
+ };
14082
+ await atomicWrite(this.overlayCacheFile, JSON.stringify(envelope)).catch(() => {
14083
+ });
14084
+ return json;
14085
+ } catch {
14086
+ const cached = await this.readCacheAt(this.overlayCacheFile);
14087
+ if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
14088
+ return void 0;
14089
+ }
14090
+ }
14091
+ async readOverlayFile() {
14092
+ if (!this.overlayFile) return void 0;
14093
+ try {
14094
+ const raw = await fsp.readFile(this.overlayFile, "utf8");
14095
+ return JSON.parse(raw);
14096
+ } catch {
14097
+ return void 0;
14098
+ }
14099
+ }
14100
+ async refresh() {
14101
+ const base = await this.refreshBase();
14102
+ const overlay = await this.loadOverlay({ force: true });
14103
+ this.payload = mergeModelsPayload(base, overlay);
14104
+ return this.payload;
14105
+ }
13590
14106
  async listProviders() {
13591
14107
  const payload = await this.load();
13592
14108
  return Object.values(payload).map((p) => this.resolveProvider(p));
@@ -13627,7 +14143,7 @@ var DefaultModelsRegistry = class {
13627
14143
  }
13628
14144
  async ageSeconds() {
13629
14145
  if (!this.fetchedAt) {
13630
- const cached = await this.readCache();
14146
+ const cached = await this.readCacheAt(this.cacheFile);
13631
14147
  if (!cached) return Number.POSITIVE_INFINITY;
13632
14148
  return (Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3;
13633
14149
  }
@@ -13651,9 +14167,9 @@ var DefaultModelsRegistry = class {
13651
14167
  isWithinMaxStaleAge(fetchedAtIso) {
13652
14168
  return Date.now() - new Date(fetchedAtIso).getTime() < this.maxStaleAgeMs;
13653
14169
  }
13654
- async readCache() {
14170
+ async readCacheAt(file) {
13655
14171
  try {
13656
- const raw = await fsp.readFile(this.cacheFile, "utf8");
14172
+ const raw = await fsp.readFile(file, "utf8");
13657
14173
  return JSON.parse(raw);
13658
14174
  } catch {
13659
14175
  return void 0;
@@ -17745,6 +18261,6 @@ var allServers = () => ({
17745
18261
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
17746
18262
  });
17747
18263
 
17748
- export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
18264
+ export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, createSessionEventBridge, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, resolveAuditLevel, resolveSessionLoggingConfig, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
17749
18265
  //# sourceMappingURL=index.js.map
17750
18266
  //# sourceMappingURL=index.js.map