@wrongstack/core 0.24.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { randomUUID, randomBytes } from 'crypto';
2
- import * as fsp5 from 'fs/promises';
2
+ import * as fsp6 from 'fs/promises';
3
3
  import * as path4 from 'path';
4
+ import { isAbsolute, resolve } from 'path';
4
5
  import { EventEmitter } from 'events';
5
6
 
6
7
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -11,16 +12,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
11
12
  });
12
13
  async function atomicWrite(targetPath, content, opts = {}) {
13
14
  const dir = path4.dirname(targetPath);
14
- await fsp5.mkdir(dir, { recursive: true });
15
+ await fsp6.mkdir(dir, { recursive: true });
15
16
  const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
16
17
  try {
17
18
  if (typeof content === "string") {
18
- await fsp5.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
19
+ await fsp6.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
19
20
  } else {
20
- await fsp5.writeFile(tmp, content, { flag: "wx" });
21
+ await fsp6.writeFile(tmp, content, { flag: "wx" });
21
22
  }
22
23
  try {
23
- const fh = await fsp5.open(tmp, "r+");
24
+ const fh = await fsp6.open(tmp, "r+");
24
25
  try {
25
26
  await fh.sync();
26
27
  } finally {
@@ -30,37 +31,37 @@ async function atomicWrite(targetPath, content, opts = {}) {
30
31
  }
31
32
  let mode;
32
33
  try {
33
- const stat3 = await fsp5.stat(targetPath);
34
- mode = stat3.mode & 511;
34
+ const stat4 = await fsp6.stat(targetPath);
35
+ mode = stat4.mode & 511;
35
36
  } catch {
36
37
  mode = opts.mode;
37
38
  }
38
39
  if (mode !== void 0) {
39
- await fsp5.chmod(tmp, mode);
40
+ await fsp6.chmod(tmp, mode);
40
41
  }
41
42
  await renameWithRetry(tmp, targetPath);
42
43
  } catch (err) {
43
44
  try {
44
- await fsp5.unlink(tmp);
45
+ await fsp6.unlink(tmp);
45
46
  } catch {
46
47
  }
47
48
  throw err;
48
49
  }
49
50
  }
50
51
  async function ensureDir(dir) {
51
- await fsp5.mkdir(dir, { recursive: true });
52
+ await fsp6.mkdir(dir, { recursive: true });
52
53
  }
53
54
  var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
54
55
  async function renameWithRetry(from, to) {
55
56
  if (process.platform !== "win32") {
56
- await fsp5.rename(from, to);
57
+ await fsp6.rename(from, to);
57
58
  return;
58
59
  }
59
60
  const delays = [10, 25, 60, 120, 250];
60
61
  let lastErr;
61
62
  for (let i = 0; i <= delays.length; i++) {
62
63
  try {
63
- await fsp5.rename(from, to);
64
+ await fsp6.rename(from, to);
64
65
  return;
65
66
  } catch (err) {
66
67
  lastErr = err;
@@ -68,15 +69,89 @@ async function renameWithRetry(from, to) {
68
69
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
69
70
  throw err;
70
71
  }
71
- await new Promise((resolve) => setTimeout(resolve, delays[i]));
72
+ await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
72
73
  }
73
74
  }
74
75
  throw lastErr;
75
76
  }
77
+
78
+ // src/coordination/large-answer-store.ts
79
+ var LargeAnswerStore = class {
80
+ /**
81
+ * Responses above this size (in characters) are stored out-of-context.
82
+ * Below this, the full answer is returned inline (no overhead).
83
+ * Default: 2000 chars ≈ 400-600 tokens.
84
+ */
85
+ sizeThreshold;
86
+ store = /* @__PURE__ */ new Map();
87
+ constructor(sizeThreshold = 2e3) {
88
+ this.sizeThreshold = sizeThreshold;
89
+ }
90
+ /**
91
+ * Store a value, returning a summary + key for inline use.
92
+ * If the value is below sizeThreshold, returns it as-is (no store entry).
93
+ */
94
+ storeAnswer(value) {
95
+ if (value === void 0 || value === null) {
96
+ return { summary: String(value), inline: true };
97
+ }
98
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
99
+ const size = serialized.length;
100
+ if (size <= this.sizeThreshold) {
101
+ return { summary: serialized.slice(0, 500), inline: true };
102
+ }
103
+ const key = `a-${hashStr(serialized)}`;
104
+ this.store.set(key, {
105
+ key,
106
+ value,
107
+ size,
108
+ storedAt: Date.now()
109
+ });
110
+ return {
111
+ key,
112
+ summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
113
+ inline: false
114
+ };
115
+ }
116
+ /**
117
+ * Retrieve a previously stored answer by its key.
118
+ * Returns undefined if the key is unknown or the store was cleared.
119
+ */
120
+ retrieveAnswer(key) {
121
+ return this.store.get(key)?.value;
122
+ }
123
+ /**
124
+ * Check if a key exists in the store.
125
+ */
126
+ hasAnswer(key) {
127
+ return this.store.has(key);
128
+ }
129
+ /** Number of stored entries. */
130
+ get size() {
131
+ return this.store.size;
132
+ }
133
+ /** Total characters stored. */
134
+ get totalChars() {
135
+ let total = 0;
136
+ for (const e of this.store.values()) total += e.size;
137
+ return total;
138
+ }
139
+ /** Clear all stored entries. Call at the end of a director run. */
140
+ clear() {
141
+ this.store.clear();
142
+ }
143
+ };
144
+ function hashStr(s) {
145
+ let h = 5381;
146
+ for (let i = 0; i < s.length; i++) {
147
+ h = h * 33 ^ s.charCodeAt(i);
148
+ }
149
+ return (h >>> 0).toString(36);
150
+ }
76
151
  async function acquireDirectorStateLock(lockPath, processId = process.pid) {
77
152
  let existing;
78
153
  try {
79
- existing = await fsp5.readFile(lockPath, "utf8");
154
+ existing = await fsp6.readFile(lockPath, "utf8");
80
155
  } catch {
81
156
  }
82
157
  if (existing) {
@@ -100,7 +175,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
100
175
  }
101
176
  async function releaseDirectorStateLock(lockPath) {
102
177
  try {
103
- await fsp5.unlink(lockPath);
178
+ await fsp6.unlink(lockPath);
104
179
  } catch {
105
180
  }
106
181
  }
@@ -334,7 +409,7 @@ var InMemoryAgentBridge = class {
334
409
  );
335
410
  }
336
411
  this.inflightGuards.add(correlationId);
337
- return new Promise((resolve, reject) => {
412
+ return new Promise((resolve2, reject) => {
338
413
  const timer = setTimeout(() => {
339
414
  this.inflightGuards.delete(correlationId);
340
415
  this.pendingRequests.delete(correlationId);
@@ -346,7 +421,7 @@ var InMemoryAgentBridge = class {
346
421
  return;
347
422
  }
348
423
  this.pendingRequests.set(correlationId, {
349
- resolve,
424
+ resolve: resolve2,
350
425
  reject,
351
426
  timer
352
427
  });
@@ -707,6 +782,10 @@ var NICKNAME_POOL = {
707
782
  "pasteur": { name: "Pasteur", domain: "biology" },
708
783
  "hawking": { name: "Hawking", domain: "cosmology" },
709
784
  "sagan": { name: "Sagan", domain: "cosmology" },
785
+ // Exploration & navigation
786
+ "columbus": { name: "Columbus", domain: "exploration" },
787
+ "polo": { name: "Polo", domain: "exploration" },
788
+ "magellan": { name: "Magellan", domain: "exploration" },
710
789
  // Chemistry / materials
711
790
  "lavoisier": { name: "Lavoisier", domain: "chemistry" },
712
791
  "mendeleev": { name: "Mendeleev", domain: "chemistry" }
@@ -1083,12 +1162,12 @@ var SubagentBudget = class _SubagentBudget {
1083
1162
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
1084
1163
  return Promise.resolve("stop");
1085
1164
  }
1086
- return new Promise((resolve) => {
1165
+ return new Promise((resolve2) => {
1087
1166
  let resolved = false;
1088
1167
  const respond = (d) => {
1089
1168
  if (resolved) return;
1090
1169
  resolved = true;
1091
- resolve(d);
1170
+ resolve2(d);
1092
1171
  };
1093
1172
  const fallback = setTimeout(
1094
1173
  () => respond("stop"),
@@ -3686,7 +3765,7 @@ var FLEET_ROSTER_WITHACP = {
3686
3765
  };
3687
3766
 
3688
3767
  // src/coordination/multi-agent-coordinator.ts
3689
- var DefaultMultiAgentCoordinator = class extends EventEmitter {
3768
+ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends EventEmitter {
3690
3769
  coordinatorId;
3691
3770
  config;
3692
3771
  runner;
@@ -3701,8 +3780,12 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3701
3780
  * names) to memorable ones here — that's what surfaces in the fleet monitor.
3702
3781
  */
3703
3782
  usedNicknames = /* @__PURE__ */ new Set();
3783
+ /** Maps subagentId → nickname key (e.g. 'einstein'). Used to free the slot on remove(). */
3784
+ subagentNicknames = /* @__PURE__ */ new Map();
3704
3785
  pendingTasks = [];
3705
3786
  completedResults = [];
3787
+ /** Prevents completedResults from growing unbounded in long-running coordinators. */
3788
+ static MAX_COMPLETED_RESULTS = 1e4;
3706
3789
  totalIterations = 0;
3707
3790
  inFlight = 0;
3708
3791
  /**
@@ -3757,7 +3840,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3757
3840
  * Explicit, human-chosen names — including nicknames already assigned by
3758
3841
  * `Director.spawn()` — are left untouched, so this never double-assigns.
3759
3842
  */
3760
- withNickname(subagent) {
3843
+ withNickname(subagent, subagentId) {
3761
3844
  const role = subagent.role ?? "subagent";
3762
3845
  const name = subagent.name?.trim() ?? "";
3763
3846
  const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
@@ -3765,11 +3848,12 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3765
3848
  const nickname = assignNickname(role, this.usedNicknames);
3766
3849
  const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
3767
3850
  this.usedNicknames.add(baseKey);
3851
+ this.subagentNicknames.set(subagentId, baseKey);
3768
3852
  return { ...subagent, name: nickname };
3769
3853
  }
3770
3854
  async spawn(subagent) {
3771
- subagent = this.withNickname(subagent);
3772
3855
  const id = subagent.id || randomUUID();
3856
+ subagent = this.withNickname(subagent, id);
3773
3857
  if (this.subagents.has(id)) {
3774
3858
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
3775
3859
  }
@@ -3913,7 +3997,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3913
3997
  taskIds.map((id) => {
3914
3998
  const cached = this.completedResults.find((r) => r.taskId === id);
3915
3999
  if (cached) return cached;
3916
- return new Promise((resolve, reject) => {
4000
+ return new Promise((resolve2, reject) => {
3917
4001
  const timeout = setTimeout(() => {
3918
4002
  this.off("task.completed", handler);
3919
4003
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -3922,7 +4006,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3922
4006
  if (result.taskId === id) {
3923
4007
  clearTimeout(timeout);
3924
4008
  this.off("task.completed", handler);
3925
- resolve(result);
4009
+ resolve2(result);
3926
4010
  }
3927
4011
  };
3928
4012
  this.on("task.completed", handler);
@@ -4211,6 +4295,12 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4211
4295
  }
4212
4296
  recordCompletion(result) {
4213
4297
  this.completedResults.push(result);
4298
+ if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
4299
+ this.completedResults.splice(
4300
+ 0,
4301
+ this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
4302
+ );
4303
+ }
4214
4304
  this.totalIterations += result.iterations;
4215
4305
  if (this.inFlight > 0) {
4216
4306
  this.inFlight--;
@@ -4280,7 +4370,29 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4280
4370
  }
4281
4371
  this.subagents.delete(subagentId);
4282
4372
  this.terminating.delete(subagentId);
4373
+ const nicknameKey = this.subagentNicknames.get(subagentId);
4374
+ if (nicknameKey) {
4375
+ this.usedNicknames.delete(nicknameKey);
4376
+ this.subagentNicknames.delete(subagentId);
4377
+ }
4378
+ const orphaned = this.pendingTasks.filter((t) => t.subagentId === subagentId);
4283
4379
  this.pendingTasks = this.pendingTasks.filter((t) => t.subagentId !== subagentId);
4380
+ for (const t of orphaned) {
4381
+ const synthetic = {
4382
+ subagentId,
4383
+ taskId: t.id,
4384
+ status: "stopped",
4385
+ error: {
4386
+ kind: "aborted_by_parent",
4387
+ message: `Subagent "${subagentId}" was removed while task "${t.id}" was pending`,
4388
+ retryable: false
4389
+ },
4390
+ iterations: 0,
4391
+ toolCalls: 0,
4392
+ durationMs: 0
4393
+ };
4394
+ this.recordCompletion(synthetic);
4395
+ }
4284
4396
  this.fleetBus?.emit({
4285
4397
  subagentId,
4286
4398
  ts: Date.now(),
@@ -4528,9 +4640,12 @@ function makeSpawnTool(director, roster) {
4528
4640
  provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
4529
4641
  model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
4530
4642
  systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
4531
- maxIterations: { type: "number" },
4532
- maxToolCalls: { type: "number" },
4533
- maxCostUsd: { type: "number" }
4643
+ maxIterations: { type: "number", minimum: 1 },
4644
+ maxToolCalls: { type: "number", minimum: 1 },
4645
+ maxCostUsd: { type: "number", minimum: 0 },
4646
+ timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
4647
+ 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." },
4648
+ maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
4534
4649
  },
4535
4650
  required: []
4536
4651
  };
@@ -4576,6 +4691,9 @@ function makeSpawnTool(director, roster) {
4576
4691
  if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
4577
4692
  if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
4578
4693
  if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
4694
+ if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
4695
+ if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
4696
+ if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
4579
4697
  try {
4580
4698
  const subagentId = await director.spawn(cfg);
4581
4699
  return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
@@ -4603,10 +4721,10 @@ function makeAssignTool(director) {
4603
4721
  const inputSchema = {
4604
4722
  type: "object",
4605
4723
  properties: {
4606
- subagentId: { type: "string", description: "Target subagent id. Required." },
4607
- description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
4608
- maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
4609
- timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
4724
+ subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
4725
+ description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
4726
+ maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
4727
+ timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
4610
4728
  },
4611
4729
  required: ["subagentId", "description"]
4612
4730
  };
@@ -4647,9 +4765,9 @@ function makeAskTool(director) {
4647
4765
  inputSchema: {
4648
4766
  type: "object",
4649
4767
  properties: {
4650
- subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
4651
- question: { type: "string", description: "The question or instruction." },
4652
- timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
4768
+ subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
4769
+ question: { type: "string", minLength: 1, description: "The question or instruction." },
4770
+ timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
4653
4771
  },
4654
4772
  required: ["subagentId", "question"]
4655
4773
  },
@@ -4657,13 +4775,49 @@ function makeAskTool(director) {
4657
4775
  const i = input;
4658
4776
  try {
4659
4777
  const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
4660
- return { ok: true, answer };
4778
+ const stored = director.largeAnswerStore.storeAnswer(answer);
4779
+ if (stored.inline) {
4780
+ return { ok: true, answer: stored.summary };
4781
+ }
4782
+ return {
4783
+ ok: true,
4784
+ answer: stored.summary,
4785
+ _answerKey: stored.key,
4786
+ _hint: "Response was large and stored. Use ask_result with the key to retrieve it."
4787
+ };
4661
4788
  } catch (err) {
4662
4789
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
4663
4790
  }
4664
4791
  }
4665
4792
  };
4666
4793
  }
4794
+ function makeAskResultTool(director) {
4795
+ return {
4796
+ name: "ask_result",
4797
+ description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
4798
+ permission: "auto",
4799
+ mutating: false,
4800
+ inputSchema: {
4801
+ type: "object",
4802
+ properties: {
4803
+ key: {
4804
+ type: "string",
4805
+ minLength: 1,
4806
+ description: "The `_answerKey` returned by `ask_subagent` for a large response."
4807
+ }
4808
+ },
4809
+ required: ["key"]
4810
+ },
4811
+ async execute(input) {
4812
+ const i = input;
4813
+ const value = director.largeAnswerStore.retrieveAnswer(i.key);
4814
+ if (value === void 0) {
4815
+ return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
4816
+ }
4817
+ return { ok: true, value };
4818
+ }
4819
+ };
4820
+ }
4667
4821
  function makeRollUpTool(director) {
4668
4822
  return {
4669
4823
  name: "roll_up",
@@ -4817,7 +4971,18 @@ function makeCollabDebugTool(director) {
4817
4971
  },
4818
4972
  timeoutMs: {
4819
4973
  type: "number",
4974
+ minimum: 1,
4820
4975
  description: "Timeout in ms. Default: 600000 (10 minutes)."
4976
+ },
4977
+ maxTargetFiles: {
4978
+ type: "number",
4979
+ minimum: 1,
4980
+ description: "Maximum number of files to include in the snapshot. If not set, the limit is computed dynamically from contextWindow or falls back to the default (30)."
4981
+ },
4982
+ contextWindow: {
4983
+ type: "number",
4984
+ minimum: 1,
4985
+ description: "Context window size (tokens) of the model. When provided and maxTargetFiles is not set, the file limit is computed dynamically as floor((contextWindow * 0.4) / 2000)."
4821
4986
  }
4822
4987
  },
4823
4988
  required: ["targetPaths"]
@@ -4829,7 +4994,9 @@ function makeCollabDebugTool(director) {
4829
4994
  }
4830
4995
  const options = {
4831
4996
  targetPaths: i.targetPaths,
4832
- timeoutMs: i.timeoutMs
4997
+ timeoutMs: i.timeoutMs,
4998
+ maxTargetFiles: i.maxTargetFiles,
4999
+ contextWindow: i.contextWindow
4833
5000
  };
4834
5001
  try {
4835
5002
  const report = await director.spawnCollab(options);
@@ -4896,6 +5063,123 @@ function makeWorkCompleteTool(director) {
4896
5063
  }
4897
5064
  };
4898
5065
  }
5066
+ var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
5067
+ var IS_WINDOWS = process.platform === "win32";
5068
+ var SEP = IS_WINDOWS ? "\\" : "/";
5069
+ function isGlob(p) {
5070
+ for (const c of p) {
5071
+ if (GLOB_CHARS.has(c)) return true;
5072
+ }
5073
+ return false;
5074
+ }
5075
+ function globToRegex(pat) {
5076
+ let i = 0, re = "^";
5077
+ while (i < pat.length) {
5078
+ const c = pat[i];
5079
+ if (c === "*") {
5080
+ if (pat[i + 1] === "*") {
5081
+ re += ".*";
5082
+ i += 2;
5083
+ if (pat[i] === "/") i++;
5084
+ } else {
5085
+ re += "[^/\\\\]*";
5086
+ i++;
5087
+ }
5088
+ } else if (c === "?") {
5089
+ re += "[^/\\\\]";
5090
+ i++;
5091
+ } else if (c === "[") {
5092
+ let cls = "[";
5093
+ i++;
5094
+ if (pat[i] === "!" || pat[i] === "^") {
5095
+ cls += "^";
5096
+ i++;
5097
+ }
5098
+ while (i < pat.length && pat[i] !== "]") {
5099
+ const ch = pat[i] ?? "";
5100
+ if (ch === "\\") cls += "\\\\";
5101
+ else if (ch === "]" || ch === "^") cls += `\\${ch}`;
5102
+ else cls += ch;
5103
+ i++;
5104
+ }
5105
+ cls += "]";
5106
+ re += cls;
5107
+ i++;
5108
+ } else {
5109
+ re += c.replace(/[.+^${}()|\\]/g, "\\$&");
5110
+ i++;
5111
+ }
5112
+ }
5113
+ return new RegExp(re + "$");
5114
+ }
5115
+ function baseDir(pat) {
5116
+ let i = pat.length - 1;
5117
+ while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
5118
+ const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
5119
+ return cut < 0 ? "." : pat.slice(0, cut);
5120
+ }
5121
+ async function expandGlob(pattern) {
5122
+ if (!isGlob(pattern)) return [pattern];
5123
+ const results = /* @__PURE__ */ new Set();
5124
+ const abs = isAbsolute(pattern);
5125
+ const base = abs ? baseDir(pattern) : baseDir(pattern);
5126
+ const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
5127
+ async function walk(dir, pat) {
5128
+ let entries;
5129
+ try {
5130
+ entries = await fsp6.readdir(dir);
5131
+ } catch {
5132
+ return;
5133
+ }
5134
+ const firstGlob = pat.search(/[*?[\[]/);
5135
+ if (firstGlob < 0) {
5136
+ const re = globToRegex(pat);
5137
+ for (const e of entries) {
5138
+ if (re.test(e)) {
5139
+ const full = `${dir}${SEP}${e}`;
5140
+ results.add(abs ? resolve(full) : full);
5141
+ }
5142
+ }
5143
+ return;
5144
+ }
5145
+ const before = pat.slice(0, firstGlob);
5146
+ const rest = pat.slice(firstGlob);
5147
+ if (before.endsWith("**")) {
5148
+ await walk(dir, rest);
5149
+ for (const e of entries) {
5150
+ const full = `${dir}${SEP}${e}`;
5151
+ try {
5152
+ const stat4 = await fsp6.stat(full);
5153
+ if (stat4.isDirectory()) await walk(full, rest);
5154
+ } catch {
5155
+ }
5156
+ }
5157
+ } else if (before === "") {
5158
+ const re = globToRegex(rest);
5159
+ for (const e of entries) {
5160
+ if (re.test(e)) {
5161
+ const full = `${dir}${SEP}${e}`;
5162
+ results.add(abs ? resolve(full) : full);
5163
+ }
5164
+ }
5165
+ } else {
5166
+ const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
5167
+ if (entries.includes(seg)) {
5168
+ const full = `${dir}${SEP}${seg}`;
5169
+ try {
5170
+ const stat4 = await fsp6.stat(full);
5171
+ if (stat4.isDirectory()) await walk(full, rest);
5172
+ } catch {
5173
+ }
5174
+ }
5175
+ }
5176
+ }
5177
+ await walk(base === "." ? "." : base, relPat);
5178
+ return [...results];
5179
+ }
5180
+
5181
+ // src/coordination/collab-debug.ts
5182
+ var DEFAULT_MAX_TARGET_FILES = 30;
4899
5183
  var DirectorAlertLevel = /* @__PURE__ */ ((DirectorAlertLevel2) => {
4900
5184
  DirectorAlertLevel2["WARNING"] = "warning";
4901
5185
  DirectorAlertLevel2["CRITICAL"] = "critical";
@@ -4958,11 +5242,36 @@ var CollabSession = class extends EventEmitter {
4958
5242
  getSubagentIds() {
4959
5243
  return new Map(this.subagentIds);
4960
5244
  }
5245
+ /**
5246
+ * Returns the effective file limit for this session.
5247
+ * Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
5248
+ */
5249
+ effectiveFileLimit() {
5250
+ if (this.options.maxTargetFiles !== void 0) {
5251
+ return this.options.maxTargetFiles;
5252
+ }
5253
+ if (this.options.contextWindow !== void 0) {
5254
+ return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
5255
+ }
5256
+ return DEFAULT_MAX_TARGET_FILES;
5257
+ }
4961
5258
  async buildSnapshot() {
4962
5259
  if (this.snapshot.files.length > 0) return this.snapshot;
4963
- for (const filePath of this.options.targetPaths) {
5260
+ const allFiles = [];
5261
+ for (const pattern of this.options.targetPaths) {
5262
+ const expanded = await expandGlob(pattern);
5263
+ allFiles.push(...expanded);
5264
+ }
5265
+ const limit = this.effectiveFileLimit();
5266
+ if (allFiles.length > limit) {
5267
+ const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
5268
+ throw new Error(
5269
+ `[collab_debug] Target has ${allFiles.length} files, which exceeds the limit (${hint}). Narrow the target or pass maxTargetFiles / contextWindow to override. For large codebases, run package-by-package or module-by-module sessions instead of targeting the entire repo.`
5270
+ );
5271
+ }
5272
+ for (const filePath of allFiles) {
4964
5273
  try {
4965
- const content = await fsp5.readFile(filePath, "utf8");
5274
+ const content = await fsp6.readFile(filePath, "utf8");
4966
5275
  const ext = filePath.split(".").pop() ?? "";
4967
5276
  const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
4968
5277
  this.snapshot.files.push({ path: filePath, content, language });
@@ -5016,7 +5325,7 @@ var CollabSession = class extends EventEmitter {
5016
5325
  reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
5017
5326
  }, this.timeoutMs);
5018
5327
  });
5019
- let results;
5328
+ let results = null;
5020
5329
  try {
5021
5330
  results = await Promise.race([
5022
5331
  Promise.all([
@@ -5447,7 +5756,7 @@ var FleetContextOverflowError = class extends Error {
5447
5756
  this.observed = observed;
5448
5757
  }
5449
5758
  };
5450
- var Director = class {
5759
+ var Director = class _Director {
5451
5760
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
5452
5761
  get coordinatorId() {
5453
5762
  return this.id;
@@ -5505,6 +5814,8 @@ var Director = class {
5505
5814
  * coordinator already fired the event — `awaitTasks(['t-1'])` after
5506
5815
  * t-1 finished should resolve immediately, not hang. */
5507
5816
  completed = /* @__PURE__ */ new Map();
5817
+ /** Prevents the completed Map from growing unbounded in long-running directors. */
5818
+ static MAX_COMPLETED = 1e4;
5508
5819
  /** Per-subagent provider/model metadata, captured at spawn time so the
5509
5820
  * FleetUsageAggregator's metaLookup can surface readable rows. */
5510
5821
  subagentMeta = /* @__PURE__ */ new Map();
@@ -5584,6 +5895,8 @@ var Director = class {
5584
5895
  _leaderBtwNotes = [];
5585
5896
  /** Active collab sessions tracked by sessionId (see spawnCollab). */
5586
5897
  _activeCollabSessions = /* @__PURE__ */ new Map();
5898
+ /** Prevents large `ask_subagent` answers from bloating the leader's context window. */
5899
+ largeAnswerStore;
5587
5900
  constructor(opts) {
5588
5901
  this.id = opts.config.coordinatorId || randomUUID();
5589
5902
  this.manifestPath = opts.manifestPath;
@@ -5612,7 +5925,7 @@ var Director = class {
5612
5925
  }, opts.checkpointDebounceMs ?? 250) : null;
5613
5926
  this.fleetManager = opts.fleetManager;
5614
5927
  if (this.sharedScratchpadPath) {
5615
- void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
5928
+ void fsp6.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
5616
5929
  (err) => this.logShutdownError("shared_scratchpad_mkdir", err)
5617
5930
  );
5618
5931
  }
@@ -5644,6 +5957,11 @@ var Director = class {
5644
5957
  this.taskCompletedListener = (payload) => {
5645
5958
  const r = payload.result;
5646
5959
  this.completed.set(r.taskId, r);
5960
+ if (this.completed.size > _Director.MAX_COMPLETED) {
5961
+ const toDelete = this.completed.size - _Director.MAX_COMPLETED;
5962
+ const keys = [...this.completed.keys()].slice(0, toDelete);
5963
+ for (const k of keys) this.completed.delete(k);
5964
+ }
5647
5965
  const waiter = this.taskWaiters.get(r.taskId);
5648
5966
  if (waiter) {
5649
5967
  waiter.resolve(r);
@@ -5750,6 +6068,7 @@ var Director = class {
5750
6068
  payload.extend(extra);
5751
6069
  });
5752
6070
  });
6071
+ this.largeAnswerStore = new LargeAnswerStore(2e3);
5753
6072
  }
5754
6073
  /**
5755
6074
  * Record a granted budget extension and broadcast it on the FleetBus so
@@ -5976,6 +6295,20 @@ var Director = class {
5976
6295
  );
5977
6296
  this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
5978
6297
  this.subagentBridges.set(result.subagentId, subagentBridge);
6298
+ this.fleet.emit({
6299
+ subagentId: result.subagentId,
6300
+ ts: Date.now(),
6301
+ type: "subagent.spawned",
6302
+ payload: {
6303
+ subagentId: result.subagentId,
6304
+ taskId: "",
6305
+ // taskId will be set when assign() is called
6306
+ name: config.name,
6307
+ role: config.role,
6308
+ provider: config.provider,
6309
+ model: config.model
6310
+ }
6311
+ });
5979
6312
  if (!this.fleetManager) {
5980
6313
  this.manifestEntries.set(result.subagentId, {
5981
6314
  subagentId: result.subagentId,
@@ -6115,7 +6448,7 @@ var Director = class {
6115
6448
  })),
6116
6449
  usage: this.usage.snapshot()
6117
6450
  };
6118
- await fsp5.mkdir(path4.dirname(this.manifestPath), { recursive: true });
6451
+ await fsp6.mkdir(path4.dirname(this.manifestPath), { recursive: true });
6119
6452
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
6120
6453
  return this.manifestPath;
6121
6454
  }
@@ -6230,11 +6563,11 @@ var Director = class {
6230
6563
  if (cached) return cached;
6231
6564
  const existing = this.taskWaiters.get(id);
6232
6565
  if (existing) return existing.promise;
6233
- let resolve;
6566
+ let resolve2;
6234
6567
  const promise = new Promise((res) => {
6235
- resolve = res;
6568
+ resolve2 = res;
6236
6569
  });
6237
- this.taskWaiters.set(id, { promise, resolve });
6570
+ this.taskWaiters.set(id, { promise, resolve: resolve2 });
6238
6571
  return promise;
6239
6572
  })
6240
6573
  );
@@ -6247,7 +6580,24 @@ var Director = class {
6247
6580
  }
6248
6581
  async remove(subagentId) {
6249
6582
  await this.coordinator.remove(subagentId);
6583
+ const bridge = this.subagentBridges.get(subagentId);
6584
+ if (bridge) {
6585
+ await bridge.stop();
6586
+ this.subagentBridges.delete(subagentId);
6587
+ }
6250
6588
  this.usage.removeSubagent(subagentId);
6589
+ if (this.fleetManager) {
6590
+ this.fleetManager.removeSubagent(subagentId);
6591
+ } else {
6592
+ const entry = this.manifestEntries.get(subagentId);
6593
+ if (entry?.name) {
6594
+ const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
6595
+ this._usedNicknames.delete(nicknameKey);
6596
+ }
6597
+ }
6598
+ this.manifestEntries.delete(subagentId);
6599
+ this.taskOwners.delete(subagentId);
6600
+ this.taskDescriptions.delete(subagentId);
6251
6601
  }
6252
6602
  status() {
6253
6603
  const base = this.coordinator.getStatus();
@@ -6303,7 +6653,7 @@ var Director = class {
6303
6653
  const filePath = path4.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
6304
6654
  let raw;
6305
6655
  try {
6306
- raw = await fsp5.readFile(filePath, "utf8");
6656
+ raw = await fsp6.readFile(filePath, "utf8");
6307
6657
  } catch {
6308
6658
  return null;
6309
6659
  }
@@ -6409,6 +6759,7 @@ var Director = class {
6409
6759
  makeAssignTool(this),
6410
6760
  makeAwaitTasksTool(this),
6411
6761
  makeAskTool(this),
6762
+ makeAskResultTool(this),
6412
6763
  makeRollUpTool(this),
6413
6764
  makeTerminateTool(this),
6414
6765
  makeTerminateAllTool(this),
@@ -6491,15 +6842,33 @@ function createDelegateTool(opts) {
6491
6842
  },
6492
6843
  timeoutMs: {
6493
6844
  type: "number",
6845
+ minimum: 1,
6494
6846
  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.`
6495
6847
  },
6496
6848
  maxIterations: {
6497
6849
  type: "number",
6850
+ minimum: 1,
6498
6851
  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)."
6499
6852
  },
6500
6853
  maxToolCalls: {
6501
6854
  type: "number",
6855
+ minimum: 1,
6502
6856
  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)."
6857
+ },
6858
+ idleTimeoutMs: {
6859
+ type: "number",
6860
+ minimum: 1,
6861
+ 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."
6862
+ },
6863
+ maxTokens: {
6864
+ type: "number",
6865
+ minimum: 1,
6866
+ description: "Maximum total tokens (input + output) the subagent may use. Unset = use the role/coordinator default."
6867
+ },
6868
+ maxCostUsd: {
6869
+ type: "number",
6870
+ minimum: 0,
6871
+ description: "Maximum estimated USD cost the subagent may incur. Unset = use the role/coordinator default."
6503
6872
  }
6504
6873
  },
6505
6874
  required: ["task"]
@@ -6557,12 +6926,21 @@ function createDelegateTool(opts) {
6557
6926
  };
6558
6927
  cfg = applyRosterBudget({ ...cfg, name: i.name });
6559
6928
  }
6560
- if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
6929
+ if (typeof i.maxIterations === "number") {
6561
6930
  cfg.maxIterations = i.maxIterations;
6562
6931
  }
6563
- if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
6932
+ if (typeof i.maxToolCalls === "number") {
6564
6933
  cfg.maxToolCalls = i.maxToolCalls;
6565
6934
  }
6935
+ if (typeof i.idleTimeoutMs === "number") {
6936
+ cfg.idleTimeoutMs = i.idleTimeoutMs;
6937
+ }
6938
+ if (typeof i.maxTokens === "number") {
6939
+ cfg.maxTokens = i.maxTokens;
6940
+ }
6941
+ if (typeof i.maxCostUsd === "number") {
6942
+ cfg.maxCostUsd = i.maxCostUsd;
6943
+ }
6566
6944
  const SUBAGENT_TIMEOUT_BUFFER_MS = opts.subagentTimeoutBufferMs ?? 6e4;
6567
6945
  if (!cfg.timeoutMs) {
6568
6946
  cfg.timeoutMs = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
@@ -6574,7 +6952,7 @@ function createDelegateTool(opts) {
6574
6952
  subagentId
6575
6953
  });
6576
6954
  const dir = director;
6577
- const result = await new Promise((resolve) => {
6955
+ const result = await new Promise((resolve2) => {
6578
6956
  let settled = false;
6579
6957
  let timer;
6580
6958
  const finish = (value) => {
@@ -6584,7 +6962,7 @@ function createDelegateTool(opts) {
6584
6962
  offTool();
6585
6963
  offIter();
6586
6964
  offProgress();
6587
- resolve(value);
6965
+ resolve2(value);
6588
6966
  };
6589
6967
  const arm = () => {
6590
6968
  if (timer) clearTimeout(timer);
@@ -6731,7 +7109,7 @@ async function readSubagentPartial(opts, subagentId) {
6731
7109
  candidates.push(path4.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
6732
7110
  } else {
6733
7111
  try {
6734
- const entries = await fsp5.readdir(opts.sessionsRoot, { withFileTypes: true });
7112
+ const entries = await fsp6.readdir(opts.sessionsRoot, { withFileTypes: true });
6735
7113
  for (const entry of entries) {
6736
7114
  if (entry.isDirectory()) {
6737
7115
  candidates.push(path4.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
@@ -6744,7 +7122,7 @@ async function readSubagentPartial(opts, subagentId) {
6744
7122
  for (const file of candidates) {
6745
7123
  let raw;
6746
7124
  try {
6747
- raw = await fsp5.readFile(file, "utf8");
7125
+ raw = await fsp6.readFile(file, "utf8");
6748
7126
  } catch {
6749
7127
  continue;
6750
7128
  }
@@ -7063,7 +7441,7 @@ var DefaultSessionStore = class {
7063
7441
  const file = path4.join(shardDir, `${id}.jsonl`);
7064
7442
  let handle;
7065
7443
  try {
7066
- handle = await fsp5.open(file, "a", 384);
7444
+ handle = await fsp6.open(file, "a", 384);
7067
7445
  } catch (err) {
7068
7446
  throw new Error(
7069
7447
  `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
@@ -7087,7 +7465,7 @@ var DefaultSessionStore = class {
7087
7465
  const data = await this.load(id);
7088
7466
  let handle;
7089
7467
  try {
7090
- handle = await fsp5.open(file, "a", 384);
7468
+ handle = await fsp6.open(file, "a", 384);
7091
7469
  } catch (err) {
7092
7470
  throw new Error(
7093
7471
  `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
@@ -7116,7 +7494,7 @@ var DefaultSessionStore = class {
7116
7494
  }
7117
7495
  async load(id) {
7118
7496
  const file = this.sessionPath(id, ".jsonl");
7119
- const raw = await fsp5.readFile(file, "utf8");
7497
+ const raw = await fsp6.readFile(file, "utf8");
7120
7498
  const lines = raw.split("\n").filter((l) => l.trim());
7121
7499
  const events = [];
7122
7500
  for (const line of lines) {
@@ -7151,7 +7529,7 @@ var DefaultSessionStore = class {
7151
7529
  /** Recursively collect all session IDs from shard subdirectories. */
7152
7530
  async collectSessionIds(dir) {
7153
7531
  const ids = [];
7154
- const entries = await fsp5.readdir(dir, { withFileTypes: true });
7532
+ const entries = await fsp6.readdir(dir, { withFileTypes: true });
7155
7533
  for (const entry of entries) {
7156
7534
  const full = path4.join(dir, entry.name);
7157
7535
  if (entry.isDirectory()) {
@@ -7165,12 +7543,12 @@ var DefaultSessionStore = class {
7165
7543
  async summaryFor(id) {
7166
7544
  const manifest = this.sessionPath(id, ".summary.json");
7167
7545
  try {
7168
- const raw = await fsp5.readFile(manifest, "utf8");
7546
+ const raw = await fsp6.readFile(manifest, "utf8");
7169
7547
  return JSON.parse(raw);
7170
7548
  } catch {
7171
7549
  const full = this.sessionPath(id, ".jsonl");
7172
- const stat3 = await fsp5.stat(full);
7173
- const summary = await this.summarize(id, stat3.mtime.toISOString());
7550
+ const stat4 = await fsp6.stat(full);
7551
+ const summary = await this.summarize(id, stat4.mtime.toISOString());
7174
7552
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
7175
7553
  console.warn(
7176
7554
  `[session-store] Failed to write manifest for "${id}":`,
@@ -7181,8 +7559,8 @@ var DefaultSessionStore = class {
7181
7559
  }
7182
7560
  }
7183
7561
  async delete(id) {
7184
- await fsp5.unlink(this.sessionPath(id, ".jsonl"));
7185
- await fsp5.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
7562
+ await fsp6.unlink(this.sessionPath(id, ".jsonl"));
7563
+ await fsp6.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
7186
7564
  }
7187
7565
  async clearHistory(id) {
7188
7566
  await this.ensureShardDir(id);
@@ -7196,8 +7574,8 @@ var DefaultSessionStore = class {
7196
7574
  provider: "unknown"
7197
7575
  })}
7198
7576
  `;
7199
- await fsp5.writeFile(file, record, "utf8");
7200
- await fsp5.unlink(meta).catch(() => void 0);
7577
+ await fsp6.writeFile(file, record, "utf8");
7578
+ await fsp6.unlink(meta).catch(() => void 0);
7201
7579
  }
7202
7580
  async summarize(id, mtime) {
7203
7581
  try {
@@ -7383,7 +7761,7 @@ var FileSessionWriter = class {
7383
7761
  `;
7384
7762
  try {
7385
7763
  if (this.filePath) {
7386
- await fsp5.writeFile(this.filePath, record, { flag: "a", mode: 384 });
7764
+ await fsp6.writeFile(this.filePath, record, { flag: "a", mode: 384 });
7387
7765
  }
7388
7766
  } catch {
7389
7767
  }
@@ -7476,7 +7854,7 @@ var FileSessionWriter = class {
7476
7854
  }
7477
7855
  async truncateToCheckpoint(targetPromptIndex) {
7478
7856
  if (!this.filePath) return 0;
7479
- const raw = await fsp5.readFile(this.filePath, "utf8");
7857
+ const raw = await fsp6.readFile(this.filePath, "utf8");
7480
7858
  const lines = raw.split("\n");
7481
7859
  const kept = [];
7482
7860
  let removedCount = 0;
@@ -7514,13 +7892,13 @@ var FileSessionWriter = class {
7514
7892
  }
7515
7893
  const truncated = kept.join("\n");
7516
7894
  const tmpPath = `${this.filePath}.rewind.tmp`;
7517
- await fsp5.writeFile(tmpPath, truncated + "\n", "utf8");
7895
+ await fsp6.writeFile(tmpPath, truncated + "\n", "utf8");
7518
7896
  try {
7519
7897
  await this.handle.close();
7520
- await fsp5.rename(tmpPath, this.filePath);
7521
- this.handle = await fsp5.open(this.filePath, "a", 384);
7898
+ await fsp6.rename(tmpPath, this.filePath);
7899
+ this.handle = await fsp6.open(this.filePath, "a", 384);
7522
7900
  } catch (err) {
7523
- await fsp5.unlink(tmpPath).catch(() => void 0);
7901
+ await fsp6.unlink(tmpPath).catch(() => void 0);
7524
7902
  throw err;
7525
7903
  }
7526
7904
  await this.append({
@@ -7546,7 +7924,7 @@ var FileSessionWriter = class {
7546
7924
  provider: this.meta.provider ?? "unknown"
7547
7925
  })}
7548
7926
  `;
7549
- await fsp5.writeFile(this.filePath, record, "utf8");
7927
+ await fsp6.writeFile(this.filePath, record, "utf8");
7550
7928
  }
7551
7929
  /**
7552
7930
  * Idea #1 — write an in-flight marker. The agent loop should call
@@ -7892,7 +8270,7 @@ var FleetManager = class {
7892
8270
  })),
7893
8271
  usage: this.usage.snapshot()
7894
8272
  };
7895
- await fsp5.mkdir(path4.dirname(this.manifestPath), { recursive: true });
8273
+ await fsp6.mkdir(path4.dirname(this.manifestPath), { recursive: true });
7896
8274
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
7897
8275
  return this.manifestPath;
7898
8276
  }
@@ -8002,6 +8380,23 @@ var FleetManager = class {
8002
8380
  }));
8003
8381
  return { pending, live: [] };
8004
8382
  }
8383
+ /**
8384
+ * Clean up all fleet-manager state associated with a removed subagent:
8385
+ * - Frees the nickname slot so the same name can be reused
8386
+ * - Removes any pending tasks for this subagent
8387
+ */
8388
+ removeSubagent(subagentId) {
8389
+ const entry = this.manifestEntries.get(subagentId);
8390
+ if (entry?.name) {
8391
+ const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
8392
+ this._usedNicknames.delete(nicknameKey);
8393
+ }
8394
+ for (const [taskId, task] of this.pendingTasks) {
8395
+ if (task.subagentId === subagentId) {
8396
+ this.pendingTasks.delete(taskId);
8397
+ }
8398
+ }
8399
+ }
8005
8400
  /** Release all resources: clear the manifest debounce timer and dispose the usage aggregator. */
8006
8401
  dispose() {
8007
8402
  if (this.manifestTimer) {
@@ -8012,6 +8407,6 @@ var FleetManager = class {
8012
8407
  }
8013
8408
  };
8014
8409
 
8015
- export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, getAgentDefinition, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, rosterSummaryFromConfigs, scoreAgents };
8410
+ export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, getAgentDefinition, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, rosterSummaryFromConfigs, scoreAgents };
8016
8411
  //# sourceMappingURL=index.js.map
8017
8412
  //# sourceMappingURL=index.js.map