@wrongstack/core 0.291.2 → 0.292.1

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 (136) hide show
  1. package/dist/coordination/director-tools.d.ts.map +1 -1
  2. package/dist/coordination/index.d.ts +1 -1
  3. package/dist/coordination/index.d.ts.map +1 -1
  4. package/dist/coordination/index.js +276 -152
  5. package/dist/coordination/index.js.map +4 -4
  6. package/dist/coordination/mailbox-http-router.d.ts +38 -0
  7. package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
  8. package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
  9. package/dist/core/agent-loop.d.ts.map +1 -1
  10. package/dist/core/agent-response.d.ts.map +1 -1
  11. package/dist/core/context.d.ts +10 -0
  12. package/dist/core/context.d.ts.map +1 -1
  13. package/dist/core/fallback-model.d.ts.map +1 -1
  14. package/dist/core/system-prompt-builder.d.ts +20 -1
  15. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  16. package/dist/defaults/index.d.ts +1 -0
  17. package/dist/defaults/index.d.ts.map +1 -1
  18. package/dist/defaults/index.js +3137 -2441
  19. package/dist/defaults/index.js.map +4 -4
  20. package/dist/execution/auto-compaction-middleware.d.ts +34 -2
  21. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  22. package/dist/execution/compaction-core.d.ts +59 -0
  23. package/dist/execution/compaction-core.d.ts.map +1 -1
  24. package/dist/execution/compactor.d.ts.map +1 -1
  25. package/dist/execution/error-handler.d.ts +7 -0
  26. package/dist/execution/error-handler.d.ts.map +1 -1
  27. package/dist/execution/index.d.ts +1 -0
  28. package/dist/execution/index.d.ts.map +1 -1
  29. package/dist/execution/index.js +4684 -3924
  30. package/dist/execution/index.js.map +4 -4
  31. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  32. package/dist/execution/model-runtime.d.ts.map +1 -1
  33. package/dist/execution/one-shot-llm.d.ts.map +1 -1
  34. package/dist/execution/selective-compactor.d.ts +10 -0
  35. package/dist/execution/selective-compactor.d.ts.map +1 -1
  36. package/dist/execution/subagent-compaction.d.ts +24 -0
  37. package/dist/execution/subagent-compaction.d.ts.map +1 -0
  38. package/dist/hq/alerts.d.ts +5 -0
  39. package/dist/hq/alerts.d.ts.map +1 -1
  40. package/dist/hq/auth-audit.d.ts +115 -0
  41. package/dist/hq/auth-audit.d.ts.map +1 -0
  42. package/dist/hq/auth-store.d.ts +98 -2
  43. package/dist/hq/auth-store.d.ts.map +1 -1
  44. package/dist/hq/brain-bridge.d.ts +2 -8
  45. package/dist/hq/brain-bridge.d.ts.map +1 -1
  46. package/dist/hq/bridge-context.d.ts +74 -0
  47. package/dist/hq/bridge-context.d.ts.map +1 -0
  48. package/dist/hq/cost-bridge.d.ts +4 -10
  49. package/dist/hq/cost-bridge.d.ts.map +1 -1
  50. package/dist/hq/fleet-bridge.d.ts +2 -8
  51. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  52. package/dist/hq/index.d.ts +2 -0
  53. package/dist/hq/index.d.ts.map +1 -1
  54. package/dist/hq/index.js +358 -222
  55. package/dist/hq/index.js.map +4 -4
  56. package/dist/hq/protocol/session.d.ts +13 -0
  57. package/dist/hq/protocol/session.d.ts.map +1 -1
  58. package/dist/hq/tool-bridge.d.ts +2 -8
  59. package/dist/hq/tool-bridge.d.ts.map +1 -1
  60. package/dist/hq/worktree-bridge.d.ts +2 -8
  61. package/dist/hq/worktree-bridge.d.ts.map +1 -1
  62. package/dist/index.d.ts +4 -2
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +6332 -4853
  65. package/dist/index.js.map +4 -4
  66. package/dist/infrastructure/index.js +32 -15
  67. package/dist/infrastructure/index.js.map +2 -2
  68. package/dist/infrastructure/provider-cache-ledger.d.ts +48 -0
  69. package/dist/infrastructure/provider-cache-ledger.d.ts.map +1 -0
  70. package/dist/infrastructure/token-counter.d.ts +4 -2
  71. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  72. package/dist/kernel/events/brain-events.d.ts +2 -0
  73. package/dist/kernel/events/brain-events.d.ts.map +1 -1
  74. package/dist/kernel/events/memory-events.d.ts +102 -0
  75. package/dist/kernel/events/memory-events.d.ts.map +1 -1
  76. package/dist/kernel/events/session-events.d.ts +35 -0
  77. package/dist/kernel/events/session-events.d.ts.map +1 -1
  78. package/dist/models/index.js +161 -78
  79. package/dist/models/index.js.map +3 -3
  80. package/dist/models/llm-selector.d.ts.map +1 -1
  81. package/dist/notifications/index.d.ts +40 -0
  82. package/dist/notifications/index.d.ts.map +1 -0
  83. package/dist/notifications/notifier-impl.d.ts +34 -0
  84. package/dist/notifications/notifier-impl.d.ts.map +1 -0
  85. package/dist/notifications/notifier.d.ts +95 -0
  86. package/dist/notifications/notifier.d.ts.map +1 -0
  87. package/dist/notifications/types.d.ts +111 -0
  88. package/dist/notifications/types.d.ts.map +1 -0
  89. package/dist/plugin/api.d.ts +10 -1
  90. package/dist/plugin/api.d.ts.map +1 -1
  91. package/dist/registry/provider-registry.d.ts +1 -1
  92. package/dist/registry/provider-registry.d.ts.map +1 -1
  93. package/dist/security/index.js +15 -15
  94. package/dist/security/index.js.map +3 -3
  95. package/dist/skills/index.js +80 -80
  96. package/dist/skills/index.js.map +3 -3
  97. package/dist/storage/config-loader.d.ts +3 -5
  98. package/dist/storage/config-loader.d.ts.map +1 -1
  99. package/dist/storage/index.js +325 -259
  100. package/dist/storage/index.js.map +4 -4
  101. package/dist/storage/memory-consolidator.d.ts +14 -2
  102. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  103. package/dist/storage/queue-store.d.ts +2 -0
  104. package/dist/storage/queue-store.d.ts.map +1 -1
  105. package/dist/tools/index.js +14 -7
  106. package/dist/tools/index.js.map +3 -3
  107. package/dist/tools/mcp-control.d.ts +1 -1
  108. package/dist/types/config.d.ts +64 -20
  109. package/dist/types/config.d.ts.map +1 -1
  110. package/dist/types/index.js +65 -35
  111. package/dist/types/index.js.map +3 -3
  112. package/dist/types/plugin.d.ts +11 -0
  113. package/dist/types/plugin.d.ts.map +1 -1
  114. package/dist/types/provider.d.ts +31 -0
  115. package/dist/types/provider.d.ts.map +1 -1
  116. package/dist/types/token-counter.d.ts +8 -1
  117. package/dist/types/token-counter.d.ts.map +1 -1
  118. package/dist/utils/cache-key.d.ts +19 -0
  119. package/dist/utils/cache-key.d.ts.map +1 -0
  120. package/dist/utils/compaction-preview.d.ts +8 -0
  121. package/dist/utils/compaction-preview.d.ts.map +1 -0
  122. package/dist/utils/connectivity.d.ts +36 -0
  123. package/dist/utils/connectivity.d.ts.map +1 -0
  124. package/dist/utils/context-breakdown.d.ts +59 -0
  125. package/dist/utils/context-breakdown.d.ts.map +1 -0
  126. package/dist/utils/context-evidence.d.ts +22 -0
  127. package/dist/utils/context-evidence.d.ts.map +1 -1
  128. package/dist/utils/index.d.ts +3 -0
  129. package/dist/utils/index.d.ts.map +1 -1
  130. package/dist/utils/index.js +1761 -1541
  131. package/dist/utils/index.js.map +4 -4
  132. package/dist/utils/token-estimate.d.ts +27 -0
  133. package/dist/utils/token-estimate.d.ts.map +1 -1
  134. package/instructions/llm/memory-consolidator.md +19 -3
  135. package/instructions/system.md +10 -2
  136. package/package.json +2 -2
