@wrongstack/core 0.289.0 → 0.291.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 (177) hide show
  1. package/dist/boot.d.ts.map +1 -1
  2. package/dist/chronicle/domain-adapter.d.ts.map +1 -1
  3. package/dist/chronicle/index.d.ts +1 -1
  4. package/dist/chronicle/index.d.ts.map +1 -1
  5. package/dist/chronicle/index.js +964 -245
  6. package/dist/chronicle/index.js.map +3 -3
  7. package/dist/chronicle/journal.d.ts +35 -3
  8. package/dist/chronicle/journal.d.ts.map +1 -1
  9. package/dist/chronicle/query.d.ts +8 -5
  10. package/dist/chronicle/query.d.ts.map +1 -1
  11. package/dist/chronicle/tool-adapter.d.ts.map +1 -1
  12. package/dist/coordination/agent-subagent-runner.d.ts +6 -0
  13. package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
  14. package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
  15. package/dist/coordination/brain.d.ts +2 -1
  16. package/dist/coordination/brain.d.ts.map +1 -1
  17. package/dist/coordination/collab-debug.d.ts +0 -9
  18. package/dist/coordination/collab-debug.d.ts.map +1 -1
  19. package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
  20. package/dist/coordination/dep-watcher.d.ts +2 -1
  21. package/dist/coordination/dep-watcher.d.ts.map +1 -1
  22. package/dist/coordination/director/director-collab.d.ts +1 -1
  23. package/dist/coordination/director/director-collab.d.ts.map +1 -1
  24. package/dist/coordination/director-tools.d.ts.map +1 -1
  25. package/dist/coordination/director.d.ts +19 -1
  26. package/dist/coordination/director.d.ts.map +1 -1
  27. package/dist/coordination/fleet-event-validation.d.ts +0 -7
  28. package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
  29. package/dist/coordination/fleet-spawn.d.ts.map +1 -1
  30. package/dist/coordination/fleet-supervisor.d.ts +1 -1
  31. package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
  32. package/dist/coordination/fleet.d.ts +0 -44
  33. package/dist/coordination/fleet.d.ts.map +1 -1
  34. package/dist/coordination/index.d.ts +2 -2
  35. package/dist/coordination/index.d.ts.map +1 -1
  36. package/dist/coordination/index.js +733 -111
  37. package/dist/coordination/index.js.map +4 -4
  38. package/dist/coordination/knowledge-graph.d.ts +2 -1
  39. package/dist/coordination/knowledge-graph.d.ts.map +1 -1
  40. package/dist/coordination/large-answer-store.d.ts +0 -6
  41. package/dist/coordination/large-answer-store.d.ts.map +1 -1
  42. package/dist/coordination/mail-tools.d.ts.map +1 -1
  43. package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
  44. package/dist/coordination/mailbox-message-codec.d.ts +35 -0
  45. package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
  46. package/dist/coordination/mailbox-tool.d.ts +1 -1
  47. package/dist/coordination/mailbox-tool.d.ts.map +1 -1
  48. package/dist/coordination/mailbox-types.d.ts +151 -0
  49. package/dist/coordination/mailbox-types.d.ts.map +1 -1
  50. package/dist/coordination/provider-status-tracker.d.ts +27 -2
  51. package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
  52. package/dist/coordination/subagent-budget.d.ts +2 -1
  53. package/dist/coordination/subagent-budget.d.ts.map +1 -1
  54. package/dist/core/agent-response.d.ts +2 -1
  55. package/dist/core/agent-response.d.ts.map +1 -1
  56. package/dist/core/agent-tools.d.ts.map +1 -1
  57. package/dist/core/context.d.ts +51 -0
  58. package/dist/core/context.d.ts.map +1 -1
  59. package/dist/core/fallback-model.d.ts +1 -1
  60. package/dist/core/fallback-model.d.ts.map +1 -1
  61. package/dist/core/fallback-profile-manager.d.ts +1 -1
  62. package/dist/core/fallback-profile-manager.d.ts.map +1 -1
  63. package/dist/core/fleet-pulse.d.ts +2 -1
  64. package/dist/core/fleet-pulse.d.ts.map +1 -1
  65. package/dist/core/iteration-limit.d.ts +2 -1
  66. package/dist/core/iteration-limit.d.ts.map +1 -1
  67. package/dist/core/mailbox-loop.d.ts +11 -1
  68. package/dist/core/mailbox-loop.d.ts.map +1 -1
  69. package/dist/core/model-availability-calendar.d.ts +27 -0
  70. package/dist/core/model-availability-calendar.d.ts.map +1 -0
  71. package/dist/core/system-prompt-builder.d.ts +2 -1
  72. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  73. package/dist/defaults/index.js +1459 -560
  74. package/dist/defaults/index.js.map +4 -4
  75. package/dist/execution/auto-compaction-middleware.d.ts +3 -2
  76. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  77. package/dist/execution/enhance-recovery.d.ts +8 -0
  78. package/dist/execution/enhance-recovery.d.ts.map +1 -1
  79. package/dist/execution/error-handler.d.ts +2 -2
  80. package/dist/execution/error-handler.d.ts.map +1 -1
  81. package/dist/execution/index.js +643 -134
  82. package/dist/execution/index.js.map +4 -4
  83. package/dist/execution/intelligent-compactor.d.ts +0 -16
  84. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  85. package/dist/execution/one-shot-llm.d.ts.map +1 -1
  86. package/dist/execution/retry-policy.d.ts +0 -13
  87. package/dist/execution/retry-policy.d.ts.map +1 -1
  88. package/dist/execution/skill-loader.d.ts.map +1 -1
  89. package/dist/execution/tool-executor.d.ts.map +1 -1
  90. package/dist/hq/auth-store.d.ts.map +1 -1
  91. package/dist/hq/index.js +39 -1
  92. package/dist/hq/index.js.map +3 -3
  93. package/dist/index.d.ts +32 -30
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +22263 -19999
  96. package/dist/index.js.map +4 -4
  97. package/dist/infrastructure/index.js +59 -5
  98. package/dist/infrastructure/index.js.map +2 -2
  99. package/dist/kernel/events/agent-events.d.ts +2 -0
  100. package/dist/kernel/events/agent-events.d.ts.map +1 -1
  101. package/dist/kernel/events/file-events.d.ts +46 -0
  102. package/dist/kernel/events/file-events.d.ts.map +1 -1
  103. package/dist/kernel/events/memory-events.d.ts +9 -0
  104. package/dist/kernel/events/memory-events.d.ts.map +1 -1
  105. package/dist/kernel/events/provider-events.d.ts +2 -1
  106. package/dist/kernel/events/provider-events.d.ts.map +1 -1
  107. package/dist/kernel/events/tool-events.d.ts +25 -0
  108. package/dist/kernel/events/tool-events.d.ts.map +1 -1
  109. package/dist/mailbox-attach.d.ts.map +1 -1
  110. package/dist/plugins/auto-review-plugin.d.ts +10 -0
  111. package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
  112. package/dist/plugins/chimera-plugin.d.ts +5 -0
  113. package/dist/plugins/chimera-plugin.d.ts.map +1 -1
  114. package/dist/security/index.d.ts +1 -0
  115. package/dist/security/index.d.ts.map +1 -1
  116. package/dist/security/index.js +317 -9
  117. package/dist/security/index.js.map +4 -4
  118. package/dist/security/kanban-boundary.d.ts +10 -0
  119. package/dist/security/kanban-boundary.d.ts.map +1 -0
  120. package/dist/security/permission-policy.d.ts +8 -1
  121. package/dist/security/permission-policy.d.ts.map +1 -1
  122. package/dist/storage/config-loader.d.ts +15 -0
  123. package/dist/storage/config-loader.d.ts.map +1 -1
  124. package/dist/storage/index.d.ts +2 -2
  125. package/dist/storage/index.d.ts.map +1 -1
  126. package/dist/storage/index.js +627 -428
  127. package/dist/storage/index.js.map +4 -4
  128. package/dist/storage/memory-consolidator.d.ts +49 -4
  129. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  130. package/dist/storage/memory-graph-backend.d.ts +11 -0
  131. package/dist/storage/memory-graph-backend.d.ts.map +1 -1
  132. package/dist/storage/provider-config-watcher.d.ts +17 -0
  133. package/dist/storage/provider-config-watcher.d.ts.map +1 -1
  134. package/dist/storage/session-event-bridge.d.ts +2 -2
  135. package/dist/storage/session-event-bridge.d.ts.map +1 -1
  136. package/dist/tools/fallback-manage-tools.d.ts.map +1 -1
  137. package/dist/tools/index.js +166 -26
  138. package/dist/tools/index.js.map +3 -3
  139. package/dist/types/config.d.ts +12 -4
  140. package/dist/types/config.d.ts.map +1 -1
  141. package/dist/types/file-event-record.d.ts +68 -0
  142. package/dist/types/file-event-record.d.ts.map +1 -0
  143. package/dist/types/index.d.ts +1 -0
  144. package/dist/types/index.d.ts.map +1 -1
  145. package/dist/types/index.js +13 -8
  146. package/dist/types/index.js.map +3 -3
  147. package/dist/types/permission.d.ts +38 -0
  148. package/dist/types/permission.d.ts.map +1 -1
  149. package/dist/types/provider.d.ts +1 -1
  150. package/dist/types/provider.d.ts.map +1 -1
  151. package/dist/types/session.d.ts +27 -0
  152. package/dist/types/session.d.ts.map +1 -1
  153. package/dist/types/tool-executor.d.ts +2 -0
  154. package/dist/types/tool-executor.d.ts.map +1 -1
  155. package/dist/utils/config-backup.d.ts +20 -0
  156. package/dist/utils/config-backup.d.ts.map +1 -0
  157. package/dist/utils/index.d.ts +12 -11
  158. package/dist/utils/index.d.ts.map +1 -1
  159. package/dist/utils/index.js +436 -290
  160. package/dist/utils/index.js.map +4 -4
  161. package/dist/utils/message-invariants.d.ts +12 -0
  162. package/dist/utils/message-invariants.d.ts.map +1 -1
  163. package/dist/utils/safe-json.d.ts +1 -1
  164. package/dist/utils/safe-json.d.ts.map +1 -1
  165. package/dist/utils/term.d.ts +6 -0
  166. package/dist/utils/term.d.ts.map +1 -1
  167. package/dist/utils/tool-wire-compact.d.ts +9 -0
  168. package/dist/utils/tool-wire-compact.d.ts.map +1 -1
  169. package/dist/utils/wstack-paths.d.ts +21 -1
  170. package/dist/utils/wstack-paths.d.ts.map +1 -1
  171. package/instructions/agents/ios.md +113 -0
  172. package/instructions/llm/memory-consolidator.md +4 -12
  173. package/instructions/system.md +4 -1
  174. package/package.json +3 -3
  175. package/skills/auto-review/SKILL.md +176 -0
  176. package/skills/mnemosyne/SKILL.md +156 -0
  177. package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
