@wrongstack/core 0.24.0 → 0.31.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.
@@ -2,6 +2,7 @@ import * as crypto2 from 'crypto';
2
2
  import { randomBytes, randomUUID, createCipheriv, createDecipheriv, createHash } from 'crypto';
3
3
  import * as fsp from 'fs/promises';
4
4
  import * as path15 from 'path';
5
+ import { isAbsolute, resolve } from 'path';
5
6
  import * as fs5 from 'fs';
6
7
  import * as os from 'os';
7
8
  import { execFile } from 'child_process';
@@ -51,8 +52,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
51
52
  }
52
53
  let mode;
53
54
  try {
54
- const stat4 = await fsp.stat(targetPath);
55
- mode = stat4.mode & 511;
55
+ const stat5 = await fsp.stat(targetPath);
56
+ mode = stat5.mode & 511;
56
57
  } catch {
57
58
  mode = opts.mode;
58
59
  }
@@ -88,7 +89,7 @@ async function renameWithRetry(from, to) {
88
89
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
89
90
  throw err;
90
91
  }
91
- await new Promise((resolve3) => setTimeout(resolve3, delays[i]));
92
+ await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
92
93
  }
93
94
  }
94
95
  throw lastErr;
@@ -446,8 +447,8 @@ var DefaultSessionStore = class {
446
447
  return JSON.parse(raw);
447
448
  } catch {
448
449
  const full = this.sessionPath(id, ".jsonl");
449
- const stat4 = await fsp.stat(full);
450
- const summary = await this.summarize(id, stat4.mtime.toISOString());
450
+ const stat5 = await fsp.stat(full);
451
+ const summary = await this.summarize(id, stat5.mtime.toISOString());
451
452
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
452
453
  console.warn(
453
454
  `[session-store] Failed to write manifest for "${id}":`,
@@ -4297,8 +4298,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
4297
4298
  try {
4298
4299
  await Promise.race([
4299
4300
  Promise.resolve(iter.return?.()),
4300
- new Promise((resolve3) => {
4301
- drainTimer = setTimeout(resolve3, 500);
4301
+ new Promise((resolve4) => {
4302
+ drainTimer = setTimeout(resolve4, 500);
4302
4303
  })
4303
4304
  ]);
4304
4305
  } finally {
@@ -4359,7 +4360,7 @@ async function runProviderWithRetry(opts) {
4359
4360
  description
4360
4361
  });
4361
4362
  }
4362
- await new Promise((resolve3, reject) => {
4363
+ await new Promise((resolve4, reject) => {
4363
4364
  let settled = false;
4364
4365
  const onAbort = () => {
4365
4366
  if (settled) return;
@@ -4372,7 +4373,7 @@ async function runProviderWithRetry(opts) {
4372
4373
  settled = true;
4373
4374
  clearTimeout(t);
4374
4375
  signal.removeEventListener("abort", onAbort);
4375
- resolve3();
4376
+ resolve4();
4376
4377
  }, delay);
4377
4378
  if (signal.aborted) {
4378
4379
  onAbort();
@@ -4466,13 +4467,18 @@ function estimateRequestTokens(messages, systemPrompt, tools) {
4466
4467
  for (const t of tools) {
4467
4468
  toolsTokens += estimateToolDefTokens(t);
4468
4469
  }
4470
+ const total = messagesTokens + systemTokens + toolsTokens;
4469
4471
  return {
4470
4472
  messages: messagesTokens,
4471
4473
  systemPrompt: systemTokens,
4472
4474
  tools: toolsTokens,
4473
- total: messagesTokens + systemTokens + toolsTokens
4475
+ total
4474
4476
  };
4475
4477
  }
4478
+ function estimateRequestTokensCalibrated(messages, systemPrompt, tools) {
4479
+ const result = estimateRequestTokens(messages, systemPrompt, tools);
4480
+ return result;
4481
+ }
4476
4482
 
4477
4483
  // src/execution/compactor.ts
4478
4484
  var HybridCompactor = class {
@@ -5368,7 +5374,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5368
5374
  }
5369
5375
  handler() {
5370
5376
  return async (ctx, next) => {
5371
- const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
5377
+ const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokensCalibrated(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
5372
5378
  const load = tokens / this._maxContext;
5373
5379
  const policy = this.policyProvider?.(ctx);
5374
5380
  const thresholds = policy?.thresholds ?? {
@@ -6740,7 +6746,7 @@ ${recentJournal}` : "No prior iterations.",
6740
6746
  }
6741
6747
  };
6742
6748
  function sleep(ms) {
6743
- return new Promise((resolve3) => setTimeout(resolve3, ms));
6749
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
6744
6750
  }
6745
6751
 
6746
6752
  // src/coordination/subagent-budget.ts
@@ -6956,12 +6962,12 @@ var SubagentBudget = class _SubagentBudget {
6956
6962
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
6957
6963
  return Promise.resolve("stop");
6958
6964
  }
6959
- return new Promise((resolve3) => {
6965
+ return new Promise((resolve4) => {
6960
6966
  let resolved = false;
6961
6967
  const respond = (d) => {
6962
6968
  if (resolved) return;
6963
6969
  resolved = true;
6964
- resolve3(d);
6970
+ resolve4(d);
6965
6971
  };
6966
6972
  const fallback = setTimeout(
6967
6973
  () => respond("stop"),
@@ -9898,6 +9904,10 @@ var NICKNAME_POOL = {
9898
9904
  "pasteur": { name: "Pasteur", domain: "biology" },
9899
9905
  "hawking": { name: "Hawking", domain: "cosmology" },
9900
9906
  "sagan": { name: "Sagan", domain: "cosmology" },
9907
+ // Exploration & navigation
9908
+ "columbus": { name: "Columbus", domain: "exploration" },
9909
+ "polo": { name: "Polo", domain: "exploration" },
9910
+ "magellan": { name: "Magellan", domain: "exploration" },
9901
9911
  // Chemistry / materials
9902
9912
  "lavoisier": { name: "Lavoisier", domain: "chemistry" },
9903
9913
  "mendeleev": { name: "Mendeleev", domain: "chemistry" }
@@ -9952,7 +9962,7 @@ function formatRole(role) {
9952
9962
  }
9953
9963
 
9954
9964
  // src/coordination/multi-agent-coordinator.ts
9955
- var DefaultMultiAgentCoordinator = class extends EventEmitter {
9965
+ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends EventEmitter {
9956
9966
  coordinatorId;
9957
9967
  config;
9958
9968
  runner;
@@ -9967,8 +9977,12 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9967
9977
  * names) to memorable ones here — that's what surfaces in the fleet monitor.
9968
9978
  */
9969
9979
  usedNicknames = /* @__PURE__ */ new Set();
9980
+ /** Maps subagentId → nickname key (e.g. 'einstein'). Used to free the slot on remove(). */
9981
+ subagentNicknames = /* @__PURE__ */ new Map();
9970
9982
  pendingTasks = [];
9971
9983
  completedResults = [];
9984
+ /** Prevents completedResults from growing unbounded in long-running coordinators. */
9985
+ static MAX_COMPLETED_RESULTS = 1e4;
9972
9986
  totalIterations = 0;
9973
9987
  inFlight = 0;
9974
9988
  /**
@@ -10023,7 +10037,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10023
10037
  * Explicit, human-chosen names — including nicknames already assigned by
10024
10038
  * `Director.spawn()` — are left untouched, so this never double-assigns.
10025
10039
  */
10026
- withNickname(subagent) {
10040
+ withNickname(subagent, subagentId) {
10027
10041
  const role = subagent.role ?? "subagent";
10028
10042
  const name = subagent.name?.trim() ?? "";
10029
10043
  const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
@@ -10031,11 +10045,12 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10031
10045
  const nickname = assignNickname(role, this.usedNicknames);
10032
10046
  const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
10033
10047
  this.usedNicknames.add(baseKey);
10048
+ this.subagentNicknames.set(subagentId, baseKey);
10034
10049
  return { ...subagent, name: nickname };
10035
10050
  }
10036
10051
  async spawn(subagent) {
10037
- subagent = this.withNickname(subagent);
10038
10052
  const id = subagent.id || randomUUID();
10053
+ subagent = this.withNickname(subagent, id);
10039
10054
  if (this.subagents.has(id)) {
10040
10055
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
10041
10056
  }
@@ -10179,7 +10194,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10179
10194
  taskIds.map((id) => {
10180
10195
  const cached = this.completedResults.find((r) => r.taskId === id);
10181
10196
  if (cached) return cached;
10182
- return new Promise((resolve3, reject) => {
10197
+ return new Promise((resolve4, reject) => {
10183
10198
  const timeout = setTimeout(() => {
10184
10199
  this.off("task.completed", handler);
10185
10200
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -10188,7 +10203,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10188
10203
  if (result.taskId === id) {
10189
10204
  clearTimeout(timeout);
10190
10205
  this.off("task.completed", handler);
10191
- resolve3(result);
10206
+ resolve4(result);
10192
10207
  }
10193
10208
  };
10194
10209
  this.on("task.completed", handler);
@@ -10477,6 +10492,12 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10477
10492
  }
10478
10493
  recordCompletion(result) {
10479
10494
  this.completedResults.push(result);
10495
+ if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
10496
+ this.completedResults.splice(
10497
+ 0,
10498
+ this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
10499
+ );
10500
+ }
10480
10501
  this.totalIterations += result.iterations;
10481
10502
  if (this.inFlight > 0) {
10482
10503
  this.inFlight--;
@@ -10546,7 +10567,29 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
10546
10567
  }
10547
10568
  this.subagents.delete(subagentId);
10548
10569
  this.terminating.delete(subagentId);
10570
+ const nicknameKey = this.subagentNicknames.get(subagentId);
10571
+ if (nicknameKey) {
10572
+ this.usedNicknames.delete(nicknameKey);
10573
+ this.subagentNicknames.delete(subagentId);
10574
+ }
10575
+ const orphaned = this.pendingTasks.filter((t) => t.subagentId === subagentId);
10549
10576
  this.pendingTasks = this.pendingTasks.filter((t) => t.subagentId !== subagentId);
10577
+ for (const t of orphaned) {
10578
+ const synthetic = {
10579
+ subagentId,
10580
+ taskId: t.id,
10581
+ status: "stopped",
10582
+ error: {
10583
+ kind: "aborted_by_parent",
10584
+ message: `Subagent "${subagentId}" was removed while task "${t.id}" was pending`,
10585
+ retryable: false
10586
+ },
10587
+ iterations: 0,
10588
+ toolCalls: 0,
10589
+ durationMs: 0
10590
+ };
10591
+ this.recordCompletion(synthetic);
10592
+ }
10550
10593
  this.fleetBus?.emit({
10551
10594
  subagentId,
10552
10595
  ts: Date.now(),
@@ -10663,7 +10706,7 @@ function providerErrorToSubagentError(err, message, cause) {
10663
10706
 
10664
10707
  // src/execution/parallel-eternal-engine.ts
10665
10708
  function sleep2(ms) {
10666
- return new Promise((resolve3) => setTimeout(resolve3, ms));
10709
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
10667
10710
  }
10668
10711
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
10669
10712
  var ParallelEternalEngine = class {
@@ -11161,6 +11204,80 @@ function buildGoalPreamble(goal) {
11161
11204
  // src/coordination/director.ts
11162
11205
  init_atomic_write();
11163
11206
 
11207
+ // src/coordination/large-answer-store.ts
11208
+ var LargeAnswerStore = class {
11209
+ /**
11210
+ * Responses above this size (in characters) are stored out-of-context.
11211
+ * Below this, the full answer is returned inline (no overhead).
11212
+ * Default: 2000 chars ≈ 400-600 tokens.
11213
+ */
11214
+ sizeThreshold;
11215
+ store = /* @__PURE__ */ new Map();
11216
+ constructor(sizeThreshold = 2e3) {
11217
+ this.sizeThreshold = sizeThreshold;
11218
+ }
11219
+ /**
11220
+ * Store a value, returning a summary + key for inline use.
11221
+ * If the value is below sizeThreshold, returns it as-is (no store entry).
11222
+ */
11223
+ storeAnswer(value) {
11224
+ if (value === void 0 || value === null) {
11225
+ return { summary: String(value), inline: true };
11226
+ }
11227
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
11228
+ const size = serialized.length;
11229
+ if (size <= this.sizeThreshold) {
11230
+ return { summary: serialized.slice(0, 500), inline: true };
11231
+ }
11232
+ const key = `a-${hashStr(serialized)}`;
11233
+ this.store.set(key, {
11234
+ key,
11235
+ value,
11236
+ size,
11237
+ storedAt: Date.now()
11238
+ });
11239
+ return {
11240
+ key,
11241
+ summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
11242
+ inline: false
11243
+ };
11244
+ }
11245
+ /**
11246
+ * Retrieve a previously stored answer by its key.
11247
+ * Returns undefined if the key is unknown or the store was cleared.
11248
+ */
11249
+ retrieveAnswer(key) {
11250
+ return this.store.get(key)?.value;
11251
+ }
11252
+ /**
11253
+ * Check if a key exists in the store.
11254
+ */
11255
+ hasAnswer(key) {
11256
+ return this.store.has(key);
11257
+ }
11258
+ /** Number of stored entries. */
11259
+ get size() {
11260
+ return this.store.size;
11261
+ }
11262
+ /** Total characters stored. */
11263
+ get totalChars() {
11264
+ let total = 0;
11265
+ for (const e of this.store.values()) total += e.size;
11266
+ return total;
11267
+ }
11268
+ /** Clear all stored entries. Call at the end of a director run. */
11269
+ clear() {
11270
+ this.store.clear();
11271
+ }
11272
+ };
11273
+ function hashStr(s) {
11274
+ let h = 5381;
11275
+ for (let i = 0; i < s.length; i++) {
11276
+ h = h * 33 ^ s.charCodeAt(i);
11277
+ }
11278
+ return (h >>> 0).toString(36);
11279
+ }
11280
+
11164
11281
  // src/coordination/in-memory-transport.ts
11165
11282
  var InMemoryBridgeTransport = class {
11166
11283
  subs = /* @__PURE__ */ new Map();
@@ -11258,7 +11375,7 @@ var InMemoryAgentBridge = class {
11258
11375
  );
11259
11376
  }
11260
11377
  this.inflightGuards.add(correlationId);
11261
- return new Promise((resolve3, reject) => {
11378
+ return new Promise((resolve4, reject) => {
11262
11379
  const timer = setTimeout(() => {
11263
11380
  this.inflightGuards.delete(correlationId);
11264
11381
  this.pendingRequests.delete(correlationId);
@@ -11270,7 +11387,7 @@ var InMemoryAgentBridge = class {
11270
11387
  return;
11271
11388
  }
11272
11389
  this.pendingRequests.set(correlationId, {
11273
- resolve: resolve3,
11390
+ resolve: resolve4,
11274
11391
  reject,
11275
11392
  timer
11276
11393
  });
@@ -11592,9 +11709,12 @@ function makeSpawnTool(director, roster) {
11592
11709
  provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
11593
11710
  model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
11594
11711
  systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
11595
- maxIterations: { type: "number" },
11596
- maxToolCalls: { type: "number" },
11597
- maxCostUsd: { type: "number" }
11712
+ maxIterations: { type: "number", minimum: 1 },
11713
+ maxToolCalls: { type: "number", minimum: 1 },
11714
+ maxCostUsd: { type: "number", minimum: 0 },
11715
+ timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
11716
+ idleTimeoutMs: { type: "number", minimum: 1, description: "Idle timeout in ms: reap the subagent after this long with no activity. Resets on every iteration/tool call. Default is role/coordinator-specific." },
11717
+ maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
11598
11718
  },
11599
11719
  required: []
11600
11720
  };
@@ -11640,6 +11760,9 @@ function makeSpawnTool(director, roster) {
11640
11760
  if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
11641
11761
  if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
11642
11762
  if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
11763
+ if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
11764
+ if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
11765
+ if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
11643
11766
  try {
11644
11767
  const subagentId = await director.spawn(cfg);
11645
11768
  return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
@@ -11667,10 +11790,10 @@ function makeAssignTool(director) {
11667
11790
  const inputSchema = {
11668
11791
  type: "object",
11669
11792
  properties: {
11670
- subagentId: { type: "string", description: "Target subagent id. Required." },
11671
- description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
11672
- maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
11673
- timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
11793
+ subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
11794
+ description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
11795
+ maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
11796
+ timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
11674
11797
  },
11675
11798
  required: ["subagentId", "description"]
11676
11799
  };
@@ -11711,9 +11834,9 @@ function makeAskTool(director) {
11711
11834
  inputSchema: {
11712
11835
  type: "object",
11713
11836
  properties: {
11714
- subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
11715
- question: { type: "string", description: "The question or instruction." },
11716
- timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
11837
+ subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
11838
+ question: { type: "string", minLength: 1, description: "The question or instruction." },
11839
+ timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
11717
11840
  },
11718
11841
  required: ["subagentId", "question"]
11719
11842
  },
@@ -11721,13 +11844,49 @@ function makeAskTool(director) {
11721
11844
  const i = input;
11722
11845
  try {
11723
11846
  const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
11724
- return { ok: true, answer };
11847
+ const stored = director.largeAnswerStore.storeAnswer(answer);
11848
+ if (stored.inline) {
11849
+ return { ok: true, answer: stored.summary };
11850
+ }
11851
+ return {
11852
+ ok: true,
11853
+ answer: stored.summary,
11854
+ _answerKey: stored.key,
11855
+ _hint: "Response was large and stored. Use ask_result with the key to retrieve it."
11856
+ };
11725
11857
  } catch (err) {
11726
11858
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
11727
11859
  }
11728
11860
  }
11729
11861
  };
11730
11862
  }
11863
+ function makeAskResultTool(director) {
11864
+ return {
11865
+ name: "ask_result",
11866
+ description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
11867
+ permission: "auto",
11868
+ mutating: false,
11869
+ inputSchema: {
11870
+ type: "object",
11871
+ properties: {
11872
+ key: {
11873
+ type: "string",
11874
+ minLength: 1,
11875
+ description: "The `_answerKey` returned by `ask_subagent` for a large response."
11876
+ }
11877
+ },
11878
+ required: ["key"]
11879
+ },
11880
+ async execute(input) {
11881
+ const i = input;
11882
+ const value = director.largeAnswerStore.retrieveAnswer(i.key);
11883
+ if (value === void 0) {
11884
+ return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
11885
+ }
11886
+ return { ok: true, value };
11887
+ }
11888
+ };
11889
+ }
11731
11890
  function makeRollUpTool(director) {
11732
11891
  return {
11733
11892
  name: "roll_up",
@@ -11881,6 +12040,7 @@ function makeCollabDebugTool(director) {
11881
12040
  },
11882
12041
  timeoutMs: {
11883
12042
  type: "number",
12043
+ minimum: 1,
11884
12044
  description: "Timeout in ms. Default: 600000 (10 minutes)."
11885
12045
  }
11886
12046
  },
@@ -11960,6 +12120,122 @@ function makeWorkCompleteTool(director) {
11960
12120
  }
11961
12121
  };
11962
12122
  }
12123
+ var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
12124
+ var IS_WINDOWS = process.platform === "win32";
12125
+ var SEP = IS_WINDOWS ? "\\" : "/";
12126
+ function isGlob(p) {
12127
+ for (const c of p) {
12128
+ if (GLOB_CHARS.has(c)) return true;
12129
+ }
12130
+ return false;
12131
+ }
12132
+ function globToRegex(pat) {
12133
+ let i = 0, re = "^";
12134
+ while (i < pat.length) {
12135
+ const c = pat[i];
12136
+ if (c === "*") {
12137
+ if (pat[i + 1] === "*") {
12138
+ re += ".*";
12139
+ i += 2;
12140
+ if (pat[i] === "/") i++;
12141
+ } else {
12142
+ re += "[^/\\\\]*";
12143
+ i++;
12144
+ }
12145
+ } else if (c === "?") {
12146
+ re += "[^/\\\\]";
12147
+ i++;
12148
+ } else if (c === "[") {
12149
+ let cls = "[";
12150
+ i++;
12151
+ if (pat[i] === "!" || pat[i] === "^") {
12152
+ cls += "^";
12153
+ i++;
12154
+ }
12155
+ while (i < pat.length && pat[i] !== "]") {
12156
+ const ch = pat[i] ?? "";
12157
+ if (ch === "\\") cls += "\\\\";
12158
+ else if (ch === "]" || ch === "^") cls += `\\${ch}`;
12159
+ else cls += ch;
12160
+ i++;
12161
+ }
12162
+ cls += "]";
12163
+ re += cls;
12164
+ i++;
12165
+ } else {
12166
+ re += c.replace(/[.+^${}()|\\]/g, "\\$&");
12167
+ i++;
12168
+ }
12169
+ }
12170
+ return new RegExp(re + "$");
12171
+ }
12172
+ function baseDir(pat) {
12173
+ let i = pat.length - 1;
12174
+ while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
12175
+ const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
12176
+ return cut < 0 ? "." : pat.slice(0, cut);
12177
+ }
12178
+ async function expandGlob(pattern) {
12179
+ if (!isGlob(pattern)) return [pattern];
12180
+ const results = /* @__PURE__ */ new Set();
12181
+ const abs = isAbsolute(pattern);
12182
+ const base = abs ? baseDir(pattern) : baseDir(pattern);
12183
+ const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
12184
+ async function walk4(dir, pat) {
12185
+ let entries;
12186
+ try {
12187
+ entries = await fsp.readdir(dir);
12188
+ } catch {
12189
+ return;
12190
+ }
12191
+ const firstGlob = pat.search(/[*?[\[]/);
12192
+ if (firstGlob < 0) {
12193
+ const re = globToRegex(pat);
12194
+ for (const e of entries) {
12195
+ if (re.test(e)) {
12196
+ const full = `${dir}${SEP}${e}`;
12197
+ results.add(abs ? resolve(full) : full);
12198
+ }
12199
+ }
12200
+ return;
12201
+ }
12202
+ const before = pat.slice(0, firstGlob);
12203
+ const rest = pat.slice(firstGlob);
12204
+ if (before.endsWith("**")) {
12205
+ await walk4(dir, rest);
12206
+ for (const e of entries) {
12207
+ const full = `${dir}${SEP}${e}`;
12208
+ try {
12209
+ const stat5 = await fsp.stat(full);
12210
+ if (stat5.isDirectory()) await walk4(full, rest);
12211
+ } catch {
12212
+ }
12213
+ }
12214
+ } else if (before === "") {
12215
+ const re = globToRegex(rest);
12216
+ for (const e of entries) {
12217
+ if (re.test(e)) {
12218
+ const full = `${dir}${SEP}${e}`;
12219
+ results.add(abs ? resolve(full) : full);
12220
+ }
12221
+ }
12222
+ } else {
12223
+ const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
12224
+ if (entries.includes(seg)) {
12225
+ const full = `${dir}${SEP}${seg}`;
12226
+ try {
12227
+ const stat5 = await fsp.stat(full);
12228
+ if (stat5.isDirectory()) await walk4(full, rest);
12229
+ } catch {
12230
+ }
12231
+ }
12232
+ }
12233
+ }
12234
+ await walk4(base === "." ? "." : base, relPat);
12235
+ return [...results];
12236
+ }
12237
+
12238
+ // src/coordination/collab-debug.ts
11963
12239
  var CollabSession = class extends EventEmitter {
11964
12240
  sessionId;
11965
12241
  options;
@@ -12018,7 +12294,7 @@ var CollabSession = class extends EventEmitter {
12018
12294
  }
12019
12295
  async buildSnapshot() {
12020
12296
  if (this.snapshot.files.length > 0) return this.snapshot;
12021
- for (const filePath of this.options.targetPaths) {
12297
+ for (const filePath of (await Promise.all(this.options.targetPaths.map((p) => expandGlob(p)))).flat()) {
12022
12298
  try {
12023
12299
  const content = await fsp.readFile(filePath, "utf8");
12024
12300
  const ext = filePath.split(".").pop() ?? "";
@@ -12074,7 +12350,7 @@ var CollabSession = class extends EventEmitter {
12074
12350
  reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
12075
12351
  }, this.timeoutMs);
12076
12352
  });
12077
- let results;
12353
+ let results = null;
12078
12354
  try {
12079
12355
  results = await Promise.race([
12080
12356
  Promise.all([
@@ -12505,7 +12781,7 @@ var FleetContextOverflowError = class extends Error {
12505
12781
  this.observed = observed;
12506
12782
  }
12507
12783
  };
12508
- var Director = class {
12784
+ var Director = class _Director {
12509
12785
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
12510
12786
  get coordinatorId() {
12511
12787
  return this.id;
@@ -12563,6 +12839,8 @@ var Director = class {
12563
12839
  * coordinator already fired the event — `awaitTasks(['t-1'])` after
12564
12840
  * t-1 finished should resolve immediately, not hang. */
12565
12841
  completed = /* @__PURE__ */ new Map();
12842
+ /** Prevents the completed Map from growing unbounded in long-running directors. */
12843
+ static MAX_COMPLETED = 1e4;
12566
12844
  /** Per-subagent provider/model metadata, captured at spawn time so the
12567
12845
  * FleetUsageAggregator's metaLookup can surface readable rows. */
12568
12846
  subagentMeta = /* @__PURE__ */ new Map();
@@ -12642,6 +12920,8 @@ var Director = class {
12642
12920
  _leaderBtwNotes = [];
12643
12921
  /** Active collab sessions tracked by sessionId (see spawnCollab). */
12644
12922
  _activeCollabSessions = /* @__PURE__ */ new Map();
12923
+ /** Prevents large `ask_subagent` answers from bloating the leader's context window. */
12924
+ largeAnswerStore;
12645
12925
  constructor(opts) {
12646
12926
  this.id = opts.config.coordinatorId || randomUUID();
12647
12927
  this.manifestPath = opts.manifestPath;
@@ -12702,6 +12982,11 @@ var Director = class {
12702
12982
  this.taskCompletedListener = (payload) => {
12703
12983
  const r = payload.result;
12704
12984
  this.completed.set(r.taskId, r);
12985
+ if (this.completed.size > _Director.MAX_COMPLETED) {
12986
+ const toDelete = this.completed.size - _Director.MAX_COMPLETED;
12987
+ const keys = [...this.completed.keys()].slice(0, toDelete);
12988
+ for (const k of keys) this.completed.delete(k);
12989
+ }
12705
12990
  const waiter = this.taskWaiters.get(r.taskId);
12706
12991
  if (waiter) {
12707
12992
  waiter.resolve(r);
@@ -12808,6 +13093,7 @@ var Director = class {
12808
13093
  payload.extend(extra);
12809
13094
  });
12810
13095
  });
13096
+ this.largeAnswerStore = new LargeAnswerStore(2e3);
12811
13097
  }
12812
13098
  /**
12813
13099
  * Record a granted budget extension and broadcast it on the FleetBus so
@@ -13288,11 +13574,11 @@ var Director = class {
13288
13574
  if (cached) return cached;
13289
13575
  const existing = this.taskWaiters.get(id);
13290
13576
  if (existing) return existing.promise;
13291
- let resolve3;
13577
+ let resolve4;
13292
13578
  const promise = new Promise((res) => {
13293
- resolve3 = res;
13579
+ resolve4 = res;
13294
13580
  });
13295
- this.taskWaiters.set(id, { promise, resolve: resolve3 });
13581
+ this.taskWaiters.set(id, { promise, resolve: resolve4 });
13296
13582
  return promise;
13297
13583
  })
13298
13584
  );
@@ -13305,7 +13591,24 @@ var Director = class {
13305
13591
  }
13306
13592
  async remove(subagentId) {
13307
13593
  await this.coordinator.remove(subagentId);
13594
+ const bridge = this.subagentBridges.get(subagentId);
13595
+ if (bridge) {
13596
+ await bridge.stop();
13597
+ this.subagentBridges.delete(subagentId);
13598
+ }
13308
13599
  this.usage.removeSubagent(subagentId);
13600
+ if (this.fleetManager) {
13601
+ this.fleetManager.removeSubagent(subagentId);
13602
+ } else {
13603
+ const entry = this.manifestEntries.get(subagentId);
13604
+ if (entry?.name) {
13605
+ const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
13606
+ this._usedNicknames.delete(nicknameKey);
13607
+ }
13608
+ }
13609
+ this.manifestEntries.delete(subagentId);
13610
+ this.taskOwners.delete(subagentId);
13611
+ this.taskDescriptions.delete(subagentId);
13309
13612
  }
13310
13613
  status() {
13311
13614
  const base = this.coordinator.getStatus();
@@ -13467,6 +13770,7 @@ var Director = class {
13467
13770
  makeAssignTool(this),
13468
13771
  makeAwaitTasksTool(this),
13469
13772
  makeAskTool(this),
13773
+ makeAskResultTool(this),
13470
13774
  makeRollUpTool(this),
13471
13775
  makeTerminateTool(this),
13472
13776
  makeTerminateAllTool(this),
@@ -13549,15 +13853,33 @@ function createDelegateTool(opts) {
13549
13853
  },
13550
13854
  timeoutMs: {
13551
13855
  type: "number",
13856
+ minimum: 1,
13552
13857
  description: `Wall-clock budget for this delegate in milliseconds. No hard cap \u2014 set as high as the task realistically needs (a monorepo audit can take hours, a single-file lint takes seconds). Default ${Math.round(defaultTimeoutMs / 1e3 / 60)} minutes.`
13553
13858
  },
13554
13859
  maxIterations: {
13555
13860
  type: "number",
13861
+ minimum: 1,
13556
13862
  description: "Maximum LLM iterations the subagent may take. Unset = use the role/coordinator default. Raise this for tasks with many tool-think-tool cycles (deep code analysis, multi-file refactors)."
13557
13863
  },
13558
13864
  maxToolCalls: {
13559
13865
  type: "number",
13866
+ minimum: 1,
13560
13867
  description: "Maximum number of tool invocations the subagent may make. Unset = use the role/coordinator default. Raise this for tasks that touch many files (large grep + read + report)."
13868
+ },
13869
+ idleTimeoutMs: {
13870
+ type: "number",
13871
+ minimum: 1,
13872
+ description: "Idle timeout in ms: reap the subagent after this long with no activity. Resets on every iteration/tool call. Unset = use the role/coordinator default."
13873
+ },
13874
+ maxTokens: {
13875
+ type: "number",
13876
+ minimum: 1,
13877
+ description: "Maximum total tokens (input + output) the subagent may use. Unset = use the role/coordinator default."
13878
+ },
13879
+ maxCostUsd: {
13880
+ type: "number",
13881
+ minimum: 0,
13882
+ description: "Maximum estimated USD cost the subagent may incur. Unset = use the role/coordinator default."
13561
13883
  }
13562
13884
  },
13563
13885
  required: ["task"]
@@ -13615,12 +13937,21 @@ function createDelegateTool(opts) {
13615
13937
  };
13616
13938
  cfg = applyRosterBudget({ ...cfg, name: i.name });
13617
13939
  }
13618
- if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
13940
+ if (typeof i.maxIterations === "number") {
13619
13941
  cfg.maxIterations = i.maxIterations;
13620
13942
  }
13621
- if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
13943
+ if (typeof i.maxToolCalls === "number") {
13622
13944
  cfg.maxToolCalls = i.maxToolCalls;
13623
13945
  }
13946
+ if (typeof i.idleTimeoutMs === "number") {
13947
+ cfg.idleTimeoutMs = i.idleTimeoutMs;
13948
+ }
13949
+ if (typeof i.maxTokens === "number") {
13950
+ cfg.maxTokens = i.maxTokens;
13951
+ }
13952
+ if (typeof i.maxCostUsd === "number") {
13953
+ cfg.maxCostUsd = i.maxCostUsd;
13954
+ }
13624
13955
  const SUBAGENT_TIMEOUT_BUFFER_MS = opts.subagentTimeoutBufferMs ?? 6e4;
13625
13956
  if (!cfg.timeoutMs) {
13626
13957
  cfg.timeoutMs = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
@@ -13632,7 +13963,7 @@ function createDelegateTool(opts) {
13632
13963
  subagentId
13633
13964
  });
13634
13965
  const dir = director;
13635
- const result = await new Promise((resolve3) => {
13966
+ const result = await new Promise((resolve4) => {
13636
13967
  let settled = false;
13637
13968
  let timer;
13638
13969
  const finish = (value) => {
@@ -13642,7 +13973,7 @@ function createDelegateTool(opts) {
13642
13973
  offTool();
13643
13974
  offIter();
13644
13975
  offProgress();
13645
- resolve3(value);
13976
+ resolve4(value);
13646
13977
  };
13647
13978
  const arm = () => {
13648
13979
  if (timer) clearTimeout(timer);
@@ -14554,8 +14885,8 @@ async function loadProjectModes(modesDir) {
14554
14885
  for (const entry of entries) {
14555
14886
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
14556
14887
  const filePath = path15.join(modesDir, entry);
14557
- const stat4 = await fsp.stat(filePath);
14558
- if (!stat4.isFile()) continue;
14888
+ const stat5 = await fsp.stat(filePath);
14889
+ if (!stat5.isFile()) continue;
14559
14890
  const content = await fsp.readFile(filePath, "utf8");
14560
14891
  const id = path15.basename(entry, path15.extname(entry));
14561
14892
  modes.push({
@@ -15898,10 +16229,10 @@ var AISpecBuilder = class {
15898
16229
  async saveSession() {
15899
16230
  if (!this.sessionPath) return;
15900
16231
  try {
15901
- const fsp15 = await import('fs/promises');
16232
+ const fsp16 = await import('fs/promises');
15902
16233
  const path18 = await import('path');
15903
16234
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
15904
- await fsp15.mkdir(path18.dirname(this.sessionPath), { recursive: true });
16235
+ await fsp16.mkdir(path18.dirname(this.sessionPath), { recursive: true });
15905
16236
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
15906
16237
  } catch {
15907
16238
  }
@@ -15910,8 +16241,8 @@ var AISpecBuilder = class {
15910
16241
  async loadSession() {
15911
16242
  if (!this.sessionPath) return false;
15912
16243
  try {
15913
- const fsp15 = await import('fs/promises');
15914
- const raw = await fsp15.readFile(this.sessionPath, "utf8");
16244
+ const fsp16 = await import('fs/promises');
16245
+ const raw = await fsp16.readFile(this.sessionPath, "utf8");
15915
16246
  const loaded = JSON.parse(raw);
15916
16247
  if (loaded?.id && loaded?.phase && loaded?.title) {
15917
16248
  this.session = loaded;
@@ -15925,8 +16256,8 @@ var AISpecBuilder = class {
15925
16256
  async deleteSession() {
15926
16257
  if (!this.sessionPath) return;
15927
16258
  try {
15928
- const fsp15 = await import('fs/promises');
15929
- await fsp15.unlink(this.sessionPath);
16259
+ const fsp16 = await import('fs/promises');
16260
+ await fsp16.unlink(this.sessionPath);
15930
16261
  } catch {
15931
16262
  }
15932
16263
  }
@@ -17412,9 +17743,9 @@ var DefaultHealthRegistry = class {
17412
17743
  }
17413
17744
  async runOne(check) {
17414
17745
  let timer = null;
17415
- const timeout = new Promise((resolve3) => {
17746
+ const timeout = new Promise((resolve4) => {
17416
17747
  timer = setTimeout(
17417
- () => resolve3({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
17748
+ () => resolve4({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
17418
17749
  this.timeoutMs
17419
17750
  );
17420
17751
  });
@@ -17653,14 +17984,14 @@ async function startMetricsServer(opts) {
17653
17984
  const { createServer } = await import('http');
17654
17985
  server = createServer(listener);
17655
17986
  }
17656
- await new Promise((resolve3, reject) => {
17987
+ await new Promise((resolve4, reject) => {
17657
17988
  const onError = (err) => {
17658
17989
  server.off("listening", onListening);
17659
17990
  reject(err);
17660
17991
  };
17661
17992
  const onListening = () => {
17662
17993
  server.off("error", onError);
17663
- resolve3();
17994
+ resolve4();
17664
17995
  };
17665
17996
  server.once("error", onError);
17666
17997
  server.once("listening", onListening);
@@ -17672,8 +18003,8 @@ async function startMetricsServer(opts) {
17672
18003
  return {
17673
18004
  port: boundPort,
17674
18005
  url: `${protocol}://${host}:${boundPort}${path18}`,
17675
- close: () => new Promise((resolve3, reject) => {
17676
- server.close((err) => err ? reject(err) : resolve3());
18006
+ close: () => new Promise((resolve4, reject) => {
18007
+ server.close((err) => err ? reject(err) : resolve4());
17677
18008
  })
17678
18009
  };
17679
18010
  }