@@ -328,6 +328,112 @@ function expectDefined(value, label) {
328
328
  return value;
329
329
  }
330
330
 
331
+ // src/utils/message-invariants.ts
332
+ function repairToolUseAdjacency(messages) {
333
+ const removedToolUses = [];
334
+ const removedToolResults = [];
335
+ let removedMessages = 0;
336
+ let changed = false;
337
+ const out = [];
338
+ for (let i = 0; i < messages.length; i++) {
339
+ const original = expectDefined(messages[i]);
340
+ let msg = original;
341
+ if (hasToolUse(msg)) {
342
+ const nextIds = toolResultIds(messages[i + 1]);
343
+ const filtered = mapContent(msg, (blocks) => {
344
+ const next = [];
345
+ for (const block of blocks) {
346
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
347
+ removedToolUses.push(block.id);
348
+ changed = true;
349
+ continue;
350
+ }
351
+ next.push(block);
352
+ }
353
+ return next;
354
+ });
355
+ msg = filtered ?? msg;
356
+ }
357
+ if (hasToolResult(msg)) {
358
+ const allowed = toolUseIds(out[out.length - 1]);
359
+ const filtered = mapContent(msg, (blocks) => {
360
+ const next = [];
361
+ for (const block of blocks) {
362
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
363
+ removedToolResults.push(block.tool_use_id);
364
+ changed = true;
365
+ continue;
366
+ }
367
+ next.push(block);
368
+ }
369
+ return next;
370
+ });
371
+ msg = filtered ?? msg;
372
+ }
373
+ if (isEmptyMessage(msg)) {
374
+ removedMessages++;
375
+ changed = true;
376
+ continue;
377
+ }
378
+ out.push(msg);
379
+ }
380
+ return {
381
+ messages: changed ? out : messages,
382
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
383
+ };
384
+ }
385
+ function hasToolUse(msg) {
386
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
387
+ }
388
+ function hasToolResult(msg) {
389
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
390
+ }
391
+ function toolUseIds(msg) {
392
+ const ids = /* @__PURE__ */ new Set();
393
+ if (msg?.role !== "assistant") return ids;
394
+ for (const block of contentBlocks(msg)) {
395
+ if (block.type === "tool_use") ids.add(block.id);
396
+ }
397
+ return ids;
398
+ }
399
+ function toolResultIds(msg) {
400
+ const ids = /* @__PURE__ */ new Set();
401
+ if (msg?.role !== "user") return ids;
402
+ for (const block of contentBlocks(msg)) {
403
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
404
+ }
405
+ return ids;
406
+ }
407
+ function contentBlocks(msg) {
408
+ return msg && Array.isArray(msg.content) ? msg.content : [];
409
+ }
410
+ function mapContent(msg, fn) {
411
+ if (!Array.isArray(msg.content)) return msg;
412
+ const next = fn(msg.content);
413
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
414
+ return msg;
415
+ }
416
+ return { ...msg, content: next };
417
+ }
418
+ function hasMeaningfulContent(content) {
419
+ if (typeof content === "string") return content.trim().length > 0;
420
+ for (const block of content) {
421
+ if (block.type === "text") {
422
+ if (block.text.trim().length > 0) return true;
423
+ continue;
424
+ }
425
+ if (block.type === "thinking") {
426
+ if (block.thinking.trim().length > 0 || block.signature) return true;
427
+ continue;
428
+ }
429
+ return true;
430
+ }
431
+ return false;
432
+ }
433
+ function isEmptyMessage(msg) {
434
+ return !hasMeaningfulContent(msg.content);
435
+ }
436
+
331
437
  // src/utils/glob-expand.ts