@@ -32,8 +32,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
32
32
  }
33
33
  let mode;
34
34
  try {
35
- const stat3 = await fs.stat(targetPath);
36
- mode = stat3.mode & 511;
35
+ const stat4 = await fs.stat(targetPath);
36
+ mode = stat4.mode & 511;
37
37
  } catch {
38
38
  mode = opts.mode;
39
39
  }
@@ -76,7 +76,7 @@ async function renameWithRetry(from, to) {
76
76
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
77
77
  throw err;
78
78
  }
79
- await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
79
+ await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
80
80
  }
81
81
  }
82
82
  throw lastErr;
@@ -181,10 +181,10 @@ function validateAgainstSchema(value, schema) {
181
181
  return { ok: errors.length === 0, errors };
182
182
  }
183
183
  var MAX_SCHEMA_DEPTH = 64;
184
- function walk(value, schema, path11, errors, depth) {
184
+ function walk(value, schema, path12, errors, depth) {
185
185
  if (depth > MAX_SCHEMA_DEPTH) {
186
186
  errors.push({
187
- path: path11 || "<root>",
187
+ path: path12 || "<root>",
188
188
  message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
189
189
  });
190
190
  return;
@@ -192,7 +192,7 @@ function walk(value, schema, path11, errors, depth) {
192
192
  if (schema.enum !== void 0) {
193
193
  if (!enumIncludes(schema.enum, value)) {
194
194
  errors.push({
195
- path: path11 || "<root>",
195
+ path: path12 || "<root>",
196
196
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
197
197
  });
198
198
  return;
@@ -201,7 +201,7 @@ function walk(value, schema, path11, errors, depth) {
201
201
  if (typeof schema.type === "string") {
202
202
  if (!checkType(value, schema.type)) {
203
203
  errors.push({
204
- path: path11 || "<root>",
204
+ path: path12 || "<root>",
205
205
  message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
206
206
  });
207
207
  return;
@@ -213,7 +213,7 @@ function walk(value, schema, path11, errors, depth) {
213
213
  if (!(req in obj)) {
214
214
  const expected = schema.properties?.[req]?.type;
215
215
  errors.push({
216
- path: joinPath(path11, req),
216
+ path: joinPath(path12, req),
217
217
  message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
218
218
  });
219
219
  }
@@ -221,14 +221,14 @@ function walk(value, schema, path11, errors, depth) {
221
221
  if (schema.properties) {
222
222
  for (const [key, subSchema] of Object.entries(schema.properties)) {
223
223
  if (key in obj) {
224
- walk(obj[key], subSchema, joinPath(path11, key), errors, depth + 1);
224
+ walk(obj[key], subSchema, joinPath(path12, key), errors, depth + 1);
225
225
  }
226
226
  }
227
227
  }
228
228
  }
229
229
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
230
230
  for (let i = 0; i < value.length; i++) {
231
- walk(value[i], schema.items, `${path11}[${i}]`, errors, depth + 1);
231
+ walk(value[i], schema.items, `${path12}[${i}]`, errors, depth + 1);
232
232
  }
233
233
  }
234
234
  }
@@ -445,9 +445,23 @@ function mapContent(msg, fn) {
445
445
  }
446
446
  return { ...msg, content: next };
447
447
  }
448
+ function hasMeaningfulContent(content) {
449
+ if (typeof content === "string") return content.trim().length > 0;
450
+ for (const block of content) {
451
+ if (block.type === "text") {
452
+ if (block.text.trim().length > 0) return true;
453
+ continue;
454
+ }
455
+ if (block.type === "thinking") {
456
+ if (block.thinking.trim().length > 0 || block.signature) return true;
457
+ continue;
458
+ }
459
+ return true;
460
+ }
461
+ return false;
462
+ }
448
463
  function isEmptyMessage(msg) {
449
- if (typeof msg.content === "string") return msg.content.trim().length === 0;
450
- return msg.content.length === 0;
464
+ return !hasMeaningfulContent(msg.content);
451
465
  }
452
466
 
453
467
  // src/utils/regex-guard.ts
@@ -488,7 +502,7 @@ function compileUserRegex(pattern, flags) {
488
502
 
489
503
  // src/utils/safe-json.ts
490
504
  function safeParse(input, maxBytes = 5e6) {
491
- if (input.length > maxBytes) {
505
+ if (Buffer.byteLength(input, "utf8") > maxBytes) {
492
506
  return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
493
507
  }
494
508
  try {
@@ -501,84 +515,21 @@ function safeParse(input, maxBytes = 5e6) {
501
515
  }
502
516
  }
503
517
 
518
+ // src/utils/sleep.ts
519
+ function sleep(ms) {
520
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
521
+ }
522
+
504
523
  // src/utils/slug.ts
505
524
  function slugify(name, fallback = "prompt", maxLen = 64) {
506
525
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
507
526
  }
508
527
 
509
- // src/utils/ulid.ts
510
- import { randomBytes as randomBytes2 } from "node:crypto";
511
- var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
512
- var ENCODING_LEN = ENCODING.length;
513
- var TIME_LEN = 10;
514
- var RANDOM_LEN = 16;
515
- function encodeTime(now, len) {
516
- let mod;
517
- let str = "";
518
- for (let i = len - 1; i >= 0; i--) {
519
- mod = now % ENCODING_LEN;
520
- str = ENCODING[mod] + str;
521
- now = (now - mod) / ENCODING_LEN;
522
- }
523
- return str;
524
- }
525
- function encodeRandom(len) {
526
- const bytes = randomBytes2(len);
527
- let str = "";
528
- for (let i = 0; i < len; i++) {
529
- str += ENCODING[bytes[i] % ENCODING_LEN];
530
- }
531
- return str;
532
- }
533
- function ulid(seedTime = Date.now()) {
534
- return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
535
- }
536
-
537
- // src/utils/sleep.ts
538
- function sleep(ms) {
539
- return new Promise((resolve4) => setTimeout(resolve4, ms));
540
- }
541
-
542
528
  // src/utils/string.ts
543
529
  function truncate(s, max) {
544
530
  return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
545
531
  }
546
532
 
547
- // src/utils/tool-subject.ts
548
- var GLOB_METACHARACTERS = /[*?[\]]/g;
549
- function escapeGlobSubject(value) {
550
- return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
551
- }
552
- function normalizePathSubject(value) {
553
- return escapeGlobSubject(value.replace(/\\/g, "/"));
554
- }
555
- function isPathSubjectKey(subjectKey) {
556
- return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
557
- }
558
- function subjectForToolInput(toolName, input, subjectKey) {
559
- if (!input || typeof input !== "object") return void 0;
560
- const obj = input;
561
- if (subjectKey) {
562
- const value = obj[subjectKey];
563
- if (typeof value === "string") {
564
- return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
565
- }
566
- }
567
- if (toolName === "bash" && typeof obj.command === "string") {
568
- return escapeGlobSubject(obj.command);
569
- }
570
- if (typeof obj.path === "string") {
571
- return normalizePathSubject(obj.path);
572
- }
573
- if (typeof obj.url === "string") {
574
- return escapeGlobSubject(obj.url);
575
- }
576
- if (typeof obj.name === "string") {
577
- return escapeGlobSubject(obj.name);
578
- }
579
- return void 0;
580
- }
581
-
582
533
  // src/utils/tool-wire-compact.ts
583
534
  var TOOL_DESCRIPTION_MAX_CHARS = 400;
584
535
  var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
@@ -595,9 +546,11 @@ function compactToolDefinitionForWire(tool, opts = {}) {
595
546
  tool.description ?? "",
596
547
  opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
597
548
  ),
598
- inputSchema: compactSchemaDescriptions(
599
- tool.inputSchema,
600
- opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
549
+ inputSchema: normalizeTopLevelToolSchema(
550
+ compactSchemaDescriptions(
551
+ tool.inputSchema,
552
+ opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
553
+ )
601
554
  )
602
555
  };
603
556
  if (useDefaultOptions && typeof tool === "object" && tool !== null) {
@@ -605,6 +558,44 @@ function compactToolDefinitionForWire(tool, opts = {}) {
605
558
  }
606
559
  return compact;
607
560
  }
561
+ function normalizeTopLevelToolSchema(schema) {
562
+ const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
563
+ (entry) => Array.isArray(entry.branches)
564
+ );
565
+ if (combinators.length === 0) return schema;
566
+ const out = { ...schema, type: "object" };
567
+ delete out["oneOf"];
568
+ delete out["anyOf"];
569
+ delete out["allOf"];
570
+ const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
571
+ let required = stringSet(schema["required"]);
572
+ for (const { keyword, branches } of combinators) {
573
+ const objectBranches = branches.filter(isRecord);
574
+ for (const branch of objectBranches) {
575
+ if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
576
+ }
577
+ const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
578
+ if (keyword === "allOf") {
579
+ for (const fields of branchRequired) for (const field of fields) required.add(field);
580
+ } else if (branchRequired.length > 0) {
581
+ const common = new Set(
582
+ [...branchRequired[0]].filter(
583
+ (field) => branchRequired.slice(1).every((fields) => fields.has(field))
584
+ )
585
+ );
586
+ required = /* @__PURE__ */ new Set([...required, ...common]);
587
+ }
588
+ }
589
+ out["properties"] = properties;
590
+ if (required.size > 0) out["required"] = [...required];
591
+ else delete out["required"];
592
+ return out;
593
+ }
594
+ function stringSet(value) {
595
+ return new Set(
596
+ Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
597
+ );
598
+ }
608
599
  function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
609
600
  const compact = compactSchemaNode(schema, maxDescriptionChars);
610
601
  return isRecord(compact) ? compact : { type: "object", properties: {} };
@@ -1499,6 +1490,69 @@ function resolveToolResultRenderMode(modes, toolName) {
1499
1490
  return normalizeToolResultRenderMode(modes?.[toolName]) ?? DEFAULT_TOOL_RESULT_RENDER_MODE;
1500
1491
  }
1501
1492
 
1493
+ // src/utils/tool-subject.ts
1494
+ var GLOB_METACHARACTERS = /[*?[\]]/g;
1495
+ function escapeGlobSubject(value) {
1496
+ return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
1497
+ }
1498
+ function normalizePathSubject(value) {
1499
+ return escapeGlobSubject(value.replace(/\\/g, "/"));
1500
+ }
1501
+ function isPathSubjectKey(subjectKey) {
1502
+ return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
1503
+ }
1504
+ function subjectForToolInput(toolName, input, subjectKey) {
1505
+ if (!input || typeof input !== "object") return void 0;
1506
+ const obj = input;
1507
+ if (subjectKey) {
1508
+ const value = obj[subjectKey];
1509
+ if (typeof value === "string") {
1510
+ return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
1511
+ }
1512
+ }
1513
+ if (toolName === "bash" && typeof obj.command === "string") {
1514
+ return escapeGlobSubject(obj.command);
1515
+ }
1516
+ if (typeof obj.path === "string") {
1517
+ return normalizePathSubject(obj.path);
1518
+ }
1519
+ if (typeof obj.url === "string") {
1520
+ return escapeGlobSubject(obj.url);
1521
+ }
1522
+ if (typeof obj.name === "string") {
1523
+ return escapeGlobSubject(obj.name);
1524
+ }
1525
+ return void 0;
1526
+ }
1527
+
1528
+ // src/utils/ulid.ts
1529
+ import { randomBytes as randomBytes2 } from "node:crypto";
1530
+ var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1531
+ var ENCODING_LEN = ENCODING.length;
1532
+ var TIME_LEN = 10;
1533
+ var RANDOM_LEN = 16;
1534
+ function encodeTime(now, len) {
1535
+ let mod;
1536
+ let str = "";
1537
+ for (let i = len - 1; i >= 0; i--) {
1538
+ mod = now % ENCODING_LEN;
1539
+ str = ENCODING[mod] + str;
1540
+ now = (now - mod) / ENCODING_LEN;
1541
+ }
1542
+ return str;
1543
+ }
1544
+ function encodeRandom(len) {
1545
+ const bytes = randomBytes2(len);
1546
+ let str = "";
1547
+ for (let i = 0; i < len; i++) {
1548
+ str += ENCODING[bytes[i] % ENCODING_LEN];
1549
+ }
1550
+ return str;
1551
+ }
1552
+ function ulid(seedTime = Date.now()) {
1553
+ return encodeTime(seedTime, TIME_LEN) + encodeRandom(RANDOM_LEN);
1554
+ }
1555
+
1502
1556
  // src/utils/wstack-paths.ts
1503
1557
  import { createHash } from "node:crypto";
1504
1558
  import * as fs2 from "node:fs";
@@ -1551,6 +1605,27 @@ function resolveWstackPaths(opts) {
1551
1605
  homeDir,
1552
1606
  configDir: globalRoot,
1553
1607
  globalConfig: path2.join(globalRoot, "config.json"),
1608
+ profilesDir: path2.join(globalRoot, "profiles"),
1609
+ profileConfig: (name) => {
1610
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
1611
+ return path2.join(globalRoot, "profiles", safe || "default", "config.json");
1612
+ },
1613
+ profileStatuslineConfig: (name) => {
1614
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
1615
+ return path2.join(globalRoot, "profiles", safe || "default", "statusline.json");
1616
+ },
1617
+ profileModeConfig: (name) => {
1618
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
1619
+ return path2.join(globalRoot, "profiles", safe || "default", "mode.json");
1620
+ },
1621
+ profileProviderStatus: (name) => {
1622
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
1623
+ return path2.join(globalRoot, "profiles", safe || "default", "provider-status.json");
1624
+ },
1625
+ profileUpdateCache: (name) => {
1626
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
1627
+ return path2.join(globalRoot, "profiles", safe || "default", "update-cache.json");
1628
+ },
1554
1629
  secretsKey: path2.join(globalRoot, ".key"),
1555
1630
  globalMemory: path2.join(globalRoot, "memory.md"),
1556
1631
  globalSkills: path2.join(globalRoot, "skills"),
@@ -1590,6 +1665,7 @@ function resolveWstackPaths(opts) {
1590
1665
  projectAutophase: path2.join(projectDir, "autophase"),
1591
1666
  projectSddBoards: path2.join(projectDir, "sdd-boards"),
1592
1667
  syncConfig: path2.join(globalRoot, "sync.json"),
1668
+ configHistoryDir: path2.join(globalRoot, "config-history"),
1593
1669
  projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
1594
1670
  };
1595
1671
  }
@@ -2559,18 +2635,20 @@ function isTextBlock(b) {
2559
2635
  // src/types/provider.ts
2560
2636
  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;
2561
2637
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
2638
+ 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;
2562
2639
  function classifyProviderError(status, body, message) {
2563
2640
  const type = body?.type;
2641
+ const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
2564
2642
  if (status === 0) return "network";
2565
2643
  if (status === 408) return "timeout";
2566
2644
  if (status === 599) return "stream_hang";
2645
+ if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
2567
2646
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
2568
2647
  if (type === "overloaded_error" || status === 529) return "overloaded";
2569
2648
  if (status >= 500) return "server";
2570
2649
  if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
2571
2650
  return "auth";
2572
2651
  }
2573
- const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
2574
2652
  if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
2575
2653
  if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
2576
2654
  return "context_overflow";
@@ -2583,6 +2661,7 @@ function isRetryableKind(kind) {
2583
2661
  }
2584
2662
  var RETRYABLE_BY_KIND = {
2585
2663
  rate_limit: true,
2664
+ quota_exhausted: false,
2586
2665
  overloaded: true,
2587
2666
  server: true,
2588
2667
  timeout: true,
@@ -2599,6 +2678,7 @@ function isFallbackWorthy(kind) {
2599
2678
  }
2600
2679
  var FALLBACK_WORTHY_BY_KIND = {
2601
2680
  rate_limit: true,
2681
+ quota_exhausted: true,
2602
2682
  overloaded: true,
2603
2683
  server: true,
2604
2684
  timeout: true,
@@ -2677,6 +2757,7 @@ var KIND_TO_CODE = {
2677
2757
  network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2678
2758
  timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
2679
2759
  rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
2760
+ quota_exhausted: ERROR_CODES.PROVIDER_RATE_LIMITED,
2680
2761
  auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
2681
2762
  overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
2682
2763
  context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
@@ -2690,6 +2771,84 @@ function kindToCode(kind) {
2690
2771
  return KIND_TO_CODE[kind];
2691
2772
  }
2692
2773
 
2774
+ // src/core/model-availability-calendar.ts
2775
+ function logicalCalendarTarget(providerId, model) {
2776
+ if (providerId !== "omniroute") return { providerId, model };
2777
+ const slash = model.indexOf("/");
2778
+ return slash > 0 && slash < model.length - 1 ? { providerId: model.slice(0, slash), model: model.slice(slash + 1) } : { providerId, model };
2779
+ }
2780
+ var WEEKDAYS = {
2781
+ Sun: 0,
2782
+ Mon: 1,
2783
+ Tue: 2,
2784
+ Wed: 3,
2785
+ Thu: 4,
2786
+ Fri: 5,
2787
+ Sat: 6
2788
+ };
2789
+ function minuteOfDay(value) {
2790
+ const match = /^(\d{2}):(\d{2})$/.exec(value);
2791
+ if (!match) return void 0;
2792
+ const hour = Number(match[1]);
2793
+ const minute = Number(match[2]);
2794
+ if (hour > 23 || minute > 59) return void 0;
2795
+ return hour * 60 + minute;
2796
+ }
2797
+ function clockAt(date, timezone) {
2798
+ try {
2799
+ const parts = new Intl.DateTimeFormat("en-US", {
2800
+ timeZone: timezone,
2801
+ weekday: "short",
2802
+ hour: "2-digit",
2803
+ minute: "2-digit",
2804
+ hourCycle: "h23"
2805
+ }).formatToParts(date);
2806
+ const weekday = parts.find((part) => part.type === "weekday")?.value;
2807
+ const hour = Number(parts.find((part) => part.type === "hour")?.value);
2808
+ const minute = Number(parts.find((part) => part.type === "minute")?.value);
2809
+ if (!weekday || WEEKDAYS[weekday] === void 0 || !Number.isFinite(hour + minute))
2810
+ return void 0;
2811
+ return { day: WEEKDAYS[weekday], minute: hour * 60 + minute };
2812
+ } catch {
2813
+ return void 0;
2814
+ }
2815
+ }
2816
+ function targetMatches(rule, providerId, model) {
2817
+ if (rule.provider && rule.provider !== providerId) return false;
2818
+ if (rule.model && rule.model !== model) return false;
2819
+ return Boolean(rule.provider || rule.model);
2820
+ }
2821
+ function timeMatches(rule, day, minute) {
2822
+ const start = minuteOfDay(rule.start);
2823
+ const end = minuteOfDay(rule.end);
2824
+ if (start === void 0 || end === void 0) return false;
2825
+ const days = rule.days?.length ? new Set(rule.days) : void 0;
2826
+ if (start === end) return !days || days.has(day);
2827
+ if (start < end) return (!days || days.has(day)) && minute >= start && minute < end;
2828
+ if (minute >= start) return !days || days.has(day);
2829
+ const previousDay = (day + 6) % 7;
2830
+ return minute < end && (!days || days.has(previousDay));
2831
+ }
2832
+ function evaluateModelCalendar(rules, providerId, model, at = /* @__PURE__ */ new Date()) {
2833
+ ({ providerId, model } = logicalCalendarTarget(providerId, model));
2834
+ const allowRules = [];
2835
+ let allowMatched = false;
2836
+ for (const rule of rules ?? []) {
2837
+ if (rule.enabled === false || !targetMatches(rule, providerId, model)) continue;
2838
+ const clock = clockAt(at, rule.timezone);
2839
+ if (!clock || minuteOfDay(rule.start) === void 0 || minuteOfDay(rule.end) === void 0)
2840
+ continue;
2841
+ if (rule.mode === "allow_only") {
2842
+ allowRules.push(rule);
2843
+ if (timeMatches(rule, clock.day, clock.minute)) allowMatched = true;
2844
+ } else if (timeMatches(rule, clock.day, clock.minute)) {
2845
+ return { allowed: false, rule };
2846
+ }
2847
+ }
2848
+ if (allowRules.length > 0 && !allowMatched) return { allowed: false, rule: allowRules[0] };
2849
+ return { allowed: true };
2850
+ }
2851
+
2693
2852
  // src/core/fallback-model.ts
2694
2853
  function parseModelRef(ref) {
2695
2854
  const trimmed = ref.trim();
@@ -3157,9 +3316,9 @@ function normalizeOptions(options) {
3157
3316
  };
3158
3317
  });
3159
3318
  }
3160
- function requiredInstruction(path11) {
3161
- const text = readBundledInstructionText(path11);
3162
- if (!text) throw new Error(`Council instruction file is unavailable: ${path11}`);
3319
+ function requiredInstruction(path12) {
3320
+ const text = readBundledInstructionText(path12);
3321
+ if (!text) throw new Error(`Council instruction file is unavailable: ${path12}`);
3163
3322
  return text;
3164
3323
  }
3165
3324
 
@@ -5921,12 +6080,12 @@ function verifyFiles(tokens, files) {
5921
6080
  const violations = [];
5922
6081
  let onPalette = 0;
5923
6082
  let offPalette = 0;
5924
- for (const { path: path11, text } of files) {
6083
+ for (const { path: path12, text } of files) {
5925
6084
  const lines = text.split("\n");
5926
6085
  lines.forEach((lineText, i) => {
5927
6086
  const lineNo = i + 1;
5928
6087
  const flag = (snippet, reason) => {
5929
- violations.push({ file: path11, line: lineNo, snippet: snippet.slice(0, 80), reason });
6088
+ violations.push({ file: path12, line: lineNo, snippet: snippet.slice(0, 80), reason });
5930
6089
  };
5931
6090
  for (const re of [HEX_RE, FUNC_COLOR_RE]) {
5932
6091
  re.lastIndex = 0;
@@ -6214,16 +6373,20 @@ function buildRecoveryStrategies(opts) {
6214
6373
  const delayMs = err.body?.retryAfterMs ?? 5e3;
6215
6374
  const delay = Math.min(6e4, Math.max(1e3, delayMs));
6216
6375
  if (ctx?.signal) {
6217
- await new Promise((resolve4) => {
6376
+ await new Promise((resolve6) => {
6218
6377
  if (ctx.signal.aborted) {
6219
- resolve4();
6378
+ resolve6();
6220
6379
  return;
6221
6380
  }
6222
- const timer = setTimeout(resolve4, delay);
6223
- ctx.signal.addEventListener("abort", () => {
6224
- clearTimeout(timer);
6225
- resolve4();
6226
- }, { once: true });
6381
+ const timer = setTimeout(resolve6, delay);
6382
+ ctx.signal.addEventListener(
6383
+ "abort",
6384
+ () => {
6385
+ clearTimeout(timer);
6386
+ resolve6();
6387
+ },
6388
+ { once: true }
6389
+ );
6227
6390
  });
6228
6391
  } else {
6229
6392
  await new Promise((r) => setTimeout(r, delay));
@@ -6314,6 +6477,7 @@ function buildRecoveryStrategies(opts) {
6314
6477
  var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
6315
6478
  var HANDLER_KIND_BY_PROVIDER_KIND = {
6316
6479
  rate_limit: "rate_limit",
6480
+ quota_exhausted: "rate_limit",
6317
6481
  overloaded: "overloaded",
6318
6482
  server: "server",
6319
6483
  stream_hang: "server",
@@ -7923,13 +8087,13 @@ var SubagentBudget = class _SubagentBudget {
7923
8087
  if (!bus?.hasListenerFor("budget.threshold_reached")) {
7924
8088
  return Promise.resolve("stop");
7925
8089
  }
7926
- return new Promise((resolve4) => {
8090
+ return new Promise((resolve6) => {
7927
8091
  let resolved = false;
7928
8092
  const respond = (d) => {
7929
8093
  if (resolved) return;
7930
8094
  resolved = true;
7931
8095
  clearTimeout(fallback);
7932
- resolve4(d);
8096
+ resolve6(d);
7933
8097
  };
7934
8098
  const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
7935
8099
  const sessionId = this.currentSessionId();
@@ -8228,8 +8392,34 @@ function makeAgentSubagentRunner(opts) {
8228
8392
  const taskStartedAt = Date.now();
8229
8393
  const factoryResult = await opts.factory(ctx.config, task);
8230
8394
  const { agent, events } = factoryResult;
8395
+ const taskContext = task.context ?? {};
8396
+ const hasExplicitTelemetryTask = typeof taskContext["telemetryTaskId"] === "string";
8397
+ const telemetryTaskId = hasExplicitTelemetryTask ? taskContext["telemetryTaskId"] : task.id;
8398
+ const telemetryRunId = typeof taskContext["telemetryRunId"] === "string" ? taskContext["telemetryRunId"] : void 0;
8399
+ const telemetryBoardId = typeof taskContext["telemetryBoardId"] === "string" ? taskContext["telemetryBoardId"] : void 0;
8231
8400
  const agentContext = agent.ctx;
8232
- if (agentContext?.meta) agentContext.meta["subagentTaskId"] = task.id;
8401
+ if (agentContext?.meta) {
8402
+ agentContext.meta["subagentTaskId"] = task.id;
8403
+ const kanbanContext = taskContext["kanban"];
8404
+ if (kanbanContext && typeof kanbanContext === "object") {
8405
+ agentContext.meta["kanban"] = kanbanContext;
8406
+ }
8407
+ if (hasExplicitTelemetryTask) {
8408
+ agentContext.meta["telemetryTaskId"] = telemetryTaskId;
8409
+ if (telemetryRunId) agentContext.meta["telemetryRunId"] = telemetryRunId;
8410
+ }
8411
+ }
8412
+ if (hasExplicitTelemetryTask) {
8413
+ agentContext?.setCurrentKanbanTask?.(telemetryTaskId, telemetryBoardId);
8414
+ } else {
8415
+ const kanbanContext = taskContext["kanban"];
8416
+ if (kanbanContext && typeof kanbanContext === "object") {
8417
+ const kanban = kanbanContext;
8418
+ const boardId = typeof kanban["boardId"] === "string" ? kanban["boardId"] : void 0;
8419
+ const taskId = typeof kanban["taskId"] === "string" ? kanban["taskId"] : void 0;
8420
+ if (taskId) agentContext?.setCurrentKanbanTask?.(taskId, boardId);
8421
+ }
8422
+ }
8233
8423
  const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
8234
8424
  const aborter = new AbortController();
8235
8425
  ctx.budget._events = events;
@@ -8254,6 +8444,27 @@ function makeAgentSubagentRunner(opts) {
8254
8444
  const unsub = [];
8255
8445
  unsub.push(
8256
8446
  events.on("tool.executed", (e) => {
8447
+ if (hasExplicitTelemetryTask) {
8448
+ opts.hostEvents?.emit("subagent.tool_executed", {
8449
+ sessionId: e.sessionId,
8450
+ subagentId: ctx.subagentId,
8451
+ agentSessionId: e.sessionId,
8452
+ agentName: e.agentName ?? ctx.config.name,
8453
+ taskId: telemetryTaskId,
8454
+ runId: telemetryRunId,
8455
+ traceId: e.traceId,
8456
+ id: e.id,
8457
+ name: e.name,
8458
+ durationMs: e.durationMs,
8459
+ ok: e.ok,
8460
+ // Raw tool inputs may contain credentials or sensitive arguments.
8461
+ // Task telemetry crosses from the isolated worker bus to the shared
8462
+ // host bus, so expose only execution metadata at this boundary.
8463
+ outputBytes: e.outputBytes,
8464
+ outputTokens: e.outputTokens,
8465
+ outputLines: e.outputLines
8466
+ });
8467
+ }
8257
8468
  try {
8258
8469
  ctx.budget.recordToolCall();
8259
8470
  } catch (eb) {
@@ -8265,6 +8476,17 @@ function makeAgentSubagentRunner(opts) {
8265
8476
  lastToolFailed = null;
8266
8477
  }
8267
8478
  }),
8479
+ events.on("file.event", (e) => {
8480
+ if (!hasExplicitTelemetryTask) return;
8481
+ if (e.runId === telemetryRunId && e.taskId === telemetryTaskId) return;
8482
+ opts.hostEvents?.emit("file.event", {
8483
+ ...e,
8484
+ scope: "task",
8485
+ taskId: telemetryTaskId,
8486
+ boardId: telemetryBoardId,
8487
+ runId: telemetryRunId
8488
+ });
8489
+ }),
8268
8490
  events.on("provider.response", (e) => {
8269
8491
  try {
8270
8492
  ctx.budget.recordUsage(e.usage);
@@ -9558,6 +9780,40 @@ var DOMAIN_AGENTS = [
9558
9780
  "information architecture"
9559
9781
  ]
9560
9782
  }
9783
+ },
9784
+ {
9785
+ config: {
9786
+ id: "ios",
9787
+ name: "iOS",
9788
+ role: "ios",
9789
+ tools: [...TOOLS.build, "fetch"],
9790
+ prompt: agentPrompt("ios")
9791
+ },
9792
+ budget: HEAVY_BUDGET,
9793
+ capability: {
9794
+ phase: "domain",
9795
+ summary: "Apple-platform app development in Swift against the latest Xcode/iOS-SDK line: SwiftUI, UIKit, SwiftData, concurrency, accessibility, and App Store submission.",
9796
+ keywords: [
9797
+ "ios",
9798
+ "iphone",
9799
+ "ipad",
9800
+ "ipados",
9801
+ "watchos",
9802
+ "tvos",
9803
+ "visionos",
9804
+ "macos",
9805
+ "swift",
9806
+ "swiftui",
9807
+ "uikit",
9808
+ "xcode",
9809
+ "swiftdata",
9810
+ "app store",
9811
+ "app intents",
9812
+ "swift package manager",
9813
+ "cocoapods",
9814
+ "foundation models"
9815
+ ]
9816
+ }
9561
9817
  }
9562
9818
  ];
9563
9819
 
@@ -10166,6 +10422,7 @@ function classifySubagentError(err, hints = {}) {
10166
10422
  function providerErrorToSubagentError(err, message, cause) {
10167
10423
  switch (err.kind) {
10168
10424
  case "rate_limit":
10425
+ case "quota_exhausted":
10169
10426
  return {
10170
10427
  kind: "provider_rate_limit",
10171
10428
  message,
@@ -10665,7 +10922,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10665
10922
  taskIds.map((id) => {
10666
10923
  const cached = this.completedResults.find((r) => r.taskId === id);
10667
10924
  if (cached) return cached;
10668
- return new Promise((resolve4, reject) => {
10925
+ return new Promise((resolve6, reject) => {
10669
10926
  const timeout = setTimeout(() => {
10670
10927
  this.off("task.completed", handler);
10671
10928
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -10674,7 +10931,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10674
10931
  if (result.taskId === id) {
10675
10932
  clearTimeout(timeout);
10676
10933
  this.off("task.completed", handler);
10677
- resolve4(result);
10934
+ resolve6(result);
10678
10935
  }
10679
10936
  };
10680
10937
  this.on("task.completed", handler);
@@ -10699,13 +10956,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10699
10956
  const done = new Set(completed.map((r) => r.taskId));
10700
10957
  return { completed, pending: taskIds.filter((id) => !done.has(id)) };
10701
10958
  }
10702
- return new Promise((resolve4) => {
10959
+ return new Promise((resolve6) => {
10703
10960
  let timer;
10704
10961
  const handler = ({ result }) => {
10705
10962
  if (!ids.has(result.taskId)) return;
10706
10963
  if (timer) clearTimeout(timer);
10707
10964
  this.off("task.completed", handler);
10708
- resolve4({
10965
+ resolve6({
10709
10966
  completed: [result],
10710
10967
  pending: taskIds.filter((id) => id !== result.taskId)
10711
10968
  });
@@ -10713,7 +10970,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10713
10970
  if (opts?.timeoutMs !== void 0) {
10714
10971
  timer = setTimeout(() => {
10715
10972
  this.off("task.completed", handler);
10716
- resolve4({ completed: [], pending: [...taskIds], timedOut: true });
10973
+ resolve6({ completed: [], pending: [...taskIds], timedOut: true });
10717
10974
  }, opts.timeoutMs);
10718
10975
  }
10719
10976
  this.on("task.completed", handler);
@@ -10995,12 +11252,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10995
11252
  }
10996
11253
  return new Promise((resolveDecision) => {
10997
11254
  let settled = false;
10998
- const resolve4 = (d) => {
11255
+ const resolve6 = (d) => {
10999
11256
  if (settled) return;
11000
11257
  settled = true;
11001
11258
  resolveDecision(d);
11002
11259
  };
11003
- const fallback = setTimeout(() => resolve4("stop"), DECISION_TIMEOUT_MS);
11260
+ const fallback = setTimeout(() => resolve6("stop"), DECISION_TIMEOUT_MS);
11004
11261
  const sessionId = this.currentSessionId();
11005
11262
  budget._events?.emit("budget.threshold_reached", {
11006
11263
  ...sessionId ? { sessionId } : {},
@@ -11018,11 +11275,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11018
11275
  // disagreeing, resolves as a stop). Async grants still resolve.
11019
11276
  extend: (extra) => {
11020
11277
  clearTimeout(fallback);
11021
- queueMicrotask(() => resolve4({ extend: extra }));
11278
+ queueMicrotask(() => resolve6({ extend: extra }));
11022
11279
  },
11023
11280
  deny: () => {
11024
11281
  clearTimeout(fallback);
11025
- resolve4("stop");
11282
+ resolve6("stop");
11026
11283
  }
11027
11284
  });
11028
11285
  });
@@ -11690,6 +11947,7 @@ import { randomInt } from "node:crypto";
11690
11947
  var MAX_RETRY_AFTER_MS = 6e4;
11691
11948
  var MAX_ATTEMPTS_BY_KIND = {
11692
11949
  rate_limit: 5,
11950
+ quota_exhausted: 0,
11693
11951
  stream_hang: 5,
11694
11952
  // transient, worth retrying aggressively
11695
11953
  overloaded: 3,
@@ -12428,7 +12686,9 @@ var DefaultSkillLoader = class {
12428
12686
  const seen = /* @__PURE__ */ new Set();
12429
12687
  for (const { dir, source, originTool } of this.dirs) {
12430
12688
  try {
12431
- const entries = await fs5.readdir(dir, { withFileTypes: true });
12689
+ const entries = (await fs5.readdir(dir, { withFileTypes: true })).sort(
12690
+ (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
12691
+ );
12432
12692
  for (const e of entries) {
12433
12693
  if (!await entryIsDirectory(dir, e)) continue;
12434
12694
  const skillFile = path7.join(dir, e.name, "SKILL.md");
@@ -13055,7 +13315,7 @@ function readPolicy(ctx) {
13055
13315
  }
13056
13316
 
13057
13317
  // src/execution/tool-executor.ts
13058
- import { randomUUID as randomUUID3 } from "node:crypto";
13318
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
13059
13319
 
13060
13320
  // src/observability/process-telemetry.ts
13061
13321
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -13074,7 +13334,166 @@ function runWithNetworkTelemetry(context, run) {
13074
13334
  // src/execution/tool-executor.ts
13075
13335
  import { isDeepStrictEqual } from "node:util";
13076
13336
  import * as fs8 from "node:fs/promises";
13337
+ import * as path11 from "node:path";
13338
+
13339
+ // src/security/kanban-boundary.ts
13340
+ import { realpath } from "node:fs/promises";
13077
13341
  import * as path10 from "node:path";
13342
+ import {
13343
+ evaluateKanbanBoundaryOpaque,
13344
+ evaluateKanbanBoundaryPath,
13345
+ readBoard,
13346
+ resolveKanbanBoundaryLayers
13347
+ } from "@wrongstack/kanban";
13348
+ var PATH_KEYS = /* @__PURE__ */ new Set([
13349
+ "path",
13350
+ "paths",
13351
+ "file",
13352
+ "files",
13353
+ "directory",
13354
+ "dir",
13355
+ "cwd",
13356
+ "root",
13357
+ "baseDir",
13358
+ "out",
13359
+ "outputPath",
13360
+ "sourcePath",
13361
+ "targetPath",
13362
+ "destinationPath",
13363
+ "fromPath",
13364
+ "toPath",
13365
+ "worktreePath"
13366
+ ]);
13367
+ var TOOL_PATH_KEYS = {
13368
+ // `target` is an identifier in tools such as plan/task/document, but a
13369
+ // filesystem target in language_info. Keep ambiguous names tool-specific.
13370
+ language_info: ["target"]
13371
+ };
13372
+ async function evaluateToolKanbanBoundary(tool, input, ctx) {
13373
+ const identity = resolveKanbanIdentity(ctx);
13374
+ if (!identity.boardId) return { decision: "allow" };
13375
+ const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
13376
+ if (!board) return { decision: "allow" };
13377
+ const task = identity.taskId ? board.tasks.find((candidate) => candidate.id === identity.taskId) : void 0;
13378
+ const layers = resolveKanbanBoundaryLayers(board, task);
13379
+ if (layers.length === 0) return { decision: "allow" };
13380
+ const caps = tool.capabilities ?? [];
13381
+ const access2 = caps.includes("fs.write") || caps.includes("fs.write.outside-project") ? "write" : caps.includes("fs.read") ? "read" : void 0;
13382
+ const shellLike = caps.some((capability) => capability.startsWith("shell."));
13383
+ const candidates = access2 ? await extractCandidatePaths(tool.name, input, ctx) : [];
13384
+ let evaluation;
13385
+ if (shellLike || access2 && candidates.length === 0) {
13386
+ evaluation = evaluateKanbanBoundaryOpaque(layers, tool.name);
13387
+ } else if (access2) {
13388
+ const results = candidates.map(
13389
+ (candidate) => evaluateKanbanBoundaryPath(layers, candidate, access2)
13390
+ );
13391
+ evaluation = results.find((result) => result.decision === "block") ?? results.find((result) => result.decision === "confirm") ?? { decision: "allow" };
13392
+ } else {
13393
+ evaluation = { decision: "allow" };
13394
+ }
13395
+ return {
13396
+ ...evaluation,
13397
+ boardId: board.id,
13398
+ ...task ? { taskId: task.id } : {}
13399
+ };
13400
+ }
13401
+ function resolveKanbanIdentity(ctx) {
13402
+ const metaKanban = ctx.meta["kanban"];
13403
+ const record = metaKanban && typeof metaKanban === "object" ? metaKanban : void 0;
13404
+ const boardId = ctx.currentKanbanBoardId ?? (typeof record?.["boardId"] === "string" ? record["boardId"] : void 0);
13405
+ const taskId = ctx.currentKanbanTaskId ?? (typeof record?.["taskId"] === "string" ? record["taskId"] : void 0);
13406
+ const policyRoot = typeof record?.["projectRoot"] === "string" ? record["projectRoot"] : void 0;
13407
+ return {
13408
+ ...boardId ? { boardId } : {},
13409
+ ...taskId ? { taskId } : {},
13410
+ ...policyRoot ? { policyRoot } : {}
13411
+ };
13412
+ }
13413
+ async function extractCandidatePaths(toolName, input, ctx) {
13414
+ if (toolName === "patch" && typeof input["patch"] === "string") {
13415
+ const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
13416
+ const directory = path10.isAbsolute(directoryInput) ? directoryInput : path10.resolve(ctx.workingDir, directoryInput);
13417
+ const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
13418
+ const targets = extractPatchTargets(input["patch"], strip).map(
13419
+ (target) => relativeToProject(path10.resolve(directory, target), ctx.projectRoot)
13420
+ );
13421
+ return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
13422
+ }
13423
+ const values = [];
13424
+ const pathKeys = new Set(PATH_KEYS);
13425
+ for (const key of TOOL_PATH_KEYS[toolName] ?? []) pathKeys.add(key);
13426
+ collectPathValues(input, values, pathKeys);
13427
+ if (toolName === "scaffold" && typeof input["name"] === "string") {
13428
+ const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
13429
+ values.push(path10.join(cwd, input["name"]));
13430
+ }
13431
+ const candidates = [
13432
+ ...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
13433
+ ];
13434
+ return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
13435
+ }
13436
+ async function canonicalizeCandidatePath(candidate, ctx) {
13437
+ if (path10.isAbsolute(candidate)) return candidate;
13438
+ const absolute = path10.resolve(ctx.projectRoot, candidate);
13439
+ const canonicalRoot = await realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
13440
+ let probe = absolute;
13441
+ const missingSegments = [];
13442
+ while (true) {
13443
+ try {
13444
+ const canonical = path10.join(await realpath(probe), ...missingSegments);
13445
+ return relativeToProject(canonical, canonicalRoot);
13446
+ } catch (cause) {
13447
+ const code = cause.code;
13448
+ if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
13449
+ const parent = path10.dirname(probe);
13450
+ if (parent === probe) return absolute;
13451
+ missingSegments.unshift(path10.basename(probe));
13452
+ probe = parent;
13453
+ }
13454
+ }
13455
+ }
13456
+ function collectPathValues(value, output, pathKeys, key) {
13457
+ if (typeof value === "string") {
13458
+ if (key && pathKeys.has(key)) output.push(value);
13459
+ return;
13460
+ }
13461
+ if (Array.isArray(value)) {
13462
+ for (const item of value) collectPathValues(item, output, pathKeys, key);
13463
+ return;
13464
+ }
13465
+ if (!value || typeof value !== "object") return;
13466
+ for (const [childKey, child] of Object.entries(value)) {
13467
+ collectPathValues(child, output, pathKeys, childKey);
13468
+ }
13469
+ }
13470
+ function splitPathList(value) {
13471
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
13472
+ }
13473
+ function resolveInputPath(value, ctx) {
13474
+ const absolute = path10.isAbsolute(value) ? value : path10.resolve(ctx.workingDir, value);
13475
+ return relativeToProject(absolute, ctx.projectRoot);
13476
+ }
13477
+ function relativeToProject(absolute, projectRoot) {
13478
+ const relative2 = path10.relative(projectRoot, absolute).replace(/\\/g, "/");
13479
+ return relative2.startsWith("../") || path10.isAbsolute(relative2) ? absolute : relative2 || ".";
13480
+ }
13481
+ function extractPatchTargets(patchText, strip) {
13482
+ const targets = [];
13483
+ for (const match of patchText.matchAll(/^\+\+\+\s+([^\t\r\n]+)/gm)) {
13484
+ const raw = match[1]?.trim();
13485
+ if (!raw || raw === "/dev/null") continue;
13486
+ const parts = raw.replace(/\\/g, "/").split("/").filter((part) => part && part !== ".");
13487
+ if (parts.length > strip) targets.push(parts.slice(strip).join("/"));
13488
+ }
13489
+ return targets;
13490
+ }
13491
+ function stringValue(value) {
13492
+ return typeof value === "string" && value.trim() ? value : void 0;
13493
+ }
13494
+ function numericValue(value) {
13495
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
13496
+ }
13078
13497
 
13079
13498
  // src/types/tool-executor.ts
13080
13499
  var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
@@ -13295,35 +13714,68 @@ ${errorDetails}`,
13295
13714
  return { result, tool, durationMs: Date.now() - start };
13296
13715
  }
13297
13716
  }
13717
+ const boundary = await evaluateToolKanbanBoundary(tool, use.input, ctx);
13718
+ if (boundary.decision === "block") {
13719
+ const result = {
13720
+ type: "tool_result",
13721
+ tool_use_id: use.id,
13722
+ content: `Tool "${tool.name}" blocked by Kanban boundary. ${boundary.reason ?? ""}`.trim(),
13723
+ is_error: true
13724
+ };
13725
+ budget = this.budgetForString(result.content, budget);
13726
+ return { result, tool, durationMs: Date.now() - start };
13727
+ }
13298
13728
  const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
13299
13729
  let effectivePermission = decision.permission;
13300
13730
  const policy = this.opts.permissionPolicy;
13301
13731
  const yolo = policy.getYolo?.() === true;
13302
13732
  const authoritativeAuto = decision.source === "yolo";
13303
- if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
13733
+ const capabilityDowngraded = toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto;
13734
+ if (capabilityDowngraded) {
13735
+ effectivePermission = "confirm";
13736
+ }
13737
+ if (boundary.decision === "confirm" && effectivePermission !== "deny") {
13304
13738
  effectivePermission = "confirm";
13305
13739
  }
13740
+ this.opts.events?.emit("permission.evaluated", {
13741
+ sessionId: ctx.session.id,
13742
+ ...ctx.traceId ? { traceId: ctx.traceId } : {},
13743
+ ...ctx.agentId ? { agentId: ctx.agentId } : {},
13744
+ name: tool.name,
13745
+ id: use.id,
13746
+ inputHash: hashPermissionInput(use.input, this.opts.secretScrubber),
13747
+ policyDecision: decision.permission,
13748
+ effectiveDecision: effectivePermission,
13749
+ decisionSource: decision.source,
13750
+ ...decision.reason ? { reason: decision.reason } : {},
13751
+ ...decision.riskTier ?? tool.riskTier ? { riskTier: decision.riskTier ?? tool.riskTier } : {},
13752
+ yoloEnabled: yolo,
13753
+ boundaryDecision: boundary.decision,
13754
+ ...boundary.reason ? { boundaryReason: boundary.reason } : {},
13755
+ capabilityDowngraded
13756
+ });
13306
13757
  if (effectivePermission === "deny") {
13307
13758
  const result = this.deniedResult(use, decision.reason);
13308
13759
  budget = this.budgetForString(result.content, budget);
13309
13760
  return { result, tool, durationMs: Date.now() - start };
13310
13761
  }
13311
13762
  if (effectivePermission === "confirm") {
13763
+ const suggestedPattern = boundary.decision === "confirm" ? `kanban-boundary:${boundary.path ?? tool.name}` : subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
13312
13764
  if (this.opts.confirmAwaiter) {
13313
13765
  const awaiter = this.opts.confirmAwaiter;
13314
13766
  const choice = await new Promise(
13315
- (resolve4, reject) => {
13767
+ (resolve6, reject) => {
13316
13768
  const signal = ctx.signal;
13317
- const onAbort = () => resolve4("abort");
13769
+ const onAbort = () => resolve6("abort");
13318
13770
  if (signal.aborted) {
13319
- resolve4("abort");
13771
+ resolve6("abort");
13320
13772
  return;
13321
13773
  }
13322
13774
  signal.addEventListener("abort", onAbort, { once: true });
13323
- awaiter(tool, use.input, use.id, tool.name).then(
13775
+ awaiter(tool, use.input, use.id, suggestedPattern).then(
13324
13776
  (c) => {
13325
13777
  signal.removeEventListener("abort", onAbort);
13326
- resolve4(c);
13778
+ resolve6(c);
13327
13779
  },
13328
13780
  (e) => {
13329
13781
  signal.removeEventListener("abort", onAbort);
@@ -13343,7 +13795,6 @@ ${errorDetails}`,
13343
13795
  return { result, tool, durationMs: Date.now() - start };
13344
13796
  }
13345
13797
  } else {
13346
- const suggestedPattern = subjectForToolInput(tool.name, use.input, tool.subjectKey) ?? tool.name;
13347
13798
  const pending = {
13348
13799
  type: "tool_confirm_pending",
13349
13800
  toolUseId: use.id,
@@ -13351,7 +13802,8 @@ ${errorDetails}`,
13351
13802
  input: use.input,
13352
13803
  suggestedPattern,
13353
13804
  decisionSource: decision.source,
13354
- riskTier: decision.riskTier ?? tool.riskTier
13805
+ riskTier: decision.riskTier ?? tool.riskTier,
13806
+ ...boundary.decision === "confirm" && boundary.reason ? { boundaryReason: boundary.reason } : {}
13355
13807
  };
13356
13808
  return { result: pending, tool, durationMs: Date.now() - start };
13357
13809
  }
@@ -13365,6 +13817,17 @@ ${errorDetails}`,
13365
13817
  "tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
13366
13818
  });
13367
13819
  try {
13820
+ const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
13821
+ const caps = tool.capabilities ?? [];
13822
+ const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
13823
+ const absPath = hasFileCapability && typeof inputPath === "string" ? path11.isAbsolute(inputPath) ? inputPath : path11.resolve(ctx.projectRoot, inputPath) : void 0;
13824
+ let writeTargetExisted;
13825
+ if (tool.name === "write" && caps.includes("fs.write") && absPath) {
13826
+ writeTargetExisted = await fs8.stat(absPath).then(
13827
+ (stat4) => stat4.isFile(),
13828
+ (error) => error.code === "ENOENT" ? false : void 0
13829
+ );
13830
+ }
13368
13831
  const producedText = await this.produceToolOutput(tool, use, ctx, budget);
13369
13832
  let { block: result, bytes } = this.settleToolOutput(tool, use, producedText, budget);
13370
13833
  budget -= bytes;
@@ -13393,6 +13856,35 @@ ${post.additionalContext}`;
13393
13856
  span?.setAttribute("tool.is_error", !!result.is_error);
13394
13857
  span?.setAttribute("tool.output_bytes", outputChars);
13395
13858
  this.logToolSuccess(ctx, use, tool.name, Date.now() - start, outputChars);
13859
+ if (!result.is_error && typeof inputPath === "string" && absPath) {
13860
+ const operation = tool.name === "read" ? "read" : caps.includes("fs.write") && tool.name === "write" ? writeTargetExisted === false ? "create" : "update" : caps.includes("fs.write") ? "update" : "read";
13861
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
13862
+ ctx.recordFileEvent?.({
13863
+ operation,
13864
+ filePath: inputPath,
13865
+ absPath,
13866
+ toolName: tool.name,
13867
+ toolUseId: use.id,
13868
+ durationMs: Date.now() - start
13869
+ });
13870
+ this.opts.events?.emit("file.event", {
13871
+ operation,
13872
+ filePath: inputPath,
13873
+ absPath,
13874
+ sessionId: ctx.session.id,
13875
+ agentId: ctx.agentId,
13876
+ agentName: ctx.agentName,
13877
+ provider: typeof ctx.provider === "object" ? ctx.provider.id : String(ctx.provider),
13878
+ model: ctx.model,
13879
+ toolName: tool.name,
13880
+ toolUseId: use.id,
13881
+ scope: ctx.currentKanbanTaskId ? "task" : "session",
13882
+ taskId: ctx.currentKanbanTaskId,
13883
+ boardId: ctx.currentKanbanBoardId,
13884
+ timestamp: ts,
13885
+ durationMs: Date.now() - start
13886
+ });
13887
+ }
13396
13888
  return { result, tool, durationMs: Date.now() - start };
13397
13889
  } catch (err) {
13398
13890
  if (isWrongStackError(err)) {
@@ -13621,7 +14113,7 @@ ${post.additionalContext}`;
13621
14113
  toolPromise.catch(() => {
13622
14114
  });
13623
14115
  try {
13624
- output = await new Promise((resolve4, reject) => {
14116
+ output = await new Promise((resolve6, reject) => {
13625
14117
  const onAbort = () => {
13626
14118
  setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
13627
14119
  };
@@ -13629,7 +14121,7 @@ ${post.additionalContext}`;
13629
14121
  toolPromise.then(
13630
14122
  (v) => {
13631
14123
  combined.removeEventListener("abort", onAbort);
13632
- resolve4(v);
14124
+ resolve6(v);
13633
14125
  },
13634
14126
  (e) => {
13635
14127
  combined.removeEventListener("abort", onAbort);
@@ -13920,11 +14412,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
13920
14412
  return content;
13921
14413
  }
13922
14414
  try {
13923
- const dir = path10.join(wstackGlobalRoot(), "tool-output");
14415
+ const dir = path11.join(wstackGlobalRoot(), "tool-output");
13924
14416
  await fs8.mkdir(dir, { recursive: true });
13925
14417
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
13926
14418
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13927
- const filePath = path10.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
14419
+ const filePath = path11.join(dir, `${stamp}-${safeTool}-${randomUUID3()}.log`);
13928
14420
  await fs8.writeFile(filePath, content, "utf8");
13929
14421
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
13930
14422
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
@@ -13943,6 +14435,15 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
13943
14435
  return content;
13944
14436
  }
13945
14437
  }
14438
+ function hashPermissionInput(input, scrubber) {
14439
+ let serialized;
14440
+ try {
14441
+ serialized = JSON.stringify(input) ?? "";
14442
+ } catch {
14443
+ serialized = String(input);
14444
+ }
14445
+ return createHash2("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
14446
+ }
13946
14447
  function sliceUtf8Prefix(text, maxBytes) {
13947
14448
  if (maxBytes <= 0) return "";
13948
14449
  if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
@@ -14024,14 +14525,20 @@ var OneShotOrchestrator = class {
14024
14525
  let servingProviderId = provider.id;
14025
14526
  let servingModel = target.model;
14026
14527
  let fromFallback = false;
14027
- let lastError = void 0;
14528
+ let lastError;
14028
14529
  let fallbackEligible = false;
14029
- if (tracker && !tracker.isAvailable(target.providerId, target.model)) {
14530
+ if (tracker && !tracker.isAvailable(target.providerId, target.model) || !evaluateModelCalendar(config.modelAvailabilitySchedule, target.providerId, target.model).allowed) {
14030
14531
  this.opts.logger?.debug(
14031
14532
  `one-shot: primary "${target.providerId}/${target.model}" is blocked \u2014 trying fallback`
14032
14533
  );
14033
14534
  } else {
14034
- const primaryAttempt = await this.tryCall(provider, request, signal, target.providerId, target.model);
14535
+ const primaryAttempt = await this.tryCall(
14536
+ provider,
14537
+ request,
14538
+ signal,
14539
+ target.providerId,
14540
+ target.model
14541
+ );
14035
14542
  const result = primaryAttempt.response;
14036
14543
  lastError = primaryAttempt.error;
14037
14544
  fallbackEligible = primaryAttempt.fallbackEligible;
@@ -14042,17 +14549,13 @@ var OneShotOrchestrator = class {
14042
14549
  return this.buildResult(result, servingProviderId, servingModel, false, startedAt);
14043
14550
  }
14044
14551
  if (!fallbackEligible || chain.length === 0) {
14045
- return this.buildErrorResult(
14046
- lastError,
14047
- target.providerId,
14048
- target.model,
14049
- false,
14050
- startedAt
14051
- );
14552
+ return this.buildErrorResult(lastError, target.providerId, target.model, false, startedAt);
14052
14553
  }
14053
14554
  }
14054
14555
  const usableChain = tracker ? chain.filter((e) => tracker.isAvailable(e.providerId, e.model)) : chain;
14055
14556
  for (const entry of usableChain) {
14557
+ if (!evaluateModelCalendar(config.modelAvailabilitySchedule, entry.providerId, entry.model).allowed)
14558
+ continue;
14056
14559
  if (entry.providerId === provider.id && entry.model === target.model) continue;
14057
14560
  let fbProvider;
14058
14561
  try {
@@ -14079,7 +14582,13 @@ var OneShotOrchestrator = class {
14079
14582
  fallbackEligible = attempt.fallbackEligible;
14080
14583
  if (!fallbackEligible) break;
14081
14584
  }
14082
- return this.buildErrorResult(lastError, servingProviderId, servingModel, fromFallback, startedAt);
14585
+ return this.buildErrorResult(
14586
+ lastError,
14587
+ servingProviderId,
14588
+ servingModel,
14589
+ fromFallback,
14590
+ startedAt
14591
+ );
14083
14592
  }
14084
14593
  // ── Private helpers ─────────────────────────────────────────────
14085
14594
  /**