332
438
  import * as fsp from "node:fs/promises";
333
439
  import { isAbsolute, resolve } from "node:path";
@@ -455,112 +561,6 @@ async function expandGlob(pattern) {
455
561
  return [...results];
456
562
  }
457
563
 
458
- // src/utils/message-invariants.ts
459
- function repairToolUseAdjacency(messages) {
460
- const removedToolUses = [];
461
- const removedToolResults = [];
462
- let removedMessages = 0;
463
- let changed = false;
464
- const out = [];
465
- for (let i = 0; i < messages.length; i++) {
466
- const original = expectDefined(messages[i]);
467
- let msg = original;
468
- if (hasToolUse(msg)) {
469
- const nextIds = toolResultIds(messages[i + 1]);
470
- const filtered = mapContent(msg, (blocks) => {
471
- const next = [];
472
- for (const block of blocks) {
473
- if (block.type === "tool_use" && !nextIds.has(block.id)) {
474
- removedToolUses.push(block.id);
475
- changed = true;
476
- continue;
477
- }
478
- next.push(block);
479
- }
480
- return next;
481
- });
482
- msg = filtered ?? msg;
483
- }
484
- if (hasToolResult(msg)) {
485
- const allowed = toolUseIds(out[out.length - 1]);
486
- const filtered = mapContent(msg, (blocks) => {
487
- const next = [];
488
- for (const block of blocks) {
489
- if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
490
- removedToolResults.push(block.tool_use_id);
491
- changed = true;
492
- continue;
493
- }
494
- next.push(block);
495
- }
496
- return next;
497
- });
498
- msg = filtered ?? msg;
499
- }
500
- if (isEmptyMessage(msg)) {
501
- removedMessages++;
502
- changed = true;
503
- continue;
504
- }
505
- out.push(msg);
506
- }
507
- return {
508
- messages: changed ? out : messages,
509
- report: { changed, removedToolUses, removedToolResults, removedMessages }
510
- };
511
- }
512
- function hasToolUse(msg) {
513
- return contentBlocks(msg).some((b) => b.type === "tool_use");
514
- }
515
- function hasToolResult(msg) {
516
- return contentBlocks(msg).some((b) => b.type === "tool_result");
517
- }
518
- function toolUseIds(msg) {
519
- const ids = /* @__PURE__ */ new Set();
520
- if (msg?.role !== "assistant") return ids;
521
- for (const block of contentBlocks(msg)) {
522
- if (block.type === "tool_use") ids.add(block.id);
523
- }
524
- return ids;
525
- }
526
- function toolResultIds(msg) {
527
- const ids = /* @__PURE__ */ new Set();
528
- if (msg?.role !== "user") return ids;
529
- for (const block of contentBlocks(msg)) {
530
- if (block.type === "tool_result") ids.add(block.tool_use_id);
531
- }
532
- return ids;
533
- }
534
- function contentBlocks(msg) {
535
- return msg && Array.isArray(msg.content) ? msg.content : [];
536
- }
537
- function mapContent(msg, fn) {
538
- if (!Array.isArray(msg.content)) return msg;
539
- const next = fn(msg.content);
540
- if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
541
- return msg;
542
- }
543
- return { ...msg, content: next };
544
- }
545
- function hasMeaningfulContent(content) {
546
- if (typeof content === "string") return content.trim().length > 0;
547
- for (const block of content) {
548
- if (block.type === "text") {
549
- if (block.text.trim().length > 0) return true;
550
- continue;
551
- }
552
- if (block.type === "thinking") {
553
- if (block.thinking.trim().length > 0 || block.signature) return true;
554
- continue;
555
- }
556
- return true;
557
- }
558
- return false;
559
- }
560
- function isEmptyMessage(msg) {
561
- return !hasMeaningfulContent(msg.content);
562
- }
563
-
564
564
  // src/utils/safe-json.ts
565
565
  function safeParse(input, maxBytes = 5e6) {
566
566
  if (Buffer.byteLength(input, "utf8") > maxBytes) {
@@ -5235,8 +5235,8 @@ function createDelegateTool(opts) {
5235
5235
  };
5236
5236
  return {
5237
5237
  name: "delegate",
5238
- description: "Hand a piece of work to a subagent and wait for its result. Has own context, LLM call, auto-extending budget. Can continue remaining work with a fresh subagent on partial completion (maxHandoffs, default 1). Workers cannot recursively spawn. Use roster roles when possible; otherwise pass `name` + `task`.",
5239
- usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Override `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
5238
+ description: "Hand a piece of work to a subagent and block until it returns. This call is synchronous: the leader's iteration pauses for the full duration of the subagent's run. (Multiple `delegate` calls fired in the same assistant turn still parallelize through the provider's parallel-tool-call surface, but each one eats wall-clock time \u2014 so for fan-out you actually control, reach for the async path below.) Use `delegate` when your next step genuinely needs the subagent's verdict \u2014 a review, a fact-check, a sign-off. Has own context, own LLM call, auto-extending budget, and a partial-completion handoff path (maxHandoffs, default 1). Workers cannot recursively spawn.\n\n**Do NOT use `delegate` for fan-out you control.** Multiple sequential `delegate` calls each block the leader, wasting wall-clock time. For independent investigations you want to run in parallel \u2014 security scan + bug hunt + perf review on the same PR \u2014 use the async tool family: `spawn_subagent` to create each worker (returns a `subagentId` immediately), `assign_task` to queue work on it (returns a `taskId` immediately), then the `await_tasks` tool with `{mode: 'any'}` to fold the first useful result into the next decision while the rest keep churning. Reach for `delegate` only when the result gates your next move.",
5239
+ usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Raise `maxHandoffs` (default 1, cap 8) for multi-day or multi-refactor tasks; pass larger `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed. For parallel work, use `spawn_subagent` + `assign_task` + `await_tasks` instead.",
5240
5240
  permission: "auto",
5241
5241
  mutating: false,
5242
5242
  managesOwnTimeout: true,
@@ -6511,8 +6511,8 @@ function makeSpawnTool(director, roster) {
6511
6511
  };
6512
6512
  return {
6513
6513
  name: "spawn_subagent",
6514
- description: "Create a new subagent under this director. Returns the subagent id.",
6515
- usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
6514
+ description: "Create a new subagent under this director (own LLM context, own budget). NON-BLOCKING: returns a `subagentId` immediately without triggering any model call. Pair with `assign_task` to send work and `await_tasks` to retrieve the result later \u2014 this is the async-delegation pattern that lets the leader keep working while the subagent runs in its own context.",
6515
+ usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`. Use this instead of `delegate` when you want to fan out to multiple subagents or keep the leader unblocked while work runs in parallel.",
6516
6516
  permission: "auto",
6517
6517
  mutating: false,
6518
6518
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
@@ -6994,7 +6994,7 @@ function makeAssignTool(director) {
6994
6994
  };
6995
6995
  return {
6996
6996
  name: "assign_task",
6997
- description: "Hand a task to a previously spawned subagent. Returns the task id.",
6997
+ description: "Queue a task on a previously spawned subagent. NON-BLOCKING: returns a `taskId` IMMEDIATELY \u2014 the subagent processes the task on its next iteration with its own LLM budget. The `taskId` is the durable handle for retrieving the result later via `await_tasks`, `roll_up`, or `ask_result`. Many `assign_task` calls can be in flight in parallel against the same or different subagents. This is the primary tool for fan-out work; do NOT use `delegate` to spawn multiple investigations sequentially.",
6998
6998
  permission: "auto",
6999
6999
  mutating: false,
7000
7000
  capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
@@ -7521,7 +7521,7 @@ function resultErrorText(result) {
7521
7521
  function makeAwaitTasksTool(director) {
7522
7522
  return {
7523
7523
  name: "await_tasks",
7524
- description: 'Wait for assigned tasks. mode:"all" (default) blocks until EVERY named task completes and returns all results. mode:"any" returns as soon as AT LEAST ONE completes \u2014 use it for independent tasks so you can handle each finisher immediately (reassign work, spawn helpers) instead of idling on the slowest; call again with the returned `pending` ids to pick up the next finisher.',
7524
+ description: 'Block until one or more `taskId`s complete, then return their results. The subagents keep running in the background \u2014 only the leader\'s iteration pauses, which is the point: this is the correct tool to retrieve a result you started earlier with `assign_task`. mode:"all" (default) blocks until EVERY named task completes. mode:"any" returns as soon as AT LEAST ONE completes \u2014 use it for independent tasks so you can handle each finisher immediately (reassign work, spawn helpers) instead of idling on the slowest; call again with the returned `pending` ids to pick up the next finisher. The pattern "fan out via assign_task, then await_tasks({mode:\'any\'})" is the async replacement for serial `delegate` calls.',
7525
7525
  permission: "auto",
7526
7526
  mutating: false,
7527
7527
  capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
@@ -8192,9 +8192,10 @@ function hashStr(s) {
8192
8192
  }
8193
8193
 
8194
8194
  // src/types/provider.ts
8195
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
8195
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
8196
8196
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
8197
- var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
8197
+ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)/i;
8198
+ var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
8198
8199
  function classifyProviderError(status, body, message) {
8199
8200
  const type = body?.type;
8200
8201
  const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
@@ -8202,6 +8203,9 @@ function classifyProviderError(status, body, message) {
8202
8203
  if (status === 408) return "timeout";
8203
8204
  if (status === 599) return "stream_hang";
8204
8205
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
8206
+ if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
8207
+ return "quota_exhausted";
8208
+ }
8205
8209
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
8206
8210
  if (type === "overloaded_error" || status === 529) return "overloaded";
8207
8211
  if (status >= 500) return "server";
@@ -17354,6 +17358,8 @@ import { timingSafeEqual } from "node:crypto";
17354
17358
  var MAILBOX_HTTP_MAX_BODY_BYTES = 256 * 1024;
17355
17359
  var MAILBOX_HTTP_RATE_LIMIT_PER_MINUTE = 120;
17356
17360
  var MAILBOX_HTTP_RATE_LIMIT_WINDOW_MS = 6e4;
17361
+ var MAILBOX_HTTP_DEFAULT_MAX_AGE_MS = 60 * 60 * 1e3;
17362
+ var MAILBOX_HTTP_MAX_AGE_CEILING_MS = 7 * 24 * 60 * 60 * 1e3;
17357
17363
  function authorizeMailboxBearerToken(request, expectedToken) {
17358
17364
  const header = request.headers.authorization;
17359
17365
  if (typeof header !== "string") return { allowed: false };
@@ -17397,6 +17403,7 @@ var MailboxHttpRateLimiter = class {
17397
17403
  };
17398
17404
  function createMailboxHttpRouter(options) {
17399
17405
  const maxBodyBytes = options.maxBodyBytes ?? MAILBOX_HTTP_MAX_BODY_BYTES;
17406
+ const defaultMaxAgeMs = options.defaultMaxAgeMs ?? void 0;
17400
17407
  const closeSseStreams = /* @__PURE__ */ new Set();
17401
17408
  return {
17402
17409
  async handle(request, response, routePath) {
@@ -17411,15 +17418,17 @@ function createMailboxHttpRouter(options) {
17411
17418
  if (!access2.allowed) {
17412
17419
  const forwardedFor = request.headers["x-forwarded-for"];
17413
17420
  const clientIp = (Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor)?.split(",")[0]?.trim() ?? request.socket?.remoteAddress ?? "unknown";
17414
- console.warn(JSON.stringify({
17415
- level: "warn",
17416
- event: "mailbox.http_auth_failure",
17417
- message: `Mailbox HTTP auth rejected for ${request.method ?? "?"} ${request.url ?? "?"} from ${clientIp}`,
17418
- method: request.method,
17419
- url: request.url,
17420
- clientIp,
17421
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
17422
- }));
17421
+ console.warn(
17422
+ JSON.stringify({
17423
+ level: "warn",
17424
+ event: "mailbox.http_auth_failure",
17425
+ message: `Mailbox HTTP auth rejected for ${request.method ?? "?"} ${request.url ?? "?"} from ${clientIp}`,
17426
+ method: request.method,
17427
+ url: request.url,
17428
+ clientIp,
17429
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17430
+ })
17431
+ );
17423
17432
  writeJson(
17424
17433
  response,
17425
17434
  access2.status ?? 401,
@@ -17446,7 +17455,9 @@ function createMailboxHttpRouter(options) {
17446
17455
  method,
17447
17456
  url,
17448
17457
  maxBodyBytes,
17449
- closeSseStreams
17458
+ defaultMaxAgeMs,
17459
+ closeSseStreams,
17460
+ routePath
17450
17461
  );
17451
17462
  } catch (error) {
17452
17463
  const code = error instanceof MailboxHttpValidationError ? "VALIDATION_ERROR" : "INTERNAL_ERROR";
@@ -17465,85 +17476,134 @@ function createMailboxHttpRouter(options) {
17465
17476
  }
17466
17477
  };
17467
17478
  }
17468
- async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, method, url, maxBodyBytes, closeSseStreams) {
17469
- if (method === "POST" && url === "/mailbox/send") {
17479
+ async function dispatchMailboxRoute(mailbox, eventEmitter, request, response, method, url, maxBodyBytes, defaultMaxAgeMs, closeSseStreams, routePath) {
17480
+ if (routePath !== void 0 && routePath.indexOf("?") === 0) {
17481
+ throw validationError(`routePath must not start with '?' (got ${JSON.stringify(routePath)})`);
17482
+ }
17483
+ const queryIndex = url.indexOf("?");
17484
+ const path20 = queryIndex === -1 ? url : url.slice(0, queryIndex);
17485
+ if (method === "POST" && path20 === "/mailbox/send") {
17470
17486
  const input = validateSend(await readJsonBody(request, maxBodyBytes));
17471
17487
  writeJson(response, 201, await mailbox.send(input));
17472
17488
  return;
17473
17489
  }
17474
- if (method === "POST" && url === "/mailbox/query") {
17490
+ if (method === "POST" && path20 === "/mailbox/query") {
17491
+ const queryContext = parseSinceMs(url, defaultMaxAgeMs);
17492
+ if ("error" in queryContext) {
17493
+ writeJson(response, 400, { error: queryContext.error });
17494
+ return;
17495
+ }
17475
17496
  const messages = await mailbox.query(validateQuery(await readJsonBody(request, maxBodyBytes)));
17476
- writeJson(response, 200, { data: messages, count: messages.length });
17497
+ const filtered = filterMailboxMessagesByTimestamp(messages, queryContext.minTimestampIso);
17498
+ writeJson(response, 200, { data: filtered, count: filtered.length });
17477
17499
  return;
17478
17500
  }
17479
- if (method === "POST" && url === "/mailbox/check") {
17501
+ if (method === "POST" && path20 === "/mailbox/check") {
17502
+ const queryContext = parseSinceMs(url, defaultMaxAgeMs);
17503
+ if ("error" in queryContext) {
17504
+ writeJson(response, 400, { error: queryContext.error });
17505
+ return;
17506
+ }
17480
17507
  const result = await checkMailbox(
17481
17508
  mailbox,
17482
- validateCheck(await readJsonBody(request, maxBodyBytes))
17509
+ validateCheck(await readJsonBody(request, maxBodyBytes)),
17510
+ queryContext.minTimestampIso
17483
17511
  );
17484
17512
  writeJson(response, 200, result);
17485
17513
  return;
17486
17514
  }
17487
- if (method === "POST" && url === "/mailbox/ack") {
17515
+ if (method === "POST" && path20 === "/mailbox/ack") {
17488
17516
  const updated = await mailbox.ack(validateAck(await readJsonBody(request, maxBodyBytes)));
17489
17517
  writeJson(response, 200, { updated });
17490
17518
  return;
17491
17519
  }
17492
- if (method === "POST" && url === "/mailbox/ack-many") {
17520
+ if (method === "POST" && path20 === "/mailbox/ack-many") {
17493
17521
  const updated = await mailbox.ackMany(
17494
17522
  validateAckMany(await readJsonBody(request, maxBodyBytes))
17495
17523
  );
17496
17524
  writeJson(response, 200, { updated, count: updated.length });
17497
17525
  return;
17498
17526
  }
17499
- if (method === "POST" && url === "/mailbox/unread-count") {
17527
+ if (method === "POST" && path20 === "/mailbox/unread-count") {
17500
17528
  const body = await readJsonBody(request, maxBodyBytes);
17501
- writeJson(response, 200, { count: await mailbox.unreadCount(requireString(body, "forAgentId")) });
17529
+ writeJson(response, 200, {
17530
+ count: await mailbox.unreadCount(requireString(body, "forAgentId"))
17531
+ });
17502
17532
  return;
17503
17533
  }
17504
- if (method === "POST" && url === "/mailbox/agents/register") {
17505
- await mailbox.registerAgent(validateAgentRegistration(await readJsonBody(request, maxBodyBytes)));
17534
+ if (method === "POST" && path20 === "/mailbox/agents/register") {
17535
+ await mailbox.registerAgent(
17536
+ validateAgentRegistration(await readJsonBody(request, maxBodyBytes))
17537
+ );
17506
17538
  writeJson(response, 200, { ok: true });
17507
17539
  return;
17508
17540
  }
17509
- if (method === "POST" && url === "/mailbox/agents/heartbeat") {
17541
+ if (method === "POST" && path20 === "/mailbox/agents/heartbeat") {
17510
17542
  await mailbox.heartbeat(validateAgentHeartbeat(await readJsonBody(request, maxBodyBytes)));
17511
17543
  writeJson(response, 200, { ok: true });
17512
17544
  return;
17513
17545
  }
17514
- if (method === "POST" && url === "/mailbox/register-client") {
17515
- await mailbox.registerClient(validateClientRegistration(await readJsonBody(request, maxBodyBytes)));
17546
+ if (method === "POST" && path20 === "/mailbox/register-client") {
17547
+ await mailbox.registerClient(
17548
+ validateClientRegistration(await readJsonBody(request, maxBodyBytes))
17549
+ );
17516
17550
  writeJson(response, 200, { ok: true });
17517
17551
  return;
17518
17552
  }
17519
- if (method === "POST" && url === "/mailbox/heartbeat") {
17520
- await mailbox.clientHeartbeat(validateClientHeartbeat(await readJsonBody(request, maxBodyBytes)));
17553
+ if (method === "POST" && path20 === "/mailbox/heartbeat") {
17554
+ await mailbox.clientHeartbeat(
17555
+ validateClientHeartbeat(await readJsonBody(request, maxBodyBytes))
17556
+ );
17521
17557
  writeJson(response, 200, { ok: true });
17522
17558
  return;
17523
17559
  }
17524
- if (method === "POST" && url === "/mailbox/purge-clients") {
17560
+ if (method === "POST" && path20 === "/mailbox/purge-clients") {
17525
17561
  writeJson(response, 200, { ok: true, purged: await mailbox.purgeClients() });
17526
17562
  return;
17527
17563
  }
17528
- if (method === "GET" && url === "/mailbox/agents") {
17564
+ if (method === "GET" && path20 === "/mailbox/agents") {
17529
17565
  const agents = await mailbox.getAgentStatuses();
17530
17566
  writeJson(response, 200, { data: agents, count: agents.length });
17531
17567
  return;
17532
17568
  }
17533
- if (method === "GET" && url === "/mailbox/agents/online") {
17569
+ if (method === "GET" && path20 === "/mailbox/agents/online") {
17534
17570
  const agents = await mailbox.getOnlineAgents();
17535
17571
  writeJson(response, 200, { data: agents, count: agents.length });
17536
17572
  return;
17537
17573
  }
17538
- if (method === "GET" && url === "/mailbox/events" && eventEmitter) {
17539
- handleSse(request, response, eventEmitter, closeSseStreams);
17574
+ if (method === "GET" && path20 === "/mailbox/events" && eventEmitter) {
17575
+ const queryContext = parseSinceMs(url, defaultMaxAgeMs);
17576
+ if ("error" in queryContext) {
17577
+ writeJson(response, 400, { error: queryContext.error });
17578
+ return;
17579
+ }
17580
+ handleSse(request, response, eventEmitter, queryContext.minTimestampIso, closeSseStreams);
17540
17581
  return;
17541
17582
  }
17542
17583
  writeJson(response, 404, {
17543
17584
  error: { code: "NOT_FOUND", message: `no route for ${method} ${url}` }
17544
17585
  });
17545
17586
  }
17546
- function handleSse(request, response, eventEmitter, closeSseStreams) {
17587
+ function extractEventTimestamp(event) {
17588
+ if (event === null || typeof event !== "object") return void 0;
17589
+ const top = event.timestamp;
17590
+ if (typeof top === "string") return top;
17591
+ const nestedKeys = ["messageSent", "ackUpdated"];
17592
+ for (const key of nestedKeys) {
17593
+ const nested = event[key];
17594
+ if (nested !== null && typeof nested === "object") {
17595
+ const inner = nested.timestamp;
17596
+ if (typeof inner === "string") return inner;
17597
+ }
17598
+ }
17599
+ return void 0;
17600
+ }
17601
+ function isEventOlderThan(event, minTimestampIso) {
17602
+ const eventTimestamp = extractEventTimestamp(event);
17603
+ if (eventTimestamp === void 0) return false;
17604
+ return eventTimestamp < minTimestampIso;
17605
+ }
17606
+ function handleSse(request, response, eventEmitter, minTimestampIso, closeSseStreams) {
17547
17607
  response.writeHead(200, {
17548
17608
  "Content-Type": "text/event-stream",
17549
17609
  "Cache-Control": "no-store",
@@ -17553,6 +17613,9 @@ function handleSse(request, response, eventEmitter, closeSseStreams) {
17553
17613
  response.write(": connected\n\n");
17554
17614
  const unsubscribe = eventEmitter.subscribe((event) => {
17555
17615
  try {
17616
+ if (minTimestampIso !== void 0 && isEventOlderThan(event, minTimestampIso)) {
17617
+ return;
17618
+ }
17556
17619
  response.write(`data: ${JSON.stringify(event)}
17557
17620
 
17558
17621
  `);
@@ -17662,6 +17725,52 @@ function optionalNumber(object, key) {
17662
17725
  }
17663
17726
  return value;
17664
17727
  }
17728
+ function parseSinceMs(url, defaultMaxAgeMs) {
17729
+ const queryStart = url.indexOf("?");
17730
+ if (queryStart === -1) return resolveDefault(defaultMaxAgeMs, Date.now());
17731
+ const params = new URLSearchParams(url.slice(queryStart + 1));
17732
+ if (!params.has("sinceMs")) return resolveDefault(defaultMaxAgeMs, Date.now());
17733
+ const raw = params.get("sinceMs");
17734
+ if (raw === null || raw === void 0 || raw === "") {
17735
+ return {
17736
+ error: {
17737
+ code: "VALIDATION_ERROR",
17738
+ message: 'query parameter "sinceMs" is required (integer in milliseconds) when present'
17739
+ }
17740
+ };
17741
+ }
17742
+ if (!/^\d+$/.test(raw)) {
17743
+ return {
17744
+ error: {
17745
+ code: "VALIDATION_ERROR",
17746
+ message: 'query parameter "sinceMs" must be a non-negative integer (milliseconds)'
17747
+ }
17748
+ };
17749
+ }
17750
+ const requestedMs = Number(raw);
17751
+ if (!Number.isFinite(requestedMs) || requestedMs > Number.MAX_SAFE_INTEGER) {
17752
+ return {
17753
+ error: {
17754
+ code: "VALIDATION_ERROR",
17755
+ message: `query parameter "sinceMs" is out of range (max ${Number.MAX_SAFE_INTEGER})`
17756
+ }
17757
+ };
17758
+ }
17759
+ const now = Date.now();
17760
+ if (requestedMs === 0) return { minTimestampIso: void 0 };
17761
+ const effectiveMs = Math.min(requestedMs, MAILBOX_HTTP_MAX_AGE_CEILING_MS);
17762
+ return { minTimestampIso: new Date(now - effectiveMs).toISOString() };
17763
+ }
17764
+ function resolveDefault(defaultMaxAgeMs, now) {
17765
+ if (defaultMaxAgeMs === void 0 || !Number.isFinite(defaultMaxAgeMs) || defaultMaxAgeMs < 0 || defaultMaxAgeMs === 0) {
17766
+ return { minTimestampIso: void 0 };
17767
+ }
17768
+ return { minTimestampIso: new Date(now - defaultMaxAgeMs).toISOString() };
17769
+ }
17770
+ function filterMailboxMessagesByTimestamp(messages, minTimestampIso) {
17771
+ if (minTimestampIso === void 0) return messages.slice();
17772
+ return messages.filter((message) => message.timestamp >= minTimestampIso);
17773
+ }
17665
17774
  function optionalBoolean(object, key) {
17666
17775
  if (typeof object !== "object" || object === null) return void 0;
17667
17776
  const value = object[key];
@@ -17782,7 +17891,7 @@ function validateQuery(body) {
17782
17891
  if (incompleteOnly !== void 0) result.incompleteOnly = incompleteOnly;
17783
17892
  return result;
17784
17893
  }
17785
- async function checkMailbox(mailbox, input) {
17894
+ async function checkMailbox(mailbox, input, minTimestampIso) {
17786
17895
  const limit = input.limit ?? 20;
17787
17896
  const markRead = input.markRead ?? true;
17788
17897
  const completed = input.completed ?? false;
@@ -17790,8 +17899,9 @@ async function checkMailbox(mailbox, input) {
17790
17899
  const batches = await Promise.all(
17791
17900
  targets.map((to) => mailbox.query({ to, unreadBy: input.agentId, limit }))
17792
17901
  );
17902
+ const withinWindow = filterMailboxMessagesByTimestamp(batches.flat(), minTimestampIso);
17793
17903
  const seen = /* @__PURE__ */ new Set();
17794
- const messages = batches.flat().filter((message) => {
17904
+ const messages = withinWindow.filter((message) => {
17795
17905
  if (seen.has(message.id) || message.from === input.agentId) return false;
17796
17906
  seen.add(message.id);
17797
17907
  return true;
@@ -19354,19 +19464,24 @@ var ProviderModelStatusTracker = class {
19354
19464
  let newState = s.state;
19355
19465
  let reason = "";
19356
19466
  const quotaExhausted = kind === "quota_exhausted" || isQuotaExhausted(kind, status, message);
19467
+ const endpointUnreachable = isEndpointUnreachable(kind, status, message);
19357
19468
  if (quotaExhausted) {
19358
19469
  newState = "blocked";
19359
19470
  reason = "quota_exhausted";
19360
19471
  s.stateExpiresAt = now + this.cfg.quotaBlockDurationMs;
19472
+ } else if (endpointUnreachable) {
19473
+ newState = "blocked";
19474
+ reason = "endpoint_unreachable";
19475
+ s.stateExpiresAt = now + this.cfg.quotaBlockDurationMs;
19361
19476
  }
19362
- if (!quotaExhausted && s.state === "healthy") {
19477
+ if (!quotaExhausted && !endpointUnreachable && s.state === "healthy") {
19363
19478
  if (s.consecutiveFailures >= this.cfg.degradedAfterFailures) {
19364
19479
  newState = "degraded";
19365
19480
  reason = `consecutive_failures_${s.consecutiveFailures}`;
19366
19481
  s.stateExpiresAt = now + this.cfg.degradedDurationMs;
19367
19482
  }
19368
19483
  }
19369
- if (!quotaExhausted && (s.state === "degraded" || s.state === "healthy")) {
19484
+ if (!quotaExhausted && !endpointUnreachable && (s.state === "degraded" || s.state === "healthy")) {
19370
19485
  if (s.rateLimitHits >= this.cfg.blockAfterRateLimitHits) {
19371
19486
  newState = "blocked";
19372
19487
  reason = `rate_limit_threshold_${this.cfg.blockAfterRateLimitHits}`;
@@ -19696,13 +19811,20 @@ function unpairKey(key) {
19696
19811
  if (idx === -1) return [key, ""];
19697
19812
  return [key.slice(0, idx), key.slice(idx + 1)];
19698
19813
  }
19699
- var QUOTA_EXHAUSTED_RE2 = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit/i;
19814
+ var QUOTA_EXHAUSTED_RE2 = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)|rate[-_\s]*limit[-_\s]*exceeded/i;
19700
19815
  function isQuotaExhausted(kind, status, message) {
19701
19816
  if (status === 402) return true;
19702
19817
  if (kind !== "rate_limit" && kind !== "quota_exhausted" && kind !== "invalid_request" && kind !== "auth")
19703
19818
  return false;
19704
19819
  return QUOTA_EXHAUSTED_RE2.test(message);
19705
19820
  }
19821
+ var ENDPOINT_UNREACHABLE_RE = /(?:upstream|origin|backend|endpoint)[-_\s]*(?:unreachable|refused|connect(?:ion)?[-_\s]*(?:refused|error|fail)|unavailable|down|timeout)|no[-_\s]*(?:healthy|valid)[-_\s]*upstream|cannot[-_\s]*connect|connection[-_\s]*(?:refused|reset|closed|timed?[-_\s]out)/i;
19822
+ function isEndpointUnreachable(kind, status, message) {
19823
+ if (status !== 502 && status !== 503 && status !== 0) return false;
19824
+ if (kind !== "server" && kind !== "network" && kind !== "overloaded" && kind !== "timeout")
19825
+ return false;
19826
+ return ENDPOINT_UNREACHABLE_RE.test(message);
19827
+ }
19706
19828
  function safeCount(value) {
19707
19829
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : 0;
19708
19830
  }
@@ -22911,6 +23033,8 @@ export {
22911
23033
  MAILBOX_HEALTH_DEFAULT_FROM,
22912
23034
  MAILBOX_HEALTH_DEFAULT_INTERVAL_MS,
22913
23035
  MAILBOX_HEALTH_DEFAULT_TIMEOUT_MS,
23036
+ MAILBOX_HTTP_DEFAULT_MAX_AGE_MS,
23037
+ MAILBOX_HTTP_MAX_AGE_CEILING_MS,
22914
23038
  MAILBOX_HTTP_MAX_BODY_BYTES,
22915
23039
  MAILBOX_HTTP_RATE_LIMIT_PER_MINUTE,
22916
23040
  MAILBOX_HTTP_RATE_LIMIT_WINDOW_MS,