eminiar 0.11.0 → 0.12.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.
package/dist/cli.js CHANGED
@@ -2601,9 +2601,9 @@ function handleIntersectionResults(result, left, right) {
2601
2601
  result.value = merged.data;
2602
2602
  return result;
2603
2603
  }
2604
- function getTupleOptStart(items, key) {
2605
- for (let i = items.length - 1; i >= 0; i--) {
2606
- const omittable = key === "optin" ? items[i]._zod.optin !== void 0 : items[i]._zod.optout === "optional";
2604
+ function getTupleOptStart(items2, key) {
2605
+ for (let i = items2.length - 1; i >= 0; i--) {
2606
+ const omittable = key === "optin" ? items2[i]._zod.optin !== void 0 : items2[i]._zod.optout === "optional";
2607
2607
  if (!omittable)
2608
2608
  return i + 1;
2609
2609
  }
@@ -2615,11 +2615,11 @@ function handleTupleResult(result, final, index) {
2615
2615
  }
2616
2616
  final.value[index] = result.value;
2617
2617
  }
2618
- function handleTupleResults(itemResults, final, items, input2, optoutStart) {
2619
- for (let i = 0; i < items.length; i++) {
2618
+ function handleTupleResults(itemResults, final, items2, input2, optoutStart) {
2619
+ for (let i = 0; i < items2.length; i++) {
2620
2620
  const r = itemResults[i];
2621
2621
  const isPresent = i < input2.length;
2622
- if (!isPresent && i >= optoutStart && items[i]._zod.optin === "optional") {
2622
+ if (!isPresent && i >= optoutStart && items2[i]._zod.optin === "optional") {
2623
2623
  final.value.length = i;
2624
2624
  break;
2625
2625
  }
@@ -2633,7 +2633,7 @@ function handleTupleResults(itemResults, final, items, input2, optoutStart) {
2633
2633
  final.value[i] = r.value;
2634
2634
  }
2635
2635
  for (let i = final.value.length - 1; i >= input2.length; i--) {
2636
- if (items[i]._zod.optout === "optional" && final.value[i] === void 0) {
2636
+ if (items2[i]._zod.optout === "optional" && final.value[i] === void 0) {
2637
2637
  final.value.length = i;
2638
2638
  } else {
2639
2639
  break;
@@ -3830,7 +3830,7 @@ var init_schemas = __esm({
3830
3830
  });
3831
3831
  $ZodTuple = /* @__PURE__ */ $constructor("$ZodTuple", (inst, def) => {
3832
3832
  $ZodType.init(inst, def);
3833
- const items = def.items;
3833
+ const items2 = def.items;
3834
3834
  const memo2 = globalConfig.memoizer;
3835
3835
  memo2?.attach(inst);
3836
3836
  inst._zod.parse = (payload, ctx) => {
@@ -3846,8 +3846,8 @@ var init_schemas = __esm({
3846
3846
  }
3847
3847
  payload.value = memo2 ? memo2.alloc(inst, payload, [], ctx) : [];
3848
3848
  const proms = [];
3849
- const optinStart = getTupleOptStart(items, "optin");
3850
- const optoutStart = getTupleOptStart(items, "optout");
3849
+ const optinStart = getTupleOptStart(items2, "optin");
3850
+ const optoutStart = getTupleOptStart(items2, "optout");
3851
3851
  if (!def.rest) {
3852
3852
  if (input2.length < optinStart) {
3853
3853
  payload.issues.push({
@@ -3860,10 +3860,10 @@ var init_schemas = __esm({
3860
3860
  });
3861
3861
  return payload;
3862
3862
  }
3863
- if (input2.length > items.length) {
3863
+ if (input2.length > items2.length) {
3864
3864
  payload.issues.push({
3865
3865
  code: "too_big",
3866
- maximum: items.length,
3866
+ maximum: items2.length,
3867
3867
  inclusive: true,
3868
3868
  input: input2,
3869
3869
  inst,
@@ -3871,11 +3871,11 @@ var init_schemas = __esm({
3871
3871
  });
3872
3872
  }
3873
3873
  }
3874
- const itemResults = new Array(items.length);
3874
+ const itemResults = new Array(items2.length);
3875
3875
  const abortEarly = def.rest ? ctx?.abortEarly : void 0;
3876
3876
  let itemAborted = false;
3877
- for (let i = 0; i < items.length; i++) {
3878
- const r = items[i]._zod.run({ value: input2[i], issues: [] }, ctx);
3877
+ for (let i = 0; i < items2.length; i++) {
3878
+ const r = items2[i]._zod.run({ value: input2[i], issues: [] }, ctx);
3879
3879
  if (r instanceof Promise) {
3880
3880
  proms.push(r.then((rr) => {
3881
3881
  itemResults[i] = rr;
@@ -3887,8 +3887,8 @@ var init_schemas = __esm({
3887
3887
  }
3888
3888
  }
3889
3889
  if (def.rest && !itemAborted) {
3890
- let i = items.length - 1;
3891
- const rest2 = input2.slice(items.length);
3890
+ let i = items2.length - 1;
3891
+ const rest2 = input2.slice(items2.length);
3892
3892
  let seen = payload.issues.length;
3893
3893
  for (const el of rest2) {
3894
3894
  if (abortEarly && payload.issues.length !== seen) {
@@ -3906,9 +3906,9 @@ var init_schemas = __esm({
3906
3906
  }
3907
3907
  }
3908
3908
  if (proms.length) {
3909
- return Promise.all(proms).then(() => handleTupleResults(itemResults, payload, items, input2, optoutStart));
3909
+ return Promise.all(proms).then(() => handleTupleResults(itemResults, payload, items2, input2, optoutStart));
3910
3910
  }
3911
- return handleTupleResults(itemResults, payload, items, input2, optoutStart);
3911
+ return handleTupleResults(itemResults, payload, items2, input2, optoutStart);
3912
3912
  };
3913
3913
  });
3914
3914
  $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
@@ -4602,7 +4602,7 @@ function isRef(value) {
4602
4602
  function cloneIssues(issues) {
4603
4603
  return issues.map((iss) => iss.path ? { ...iss, path: iss.path.slice() } : { ...iss });
4604
4604
  }
4605
- function isRecursive(inst, stack, resolve9) {
4605
+ function isRecursive(inst, stack, resolve11) {
4606
4606
  const cached5 = recursive.get(inst);
4607
4607
  if (cached5 !== void 0)
4608
4608
  return cached5 ? PROVEN : NONE;
@@ -4612,7 +4612,7 @@ function isRecursive(inst, stack, resolve9) {
4612
4612
  let result = NONE;
4613
4613
  const check3 = (child) => {
4614
4614
  if (result !== PROVEN && child?._zod) {
4615
- const answer = isRecursive(child, stack, resolve9);
4615
+ const answer = isRecursive(child, stack, resolve11);
4616
4616
  if (answer > result)
4617
4617
  result = answer;
4618
4618
  }
@@ -4623,7 +4623,7 @@ function isRecursive(inst, stack, resolve9) {
4623
4623
  const desc = Object.getOwnPropertyDescriptor(sh, key);
4624
4624
  if (spread && !desc.enumerable)
4625
4625
  continue;
4626
- const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve9) : NONE;
4626
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve11) : NONE;
4627
4627
  if (child > answer)
4628
4628
  answer = child;
4629
4629
  }
@@ -4687,7 +4687,7 @@ function isRecursive(inst, stack, resolve9) {
4687
4687
  break;
4688
4688
  // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
4689
4689
  case "lazy": {
4690
- const inner = def._cachedInner ?? (resolve9 ? inst._zod.innerType : void 0);
4690
+ const inner = def._cachedInner ?? (resolve11 ? inst._zod.innerType : void 0);
4691
4691
  merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
4692
4692
  break;
4693
4693
  }
@@ -14180,20 +14180,20 @@ function generateNonOptionalCheck(doc, ctx, schema, accessor) {
14180
14180
  }
14181
14181
  function generateTupleCheck(doc, ctx, schema, accessor) {
14182
14182
  const def = schema._zod.def;
14183
- const items = def.items;
14183
+ const items2 = def.items;
14184
14184
  const rest2 = def.rest;
14185
14185
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
14186
- const optinStart = getTupleOptStart2(items, "optin");
14187
- const optoutStart = getTupleOptStart2(items, "optout");
14186
+ const optinStart = getTupleOptStart2(items2, "optin");
14187
+ const optoutStart = getTupleOptStart2(items2, "optout");
14188
14188
  if (rest2) {
14189
14189
  doc.write(`if (${accessor}.length < ${optinStart}) return INVALID;`);
14190
14190
  } else {
14191
- doc.write(`if (${accessor}.length < ${optinStart} || ${accessor}.length > ${items.length}) return INVALID;`);
14191
+ doc.write(`if (${accessor}.length < ${optinStart} || ${accessor}.length > ${items2.length}) return INVALID;`);
14192
14192
  }
14193
14193
  const outputVar = newVar(ctx);
14194
14194
  doc.write(`const ${outputVar} = [];`);
14195
- for (let i = 0; i < items.length; i++) {
14196
- const itemSchema = items[i];
14195
+ for (let i = 0; i < items2.length; i++) {
14196
+ const itemSchema = items2[i];
14197
14197
  if (i >= optoutStart) {
14198
14198
  doc.write(`if (${outputVar}.length === ${i}) {`);
14199
14199
  doc.indented((d) => {
@@ -14235,7 +14235,7 @@ function generateTupleCheck(doc, ctx, schema, accessor) {
14235
14235
  if (rest2) {
14236
14236
  const iVar = newVar(ctx);
14237
14237
  const elemVar = newVar(ctx);
14238
- doc.write(`for (let ${iVar} = ${items.length}; ${iVar} < ${accessor}.length; ${iVar}++) {`);
14238
+ doc.write(`for (let ${iVar} = ${items2.length}; ${iVar} < ${accessor}.length; ${iVar}++) {`);
14239
14239
  doc.indented((d) => {
14240
14240
  d.write(`const ${elemVar} = ${accessor}[${iVar}];`);
14241
14241
  const elemOutput = compileChild(d, ctx, rest2, elemVar);
@@ -14245,9 +14245,9 @@ function generateTupleCheck(doc, ctx, schema, accessor) {
14245
14245
  }
14246
14246
  return outputVar;
14247
14247
  }
14248
- function getTupleOptStart2(items, key) {
14249
- for (let i = items.length - 1; i >= 0; i--) {
14250
- const omittable = key === "optin" ? items[i]._zod.optin !== void 0 : items[i]._zod.optout === "optional";
14248
+ function getTupleOptStart2(items2, key) {
14249
+ for (let i = items2.length - 1; i >= 0; i--) {
14250
+ const omittable = key === "optin" ? items2[i]._zod.optin !== void 0 : items2[i]._zod.optout === "optional";
14251
14251
  if (!omittable)
14252
14252
  return i + 1;
14253
14253
  }
@@ -15438,13 +15438,13 @@ function _intersection(Class2, left, right) {
15438
15438
  });
15439
15439
  }
15440
15440
  // @__NO_SIDE_EFFECTS__
15441
- function _tuple(Class2, items, _paramsOrRest, _params) {
15441
+ function _tuple(Class2, items2, _paramsOrRest, _params) {
15442
15442
  const hasRest = _paramsOrRest instanceof $ZodType;
15443
15443
  const params = hasRest ? _params : _paramsOrRest;
15444
15444
  const rest2 = hasRest ? _paramsOrRest : null;
15445
15445
  return new Class2({
15446
15446
  type: "tuple",
15447
- items,
15447
+ items: items2,
15448
15448
  rest: rest2,
15449
15449
  ...normalizeParams(params)
15450
15450
  });
@@ -18032,13 +18032,13 @@ function intersection(left, right) {
18032
18032
  right
18033
18033
  });
18034
18034
  }
18035
- function tuple(items, _paramsOrRest, _params) {
18035
+ function tuple(items2, _paramsOrRest, _params) {
18036
18036
  const hasRest = _paramsOrRest instanceof $ZodType;
18037
18037
  const params = hasRest ? _params : _paramsOrRest;
18038
18038
  const rest2 = hasRest ? _paramsOrRest : null;
18039
18039
  return new ZodTuple({
18040
18040
  type: "tuple",
18041
- items,
18041
+ items: items2,
18042
18042
  rest: rest2,
18043
18043
  ...util_exports.normalizeParams(params)
18044
18044
  });
@@ -19364,8 +19364,8 @@ function detectVersion(schema, defaultTarget) {
19364
19364
  }
19365
19365
  return defaultTarget ?? "draft-2020-12";
19366
19366
  }
19367
- function applyMinItems(items, minItems) {
19368
- return items.map((item, index) => index < minItems ? item : item.optional());
19367
+ function applyMinItems(items2, minItems) {
19368
+ return items2.map((item, index) => index < minItems ? item : item.optional());
19369
19369
  }
19370
19370
  function decodeJSONPointerSegment(segment) {
19371
19371
  return segment.replace(/~1/g, "/").replace(/~0/g, "~");
@@ -19493,13 +19493,13 @@ function plural(n) {
19493
19493
  }
19494
19494
  function checkArrayGuards(arraySchema, guards) {
19495
19495
  const guard = z.transform((value) => value).check((payload) => {
19496
- const items = payload.value;
19497
- if (!Array.isArray(items))
19496
+ const items2 = payload.value;
19497
+ if (!Array.isArray(items2))
19498
19498
  return;
19499
19499
  if (guards.uniqueItems === true) {
19500
19500
  const firstSeen = /* @__PURE__ */ new Map();
19501
- for (let i = 0; i < items.length; i++) {
19502
- const key = canonicalKey(items[i], /* @__PURE__ */ new Set());
19501
+ for (let i = 0; i < items2.length; i++) {
19502
+ const key = canonicalKey(items2[i], /* @__PURE__ */ new Set());
19503
19503
  if (key === null)
19504
19504
  continue;
19505
19505
  const first = firstSeen.get(key);
@@ -19510,7 +19510,7 @@ function checkArrayGuards(arraySchema, guards) {
19510
19510
  payload.issues.push({
19511
19511
  code: "custom",
19512
19512
  message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
19513
- input: items,
19513
+ input: items2,
19514
19514
  path: [i],
19515
19515
  continue: true
19516
19516
  });
@@ -19520,7 +19520,7 @@ function checkArrayGuards(arraySchema, guards) {
19520
19520
  const minContains = guards.minContains ?? 1;
19521
19521
  const ceiling = guards.maxContains !== void 0 ? guards.maxContains + 1 : Number.POSITIVE_INFINITY;
19522
19522
  let matches2 = 0;
19523
- for (const item of items) {
19523
+ for (const item of items2) {
19524
19524
  if (guards.containsSchema.safeParse(item).success && ++matches2 >= ceiling)
19525
19525
  break;
19526
19526
  }
@@ -19528,7 +19528,7 @@ function checkArrayGuards(arraySchema, guards) {
19528
19528
  payload.issues.push({
19529
19529
  code: "custom",
19530
19530
  message: `Array must contain at least ${minContains} matching ${plural(minContains)}; found ${matches2}`,
19531
- input: items,
19531
+ input: items2,
19532
19532
  continue: true
19533
19533
  });
19534
19534
  }
@@ -19536,7 +19536,7 @@ function checkArrayGuards(arraySchema, guards) {
19536
19536
  payload.issues.push({
19537
19537
  code: "custom",
19538
19538
  message: `Array must contain at most ${guards.maxContains} matching ${plural(guards.maxContains)}`,
19539
- input: items,
19539
+ input: items2,
19540
19540
  continue: true
19541
19541
  });
19542
19542
  }
@@ -19821,12 +19821,12 @@ function convertBaseSchema(schema, ctx) {
19821
19821
  }
19822
19822
  case "array": {
19823
19823
  const prefixItems = schema.prefixItems;
19824
- const items = schema.items;
19824
+ const items2 = schema.items;
19825
19825
  if (prefixItems && Array.isArray(prefixItems)) {
19826
19826
  const minItems = typeof schema.minItems === "number" ? schema.minItems : 0;
19827
19827
  const tupleItems = prefixItems.map((item) => convertSchema(item, ctx));
19828
19828
  const positionalItems = applyMinItems(tupleItems, minItems);
19829
- const rest2 = !Array.isArray(items) ? getTupleRest(items, ctx) : void 0;
19829
+ const rest2 = !Array.isArray(items2) ? getTupleRest(items2, ctx) : void 0;
19830
19830
  const tupleSchema = z.tuple(positionalItems);
19831
19831
  zodSchema = rest2 ? tupleSchema.rest(rest2) : tupleSchema;
19832
19832
  if (typeof schema.minItems === "number") {
@@ -19835,9 +19835,9 @@ function convertBaseSchema(schema, ctx) {
19835
19835
  if (typeof schema.maxItems === "number") {
19836
19836
  zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
19837
19837
  }
19838
- } else if (Array.isArray(items)) {
19838
+ } else if (Array.isArray(items2)) {
19839
19839
  const minItems = typeof schema.minItems === "number" ? schema.minItems : 0;
19840
- const tupleItems = items.map((item) => convertSchema(item, ctx));
19840
+ const tupleItems = items2.map((item) => convertSchema(item, ctx));
19841
19841
  const positionalItems = applyMinItems(tupleItems, minItems);
19842
19842
  const rest2 = getTupleRest(schema.additionalItems, ctx);
19843
19843
  const tupleSchema = z.tuple(positionalItems);
@@ -19848,8 +19848,8 @@ function convertBaseSchema(schema, ctx) {
19848
19848
  if (typeof schema.maxItems === "number") {
19849
19849
  zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
19850
19850
  }
19851
- } else if (items !== void 0) {
19852
- const element = convertSchema(items, ctx);
19851
+ } else if (items2 !== void 0) {
19852
+ const element = convertSchema(items2, ctx);
19853
19853
  let arraySchema = z.array(element);
19854
19854
  if (typeof schema.minItems === "number") {
19855
19855
  arraySchema = arraySchema.min(schema.minItems);
@@ -20101,21 +20101,21 @@ function visit(schema, fnOrHandlers) {
20101
20101
  const h = fnOrHandlers[node2._zod.def.type];
20102
20102
  return h ? h(node2, rewritten) : node2;
20103
20103
  };
20104
- const cache3 = /* @__PURE__ */ new Map();
20104
+ const cache5 = /* @__PURE__ */ new Map();
20105
20105
  function run3(s) {
20106
- const cached5 = cache3.get(s);
20106
+ const cached5 = cache5.get(s);
20107
20107
  if (cached5 === RESOLVING) {
20108
20108
  return new $ZodLazy({
20109
20109
  type: "lazy",
20110
- getter: () => cache3.get(s)
20110
+ getter: () => cache5.get(s)
20111
20111
  });
20112
20112
  }
20113
20113
  if (cached5 !== void 0)
20114
20114
  return cached5;
20115
- cache3.set(s, RESOLVING);
20115
+ cache5.set(s, RESOLVING);
20116
20116
  const inner = mapInner(s);
20117
20117
  const mapped = fn(inner, inner !== s);
20118
- cache3.set(s, mapped);
20118
+ cache5.set(s, mapped);
20119
20119
  return mapped;
20120
20120
  }
20121
20121
  function mapInner(s) {
@@ -21918,11 +21918,520 @@ CREATE INDEX IF NOT EXISTS statusline_snapshots_ts ON statusline_snapshots(ts);
21918
21918
  if (!hasColumn(db, "transcript_files", "head_hash"))
21919
21919
  db.exec("ALTER TABLE transcript_files ADD COLUMN head_hash TEXT");
21920
21920
  }
21921
+ },
21922
+ {
21923
+ // Agent swarms (src/swarm): the coordination ledger. Runs own everything else, so a
21924
+ // deleted run cascades to its tasks, workers, events, blackboard notes and spawns.
21925
+ version: 7,
21926
+ sql: `
21927
+ CREATE TABLE IF NOT EXISTS swarm_runs (
21928
+ id TEXT PRIMARY KEY,
21929
+ swarm TEXT NOT NULL,
21930
+ goal TEXT NOT NULL,
21931
+ status TEXT NOT NULL DEFAULT 'active',
21932
+ topology TEXT NOT NULL,
21933
+ session_id TEXT,
21934
+ created_at INTEGER NOT NULL,
21935
+ updated_at INTEGER NOT NULL,
21936
+ ended_at INTEGER,
21937
+ summary TEXT
21938
+ );
21939
+ CREATE INDEX IF NOT EXISTS swarm_runs_status ON swarm_runs(status, updated_at);
21940
+ CREATE INDEX IF NOT EXISTS swarm_runs_swarm ON swarm_runs(swarm, status, created_at);
21941
+ CREATE TABLE IF NOT EXISTS swarm_tasks (
21942
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21943
+ run_id TEXT NOT NULL REFERENCES swarm_runs(id) ON DELETE CASCADE,
21944
+ title TEXT NOT NULL,
21945
+ detail TEXT,
21946
+ role TEXT,
21947
+ status TEXT NOT NULL DEFAULT 'pending',
21948
+ agent_id TEXT,
21949
+ deps TEXT NOT NULL DEFAULT '[]',
21950
+ files TEXT NOT NULL DEFAULT '[]',
21951
+ result TEXT,
21952
+ auto_closed INTEGER NOT NULL DEFAULT 0,
21953
+ created_by TEXT,
21954
+ created_at INTEGER NOT NULL,
21955
+ updated_at INTEGER NOT NULL
21956
+ );
21957
+ CREATE INDEX IF NOT EXISTS swarm_tasks_run ON swarm_tasks(run_id, status);
21958
+ CREATE TABLE IF NOT EXISTS swarm_agents (
21959
+ run_id TEXT NOT NULL REFERENCES swarm_runs(id) ON DELETE CASCADE,
21960
+ agent_id TEXT NOT NULL,
21961
+ agent_type TEXT NOT NULL,
21962
+ role TEXT NOT NULL,
21963
+ status TEXT NOT NULL DEFAULT 'running',
21964
+ task_id INTEGER,
21965
+ prompt TEXT,
21966
+ session_id TEXT,
21967
+ spawned_at INTEGER NOT NULL,
21968
+ last_ts INTEGER NOT NULL,
21969
+ ended_at INTEGER,
21970
+ tool_calls INTEGER NOT NULL DEFAULT 0,
21971
+ errors INTEGER NOT NULL DEFAULT 0,
21972
+ last_tool TEXT,
21973
+ summary TEXT,
21974
+ PRIMARY KEY (run_id, agent_id)
21975
+ );
21976
+ CREATE INDEX IF NOT EXISTS swarm_agents_agent ON swarm_agents(agent_id, spawned_at);
21977
+ CREATE TABLE IF NOT EXISTS swarm_events (
21978
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21979
+ run_id TEXT NOT NULL REFERENCES swarm_runs(id) ON DELETE CASCADE,
21980
+ ts INTEGER NOT NULL,
21981
+ agent_id TEXT,
21982
+ role TEXT,
21983
+ kind TEXT NOT NULL,
21984
+ task_id INTEGER,
21985
+ text TEXT,
21986
+ payload TEXT
21987
+ );
21988
+ CREATE INDEX IF NOT EXISTS swarm_events_run ON swarm_events(run_id, id);
21989
+ CREATE TABLE IF NOT EXISTS swarm_notes (
21990
+ run_id TEXT NOT NULL REFERENCES swarm_runs(id) ON DELETE CASCADE,
21991
+ key TEXT NOT NULL,
21992
+ value TEXT NOT NULL,
21993
+ author TEXT,
21994
+ ts INTEGER NOT NULL,
21995
+ version INTEGER NOT NULL DEFAULT 1,
21996
+ PRIMARY KEY (run_id, key)
21997
+ );
21998
+ -- A spawn reservation exists from PreToolUse(Agent) until the worker starts or the Agent
21999
+ -- call returns; it may precede any run (the run opens at SubagentStart).
22000
+ CREATE TABLE IF NOT EXISTS swarm_spawns (
22001
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22002
+ swarm TEXT NOT NULL,
22003
+ run_id TEXT REFERENCES swarm_runs(id) ON DELETE CASCADE,
22004
+ session_id TEXT,
22005
+ agent_type TEXT NOT NULL,
22006
+ prompt TEXT,
22007
+ task_id INTEGER,
22008
+ tool_use_id TEXT,
22009
+ ts INTEGER NOT NULL,
22010
+ consumed INTEGER NOT NULL DEFAULT 0
22011
+ );
22012
+ CREATE INDEX IF NOT EXISTS swarm_spawns_type ON swarm_spawns(agent_type, consumed, ts);
22013
+ CREATE INDEX IF NOT EXISTS swarm_spawns_swarm ON swarm_spawns(swarm, consumed, ts);
22014
+ CREATE INDEX IF NOT EXISTS swarm_spawns_run ON swarm_spawns(run_id, consumed, ts);
22015
+ CREATE INDEX IF NOT EXISTS swarm_spawns_tool ON swarm_spawns(tool_use_id);
22016
+ CREATE INDEX IF NOT EXISTS swarm_agents_session ON swarm_agents(session_id, status);
22017
+ `
21921
22018
  }
21922
22019
  ];
21923
22020
  }
21924
22021
  });
21925
22022
 
22023
+ // src/storage/sqlite/swarm.ts
22024
+ function jsonArray(raw) {
22025
+ if (typeof raw !== "string" || !raw) return [];
22026
+ try {
22027
+ const v = JSON.parse(raw);
22028
+ return Array.isArray(v) ? v : [];
22029
+ } catch {
22030
+ return [];
22031
+ }
22032
+ }
22033
+ function jsonObject(raw) {
22034
+ if (typeof raw !== "string" || !raw) return null;
22035
+ try {
22036
+ const v = JSON.parse(raw);
22037
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
22038
+ } catch {
22039
+ return null;
22040
+ }
22041
+ }
22042
+ function toTask(r) {
22043
+ return {
22044
+ ...r,
22045
+ deps: jsonArray(r.deps).filter((n) => Number.isInteger(n)),
22046
+ files: jsonArray(r.files).filter((s) => typeof s === "string"),
22047
+ auto_closed: r.auto_closed === 1
22048
+ };
22049
+ }
22050
+ function toEvent(r) {
22051
+ return { ...r, payload: jsonObject(r.payload) };
22052
+ }
22053
+ function toSpawn(r) {
22054
+ return { ...r, consumed: r.consumed === 1 };
22055
+ }
22056
+ var SqliteSwarmStore;
22057
+ var init_swarm = __esm({
22058
+ "src/storage/sqlite/swarm.ts"() {
22059
+ "use strict";
22060
+ init_esm_shims();
22061
+ SqliteSwarmStore = class {
22062
+ constructor(db) {
22063
+ this.db = db;
22064
+ }
22065
+ db;
22066
+ stmts = /* @__PURE__ */ new Map();
22067
+ prep(sql) {
22068
+ let s = this.stmts.get(sql);
22069
+ if (!s) {
22070
+ s = this.db.prepare(sql);
22071
+ this.stmts.set(sql, s);
22072
+ }
22073
+ return s;
22074
+ }
22075
+ immediate(fn) {
22076
+ if (this.db.inTransaction) return fn();
22077
+ return this.db.transaction(fn).immediate();
22078
+ }
22079
+ // --- runs -------------------------------------------------------------------------
22080
+ runCreate(r) {
22081
+ const now = Date.now();
22082
+ this.prep(
22083
+ `INSERT INTO swarm_runs (id, swarm, goal, status, topology, session_id, created_at, updated_at)
22084
+ VALUES (?, ?, ?, 'active', ?, ?, ?, ?)`
22085
+ ).run(r.id, r.swarm, r.goal, r.topology, r.session_id ?? null, now, now);
22086
+ return this.runGet(r.id);
22087
+ }
22088
+ runGet(id) {
22089
+ return this.prep("SELECT * FROM swarm_runs WHERE id = ?").get(id);
22090
+ }
22091
+ runs(opts = {}) {
22092
+ const where = [];
22093
+ const params = [];
22094
+ if (opts.swarm) {
22095
+ where.push("swarm = ?");
22096
+ params.push(opts.swarm);
22097
+ }
22098
+ if (opts.status) {
22099
+ where.push("status = ?");
22100
+ params.push(opts.status);
22101
+ }
22102
+ params.push(Math.max(1, Math.min(opts.limit ?? 50, 500)));
22103
+ return this.prep(
22104
+ `SELECT * FROM swarm_runs ${where.length ? `WHERE ${where.join(" AND ")}` : ""}
22105
+ ORDER BY created_at DESC, rowid DESC LIMIT ?`
22106
+ ).all(...params);
22107
+ }
22108
+ runActive(swarm, sessionId) {
22109
+ return this.prep(
22110
+ `SELECT * FROM swarm_runs WHERE status = 'active' AND (? IS NULL OR swarm = ?)
22111
+ ORDER BY (session_id IS NOT NULL AND session_id = ?) DESC, created_at DESC, rowid DESC
22112
+ LIMIT 1`
22113
+ ).get(swarm ?? null, swarm ?? null, sessionId ?? null);
22114
+ }
22115
+ runUpdate(id, patch) {
22116
+ const sets = ["updated_at = ?"];
22117
+ const params = [Date.now()];
22118
+ if (patch.status !== void 0) {
22119
+ sets.push("status = ?");
22120
+ params.push(patch.status);
22121
+ }
22122
+ if (patch.summary !== void 0) {
22123
+ sets.push("summary = ?");
22124
+ params.push(patch.summary);
22125
+ }
22126
+ if (patch.ended_at !== void 0) {
22127
+ sets.push("ended_at = ?");
22128
+ params.push(patch.ended_at);
22129
+ }
22130
+ params.push(id);
22131
+ this.prep(`UPDATE swarm_runs SET ${sets.join(", ")} WHERE id = ?`).run(...params);
22132
+ }
22133
+ runTouch(id) {
22134
+ this.prep("UPDATE swarm_runs SET updated_at = ? WHERE id = ?").run(Date.now(), id);
22135
+ }
22136
+ // --- tasks ------------------------------------------------------------------------
22137
+ taskAdd(t) {
22138
+ const now = Date.now();
22139
+ const info = this.prep(
22140
+ `INSERT INTO swarm_tasks (run_id, title, detail, role, status, deps, files, created_by, created_at, updated_at)
22141
+ VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?)`
22142
+ ).run(
22143
+ t.run_id,
22144
+ t.title,
22145
+ t.detail ?? null,
22146
+ t.role ?? null,
22147
+ JSON.stringify(t.deps ?? []),
22148
+ JSON.stringify(t.files ?? []),
22149
+ t.created_by ?? null,
22150
+ now,
22151
+ now
22152
+ );
22153
+ return this.taskGet(Number(info.lastInsertRowid));
22154
+ }
22155
+ taskGet(id) {
22156
+ const r = this.prep("SELECT * FROM swarm_tasks WHERE id = ?").get(id);
22157
+ return r ? toTask(r) : void 0;
22158
+ }
22159
+ tasks(runId) {
22160
+ return this.prep("SELECT * FROM swarm_tasks WHERE run_id = ? ORDER BY id").all(runId).map(toTask);
22161
+ }
22162
+ taskUpdate(id, patch) {
22163
+ const sets = ["updated_at = ?"];
22164
+ const params = [Date.now()];
22165
+ if (patch.status !== void 0) {
22166
+ sets.push("status = ?");
22167
+ params.push(patch.status);
22168
+ }
22169
+ if (patch.agent_id !== void 0) {
22170
+ sets.push("agent_id = ?");
22171
+ params.push(patch.agent_id);
22172
+ }
22173
+ if (patch.result !== void 0) {
22174
+ sets.push("result = ?");
22175
+ params.push(patch.result);
22176
+ }
22177
+ if (patch.auto_closed !== void 0) {
22178
+ sets.push("auto_closed = ?");
22179
+ params.push(patch.auto_closed ? 1 : 0);
22180
+ }
22181
+ if (patch.files !== void 0) {
22182
+ sets.push("files = ?");
22183
+ params.push(JSON.stringify(patch.files));
22184
+ }
22185
+ params.push(id);
22186
+ this.prep(`UPDATE swarm_tasks SET ${sets.join(", ")} WHERE id = ?`).run(...params);
22187
+ }
22188
+ taskClaim(id, agentId) {
22189
+ const info = this.prep(
22190
+ `UPDATE swarm_tasks SET status = 'active', agent_id = ?, auto_closed = 0, updated_at = ?
22191
+ WHERE id = ? AND status = 'pending'`
22192
+ ).run(agentId, Date.now(), id);
22193
+ return info.changes === 1;
22194
+ }
22195
+ // --- workers ----------------------------------------------------------------------
22196
+ workerUpsert(w) {
22197
+ const now = Date.now();
22198
+ this.prep(
22199
+ `INSERT INTO swarm_agents (run_id, agent_id, agent_type, role, status, task_id, prompt, session_id, spawned_at, last_ts)
22200
+ VALUES (?, ?, ?, ?, 'running', ?, ?, ?, ?, ?)
22201
+ ON CONFLICT(run_id, agent_id) DO UPDATE SET
22202
+ status = 'running', ended_at = NULL, last_ts = excluded.last_ts,
22203
+ task_id = COALESCE(excluded.task_id, swarm_agents.task_id),
22204
+ prompt = COALESCE(excluded.prompt, swarm_agents.prompt)`
22205
+ ).run(
22206
+ w.run_id,
22207
+ w.agent_id,
22208
+ w.agent_type,
22209
+ w.role,
22210
+ w.task_id ?? null,
22211
+ w.prompt ?? null,
22212
+ w.session_id ?? null,
22213
+ now,
22214
+ now
22215
+ );
22216
+ return this.workerGet(w.run_id, w.agent_id);
22217
+ }
22218
+ workerGet(runId, agentId) {
22219
+ return this.prep("SELECT * FROM swarm_agents WHERE run_id = ? AND agent_id = ?").get(
22220
+ runId,
22221
+ agentId
22222
+ );
22223
+ }
22224
+ workerFind(agentId) {
22225
+ return this.prep(
22226
+ "SELECT * FROM swarm_agents WHERE agent_id = ? ORDER BY spawned_at DESC LIMIT 1"
22227
+ ).get(agentId);
22228
+ }
22229
+ workers(runId) {
22230
+ return this.prep("SELECT * FROM swarm_agents WHERE run_id = ? ORDER BY spawned_at").all(
22231
+ runId
22232
+ );
22233
+ }
22234
+ workerUpdate(runId, agentId, patch) {
22235
+ const sets = ["last_ts = ?"];
22236
+ const params = [Date.now()];
22237
+ if (patch.status !== void 0) {
22238
+ sets.push("status = ?");
22239
+ params.push(patch.status);
22240
+ }
22241
+ if (patch.task_id !== void 0) {
22242
+ sets.push("task_id = ?");
22243
+ params.push(patch.task_id);
22244
+ }
22245
+ if (patch.ended_at !== void 0) {
22246
+ sets.push("ended_at = ?");
22247
+ params.push(patch.ended_at);
22248
+ }
22249
+ if (patch.summary !== void 0) {
22250
+ sets.push("summary = ?");
22251
+ params.push(patch.summary);
22252
+ }
22253
+ params.push(runId, agentId);
22254
+ this.prep(`UPDATE swarm_agents SET ${sets.join(", ")} WHERE run_id = ? AND agent_id = ?`).run(
22255
+ ...params
22256
+ );
22257
+ }
22258
+ workerTouch(runId, agentId, tool, error62 = false) {
22259
+ this.prep(
22260
+ `UPDATE swarm_agents SET last_ts = ?, tool_calls = tool_calls + 1, errors = errors + ?,
22261
+ last_tool = COALESCE(?, last_tool)
22262
+ WHERE run_id = ? AND agent_id = ?`
22263
+ ).run(Date.now(), error62 ? 1 : 0, tool, runId, agentId);
22264
+ }
22265
+ // --- events -----------------------------------------------------------------------
22266
+ eventAdd(e) {
22267
+ const info = this.prep(
22268
+ `INSERT INTO swarm_events (run_id, ts, agent_id, role, kind, task_id, text, payload)
22269
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
22270
+ ).run(
22271
+ e.run_id,
22272
+ e.ts ?? Date.now(),
22273
+ e.agent_id ?? null,
22274
+ e.role ?? null,
22275
+ e.kind,
22276
+ e.task_id ?? null,
22277
+ e.text ?? null,
22278
+ e.payload ? JSON.stringify(e.payload) : null
22279
+ );
22280
+ return Number(info.lastInsertRowid);
22281
+ }
22282
+ events(runId, opts = {}) {
22283
+ const limit = Math.max(1, Math.min(opts.limit ?? 100, 1e3));
22284
+ if (opts.after !== void 0) {
22285
+ return this.prep("SELECT * FROM swarm_events WHERE run_id = ? AND id > ? ORDER BY id LIMIT ?").all(
22286
+ runId,
22287
+ opts.after,
22288
+ limit
22289
+ ).map(toEvent);
22290
+ }
22291
+ return this.prep("SELECT * FROM swarm_events WHERE run_id = ? ORDER BY id DESC LIMIT ?").all(
22292
+ runId,
22293
+ limit
22294
+ ).map(toEvent).reverse();
22295
+ }
22296
+ // --- blackboard -------------------------------------------------------------------
22297
+ noteSet(runId, key, value, author) {
22298
+ this.prep(
22299
+ `INSERT INTO swarm_notes (run_id, key, value, author, ts, version) VALUES (?, ?, ?, ?, ?, 1)
22300
+ ON CONFLICT(run_id, key) DO UPDATE SET value = excluded.value, author = excluded.author,
22301
+ ts = excluded.ts, version = swarm_notes.version + 1`
22302
+ ).run(runId, key, value, author, Date.now());
22303
+ return this.noteGet(runId, key);
22304
+ }
22305
+ noteGet(runId, key) {
22306
+ return this.prep("SELECT * FROM swarm_notes WHERE run_id = ? AND key = ?").get(runId, key);
22307
+ }
22308
+ notes(runId) {
22309
+ return this.prep("SELECT * FROM swarm_notes WHERE run_id = ? ORDER BY ts DESC").all(
22310
+ runId
22311
+ );
22312
+ }
22313
+ // --- spawn reservations -----------------------------------------------------------
22314
+ spawnAdd(s) {
22315
+ const info = this.prep(
22316
+ `INSERT INTO swarm_spawns (swarm, run_id, session_id, agent_type, prompt, task_id, tool_use_id, ts)
22317
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
22318
+ ).run(
22319
+ s.swarm,
22320
+ s.run_id ?? null,
22321
+ s.session_id ?? null,
22322
+ s.agent_type,
22323
+ s.prompt ?? null,
22324
+ s.task_id ?? null,
22325
+ s.tool_use_id ?? null,
22326
+ Date.now()
22327
+ );
22328
+ return Number(info.lastInsertRowid);
22329
+ }
22330
+ spawnConsumeByToolUse(toolUseId) {
22331
+ return this.prep("UPDATE swarm_spawns SET consumed = 1 WHERE tool_use_id = ? AND consumed = 0").run(
22332
+ toolUseId
22333
+ ).changes > 0;
22334
+ }
22335
+ spawnTake(agentType, sinceTs, sessionId) {
22336
+ return this.immediate(() => {
22337
+ const r = this.prep(
22338
+ `SELECT * FROM swarm_spawns WHERE agent_type = ? AND consumed = 0 AND ts >= ?
22339
+ AND (? IS NULL OR session_id IS NULL OR session_id = ?)
22340
+ ORDER BY id LIMIT 1`
22341
+ ).get(agentType, sinceTs, sessionId ?? null, sessionId ?? null);
22342
+ if (!r) return void 0;
22343
+ this.prep("UPDATE swarm_spawns SET consumed = 1 WHERE id = ?").run(r.id);
22344
+ return toSpawn({ ...r, consumed: 1 });
22345
+ });
22346
+ }
22347
+ spawnsPending(runId, sinceTs, agentType) {
22348
+ const r = this.prep(
22349
+ `SELECT COUNT(*) n FROM swarm_spawns
22350
+ WHERE run_id = ? AND consumed = 0 AND ts >= ? AND (? IS NULL OR agent_type = ?)`
22351
+ ).get(runId, sinceTs, agentType ?? null, agentType ?? null);
22352
+ return r.n;
22353
+ }
22354
+ spawnsOpen(agentType, sinceTs, sessionId) {
22355
+ return this.prep(
22356
+ `SELECT * FROM swarm_spawns WHERE agent_type = ? AND consumed = 0 AND ts >= ?
22357
+ AND (? IS NULL OR session_id IS NULL OR session_id = ?)
22358
+ ORDER BY id`
22359
+ ).all(agentType, sinceTs, sessionId ?? null, sessionId ?? null).map(toSpawn);
22360
+ }
22361
+ spawnConsume(id) {
22362
+ return this.prep("UPDATE swarm_spawns SET consumed = 1 WHERE id = ? AND consumed = 0").run(id).changes === 1;
22363
+ }
22364
+ spawnsVoid(runId) {
22365
+ return this.prep("UPDATE swarm_spawns SET consumed = 1 WHERE run_id = ? AND consumed = 0").run(
22366
+ runId
22367
+ ).changes;
22368
+ }
22369
+ spawnsPendingOfSwarm(swarm, sinceTs, agentType) {
22370
+ const r = this.prep(
22371
+ `SELECT COUNT(*) n FROM swarm_spawns
22372
+ WHERE swarm = ? AND consumed = 0 AND ts >= ? AND (? IS NULL OR agent_type = ?)`
22373
+ ).get(swarm, sinceTs, agentType ?? null, agentType ?? null);
22374
+ return r.n;
22375
+ }
22376
+ liveWorkersOfSwarm(swarm, sinceTs) {
22377
+ return this.prep(
22378
+ `SELECT a.* FROM swarm_agents a JOIN swarm_runs r ON r.id = a.run_id
22379
+ WHERE r.swarm = ? AND a.status = 'running' AND a.last_ts >= ?`
22380
+ ).all(swarm, sinceTs);
22381
+ }
22382
+ tasksHeldBy(agentId) {
22383
+ return this.prep(
22384
+ "SELECT * FROM swarm_tasks WHERE agent_id = ? AND status = 'active' ORDER BY id"
22385
+ ).all(agentId).map(toTask);
22386
+ }
22387
+ workersOfSession(sessionId) {
22388
+ return this.prep(
22389
+ "SELECT * FROM swarm_agents WHERE session_id = ? AND status = 'running' ORDER BY spawned_at"
22390
+ ).all(sessionId);
22391
+ }
22392
+ eventsPrune(runId, keep) {
22393
+ const cut = this.prep(
22394
+ "SELECT id FROM swarm_events WHERE run_id = ? ORDER BY id DESC LIMIT 1 OFFSET ?"
22395
+ ).get(runId, Math.max(0, keep - 1));
22396
+ if (!cut) return 0;
22397
+ return this.prep("DELETE FROM swarm_events WHERE run_id = ? AND id < ?").run(runId, cut.id).changes;
22398
+ }
22399
+ workerPing(runId, agentId) {
22400
+ this.prep("UPDATE swarm_agents SET last_ts = ? WHERE run_id = ? AND agent_id = ?").run(
22401
+ Date.now(),
22402
+ runId,
22403
+ agentId
22404
+ );
22405
+ }
22406
+ eventLast(runId) {
22407
+ const r = this.prep("SELECT * FROM swarm_events WHERE run_id = ? ORDER BY id DESC LIMIT 1").get(
22408
+ runId
22409
+ );
22410
+ return r ? toEvent(r) : void 0;
22411
+ }
22412
+ eventUpdate(id, patch) {
22413
+ const sets = [];
22414
+ const params = [];
22415
+ if (patch.ts !== void 0) {
22416
+ sets.push("ts = ?");
22417
+ params.push(patch.ts);
22418
+ }
22419
+ if (patch.text !== void 0) {
22420
+ sets.push("text = ?");
22421
+ params.push(patch.text);
22422
+ }
22423
+ if (patch.payload !== void 0) {
22424
+ sets.push("payload = ?");
22425
+ params.push(patch.payload ? JSON.stringify(patch.payload) : null);
22426
+ }
22427
+ if (!sets.length) return;
22428
+ params.push(id);
22429
+ this.prep(`UPDATE swarm_events SET ${sets.join(", ")} WHERE id = ?`).run(...params);
22430
+ }
22431
+ };
22432
+ }
22433
+ });
22434
+
21926
22435
  // src/storage/sqlite/adapter.ts
21927
22436
  import Database from "better-sqlite3";
21928
22437
  import * as sqliteVec from "sqlite-vec";
@@ -21985,10 +22494,12 @@ var init_adapter = __esm({
21985
22494
  init_esm_shims();
21986
22495
  init_terms();
21987
22496
  init_schema();
22497
+ init_swarm();
21988
22498
  SqliteAdapter = class _SqliteAdapter {
21989
22499
  constructor(path3, opts = {}) {
21990
22500
  this.path = path3;
21991
22501
  this.db = new Database(path3, { timeout: 2e3 });
22502
+ this.swarm = new SqliteSwarmStore(this.db);
21992
22503
  this.db.pragma("journal_mode = WAL");
21993
22504
  this.db.pragma("synchronous = NORMAL");
21994
22505
  this.db.pragma("foreign_keys = ON");
@@ -22014,6 +22525,7 @@ var init_adapter = __esm({
22014
22525
  path;
22015
22526
  db;
22016
22527
  vectors;
22528
+ swarm;
22017
22529
  stmts = /* @__PURE__ */ new Map();
22018
22530
  /**
22019
22531
  * Prepared-statement cache. better-sqlite3 compiles the SQL on every prepare(), so
@@ -23156,6 +23668,7 @@ var init_adapter = __esm({
23156
23668
  this.db.exec(
23157
23669
  "DELETE FROM events; DELETE FROM snapshots; DELETE FROM sessions; DELETE FROM stats;"
23158
23670
  );
23671
+ this.db.exec("DELETE FROM swarm_runs;");
23159
23672
  }
23160
23673
  if (scope === "graph" || scope === "all") {
23161
23674
  this.db.exec(
@@ -23308,15 +23821,15 @@ function probeServer(root) {
23308
23821
  return `cannot start (${why.trim().slice(-220)})`;
23309
23822
  }
23310
23823
  function pluginCacheStale(stable) {
23311
- const cache3 = pluginInstallPath("eminiar@eminiar");
23312
- if (!cache3 || cache3 === stable) return void 0;
23313
- if (!existsSync8(cache3)) return `${cache3} is missing`;
23824
+ const cache5 = pluginInstallPath("eminiar@eminiar");
23825
+ if (!cache5 || cache5 === stable) return void 0;
23826
+ if (!existsSync8(cache5)) return `${cache5} is missing`;
23314
23827
  for (const rel of CACHE_FINGERPRINT) {
23315
- if (fileHash(join11(cache3, rel)) !== fileHash(join11(stable, rel))) return `${rel} differs`;
23828
+ if (fileHash(join11(cache5, rel)) !== fileHash(join11(stable, rel))) return `${rel} differs`;
23316
23829
  }
23317
- if (existsSync8(join11(cache3, ".mcp.json")) && !existsSync8(join11(stable, ".mcp.json")))
23830
+ if (existsSync8(join11(cache5, ".mcp.json")) && !existsSync8(join11(stable, ".mcp.json")))
23318
23831
  return ".mcp.json is present in the cache but not in the stable copy";
23319
- const probe3 = probeServer(cache3);
23832
+ const probe3 = probeServer(cache5);
23320
23833
  if (probe3) return `cached copy ${probe3}`;
23321
23834
  return void 0;
23322
23835
  }
@@ -23380,7 +23893,9 @@ function registerSettings(stable) {
23380
23893
  const entry = {
23381
23894
  hooks: [{ type: "command", command: hookCmd, timeout: ev2 === "PreCompact" ? 15 : 10 }]
23382
23895
  };
23383
- if (ev2 === "PreToolUse") entry.matcher = "Bash|Read|Grep|Glob|WebFetch|Agent|Task";
23896
+ if (ev2 === "PreToolUse")
23897
+ entry.matcher = "^(Bash|Read|Grep|Glob|WebFetch|Agent|Task|mcp__.*__em_swarm)$";
23898
+ if (ev2 === "SubagentStart") entry.matcher = "^swarm-";
23384
23899
  if (ev2 === "PostToolUse" || ev2 === "PostToolUseFailure")
23385
23900
  entry.hooks[0].async = true;
23386
23901
  filtered.push(entry);
@@ -23474,6 +23989,7 @@ var init_register = __esm({
23474
23989
  "PreToolUse",
23475
23990
  "PostToolUse",
23476
23991
  "PostToolUseFailure",
23992
+ "SubagentStart",
23477
23993
  "SubagentStop",
23478
23994
  "PreCompact",
23479
23995
  "Stop",
@@ -23789,13 +24305,13 @@ import { spawn } from "child_process";
23789
24305
  import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
23790
24306
  import { join as join15 } from "path";
23791
24307
  function mcpHandshake(bin, ms = 15e3) {
23792
- return new Promise((resolve9) => {
24308
+ return new Promise((resolve11) => {
23793
24309
  const child = spawn(bin, [], { stdio: ["pipe", "pipe", "pipe"] });
23794
24310
  let buf = "";
23795
24311
  let err = "";
23796
24312
  const timer = setTimeout(() => {
23797
24313
  child.kill();
23798
- resolve9(`no reply in ${ms / 1e3}s${err ? `: ${err.trim().slice(-200)}` : ""}`);
24314
+ resolve11(`no reply in ${ms / 1e3}s${err ? `: ${err.trim().slice(-200)}` : ""}`);
23799
24315
  }, ms);
23800
24316
  child.stdout.on("data", (d) => {
23801
24317
  buf += d.toString();
@@ -23803,7 +24319,7 @@ function mcpHandshake(bin, ms = 15e3) {
23803
24319
  clearTimeout(timer);
23804
24320
  child.kill();
23805
24321
  const m = /"serverInfo":\{"name":"([^"]+)","version":"([^"]+)"/.exec(buf);
23806
- resolve9(m ? `${m[1]} v${m[2]}` : "ok");
24322
+ resolve11(m ? `${m[1]} v${m[2]}` : "ok");
23807
24323
  }
23808
24324
  });
23809
24325
  child.stderr.on("data", (d) => {
@@ -23811,7 +24327,7 @@ function mcpHandshake(bin, ms = 15e3) {
23811
24327
  });
23812
24328
  child.on("error", (e) => {
23813
24329
  clearTimeout(timer);
23814
- resolve9(`spawn failed: ${e.message}`);
24330
+ resolve11(`spawn failed: ${e.message}`);
23815
24331
  });
23816
24332
  child.stdin.write(
23817
24333
  `${JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "doctor", version: "0" } } })}
@@ -24678,14 +25194,14 @@ var require_array = __commonJS({
24678
25194
  init_esm_shims();
24679
25195
  Object.defineProperty(exports, "__esModule", { value: true });
24680
25196
  exports.splitWhen = exports.flatten = void 0;
24681
- function flatten(items) {
24682
- return items.reduce((collection, item) => [].concat(collection, item), []);
25197
+ function flatten(items2) {
25198
+ return items2.reduce((collection, item) => [].concat(collection, item), []);
24683
25199
  }
24684
25200
  exports.flatten = flatten;
24685
- function splitWhen(items, predicate) {
25201
+ function splitWhen(items2, predicate) {
24686
25202
  const result = [[]];
24687
25203
  let groupIndex = 0;
24688
- for (const item of items) {
25204
+ for (const item of items2) {
24689
25205
  if (predicate(item)) {
24690
25206
  groupIndex++;
24691
25207
  result[groupIndex] = [];
@@ -25348,7 +25864,7 @@ var require_fill_range = __commonJS({
25348
25864
  }
25349
25865
  return options.stringify === true;
25350
25866
  };
25351
- var pad = (input2, maxLength, toNumber) => {
25867
+ var pad3 = (input2, maxLength, toNumber) => {
25352
25868
  if (maxLength > 0) {
25353
25869
  let dash = input2[0] === "-" ? "-" : "";
25354
25870
  if (dash) input2 = input2.slice(1);
@@ -25450,7 +25966,7 @@ var require_fill_range = __commonJS({
25450
25966
  if (options.toRegex === true && step > 1) {
25451
25967
  push(a);
25452
25968
  } else {
25453
- range.push(pad(format(a, index), maxLen, toNumber));
25969
+ range.push(pad3(format(a, index), maxLen, toNumber));
25454
25970
  }
25455
25971
  a = descending ? a - step : a + step;
25456
25972
  index++;
@@ -25521,7 +26037,7 @@ var require_compile = __commonJS({
25521
26037
  init_esm_shims();
25522
26038
  var fill = require_fill_range();
25523
26039
  var utils = require_utils();
25524
- var compile2 = (ast, options = {}) => {
26040
+ var compile3 = (ast, options = {}) => {
25525
26041
  const walk3 = (node2, parent = {}) => {
25526
26042
  const invalidBlock = utils.isInvalidBrace(parent);
25527
26043
  const invalidNode = node2.invalid === true && options.escapeInvalid === true;
@@ -25563,7 +26079,7 @@ var require_compile = __commonJS({
25563
26079
  };
25564
26080
  return walk3(ast);
25565
26081
  };
25566
- module.exports = compile2;
26082
+ module.exports = compile3;
25567
26083
  }
25568
26084
  });
25569
26085
 
@@ -25597,7 +26113,7 @@ var require_expand = __commonJS({
25597
26113
  }
25598
26114
  return utils.flatten(result);
25599
26115
  };
25600
- var expand2 = (ast, options = {}) => {
26116
+ var expand3 = (ast, options = {}) => {
25601
26117
  const rangeLimit = options.rangeLimit === void 0 ? 1e3 : options.rangeLimit;
25602
26118
  const walk3 = (node2, parent = {}) => {
25603
26119
  node2.queue = [];
@@ -25658,7 +26174,7 @@ var require_expand = __commonJS({
25658
26174
  };
25659
26175
  return utils.flatten(walk3(ast));
25660
26176
  };
25661
- module.exports = expand2;
26177
+ module.exports = expand3;
25662
26178
  }
25663
26179
  });
25664
26180
 
@@ -25771,7 +26287,7 @@ var require_parse = __commonJS({
25771
26287
  init_esm_shims();
25772
26288
  var stringify = require_stringify();
25773
26289
  var {
25774
- MAX_LENGTH,
26290
+ MAX_LENGTH: MAX_LENGTH2,
25775
26291
  CHAR_BACKSLASH,
25776
26292
  /* \ */
25777
26293
  CHAR_BACKTICK,
@@ -25804,7 +26320,7 @@ var require_parse = __commonJS({
25804
26320
  throw new TypeError("Expected a string");
25805
26321
  }
25806
26322
  const opts = options || {};
25807
- const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
26323
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH2, opts.maxLength) : MAX_LENGTH2;
25808
26324
  if (input2.length > max) {
25809
26325
  throw new SyntaxError(`Input length (${input2.length}), exceeds max characters (${max})`);
25810
26326
  }
@@ -26009,8 +26525,8 @@ var require_braces = __commonJS({
26009
26525
  "use strict";
26010
26526
  init_esm_shims();
26011
26527
  var stringify = require_stringify();
26012
- var compile2 = require_compile();
26013
- var expand2 = require_expand();
26528
+ var compile3 = require_compile();
26529
+ var expand3 = require_expand();
26014
26530
  var parse3 = require_parse();
26015
26531
  var braces = (input2, options = {}) => {
26016
26532
  let output2 = [];
@@ -26042,13 +26558,13 @@ var require_braces = __commonJS({
26042
26558
  if (typeof input2 === "string") {
26043
26559
  input2 = braces.parse(input2, options);
26044
26560
  }
26045
- return compile2(input2, options);
26561
+ return compile3(input2, options);
26046
26562
  };
26047
26563
  braces.expand = (input2, options = {}) => {
26048
26564
  if (typeof input2 === "string") {
26049
26565
  input2 = braces.parse(input2, options);
26050
26566
  }
26051
- let result = expand2(input2, options);
26567
+ let result = expand3(input2, options);
26052
26568
  if (options.noempty === true) {
26053
26569
  result = result.filter(Boolean);
26054
26570
  }
@@ -26670,7 +27186,7 @@ var require_parse2 = __commonJS({
26670
27186
  var constants4 = require_constants2();
26671
27187
  var utils = require_utils2();
26672
27188
  var {
26673
- MAX_LENGTH,
27189
+ MAX_LENGTH: MAX_LENGTH2,
26674
27190
  POSIX_REGEX_SOURCE,
26675
27191
  REGEX_NON_SPECIAL_CHARS,
26676
27192
  REGEX_SPECIAL_CHARS_BACKREF,
@@ -26905,7 +27421,7 @@ var require_parse2 = __commonJS({
26905
27421
  }
26906
27422
  input2 = REPLACEMENTS[input2] || input2;
26907
27423
  const opts = { ...options };
26908
- const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
27424
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH2, opts.maxLength) : MAX_LENGTH2;
26909
27425
  let len = input2.length;
26910
27426
  if (len > max) {
26911
27427
  throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
@@ -27597,7 +28113,7 @@ var require_parse2 = __commonJS({
27597
28113
  };
27598
28114
  parse3.fastpaths = (input2, options) => {
27599
28115
  const opts = { ...options };
27600
- const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
28116
+ const max = typeof opts.maxLength === "number" ? Math.min(MAX_LENGTH2, opts.maxLength) : MAX_LENGTH2;
27601
28117
  const len = input2.length;
27602
28118
  if (len > max) {
27603
28119
  throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
@@ -27835,10 +28351,10 @@ var require_micromatch = __commonJS({
27835
28351
  list2 = [].concat(list2);
27836
28352
  let omit2 = /* @__PURE__ */ new Set();
27837
28353
  let keep = /* @__PURE__ */ new Set();
27838
- let items = /* @__PURE__ */ new Set();
28354
+ let items2 = /* @__PURE__ */ new Set();
27839
28355
  let negatives = 0;
27840
28356
  let onResult = (state) => {
27841
- items.add(state.output);
28357
+ items2.add(state.output);
27842
28358
  if (options && options.onResult) {
27843
28359
  options.onResult(state);
27844
28360
  }
@@ -27859,7 +28375,7 @@ var require_micromatch = __commonJS({
27859
28375
  }
27860
28376
  }
27861
28377
  }
27862
- let result = negatives === patterns.length ? [...items] : [...keep];
28378
+ let result = negatives === patterns.length ? [...items2] : [...keep];
27863
28379
  let matches2 = result.filter((item) => !omit2.has(item));
27864
28380
  if (options && matches2.length === 0) {
27865
28381
  if (options.failglob === true) {
@@ -27878,13 +28394,13 @@ var require_micromatch = __commonJS({
27878
28394
  micromatch.not = (list2, patterns, options = {}) => {
27879
28395
  patterns = [].concat(patterns).map(String);
27880
28396
  let result = /* @__PURE__ */ new Set();
27881
- let items = [];
28397
+ let items2 = [];
27882
28398
  let onResult = (state) => {
27883
28399
  if (options.onResult) options.onResult(state);
27884
- items.push(state.output);
28400
+ items2.push(state.output);
27885
28401
  };
27886
28402
  let matches2 = new Set(micromatch(list2, patterns, { ...options, onResult }));
27887
- for (let item of items) {
28403
+ for (let item of items2) {
27888
28404
  if (!matches2.has(item)) {
27889
28405
  result.add(item);
27890
28406
  }
@@ -27918,20 +28434,20 @@ var require_micromatch = __commonJS({
27918
28434
  return res;
27919
28435
  };
27920
28436
  micromatch.some = (list2, patterns, options) => {
27921
- let items = [].concat(list2);
28437
+ let items2 = [].concat(list2);
27922
28438
  for (let pattern2 of [].concat(patterns)) {
27923
28439
  let isMatch = picomatch(String(pattern2), options);
27924
- if (items.some((item) => isMatch(item))) {
28440
+ if (items2.some((item) => isMatch(item))) {
27925
28441
  return true;
27926
28442
  }
27927
28443
  }
27928
28444
  return false;
27929
28445
  };
27930
28446
  micromatch.every = (list2, patterns, options) => {
27931
- let items = [].concat(list2);
28447
+ let items2 = [].concat(list2);
27932
28448
  for (let pattern2 of [].concat(patterns)) {
27933
28449
  let isMatch = picomatch(String(pattern2), options);
27934
- if (!items.every((item) => isMatch(item))) {
28450
+ if (!items2.every((item) => isMatch(item))) {
27935
28451
  return false;
27936
28452
  }
27937
28453
  }
@@ -28080,8 +28596,8 @@ var require_pattern = __commonJS({
28080
28596
  }
28081
28597
  exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
28082
28598
  function isAffectDepthOfReadingPattern(pattern2) {
28083
- const basename3 = path3.basename(pattern2);
28084
- return endsWithSlashGlobStar(pattern2) || isStaticPattern(basename3);
28599
+ const basename4 = path3.basename(pattern2);
28600
+ return endsWithSlashGlobStar(pattern2) || isStaticPattern(basename4);
28085
28601
  }
28086
28602
  exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
28087
28603
  function expandPatternsWithBraceExpansion(patterns) {
@@ -28126,21 +28642,21 @@ var require_pattern = __commonJS({
28126
28642
  exports.removeDuplicateSlashes = removeDuplicateSlashes;
28127
28643
  function partitionAbsoluteAndRelative(patterns) {
28128
28644
  const absolute = [];
28129
- const relative11 = [];
28645
+ const relative13 = [];
28130
28646
  for (const pattern2 of patterns) {
28131
- if (isAbsolute(pattern2)) {
28647
+ if (isAbsolute2(pattern2)) {
28132
28648
  absolute.push(pattern2);
28133
28649
  } else {
28134
- relative11.push(pattern2);
28650
+ relative13.push(pattern2);
28135
28651
  }
28136
28652
  }
28137
- return [absolute, relative11];
28653
+ return [absolute, relative13];
28138
28654
  }
28139
28655
  exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
28140
- function isAbsolute(pattern2) {
28656
+ function isAbsolute2(pattern2) {
28141
28657
  return path3.isAbsolute(pattern2);
28142
28658
  }
28143
- exports.isAbsolute = isAbsolute;
28659
+ exports.isAbsolute = isAbsolute2;
28144
28660
  }
28145
28661
  });
28146
28662
 
@@ -28565,11 +29081,11 @@ var require_out = __commonJS({
28565
29081
  async.read(path3, getSettings(optionsOrSettingsOrCallback), callback);
28566
29082
  }
28567
29083
  exports.stat = stat;
28568
- function statSync13(path3, optionsOrSettings) {
29084
+ function statSync15(path3, optionsOrSettings) {
28569
29085
  const settings = getSettings(optionsOrSettings);
28570
29086
  return sync.read(path3, settings);
28571
29087
  }
28572
- exports.statSync = statSync13;
29088
+ exports.statSync = statSync15;
28573
29089
  function getSettings(settingsOrOptions = {}) {
28574
29090
  if (settingsOrOptions instanceof settings_1.default) {
28575
29091
  return settingsOrOptions;
@@ -29027,7 +29543,7 @@ var require_queue = __commonJS({
29027
29543
  if (!(_concurrency >= 1)) {
29028
29544
  throw new Error("fastqueue concurrency must be equal to or greater than 1");
29029
29545
  }
29030
- var cache3 = reusify(Task);
29546
+ var cache5 = reusify(Task);
29031
29547
  var queueHead = null;
29032
29548
  var queueTail = null;
29033
29549
  var _running = 0;
@@ -29106,7 +29622,7 @@ var require_queue = __commonJS({
29106
29622
  return _running === 0 && self.length() === 0;
29107
29623
  }
29108
29624
  function push(value, done) {
29109
- var current = cache3.get();
29625
+ var current = cache5.get();
29110
29626
  current.context = context;
29111
29627
  current.release = release;
29112
29628
  current.value = value;
@@ -29127,7 +29643,7 @@ var require_queue = __commonJS({
29127
29643
  }
29128
29644
  }
29129
29645
  function unshift(value, done) {
29130
- var current = cache3.get();
29646
+ var current = cache5.get();
29131
29647
  current.context = context;
29132
29648
  current.release = release;
29133
29649
  current.value = value;
@@ -29149,7 +29665,7 @@ var require_queue = __commonJS({
29149
29665
  }
29150
29666
  function release(holder) {
29151
29667
  if (holder) {
29152
- cache3.release(holder);
29668
+ cache5.release(holder);
29153
29669
  }
29154
29670
  var next = queueHead;
29155
29671
  if (next && _running <= _concurrency) {
@@ -29199,7 +29715,7 @@ var require_queue = __commonJS({
29199
29715
  errorHandler2(new Error("abort"), val);
29200
29716
  }
29201
29717
  callback.call(context2, new Error("abort"));
29202
- cache3.release(current);
29718
+ cache5.release(current);
29203
29719
  current = next;
29204
29720
  }
29205
29721
  self.drain = noop;
@@ -29250,41 +29766,41 @@ var require_queue = __commonJS({
29250
29766
  queue.drained = drained;
29251
29767
  return queue;
29252
29768
  function push(value) {
29253
- var p = new Promise(function(resolve9, reject) {
29769
+ var p = new Promise(function(resolve11, reject) {
29254
29770
  pushCb(value, function(err, result) {
29255
29771
  if (err) {
29256
29772
  reject(err);
29257
29773
  return;
29258
29774
  }
29259
- resolve9(result);
29775
+ resolve11(result);
29260
29776
  });
29261
29777
  });
29262
29778
  p.catch(noop);
29263
29779
  return p;
29264
29780
  }
29265
29781
  function unshift(value) {
29266
- var p = new Promise(function(resolve9, reject) {
29782
+ var p = new Promise(function(resolve11, reject) {
29267
29783
  unshiftCb(value, function(err, result) {
29268
29784
  if (err) {
29269
29785
  reject(err);
29270
29786
  return;
29271
29787
  }
29272
- resolve9(result);
29788
+ resolve11(result);
29273
29789
  });
29274
29790
  });
29275
29791
  p.catch(noop);
29276
29792
  return p;
29277
29793
  }
29278
29794
  function drained() {
29279
- var p = new Promise(function(resolve9) {
29795
+ var p = new Promise(function(resolve11) {
29280
29796
  process.nextTick(function() {
29281
29797
  if (queue.idle()) {
29282
- resolve9();
29798
+ resolve11();
29283
29799
  } else {
29284
29800
  var previousDrain = queue.drain;
29285
29801
  queue.drain = function() {
29286
29802
  if (typeof previousDrain === "function") previousDrain();
29287
- resolve9();
29803
+ resolve11();
29288
29804
  queue.drain = previousDrain;
29289
29805
  };
29290
29806
  }
@@ -29781,9 +30297,9 @@ var require_stream3 = __commonJS({
29781
30297
  });
29782
30298
  }
29783
30299
  _getStat(filepath) {
29784
- return new Promise((resolve9, reject) => {
30300
+ return new Promise((resolve11, reject) => {
29785
30301
  this._stat(filepath, this._fsStatSettings, (error62, stats) => {
29786
- return error62 === null ? resolve9(stats) : reject(error62);
30302
+ return error62 === null ? resolve11(stats) : reject(error62);
29787
30303
  });
29788
30304
  });
29789
30305
  }
@@ -29808,10 +30324,10 @@ var require_async5 = __commonJS({
29808
30324
  this._readerStream = new stream_1.default(this._settings);
29809
30325
  }
29810
30326
  dynamic(root, options) {
29811
- return new Promise((resolve9, reject) => {
30327
+ return new Promise((resolve11, reject) => {
29812
30328
  this._walkAsync(root, options, (error62, entries) => {
29813
30329
  if (error62 === null) {
29814
- resolve9(entries);
30330
+ resolve11(entries);
29815
30331
  } else {
29816
30332
  reject(error62);
29817
30333
  }
@@ -29821,10 +30337,10 @@ var require_async5 = __commonJS({
29821
30337
  async static(patterns, options) {
29822
30338
  const entries = [];
29823
30339
  const stream = this._readerStream.static(patterns, options);
29824
- return new Promise((resolve9, reject) => {
30340
+ return new Promise((resolve11, reject) => {
29825
30341
  stream.once("error", reject);
29826
30342
  stream.on("data", (entry) => entries.push(entry));
29827
- stream.once("end", () => resolve9(entries));
30343
+ stream.once("end", () => resolve11(entries));
29828
30344
  });
29829
30345
  }
29830
30346
  };
@@ -31905,8 +32421,8 @@ function neighbors(db, ref, depth = 1, kinds) {
31905
32421
  frontier = next;
31906
32422
  if (!frontier.length || edges.length > 2e3) break;
31907
32423
  }
31908
- const uniq2 = new Map(edges.map((e) => [`${e.src}|${e.dst}|${e.kind}|${e.source}`, e]));
31909
- return { root, edges: [...uniq2.values()], nodes: db.graphGetNodes([...seen]) };
32424
+ const uniq3 = new Map(edges.map((e) => [`${e.src}|${e.dst}|${e.kind}|${e.source}`, e]));
32425
+ return { root, edges: [...uniq3.values()], nodes: db.graphGetNodes([...seen]) };
31910
32426
  }
31911
32427
  function path2(db, from, to) {
31912
32428
  const a = resolveNode(db, from);
@@ -34588,9 +35104,9 @@ function loadWorkspaces(root) {
34588
35104
  if (Array.isArray(ws)) globs.push(...ws);
34589
35105
  else if (ws?.packages) globs.push(...ws.packages);
34590
35106
  try {
34591
- const yaml = join21(root, "pnpm-workspace.yaml");
34592
- if (existsSync18(yaml)) {
34593
- for (const m of readFileSync15(yaml, "utf8").matchAll(/^\s*-\s*['"]?([^'"\n#]+?)['"]?\s*$/gm))
35107
+ const yaml3 = join21(root, "pnpm-workspace.yaml");
35108
+ if (existsSync18(yaml3)) {
35109
+ for (const m of readFileSync15(yaml3, "utf8").matchAll(/^\s*-\s*['"]?([^'"\n#]+?)['"]?\s*$/gm))
34594
35110
  globs.push(m[1].trim());
34595
35111
  }
34596
35112
  } catch {
@@ -40663,8 +41179,8 @@ function postmanBody(body) {
40663
41179
  function postmanDescription(d) {
40664
41180
  return isObj(d) ? clip(d.content, 1200) : clip(d, 1200);
40665
41181
  }
40666
- function postmanItems(items, out, depth, count) {
40667
- for (const raw of items) {
41182
+ function postmanItems(items2, out, depth, count) {
41183
+ for (const raw of items2) {
40668
41184
  if (!isObj(raw)) continue;
40669
41185
  if (Array.isArray(raw.item)) {
40670
41186
  out.push(`${"#".repeat(Math.min(2 + depth, 5))} ${String(raw.name ?? "Folder")}`, "");
@@ -42050,14 +42566,14 @@ function buildHistory(ctx, db, opts = {}) {
42050
42566
  };
42051
42567
  });
42052
42568
  const bySha = new Map(commits.map((c) => [c.sha, c]));
42053
- const resolve9 = (prefix) => {
42569
+ const resolve11 = (prefix) => {
42054
42570
  if (bySha.has(prefix)) return bySha.get(prefix);
42055
42571
  for (const c of commits) if (c.sha.startsWith(prefix)) return c;
42056
42572
  return void 0;
42057
42573
  };
42058
42574
  for (const c of commits) {
42059
42575
  if (c.kind !== "revert" || !c.revertOf) continue;
42060
- const target = resolve9(c.revertOf);
42576
+ const target = resolve11(c.revertOf);
42061
42577
  if (target) {
42062
42578
  target.revertedBy = c.sha;
42063
42579
  c.origins.push(target.sha);
@@ -46696,7 +47212,7 @@ function toolText(r) {
46696
47212
  }
46697
47213
  async function runAction(ctx, input2) {
46698
47214
  const started = Date.now();
46699
- const done = (ok, output2) => ({ ok, output: output2, ms: Date.now() - started });
47215
+ const done = (ok2, output2) => ({ ok: ok2, output: output2, ms: Date.now() - started });
46700
47216
  switch (input2.action) {
46701
47217
  case "reindex": {
46702
47218
  const { emReindex: emReindex2 } = await Promise.resolve().then(() => (init_em_graph(), em_graph_exports));
@@ -47055,14 +47571,14 @@ function graphExpand(ctx, opts) {
47055
47571
  const nodeMap = /* @__PURE__ */ new Map([[root.id, root]]);
47056
47572
  for (const m of r.nodes) nodeMap.set(m.node.id, m.node);
47057
47573
  const among = [...db.graphEdgesFrom([...keep], opts.kinds)].filter((e) => keep.has(e.dst));
47058
- const uniq2 = /* @__PURE__ */ new Map();
47059
- for (const e of [...r.edges, ...among]) uniq2.set(`${e.src}|${e.dst}|${e.kind}|${e.source}`, e);
47574
+ const uniq3 = /* @__PURE__ */ new Map();
47575
+ for (const e of [...r.edges, ...among]) uniq3.set(`${e.src}|${e.dst}|${e.kind}|${e.source}`, e);
47060
47576
  return {
47061
47577
  root: root.id,
47062
47578
  rootLabel: nodeLabel(root, root.id),
47063
47579
  truncated: r.truncated,
47064
47580
  nodes: [...keep].map((id) => vis(nodeMap.get(id), id, id === root.id)),
47065
- edges: [...uniq2.values()].map(visEdge)
47581
+ edges: [...uniq3.values()].map(visEdge)
47066
47582
  };
47067
47583
  }
47068
47584
  function graphPathPayload(ctx, from, to) {
@@ -48231,6 +48747,1574 @@ var init_knowledge_api = __esm({
48231
48747
  }
48232
48748
  });
48233
48749
 
48750
+ // src/swarm/agents.ts
48751
+ import { createHash as createHash5 } from "crypto";
48752
+ import {
48753
+ linkSync,
48754
+ lstatSync,
48755
+ mkdirSync as mkdirSync12,
48756
+ readdirSync as readdirSync11,
48757
+ readFileSync as readFileSync24,
48758
+ realpathSync as realpathSync3,
48759
+ renameSync as renameSync2,
48760
+ unlinkSync as unlinkSync2,
48761
+ writeFileSync as writeFileSync10
48762
+ } from "fs";
48763
+ import { createRequire as createRequire4 } from "module";
48764
+ import { join as join32, relative as relative11 } from "path";
48765
+ function yaml() {
48766
+ yamlLib ??= createRequire4(import.meta.url)("yaml");
48767
+ return yamlLib;
48768
+ }
48769
+ function agentsDir(root) {
48770
+ return join32(root, ".claude", "agents");
48771
+ }
48772
+ function agentRel(agentType) {
48773
+ return `${AGENTS_REL}/${agentType}.md`;
48774
+ }
48775
+ function workerTools(tools) {
48776
+ const out = tools.includes("ToolSearch") ? [...tools] : [...tools, "ToolSearch"];
48777
+ for (const prefix of EM_MCP_PREFIXES)
48778
+ for (const t of WORKER_EM_TOOLS) if (!out.includes(prefix + t)) out.push(prefix + t);
48779
+ return out;
48780
+ }
48781
+ function frontmatter(swarm, role) {
48782
+ const fm = {
48783
+ name: role.agentType,
48784
+ description: `Worker "${role.role}" of the eminiar swarm "${swarm.name}": ${role.description} Spawn it for a task of this swarm and name the task as "task #N" in the prompt; the em-swarm skill coordinates runs.`
48785
+ };
48786
+ if (role.model) fm.model = role.model;
48787
+ if (role.tools) fm.tools = workerTools(role.tools).join(", ");
48788
+ const spawns = role.tools?.some((t) => t === "Agent" || t === "Task");
48789
+ const disallowed = [.../* @__PURE__ */ new Set([...role.disallowedTools ?? [], ...spawns ? [] : ["Agent"]])];
48790
+ if (disallowed.length) fm.disallowedTools = disallowed.join(", ");
48791
+ if (role.skills.length) fm.skills = role.skills;
48792
+ if (role.hooks) {
48793
+ const hooks = Object.fromEntries(
48794
+ Object.entries(role.hooks).filter(([, v]) => Array.isArray(v) && v.length)
48795
+ );
48796
+ if (Object.keys(hooks).length) fm.hooks = hooks;
48797
+ }
48798
+ if (role.isolation) fm.isolation = role.isolation;
48799
+ if (role.maxTurns) fm.maxTurns = role.maxTurns;
48800
+ if (role.effort) fm.effort = role.effort;
48801
+ if (role.color) fm.color = role.color;
48802
+ return fm;
48803
+ }
48804
+ function renderAgentFile(config2, swarm, role) {
48805
+ const fm = yaml().stringify(frontmatter(swarm, role), { lineWidth: 0 }).trimEnd();
48806
+ if (fm.includes("---"))
48807
+ throw new Error(`agent ${role.agentType}: frontmatter would contain "---"; fix ${config2.rel}`);
48808
+ const stages = swarm.topology === "pipeline" && swarm.stages.length ? ` Stages: ${swarm.stages.join(" \u2192 ")}.` : "";
48809
+ const rest2 = [
48810
+ "",
48811
+ `You are the **${role.role}** worker of the eminiar swarm **${swarm.name}**: ${role.description}`,
48812
+ `Topology ${swarm.topology}: ${TOPOLOGY_RULE[swarm.topology]}${stages}`,
48813
+ "",
48814
+ ...role.prompt ? [role.prompt, ""] : [],
48815
+ "## Swarm protocol",
48816
+ "",
48817
+ '1. eminiar starts you with a swarm brief: the run goal, the open tasks, the shared notes and what the project knows (memories, history, structure). First claim the task your prompt names with `em_swarm {action:"claim", task:N}` (or the next open one with `em_swarm {action:"claim"}`); if the brief says you already hold a task, continue it.',
48818
+ '2. Ask eminiar before acting: `em_recall {query}` for decisions and constraints, `em_history {action:"file", path}` before editing a file, `em_graph_impact {node}` before changing a symbol, `em_symbols` / `em_search` instead of grepping.',
48819
+ '3. Share what other workers need with `em_swarm {action:"post", key, value}` and read theirs with `em_swarm {action:"read"}`.',
48820
+ '4. Close every task with `em_swarm {action:"update", task, status, result}`: status `done`, `failed` or `blocked`; the result says what changed and how you verified it.',
48821
+ "5. Record durable decisions with `em_remember` so later sessions know them.",
48822
+ "6. Your final message goes back to the coordinator: lead with the outcome, then the files you changed and anything left open.",
48823
+ ""
48824
+ ].join("\n");
48825
+ const marker = `${MARKER_PREFIX}swarm=${swarm.name} role=${role.role} hash=${hashOf(fm, rest2)} \xB7 generated from ${config2.rel} by \`eminiar swarm sync\`; edit the YAML, not this file (it is overwritten). -->`;
48826
+ return `---
48827
+ ${fm}
48828
+ ---
48829
+ ${marker}
48830
+ ${rest2}`;
48831
+ }
48832
+ function generatedState(raw) {
48833
+ const content = raw.replace(/\r\n/g, "\n");
48834
+ if (!content.startsWith("---\n")) return "foreign";
48835
+ const end = content.indexOf("\n---\n", 3);
48836
+ if (end === -1) return "foreign";
48837
+ const fm = content.slice(4, end);
48838
+ const after = content.slice(end + 5);
48839
+ const nl = after.indexOf("\n");
48840
+ const marker = nl === -1 ? after : after.slice(0, nl);
48841
+ if (!marker.startsWith(MARKER_PREFIX)) return "foreign";
48842
+ const hash2 = HASH_RE.exec(marker)?.[1];
48843
+ if (!hash2) return "foreign";
48844
+ return hashOf(fm, nl === -1 ? "" : after.slice(nl + 1)) === hash2 ? "generated" : "edited";
48845
+ }
48846
+ function inspect(file3) {
48847
+ let st;
48848
+ try {
48849
+ st = lstatSync(file3);
48850
+ } catch (err) {
48851
+ return err.code === "ENOENT" ? { kind: "missing" } : { kind: "other", why: `cannot inspect it: ${err.message}` };
48852
+ }
48853
+ if (st.isSymbolicLink()) return { kind: "other", why: "it is a symbolic link" };
48854
+ if (!st.isFile()) return { kind: "other", why: "it is not a regular file" };
48855
+ try {
48856
+ const content = readFileSync24(file3, "utf8");
48857
+ return { kind: "file", content, state: generatedState(content) };
48858
+ } catch (err) {
48859
+ return { kind: "other", why: `cannot read it: ${err.message}` };
48860
+ }
48861
+ }
48862
+ function unsafeDir(root, dir) {
48863
+ let probe3 = dir;
48864
+ for (let i = 0; i < 4; i++) {
48865
+ try {
48866
+ const real = realpathSync3(probe3);
48867
+ const rel = relative11(realpathSync3(root), real);
48868
+ if (rel.startsWith("..") || /^[A-Za-z]:/.test(rel) || rel === "" && probe3 !== root)
48869
+ return `${AGENTS_REL} resolves outside the project (${real}); swarm sync only writes inside the project`;
48870
+ return void 0;
48871
+ } catch {
48872
+ const parent = join32(probe3, "..");
48873
+ if (parent === probe3) break;
48874
+ probe3 = parent;
48875
+ }
48876
+ }
48877
+ return void 0;
48878
+ }
48879
+ function existingGenerated(dir) {
48880
+ const out = /* @__PURE__ */ new Map();
48881
+ let names;
48882
+ try {
48883
+ names = readdirSync11(dir);
48884
+ } catch {
48885
+ return out;
48886
+ }
48887
+ for (const n of names) {
48888
+ if (!n.startsWith("swarm-") || !n.endsWith(".md")) continue;
48889
+ const e = inspect(join32(dir, n));
48890
+ if (e.kind === "file" && e.state !== "foreign") out.set(n.slice(0, -3), e);
48891
+ }
48892
+ return out;
48893
+ }
48894
+ function planAgentSync(root, config2, opts = {}) {
48895
+ const dir = agentsDir(root);
48896
+ const wantedRoles = config2?.enabled ? config2.swarms.flatMap((swarm) => swarm.roles.map((role) => ({ swarm, role }))) : [];
48897
+ const unsafe = unsafeDir(root, dir);
48898
+ if (unsafe)
48899
+ return wantedRoles.map(({ role }) => ({
48900
+ agentType: role.agentType,
48901
+ path: agentRel(role.agentType),
48902
+ action: "conflict",
48903
+ reason: unsafe
48904
+ }));
48905
+ const generated = existingGenerated(dir);
48906
+ const actions = [];
48907
+ const wanted = /* @__PURE__ */ new Set();
48908
+ const editedReason = "the generated file was edited by hand; move the edits into the swarm file, then sync with --force";
48909
+ for (const { swarm, role } of wantedRoles) {
48910
+ wanted.add(role.agentType);
48911
+ const path3 = agentRel(role.agentType);
48912
+ let content;
48913
+ try {
48914
+ content = renderAgentFile(config2, swarm, role);
48915
+ } catch (err) {
48916
+ actions.push({
48917
+ agentType: role.agentType,
48918
+ path: path3,
48919
+ action: "conflict",
48920
+ reason: err.message
48921
+ });
48922
+ continue;
48923
+ }
48924
+ const e = inspect(join32(dir, `${role.agentType}.md`));
48925
+ if (e.kind === "missing")
48926
+ actions.push({ agentType: role.agentType, path: path3, action: "create", content });
48927
+ else if (e.kind === "other")
48928
+ actions.push({
48929
+ agentType: role.agentType,
48930
+ path: path3,
48931
+ action: "conflict",
48932
+ reason: `left alone: ${e.why}`
48933
+ });
48934
+ else if (e.state === "foreign")
48935
+ actions.push({
48936
+ agentType: role.agentType,
48937
+ path: path3,
48938
+ action: "conflict",
48939
+ reason: "a hand-written agent file with this name exists; rename the role or the file"
48940
+ });
48941
+ else if (e.state === "edited" && !opts.force)
48942
+ actions.push({ agentType: role.agentType, path: path3, action: "conflict", reason: editedReason });
48943
+ else
48944
+ actions.push({
48945
+ agentType: role.agentType,
48946
+ path: path3,
48947
+ action: e.content === content ? "unchanged" : "update",
48948
+ content
48949
+ });
48950
+ }
48951
+ for (const [name, e] of generated)
48952
+ if (!wanted.has(name))
48953
+ actions.push(
48954
+ e.state === "edited" && !opts.force ? { agentType: name, path: agentRel(name), action: "conflict", reason: editedReason } : {
48955
+ agentType: name,
48956
+ path: agentRel(name),
48957
+ action: "delete",
48958
+ reason: config2?.enabled ? "no longer in the swarm file" : "swarms are disabled"
48959
+ }
48960
+ );
48961
+ return actions;
48962
+ }
48963
+ function syncAgents(root, config2, opts = {}) {
48964
+ const dir = agentsDir(root);
48965
+ const planned = planAgentSync(root, config2, opts);
48966
+ const actions = [];
48967
+ let changed = 0;
48968
+ for (const { content, ...a } of planned) {
48969
+ const writes = a.action === "create" || a.action === "update" || a.action === "delete";
48970
+ if (opts.dryRun || !writes) {
48971
+ if (writes) changed++;
48972
+ actions.push(a);
48973
+ continue;
48974
+ }
48975
+ const file3 = join32(dir, `${a.agentType}.md`);
48976
+ const tmp = `${file3}.${process.pid}.${Date.now()}.tmp`;
48977
+ try {
48978
+ if (a.action === "delete") unlinkSync2(file3);
48979
+ else {
48980
+ mkdirSync12(dir, { recursive: true });
48981
+ writeFileSync10(tmp, content ?? "");
48982
+ if (a.action === "create") linkSync(tmp, file3);
48983
+ else renameSync2(tmp, file3);
48984
+ }
48985
+ changed++;
48986
+ actions.push(a);
48987
+ } catch (err) {
48988
+ actions.push({
48989
+ ...a,
48990
+ action: "conflict",
48991
+ reason: `could not ${a.action}: ${err.message}`
48992
+ });
48993
+ } finally {
48994
+ try {
48995
+ unlinkSync2(tmp);
48996
+ } catch {
48997
+ }
48998
+ }
48999
+ }
49000
+ return { actions, changed, conflicts: actions.filter((a) => a.action === "conflict").length };
49001
+ }
49002
+ var MARKER_PREFIX, HASH_RE, WORKER_EM_TOOLS, yamlLib, EM_MCP_PREFIXES, AGENTS_REL, TOPOLOGY_RULE, hashOf;
49003
+ var init_agents = __esm({
49004
+ "src/swarm/agents.ts"() {
49005
+ "use strict";
49006
+ init_esm_shims();
49007
+ MARKER_PREFIX = "<!-- eminiar:swarm ";
49008
+ HASH_RE = /\bhash=([0-9a-f]{12})\b/;
49009
+ WORKER_EM_TOOLS = [
49010
+ "em_swarm",
49011
+ "em_recall",
49012
+ "em_memory",
49013
+ "em_remember",
49014
+ "em_search",
49015
+ "em_symbols",
49016
+ "em_history",
49017
+ "em_graph_impact",
49018
+ "em_graph_query",
49019
+ "em_architecture",
49020
+ "em_execute",
49021
+ "em_execute_file",
49022
+ "em_batch_execute",
49023
+ "em_docs",
49024
+ "em_fetch_and_index"
49025
+ ];
49026
+ EM_MCP_PREFIXES = ["mcp__plugin_eminiar_eminiar__", "mcp__eminiar__"];
49027
+ AGENTS_REL = ".claude/agents";
49028
+ TOPOLOGY_RULE = {
49029
+ hierarchical: "the coordinator (the main Claude Code session) plans and assigns the work. Take tasks for your role, report back, and leave planning to the coordinator unless you find work that is missing.",
49030
+ mesh: "peers share one task board. Claim any open task (your role's first), and read the blackboard for what the other workers found.",
49031
+ pipeline: "work moves in stages. You can claim a task only once every task of the earlier stages is done."
49032
+ };
49033
+ hashOf = (fm, rest2) => createHash5("sha1").update(`${fm}
49034
+ ${rest2}`).digest("hex").slice(0, 12);
49035
+ }
49036
+ });
49037
+
49038
+ // src/swarm/glob.ts
49039
+ function normalize6(g) {
49040
+ let s = g.trim().replace(/\\/g, "/");
49041
+ while (s.startsWith("./")) s = s.slice(2);
49042
+ return s.replace(/\/+/g, "/").replace(/\/$/, "");
49043
+ }
49044
+ function expand2(g, out = []) {
49045
+ let depth = 0;
49046
+ let start = -1;
49047
+ for (let i = 0; i < g.length; i++) {
49048
+ const c = g[i];
49049
+ if (c === "{") {
49050
+ if (depth === 0) start = i;
49051
+ depth++;
49052
+ } else if (c === "}" && depth > 0) {
49053
+ depth--;
49054
+ if (depth === 0) {
49055
+ const body = g.slice(start + 1, i);
49056
+ const parts = [];
49057
+ let d = 0;
49058
+ let from = 0;
49059
+ for (let j = 0; j < body.length; j++) {
49060
+ if (body[j] === "{") d++;
49061
+ else if (body[j] === "}") d--;
49062
+ else if (body[j] === "," && d === 0) {
49063
+ parts.push(body.slice(from, j));
49064
+ from = j + 1;
49065
+ }
49066
+ }
49067
+ parts.push(body.slice(from));
49068
+ for (const p of parts) {
49069
+ expand2(g.slice(0, start) + p + g.slice(i + 1), out);
49070
+ if (out.length > MAX_EXPANSIONS)
49071
+ throw new GlobError(`expands to more than ${MAX_EXPANSIONS} alternatives`);
49072
+ }
49073
+ return out;
49074
+ }
49075
+ }
49076
+ }
49077
+ if (depth !== 0) throw new GlobError("unbalanced {");
49078
+ out.push(g);
49079
+ return out;
49080
+ }
49081
+ function tokenize(seg) {
49082
+ const toks = [];
49083
+ for (let i = 0; i < seg.length; i++) {
49084
+ const c = seg[i];
49085
+ if (c === "*") {
49086
+ if (toks.at(-1)?.t !== "any") toks.push({ t: "any" });
49087
+ } else if (c === "?") toks.push({ t: "one" });
49088
+ else if (c === "[") {
49089
+ const end = seg.indexOf("]", i + 2);
49090
+ if (end === -1) throw new GlobError("unclosed [");
49091
+ let body = seg.slice(i + 1, end);
49092
+ const neg = body.startsWith("!") || body.startsWith("^");
49093
+ if (neg) body = body.slice(1);
49094
+ const ranges = [];
49095
+ for (let j = 0; j < body.length; j++) {
49096
+ const a = body[j];
49097
+ if (body[j + 1] === "-" && j + 2 < body.length) {
49098
+ ranges.push([a, body[j + 2]]);
49099
+ j += 2;
49100
+ } else ranges.push([a, a]);
49101
+ }
49102
+ toks.push({ t: "class", neg, ranges });
49103
+ i = end;
49104
+ } else toks.push({ t: "lit", c });
49105
+ }
49106
+ return toks;
49107
+ }
49108
+ function compile2(glob2) {
49109
+ const hit = cache3.get(glob2);
49110
+ if (hit) return hit;
49111
+ const g = normalize6(glob2);
49112
+ if (g.length > MAX_LENGTH) throw new GlobError(`longer than ${MAX_LENGTH} characters`);
49113
+ const prefix = !/[*?[{]/.test(g);
49114
+ const alts = expand2(g).map(
49115
+ (p) => p.split("/").filter((s, _i, a) => s !== "" || a.length === 1).map((s) => s === "**" ? "**" : tokenize(s))
49116
+ );
49117
+ const c = { alts, prefix };
49118
+ if (cache3.size > 256) cache3.clear();
49119
+ cache3.set(glob2, c);
49120
+ return c;
49121
+ }
49122
+ function checkGlob(glob2) {
49123
+ compile2(glob2);
49124
+ }
49125
+ var MAX_EXPANSIONS, MAX_LENGTH, cache3, GlobError;
49126
+ var init_glob = __esm({
49127
+ "src/swarm/glob.ts"() {
49128
+ "use strict";
49129
+ init_esm_shims();
49130
+ MAX_EXPANSIONS = 64;
49131
+ MAX_LENGTH = 300;
49132
+ cache3 = /* @__PURE__ */ new Map();
49133
+ GlobError = class extends Error {
49134
+ };
49135
+ }
49136
+ });
49137
+
49138
+ // src/swarm/types.ts
49139
+ var TOPOLOGIES;
49140
+ var init_types4 = __esm({
49141
+ "src/swarm/types.ts"() {
49142
+ "use strict";
49143
+ init_esm_shims();
49144
+ TOPOLOGIES = ["hierarchical", "mesh", "pipeline"];
49145
+ }
49146
+ });
49147
+
49148
+ // src/swarm/config.ts
49149
+ import { createHash as createHash6 } from "crypto";
49150
+ import { existsSync as existsSync28, readFileSync as readFileSync25 } from "fs";
49151
+ import { createRequire as createRequire5 } from "module";
49152
+ import { join as join33 } from "path";
49153
+ function yaml2() {
49154
+ yamlLib2 ??= createRequire5(import.meta.url)("yaml");
49155
+ return yamlLib2;
49156
+ }
49157
+ function agentTypeOf(swarm, role) {
49158
+ return `swarm-${swarm}-${role}`;
49159
+ }
49160
+ function mergeContext(...layers) {
49161
+ const out = { ...DEFAULT_CONTEXT };
49162
+ for (const l of layers) {
49163
+ if (!l) continue;
49164
+ if (l.memories !== void 0) out.memories = l.memories;
49165
+ if (l.docs !== void 0) out.docs = l.docs;
49166
+ if (l.history !== void 0) out.history = l.history;
49167
+ if (l.structure !== void 0) out.structure = l.structure;
49168
+ if (l.insights !== void 0) out.insights = l.insights;
49169
+ if (l.max_chars !== void 0) out.maxChars = l.max_chars;
49170
+ }
49171
+ return out;
49172
+ }
49173
+ function swarmFile(root) {
49174
+ for (const rel of SWARM_FILES) {
49175
+ const file3 = join33(root, rel);
49176
+ if (existsSync28(file3)) return { file: file3, rel, exists: true };
49177
+ }
49178
+ return { file: join33(root, SWARM_FILES[0]), rel: SWARM_FILES[0], exists: false };
49179
+ }
49180
+ function enforceIntent(text3) {
49181
+ return topLevelFlag(text3, "enforce", "true") && !topLevelFlag(text3, "enabled", "false");
49182
+ }
49183
+ function topLevelFlag(text3, key, word) {
49184
+ const w = word === "true" ? "(?:true|True|TRUE)" : "(?:false|False|FALSE)";
49185
+ return new RegExp(
49186
+ `^\\uFEFF?(?:${key}|"${key}"|'${key}')[ \\t]*:[ \\t]+${w}(?:[ \\t]+#.*)?[ \\t]*\\r?$`,
49187
+ "m"
49188
+ ).test(text3);
49189
+ }
49190
+ function parseSwarmYaml(text3, file3, rel) {
49191
+ const Y = yaml2();
49192
+ const lines = new Y.LineCounter();
49193
+ if (text3.length > MAX_FILE_CHARS)
49194
+ return {
49195
+ state: "invalid",
49196
+ file: file3,
49197
+ rel,
49198
+ enforce: enforceIntent(text3),
49199
+ issues: [{ path: "", message: `the file is larger than ${MAX_FILE_CHARS / 1e3} KB` }]
49200
+ };
49201
+ const doc = Y.parseDocument(text3, {
49202
+ lineCounter: lines,
49203
+ prettyErrors: false,
49204
+ uniqueKeys: true,
49205
+ merge: true
49206
+ });
49207
+ const lineAt = (offset) => offset === void 0 ? void 0 : lines.linePos(offset).line;
49208
+ if (doc.errors.length) {
49209
+ return {
49210
+ state: "invalid",
49211
+ file: file3,
49212
+ rel,
49213
+ enforce: enforceIntent(text3),
49214
+ issues: doc.errors.map((e) => ({
49215
+ path: "",
49216
+ message: e.message.split("\n")[0] ?? e.message,
49217
+ line: lineAt(e.pos?.[0])
49218
+ }))
49219
+ };
49220
+ }
49221
+ const lineOf = (path3) => {
49222
+ const keys = path3.filter((k) => typeof k !== "symbol");
49223
+ for (let n = keys.length; n >= 0; n--) {
49224
+ const node2 = n === 0 ? doc.contents : doc.getIn(keys.slice(0, n), true);
49225
+ if (Y.isNode(node2) && node2.range) return lineAt(node2.range[0]);
49226
+ }
49227
+ return void 0;
49228
+ };
49229
+ let js;
49230
+ try {
49231
+ js = doc.toJS({ maxAliasCount: 200 });
49232
+ } catch (err) {
49233
+ return {
49234
+ state: "invalid",
49235
+ file: file3,
49236
+ rel,
49237
+ enforce: enforceIntent(text3),
49238
+ issues: [{ path: "", message: err.message.split("\n")[0] ?? "unreadable YAML" }]
49239
+ };
49240
+ }
49241
+ if (js === null || js === void 0 || typeof js !== "object" || Array.isArray(js)) {
49242
+ return {
49243
+ state: "invalid",
49244
+ file: file3,
49245
+ rel,
49246
+ enforce: enforceIntent(text3),
49247
+ issues: [{ path: "", message: "expected a mapping with a `swarms:` key", line: 1 }]
49248
+ };
49249
+ }
49250
+ const top2 = js;
49251
+ const enforce = top2.enforce === true && top2.enabled !== false;
49252
+ const issues = [];
49253
+ const issue2 = (path3, message) => issues.push({ path: path3.join("."), message, line: lineOf(path3) });
49254
+ const checkName = (path3, n, what) => {
49255
+ if (!NAME_RE3.test(n) || n.length > NAME_MAX) issue2(path3, `${what} name "${n}": ${NAME_MSG}`);
49256
+ };
49257
+ const rawSwarms = js.swarms;
49258
+ if (rawSwarms && typeof rawSwarms === "object" && !Array.isArray(rawSwarms))
49259
+ for (const [name, s] of Object.entries(rawSwarms)) {
49260
+ checkName(["swarms", name], name, "swarm");
49261
+ const agents = s?.agents;
49262
+ if (agents && typeof agents === "object" && !Array.isArray(agents))
49263
+ for (const role of Object.keys(agents))
49264
+ checkName(["swarms", name, "agents", role], role, "agent");
49265
+ }
49266
+ const parsed = RawFile.safeParse(js);
49267
+ if (!parsed.success) {
49268
+ return {
49269
+ state: "invalid",
49270
+ file: file3,
49271
+ rel,
49272
+ enforce,
49273
+ issues: [
49274
+ ...issues,
49275
+ ...parsed.error.issues.slice(0, 50).map((i) => ({
49276
+ path: i.path.map(String).join("."),
49277
+ message: i.message,
49278
+ line: lineOf(i.path)
49279
+ }))
49280
+ ]
49281
+ };
49282
+ }
49283
+ for (const k of Object.keys(js))
49284
+ if (!TOP_KEYS.has(k) && !k.startsWith("x-") && k !== "<<")
49285
+ issue2(
49286
+ [k],
49287
+ `unknown key "${k}" (top-level keys: ${[...TOP_KEYS].join(", ")}; x-* keys may hold YAML anchors)`
49288
+ );
49289
+ const config2 = resolve9(parsed.data, file3, rel, issue2);
49290
+ if (issues.length) return { state: "invalid", file: file3, rel, enforce, issues };
49291
+ return { state: "ok", config: config2 };
49292
+ }
49293
+ function resolve9(raw, file3, rel, issue2) {
49294
+ const swarms = [];
49295
+ const seenAgent = /* @__PURE__ */ new Map();
49296
+ const swarmEntries = Object.entries(raw.swarms);
49297
+ if (!swarmEntries.length) issue2(["swarms"], "define at least one swarm");
49298
+ for (const [name, s] of swarmEntries) {
49299
+ const roleEntries = Object.entries(s.agents);
49300
+ if (!roleEntries.length) issue2(["swarms", name, "agents"], "define at least one agent");
49301
+ const roles = roleEntries.map(([role, a]) => {
49302
+ const agentType = agentTypeOf(name, role);
49303
+ if (agentType.length > AGENT_NAME_MAX)
49304
+ issue2(
49305
+ ["swarms", name, "agents", role],
49306
+ `agent name "${agentType}" is longer than ${AGENT_NAME_MAX} characters; shorten the swarm or role name`
49307
+ );
49308
+ const other = seenAgent.get(agentType);
49309
+ if (other)
49310
+ issue2(
49311
+ ["swarms", name, "agents", role],
49312
+ `agent name "${agentType}" is also produced by ${other}; rename one of them`
49313
+ );
49314
+ seenAgent.set(agentType, `swarms.${name}.agents.${role}`);
49315
+ return {
49316
+ swarm: name,
49317
+ role,
49318
+ agentType,
49319
+ description: a.description,
49320
+ prompt: (a.prompt ?? "").trim(),
49321
+ model: a.model ?? raw.defaults?.model,
49322
+ tools: a.tools ? uniq2(a.tools) : void 0,
49323
+ disallowedTools: a.disallowed_tools ? uniq2(a.disallowed_tools) : void 0,
49324
+ skills: uniq2([...raw.defaults?.skills ?? [], ...a.skills ?? []]),
49325
+ hooks: a.hooks,
49326
+ scope: uniq2(a.scope ?? []),
49327
+ memoryTags: uniq2(a.memory_tags ?? []),
49328
+ count: a.count ?? 1,
49329
+ isolation: a.isolation,
49330
+ maxTurns: a.max_turns,
49331
+ effort: a.effort,
49332
+ color: a.color,
49333
+ context: mergeContext(raw.defaults?.context, s.context, a.context)
49334
+ };
49335
+ });
49336
+ const roleNames = new Set(roles.map((r) => r.role));
49337
+ const topology = s.topology ?? "hierarchical";
49338
+ const stages = s.stages ?? [];
49339
+ stages.forEach((st, i) => {
49340
+ if (!roleNames.has(st))
49341
+ issue2(["swarms", name, "stages", String(i)], `stage "${st}" is not an agent of this swarm`);
49342
+ });
49343
+ if (new Set(stages).size !== stages.length)
49344
+ issue2(["swarms", name, "stages"], "a role may appear in stages only once");
49345
+ if (stages.length && topology !== "pipeline")
49346
+ issue2(
49347
+ ["swarms", name, "stages"],
49348
+ `stages only apply to topology: pipeline (this swarm is ${topology}); remove them or change the topology`
49349
+ );
49350
+ swarms.push({
49351
+ name,
49352
+ description: s.description,
49353
+ topology,
49354
+ maxAgents: s.max_agents ?? 4,
49355
+ // A pipeline with no explicit stages runs its roles in the order they are declared.
49356
+ stages: stages.length ? stages : topology === "pipeline" ? roles.map((r) => r.role) : [],
49357
+ instructions: (s.instructions ?? "").trim(),
49358
+ roles
49359
+ });
49360
+ }
49361
+ return {
49362
+ file: file3,
49363
+ rel,
49364
+ enabled: raw.enabled ?? true,
49365
+ sync: raw.sync ?? "auto",
49366
+ enforce: raw.enforce ?? false,
49367
+ allowedAgents: uniq2(raw.allowed_agents ?? []),
49368
+ swarms,
49369
+ hash: createHash6("sha1").update(JSON.stringify(swarms)).digest("hex")
49370
+ };
49371
+ }
49372
+ function loadSwarmConfig(root) {
49373
+ const { file: file3, rel, exists } = swarmFile(root);
49374
+ if (!exists) return { state: "absent", file: file3, rel };
49375
+ let text3;
49376
+ try {
49377
+ text3 = readFileSync25(file3, "utf8");
49378
+ const hit = cache4.get(file3);
49379
+ if (hit && hit.text === text3) return hit.result;
49380
+ } catch (err) {
49381
+ return {
49382
+ state: "invalid",
49383
+ file: file3,
49384
+ rel,
49385
+ issues: [{ path: "", message: `cannot read ${rel}: ${err.message}` }]
49386
+ };
49387
+ }
49388
+ const result = parseSwarmYaml(text3, file3, rel);
49389
+ if (cache4.size > 32) cache4.clear();
49390
+ cache4.set(file3, { text: text3, result });
49391
+ return result;
49392
+ }
49393
+ function activeSwarmConfig(root) {
49394
+ const r = loadSwarmConfig(root);
49395
+ return r.state === "ok" && r.config.enabled ? r.config : void 0;
49396
+ }
49397
+ function findRole(config2, agentType) {
49398
+ if (!agentType) return void 0;
49399
+ for (const swarm of config2.swarms)
49400
+ for (const role of swarm.roles) if (role.agentType === agentType) return { swarm, role };
49401
+ return void 0;
49402
+ }
49403
+ var yamlLib2, SWARM_FILES, AGENT_NAME_MAX, NAME_RE3, NAME_MAX, NAME_MSG, noFence, FENCE_MSG, MODEL_RE, TOP_KEYS, DEFAULT_CONTEXT, AGENT_COLORS, ToolName, SkillName, Model, Glob, AgentName, RawContext, RawHookCommand, RawHookMatcher, RawHooks, RawAgent, RawSwarm, RawFile, uniq2, MAX_FILE_CHARS, cache4, STARTER_YAML;
49404
+ var init_config2 = __esm({
49405
+ "src/swarm/config.ts"() {
49406
+ "use strict";
49407
+ init_esm_shims();
49408
+ init_zod();
49409
+ init_glob();
49410
+ init_types4();
49411
+ SWARM_FILES = [".eminiar/swarm.yaml", ".eminiar/swarm.yml"];
49412
+ AGENT_NAME_MAX = 64;
49413
+ NAME_RE3 = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
49414
+ NAME_MAX = 32;
49415
+ NAME_MSG = "use 1-32 characters: lowercase letters and digits in words joined by single hyphens, starting with a letter (e.g. code-review)";
49416
+ noFence = (s) => !s.includes("---");
49417
+ FENCE_MSG = 'must not contain "---" (Claude Code would end the agent frontmatter there)';
49418
+ MODEL_RE = /^(?:inherit|opusplan|(?:sonnet|opus|haiku|fable)(?:\[1m\])?|(?:[a-z0-9-]+\.)*claude-[a-z0-9.:@-]+(?:\[1m\])?)$/;
49419
+ TOP_KEYS = /* @__PURE__ */ new Set([
49420
+ "version",
49421
+ "enabled",
49422
+ "sync",
49423
+ "enforce",
49424
+ "allowed_agents",
49425
+ "defaults",
49426
+ "swarms"
49427
+ ]);
49428
+ DEFAULT_CONTEXT = {
49429
+ memories: 4,
49430
+ docs: 2,
49431
+ history: true,
49432
+ structure: true,
49433
+ insights: true,
49434
+ maxChars: 4e3
49435
+ };
49436
+ AGENT_COLORS = [
49437
+ "red",
49438
+ "blue",
49439
+ "green",
49440
+ "yellow",
49441
+ "purple",
49442
+ "orange",
49443
+ "pink",
49444
+ "cyan"
49445
+ ];
49446
+ ToolName = external_exports.string().trim().regex(
49447
+ /^[^,\r\n\t]{1,200}$/,
49448
+ "a tool name such as Read, Bash or mcp__server__tool (no commas or tabs)"
49449
+ ).refine(noFence, FENCE_MSG);
49450
+ SkillName = external_exports.string().regex(
49451
+ /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,99}$/,
49452
+ "a skill name such as em-history or eminiar:em-graph"
49453
+ ).refine(noFence, FENCE_MSG);
49454
+ Model = external_exports.string().max(100).regex(MODEL_RE, "a model alias (inherit, sonnet, opus, haiku, fable) or a Claude model id").refine(noFence, FENCE_MSG);
49455
+ Glob = external_exports.string().min(1).max(300).refine(
49456
+ (g) => !g.startsWith("/") && !/^[A-Za-z]:/.test(g) && !g.split(/[\\/]/).includes(".."),
49457
+ "a project-relative glob without '..'"
49458
+ ).superRefine((g, c) => {
49459
+ try {
49460
+ checkGlob(g);
49461
+ } catch (err) {
49462
+ c.addIssue({
49463
+ code: "custom",
49464
+ message: `not a usable glob: ${err instanceof GlobError ? err.message : String(err)}`
49465
+ });
49466
+ }
49467
+ });
49468
+ AgentName = external_exports.string().trim().regex(
49469
+ /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/,
49470
+ "an agent type such as Explore, statusline-setup or my-plugin:reviewer"
49471
+ ).refine(
49472
+ (n) => !n.startsWith("swarm-"),
49473
+ "swarm workers need no entry: they are allowed during a run of their swarm"
49474
+ );
49475
+ RawContext = external_exports.strictObject({
49476
+ memories: external_exports.number().int().min(0).max(10).optional(),
49477
+ docs: external_exports.number().int().min(0).max(10).optional(),
49478
+ history: external_exports.boolean().optional(),
49479
+ structure: external_exports.boolean().optional(),
49480
+ insights: external_exports.boolean().optional(),
49481
+ // SubagentStart context over 10,000 characters is moved to a file Claude may never read.
49482
+ max_chars: external_exports.number().int().min(500).max(9e3).optional()
49483
+ });
49484
+ RawHookCommand = external_exports.strictObject({
49485
+ type: external_exports.literal("command"),
49486
+ command: external_exports.string().trim().min(1).max(2e3).refine(noFence, FENCE_MSG),
49487
+ timeout: external_exports.number().int().positive().max(600).optional()
49488
+ });
49489
+ RawHookMatcher = external_exports.strictObject({
49490
+ matcher: external_exports.string().max(200).refine(noFence, FENCE_MSG).refine((m) => {
49491
+ try {
49492
+ new RegExp(m);
49493
+ return true;
49494
+ } catch {
49495
+ return false;
49496
+ }
49497
+ }, "not a valid regular expression (Claude Code matches tool names with it)").optional(),
49498
+ hooks: external_exports.array(RawHookCommand).min(1).max(10)
49499
+ });
49500
+ RawHooks = external_exports.strictObject({
49501
+ PreToolUse: external_exports.array(RawHookMatcher).max(10).optional(),
49502
+ PostToolUse: external_exports.array(RawHookMatcher).max(10).optional(),
49503
+ Stop: external_exports.array(RawHookMatcher).max(10).optional()
49504
+ });
49505
+ RawAgent = external_exports.strictObject({
49506
+ description: external_exports.string().trim().min(1).max(500).refine(noFence, FENCE_MSG),
49507
+ prompt: external_exports.string().max(2e4).optional(),
49508
+ model: Model.optional(),
49509
+ tools: external_exports.array(ToolName).max(80).optional(),
49510
+ disallowed_tools: external_exports.array(ToolName).max(80).optional(),
49511
+ skills: external_exports.array(SkillName).max(20).optional(),
49512
+ hooks: RawHooks.optional(),
49513
+ scope: external_exports.array(Glob).max(30).optional(),
49514
+ memory_tags: external_exports.array(external_exports.string().trim().min(1).max(64)).max(20).optional(),
49515
+ count: external_exports.number().int().min(1).max(8).optional(),
49516
+ isolation: external_exports.literal("worktree").optional(),
49517
+ max_turns: external_exports.number().int().min(1).max(500).optional(),
49518
+ effort: external_exports.enum(["low", "medium", "high", "xhigh", "max"]).optional(),
49519
+ color: external_exports.enum(AGENT_COLORS).optional(),
49520
+ context: RawContext.optional()
49521
+ });
49522
+ RawSwarm = external_exports.strictObject({
49523
+ description: external_exports.string().trim().min(1).max(500),
49524
+ topology: external_exports.enum(TOPOLOGIES).optional(),
49525
+ max_agents: external_exports.number().int().min(1).max(16).optional(),
49526
+ stages: external_exports.array(external_exports.string()).max(32).optional(),
49527
+ instructions: external_exports.string().max(2e4).optional(),
49528
+ context: RawContext.optional(),
49529
+ // Names are checked in resolve(): zod reports a bad record key only as "Invalid key".
49530
+ agents: external_exports.record(external_exports.string(), RawAgent)
49531
+ });
49532
+ RawFile = external_exports.looseObject({
49533
+ version: external_exports.literal(1).optional(),
49534
+ enabled: external_exports.boolean().optional(),
49535
+ sync: external_exports.enum(["auto", "manual"]).optional(),
49536
+ enforce: external_exports.boolean().optional(),
49537
+ allowed_agents: external_exports.array(AgentName).max(50).optional(),
49538
+ defaults: external_exports.strictObject({
49539
+ model: Model.optional(),
49540
+ skills: external_exports.array(SkillName).max(20).optional(),
49541
+ context: RawContext.optional()
49542
+ }).optional(),
49543
+ swarms: external_exports.record(external_exports.string(), RawSwarm)
49544
+ });
49545
+ uniq2 = (xs) => [...new Set(xs)];
49546
+ MAX_FILE_CHARS = 256e3;
49547
+ cache4 = /* @__PURE__ */ new Map();
49548
+ STARTER_YAML = `# eminiar agent swarms (opt-in). Docs: https://github.com/rydn/eminiar/blob/main/docs/swarms.md
49549
+ # Each agent below becomes a Claude Code agent file .claude/agents/swarm-<swarm>-<agent>.md.
49550
+ # The main Claude Code session coordinates; start a run with: /eminiar:em-swarm feature <goal>
49551
+ version: 1
49552
+ enabled: true
49553
+ sync: auto # regenerate the agent files at session start when this file changes
49554
+ # enforce: true # only swarm workers may run as subagents, and only during a run of their swarm
49555
+ # allowed_agents: [] # with enforce: other agent types still allowed, e.g. [statusline-setup]
49556
+
49557
+ defaults:
49558
+ model: sonnet
49559
+ context: # what eminiar tells each worker when it starts
49560
+ memories: 4 # project memories matching the role and task
49561
+ docs: 2 # documentation pages matching the task
49562
+ history: true # bug-prone files, recent fixes and history lessons in the work area
49563
+ insights: true # hot files (fan-in \xD7 churn) in the work area
49564
+ structure: true # the project brief: overview, decisions, constraints
49565
+ max_chars: 4000
49566
+
49567
+ swarms:
49568
+ feature:
49569
+ description: Plan, build, test and review a change to the codebase.
49570
+ topology: hierarchical # hierarchical | mesh | pipeline
49571
+ max_agents: 4 # workers running at once in one run
49572
+ instructions: |
49573
+ Break the goal into tasks one worker can finish in one go, each naming the files it touches.
49574
+ Let the architect design first, fan the coding tasks out, then test, then review.
49575
+ agents:
49576
+ architect:
49577
+ description: Designs the change from the code graph, the history and past decisions.
49578
+ model: opus
49579
+ disallowed_tools: [Edit, Write, NotebookEdit]
49580
+ memory_tags: [decision, constraint]
49581
+ prompt: |
49582
+ Study the affected code with em_architecture, em_symbols and em_graph_impact before proposing anything.
49583
+ Check em_recall for decisions and constraints that apply.
49584
+ Post the design under the blackboard key "design": files to change, the approach, the risks.
49585
+ coder:
49586
+ description: Implements one task at a time with the smallest diff that works.
49587
+ count: 2 # at most two coders at once
49588
+ prompt: |
49589
+ Read the "design" note first: em_swarm {action:"read", key:"design"}.
49590
+ Before editing a file run em_history {action:"file", path} and respect its fix record.
49591
+ Keep the change minimal and consistent with the surrounding code, and run the relevant tests.
49592
+ tester:
49593
+ description: Writes and runs tests that pin the new behaviour and its edge cases.
49594
+ prompt: |
49595
+ Cover the behaviour the task describes, including edge cases and failure paths.
49596
+ Run the tests and report exact failures. Never weaken a test to make it pass.
49597
+ reviewer:
49598
+ description: Adversarial reviewer that tries to break the change before it ships.
49599
+ model: opus
49600
+ disallowed_tools: [Edit, Write, NotebookEdit]
49601
+ memory_tags: [constraint, preference]
49602
+ prompt: |
49603
+ Review the diff for correctness bugs, missed edge cases, broken conventions and missing tests.
49604
+ Every finding needs a concrete failing input or scenario.
49605
+ Do not fix anything yourself: add each finding as a task for the coder role with
49606
+ em_swarm {action:"plan", tasks:[{title, detail, role:"coder"}]}.
49607
+
49608
+ review:
49609
+ description: Review the working tree from several angles at once.
49610
+ topology: mesh
49611
+ max_agents: 3
49612
+ agents:
49613
+ correctness:
49614
+ description: Hunts for logic errors and unhandled edge cases.
49615
+ disallowed_tools: [Edit, Write, NotebookEdit]
49616
+ prompt: Find bugs, each with a concrete failing input. Post each under the key "finding-<n>".
49617
+ security:
49618
+ description: Looks for injection, path traversal, leaked secrets and unsafe defaults.
49619
+ disallowed_tools: [Edit, Write, NotebookEdit]
49620
+ prompt: Report only exploitable issues, each with the input that triggers it.
49621
+ tests:
49622
+ description: Checks that every changed behaviour has a test that would fail without it.
49623
+ disallowed_tools: [Edit, Write, NotebookEdit]
49624
+ prompt: List untested behaviour together with the test you would add.
49625
+ `;
49626
+ }
49627
+ });
49628
+
49629
+ // src/swarm/runtime.ts
49630
+ import { randomBytes } from "crypto";
49631
+ import { existsSync as existsSync29, statSync as statSync12 } from "fs";
49632
+ import { isAbsolute, join as join34, relative as relative12 } from "path";
49633
+ function clip3(s, n) {
49634
+ const t = s.trim();
49635
+ if (t.length <= n) return t;
49636
+ let cut = t.slice(0, Math.max(0, n - 1));
49637
+ const last = cut.charCodeAt(cut.length - 1);
49638
+ if (last >= 55296 && last <= 56319) cut = cut.slice(0, -1);
49639
+ return `${cut}\u2026`;
49640
+ }
49641
+ function localDate(ts) {
49642
+ const d = new Date(ts);
49643
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
49644
+ }
49645
+ function callerOf(config2, ids) {
49646
+ if (!ids.agentId && !ids.agentType) return { label: COORDINATOR };
49647
+ const hit = findRole(config2, ids.agentType);
49648
+ if (hit)
49649
+ return {
49650
+ label: hit.role.role,
49651
+ agentId: ids.agentId,
49652
+ agentType: ids.agentType,
49653
+ swarm: hit.swarm,
49654
+ role: hit.role
49655
+ };
49656
+ return { label: ids.agentType || "subagent", agentId: ids.agentId, agentType: ids.agentType };
49657
+ }
49658
+ function requireActive(db, run3) {
49659
+ const status = db.swarm.runGet(run3.id)?.status ?? "deleted";
49660
+ if (status !== "active")
49661
+ throw new SwarmError(
49662
+ `run ${run3.id} is ${status}; start a new run with em_swarm {action:"start", swarm, goal}`
49663
+ );
49664
+ }
49665
+ function staleHeld(db, run3, all, now = Date.now()) {
49666
+ const workers = new Map(db.swarm.workers(run3.id).map((w) => [w.agent_id, w]));
49667
+ return all.filter((t) => {
49668
+ if (t.status !== "active" || !t.agent_id || t.agent_id === COORDINATOR) return false;
49669
+ const w = workers.get(t.agent_id) ?? db.swarm.workerFind(t.agent_id);
49670
+ if (!w) return false;
49671
+ return w.status === "done" || now - w.last_ts >= STALE_MS;
49672
+ });
49673
+ }
49674
+ function finishRun(ctx, db, run3, caller, opts) {
49675
+ if (!isCoordinator(caller))
49676
+ throw new SwarmError("only the coordinator (the main session) can finish a run");
49677
+ if (!FINISH.includes(opts.status))
49678
+ throw new SwarmError(`status must be one of ${FINISH.join(", ")}`);
49679
+ const summary = opts.summary?.trim() ? clip3(opts.summary, RESULT_MAX) : null;
49680
+ const now = Date.now();
49681
+ db.swarm.immediate(() => {
49682
+ requireActive(db, run3);
49683
+ const active = db.swarm.tasks(run3.id).filter((t) => t.status === "active").length;
49684
+ const running2 = db.swarm.workers(run3.id).filter((w) => w.status === "running");
49685
+ for (const w of running2)
49686
+ db.swarm.workerUpdate(run3.id, w.agent_id, {
49687
+ status: "done",
49688
+ ended_at: now,
49689
+ summary: w.summary ?? "closed when the run finished"
49690
+ });
49691
+ db.swarm.runUpdate(run3.id, { status: opts.status, summary, ended_at: now });
49692
+ const left = [
49693
+ active ? `${active} task(s) still active` : "",
49694
+ running2.length ? `${running2.length} worker(s) still running were closed` : ""
49695
+ ].filter(Boolean);
49696
+ db.swarm.eventAdd({
49697
+ run_id: run3.id,
49698
+ kind: "run_finish",
49699
+ role: COORDINATOR,
49700
+ text: `${opts.status}${summary ? `: ${firstLine2(summary).slice(0, 160)}` : ""}${left.length ? ` (${left.join(", ")})` : ""}`
49701
+ });
49702
+ });
49703
+ const closed = db.swarm.runGet(run3.id);
49704
+ if (!opts.remember) return { run: closed };
49705
+ try {
49706
+ return { run: closed, memory: rememberRun(ctx, db, closed) };
49707
+ } catch (err) {
49708
+ return { run: closed, memoryError: err.message };
49709
+ }
49710
+ }
49711
+ function rememberRun(ctx, db, run3) {
49712
+ const tasks = db.swarm.tasks(run3.id);
49713
+ const workers = db.swarm.workers(run3.id);
49714
+ const date5 = localDate(run3.created_at);
49715
+ const minutes = Math.max(1, Math.round(((run3.ended_at ?? Date.now()) - run3.created_at) / 6e4));
49716
+ const lines = [
49717
+ `Swarm run ${run3.id} (swarm "${run3.swarm}", ${run3.topology}, ${date5}): ${run3.status}.`,
49718
+ `Goal: ${run3.goal}`,
49719
+ ...run3.summary ? [`Outcome: ${run3.summary}`] : [],
49720
+ `${tasks.length} task(s), ${workers.length} worker(s), ${minutes} min.`,
49721
+ ...tasks.map(
49722
+ (t) => `- #${t.id} [${t.status}${t.auto_closed ? ", auto-closed" : ""}]${t.role ? ` (${t.role})` : ""} ${t.title}${t.result ? ` \u2014 ${clip3(firstLine2(t.result), 200)}` : ""}`
49723
+ )
49724
+ ];
49725
+ const slug = slugify2(run3.goal.replace(/^\(ad hoc\)\s*/, ""), 32) || run3.swarm;
49726
+ const base = `swarm/run-${date5}-${slug}`;
49727
+ const taken = (n) => !!db.memoryGetByName(n) || existsSync29(join34(ctx.root, relOf(ctx, n)));
49728
+ const suffix = run3.id.slice(-6);
49729
+ let name = base;
49730
+ for (let i = 0; taken(name) && i < 50; i++)
49731
+ name = i === 0 ? `${base}-${suffix}` : `${base}-${suffix}-${i}`;
49732
+ const r = write(ctx, db, {
49733
+ name,
49734
+ kind: "note",
49735
+ content: lines.join("\n"),
49736
+ tags: ["swarm", run3.swarm],
49737
+ related: [...new Set(tasks.flatMap((t) => t.files))].filter((f) => {
49738
+ try {
49739
+ return statSync12(join34(ctx.root, f)).isFile();
49740
+ } catch {
49741
+ return false;
49742
+ }
49743
+ }).slice(0, 20)
49744
+ });
49745
+ return r.name;
49746
+ }
49747
+ function toolIo(tool) {
49748
+ const t = tool.replace(/^mcp__.*__/, "");
49749
+ if (t === "em_swarm") return "swarm";
49750
+ if (/^(Edit|Write|MultiEdit|NotebookEdit|em_remember)$/.test(t)) return "write";
49751
+ if (/^(Bash|em_execute|em_execute_file|em_batch_execute)$/.test(t)) return "exec";
49752
+ if (/^(Read|Grep|Glob|LS|WebFetch|WebSearch|em_search|em_recall|em_symbols|em_history|em_graph_\w+|em_architecture|em_docs|em_memory|em_fetch_and_index|em_index_status)$/.test(
49753
+ t
49754
+ ))
49755
+ return "read";
49756
+ return "other";
49757
+ }
49758
+ function stopWorkers(db, workers, why, now) {
49759
+ for (const w of workers) {
49760
+ for (const t of db.swarm.tasksHeldBy(w.agent_id)) {
49761
+ db.swarm.taskUpdate(t.id, { status: "pending", agent_id: null, auto_closed: false });
49762
+ db.swarm.eventAdd({
49763
+ run_id: t.run_id,
49764
+ kind: "auto_close",
49765
+ role: w.role,
49766
+ agent_id: w.agent_id,
49767
+ task_id: t.id,
49768
+ text: `released back to pending: ${why}`
49769
+ });
49770
+ }
49771
+ db.swarm.workerUpdate(w.run_id, w.agent_id, { status: "done", ended_at: now, task_id: null });
49772
+ db.swarm.eventAdd({
49773
+ run_id: w.run_id,
49774
+ kind: "stop",
49775
+ role: w.role,
49776
+ agent_id: w.agent_id,
49777
+ text: why
49778
+ });
49779
+ db.swarm.runTouch(w.run_id);
49780
+ }
49781
+ return workers;
49782
+ }
49783
+ function releaseWorkers(db, run3, caller, opts) {
49784
+ if (!isCoordinator(caller))
49785
+ throw new SwarmError("only the coordinator (the main session) can release workers");
49786
+ const now = Date.now();
49787
+ return db.swarm.immediate(() => {
49788
+ const running2 = db.swarm.workers(run3.id).filter((w) => w.status === "running");
49789
+ const agent = opts.agent?.trim().replace(/^agent-/, "");
49790
+ const pick2 = agent ? running2.filter((w) => w.agent_id.replace(/^agent-/, "").startsWith(agent)) : opts.role ? running2.filter((w) => w.role === opts.role) : running2.filter((w) => now - w.last_ts >= STALE_MS);
49791
+ if (agent && pick2.length > 1)
49792
+ throw new SwarmError(
49793
+ `"${opts.agent}" matches ${pick2.length} workers (${pick2.map((w) => w.agent_id).join(", ")}); give more of the id`
49794
+ );
49795
+ return stopWorkers(db, pick2, "released by the coordinator", now);
49796
+ });
49797
+ }
49798
+ function workerStatus(w, now = Date.now()) {
49799
+ if (w.status === "done") return "done";
49800
+ return now - w.last_ts >= STALE_MS ? "stale" : "running";
49801
+ }
49802
+ function runSnapshot(db, run3, events = 40) {
49803
+ const now = Date.now();
49804
+ const tasks = db.swarm.tasks(run3.id);
49805
+ const workers = db.swarm.workers(run3.id).map((w) => ({ ...w, status: workerStatus(w, now) }));
49806
+ const taskCounts = {
49807
+ pending: 0,
49808
+ active: 0,
49809
+ done: 0,
49810
+ failed: 0,
49811
+ blocked: 0
49812
+ };
49813
+ for (const t of tasks) taskCounts[t.status] = (taskCounts[t.status] ?? 0) + 1;
49814
+ const workerCounts = { running: 0, done: 0, stale: 0 };
49815
+ for (const w of workers) workerCounts[w.status]++;
49816
+ const mine = new Set(workers.map((w) => w.agent_id));
49817
+ const holders = [];
49818
+ for (const t of tasks) {
49819
+ if (t.status !== "active" || !t.agent_id || mine.has(t.agent_id) || t.agent_id === COORDINATOR)
49820
+ continue;
49821
+ const w = db.swarm.workerFind(t.agent_id);
49822
+ if (w && !holders.some((h) => h.agent_id === w.agent_id))
49823
+ holders.push({ ...w, status: workerStatus(w, now) });
49824
+ }
49825
+ return {
49826
+ run: run3,
49827
+ tasks,
49828
+ workers,
49829
+ holders,
49830
+ notes: db.swarm.notes(run3.id),
49831
+ events: events > 0 ? db.swarm.events(run3.id, { limit: events }) : [],
49832
+ counts: {
49833
+ tasks: taskCounts,
49834
+ workers: workerCounts,
49835
+ toolCalls: workers.reduce((n, w) => n + w.tool_calls, 0),
49836
+ errors: workers.reduce((n, w) => n + w.errors, 0),
49837
+ autoClosed: tasks.filter((t) => t.auto_closed).length,
49838
+ staleHeld: staleHeld(db, run3, tasks, now).length
49839
+ }
49840
+ };
49841
+ }
49842
+ var STALE_MS, SPAWN_TTL_MS, SPAWN_MATCH_MS, DENY_COLLAPSE_MS, RESULT_MAX, SwarmError, firstLine2, pad, COORDINATOR, isCoordinator, FINISH;
49843
+ var init_runtime = __esm({
49844
+ "src/swarm/runtime.ts"() {
49845
+ "use strict";
49846
+ init_esm_shims();
49847
+ init_store();
49848
+ init_config2();
49849
+ STALE_MS = 15 * 6e4;
49850
+ SPAWN_TTL_MS = 2 * 6e4;
49851
+ SPAWN_MATCH_MS = 10 * 6e4;
49852
+ DENY_COLLAPSE_MS = 5 * 6e4;
49853
+ RESULT_MAX = 4e3;
49854
+ SwarmError = class extends Error {
49855
+ };
49856
+ firstLine2 = (s) => s.trim().split("\n")[0] ?? "";
49857
+ pad = (n) => String(n).padStart(2, "0");
49858
+ COORDINATOR = "coordinator";
49859
+ isCoordinator = (c) => c.label === COORDINATOR && !c.agentId && !c.agentType;
49860
+ FINISH = ["done", "failed", "cancelled"];
49861
+ }
49862
+ });
49863
+
49864
+ // src/ui/swarm-api.ts
49865
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
49866
+ import { dirname as dirname12 } from "path";
49867
+ function runCounts(db, run3, now) {
49868
+ const tasks = {
49869
+ pending: 0,
49870
+ active: 0,
49871
+ done: 0,
49872
+ failed: 0,
49873
+ blocked: 0,
49874
+ total: 0
49875
+ };
49876
+ for (const t of db.swarm.tasks(run3.id)) {
49877
+ tasks[t.status]++;
49878
+ tasks.total++;
49879
+ }
49880
+ const workers = { running: 0, done: 0, stale: 0 };
49881
+ for (const w of db.swarm.workers(run3.id)) {
49882
+ const s = workerStatus(w, now);
49883
+ workers[s]++;
49884
+ }
49885
+ return { tasks, workers };
49886
+ }
49887
+ function swarmView(spec, files) {
49888
+ return {
49889
+ name: spec.name,
49890
+ description: spec.description,
49891
+ topology: spec.topology,
49892
+ maxAgents: spec.maxAgents,
49893
+ stages: spec.stages,
49894
+ instructions: spec.instructions,
49895
+ roles: spec.roles.map((r) => ({
49896
+ role: r.role,
49897
+ agentType: r.agentType,
49898
+ description: r.description,
49899
+ // null: the role inherits the session's model / keeps every tool.
49900
+ model: r.model ?? null,
49901
+ count: r.count,
49902
+ tools: r.tools ?? null,
49903
+ disallowedTools: r.disallowedTools ?? null,
49904
+ skills: r.skills,
49905
+ scope: r.scope,
49906
+ memoryTags: r.memoryTags,
49907
+ isolation: r.isolation ?? null,
49908
+ hasHooks: !!r.hooks && Object.values(r.hooks).some((v) => Array.isArray(v) && v.length > 0),
49909
+ file: files.get(r.agentType) ?? {
49910
+ path: `.claude/agents/${r.agentType}.md`,
49911
+ action: "disabled"
49912
+ }
49913
+ }))
49914
+ };
49915
+ }
49916
+ function listRuns(db, limit) {
49917
+ const active = db.swarm.runs({ status: "active", limit: RUNS_LIMIT_MAX });
49918
+ const room = Math.max(0, limit - active.length);
49919
+ const rest2 = db.swarm.runs({ limit: Math.min(RUNS_LIMIT_MAX, room + active.length + 1) }).filter((r) => r.status !== "active");
49920
+ return { runs: [...active, ...rest2.slice(0, room)], more: rest2.length > room };
49921
+ }
49922
+ function swarmOverview(ctx, limit = RUNS_LIMIT) {
49923
+ const loaded = loadSwarmConfig(ctx.root);
49924
+ const now = Date.now();
49925
+ const db = getStorage(ctx);
49926
+ const runsLimit = Math.max(1, Math.min(Math.floor(limit) || RUNS_LIMIT, RUNS_LIMIT_MAX));
49927
+ const listed = listRuns(db, runsLimit);
49928
+ const runs = listed.runs.map((r) => ({
49929
+ id: r.id,
49930
+ swarm: r.swarm,
49931
+ goal: r.goal,
49932
+ status: r.status,
49933
+ topology: r.topology,
49934
+ created_at: r.created_at,
49935
+ updated_at: r.updated_at,
49936
+ ended_at: r.ended_at,
49937
+ counts: runCounts(db, r, now)
49938
+ }));
49939
+ const runsMore = listed.more;
49940
+ if (loaded.state === "absent")
49941
+ return {
49942
+ state: "absent",
49943
+ enabled: false,
49944
+ file: loaded.rel,
49945
+ issues: [],
49946
+ swarms: [],
49947
+ orphans: [],
49948
+ syncNeeded: false,
49949
+ runs,
49950
+ runsMore,
49951
+ runsLimit,
49952
+ starter: STARTER_YAML,
49953
+ now
49954
+ };
49955
+ if (loaded.state === "invalid")
49956
+ return {
49957
+ state: "invalid",
49958
+ enabled: false,
49959
+ file: loaded.rel,
49960
+ issues: loaded.issues,
49961
+ swarms: [],
49962
+ orphans: [],
49963
+ syncNeeded: false,
49964
+ runs,
49965
+ runsMore,
49966
+ runsLimit,
49967
+ now
49968
+ };
49969
+ const config2 = loaded.config;
49970
+ const plan = planAgentSync(ctx.root, config2);
49971
+ const roleTypes = new Set(config2.swarms.flatMap((s) => s.roles.map((r) => r.agentType)));
49972
+ const files = new Map(
49973
+ plan.filter((a) => roleTypes.has(a.agentType)).map((a) => [
49974
+ a.agentType,
49975
+ { path: a.path, action: a.action, reason: a.reason }
49976
+ ])
49977
+ );
49978
+ return {
49979
+ state: "ok",
49980
+ enabled: config2.enabled,
49981
+ sync: config2.sync,
49982
+ file: config2.rel,
49983
+ issues: [],
49984
+ swarms: config2.swarms.map((s) => swarmView(s, files)),
49985
+ // Generated files the YAML no longer produces (or every one, when disabled).
49986
+ orphans: plan.filter((a) => !roleTypes.has(a.agentType)).map((a) => ({ path: a.path, agentType: a.agentType, reason: a.reason ?? null })),
49987
+ syncNeeded: plan.some(
49988
+ (a) => a.action === "create" || a.action === "update" || a.action === "delete"
49989
+ ),
49990
+ runs,
49991
+ runsMore,
49992
+ runsLimit,
49993
+ now
49994
+ };
49995
+ }
49996
+ function busySegments(events, opts = {}) {
49997
+ const gap = opts.gap ?? SEGMENT_GAP_MS;
49998
+ const out = [];
49999
+ let cur;
50000
+ for (const e of [...events].sort((a, b) => a.ts - b.ts || a.id - b.id)) {
50001
+ if (!cur || e.ts - cur.end > gap) {
50002
+ cur = {
50003
+ start: e.ts,
50004
+ end: e.ts,
50005
+ n: 0,
50006
+ errors: 0,
50007
+ io: { read: 0, write: 0, exec: 0, swarm: 0, other: 0 }
50008
+ };
50009
+ out.push(cur);
50010
+ }
50011
+ cur.end = Math.max(cur.end, e.ts);
50012
+ cur.n++;
50013
+ if (e.kind === "tool_error") cur.errors++;
50014
+ if (e.kind !== "start" && e.kind !== "stop") cur.io[ioOf(e)]++;
50015
+ }
50016
+ const last = out[out.length - 1];
50017
+ if (last && opts.running && opts.now !== void 0 && opts.now - last.end <= gap) {
50018
+ last.end = opts.now;
50019
+ last.open = true;
50020
+ }
50021
+ return out;
50022
+ }
50023
+ function newestEvents(db, runId, max = TIMELINE_EVENTS_MAX) {
50024
+ const latest = db.swarm.events(runId, { limit: READ_PAGE });
50025
+ if (latest.length < READ_PAGE)
50026
+ return latest.length > max ? { events: latest.slice(-max), cut: true } : { events: latest, cut: false };
50027
+ const first = db.swarm.events(runId, { after: 0, limit: 1 })[0];
50028
+ const chunks = [latest];
50029
+ let have = latest.length;
50030
+ let upper = latest[0].id;
50031
+ let idsPerEvent = Math.max(1, (latest[latest.length - 1].id - upper + 1) / latest.length);
50032
+ while (have < max && upper > first.id) {
50033
+ const want = Math.min(max - have, 5 * READ_PAGE);
50034
+ let lower = Math.max(first.id - 1, Math.floor(upper - want * idsPerEvent) - 1);
50035
+ let win = [];
50036
+ let after = lower;
50037
+ for (; ; ) {
50038
+ const page = db.swarm.events(runId, { after, limit: READ_PAGE });
50039
+ let end = page.length < READ_PAGE;
50040
+ for (const e of page) {
50041
+ if (e.id >= upper) {
50042
+ end = true;
50043
+ break;
50044
+ }
50045
+ win.push(e);
50046
+ }
50047
+ if (end) break;
50048
+ after = page[page.length - 1].id;
50049
+ if (win.length > 2 * want + READ_PAGE) {
50050
+ const dense = Math.max(1, (after - lower) / win.length);
50051
+ lower = Math.max(after, Math.floor(upper - want * dense) - 1);
50052
+ win = [];
50053
+ after = lower;
50054
+ }
50055
+ }
50056
+ chunks.unshift(win);
50057
+ have += win.length;
50058
+ idsPerEvent = win.length ? Math.max(1, (upper - lower) / win.length) : idsPerEvent * 2;
50059
+ upper = lower + 1;
50060
+ }
50061
+ const all = chunks.flat();
50062
+ return {
50063
+ events: all.length > max ? all.slice(-max) : all,
50064
+ cut: all.length > max || upper > first.id
50065
+ };
50066
+ }
50067
+ function clipTask(t) {
50068
+ if ((t.detail?.length ?? 0) <= TASK_TEXT_MAX && (t.result?.length ?? 0) <= TASK_TEXT_MAX)
50069
+ return t;
50070
+ return { ...t, detail: clipText(t.detail), result: clipText(t.result), clipped: true };
50071
+ }
50072
+ function runDetail(ctx, run3, opts = {}) {
50073
+ const db = getStorage(ctx);
50074
+ const snap = runSnapshot(db, run3, opts.events === false ? 0 : SNAPSHOT_EVENTS);
50075
+ const now = Date.now();
50076
+ const { events: all, cut } = newestEvents(db, run3.id);
50077
+ const byAgent2 = /* @__PURE__ */ new Map();
50078
+ for (const e of all) {
50079
+ if (!e.agent_id) continue;
50080
+ const list2 = byAgent2.get(e.agent_id) ?? [];
50081
+ list2.push(e);
50082
+ byAgent2.set(e.agent_id, list2);
50083
+ }
50084
+ const loaded = loadSwarmConfig(ctx.root);
50085
+ const spec = loaded.state === "ok" ? loaded.config.swarms.find((s) => s.name === run3.swarm) : void 0;
50086
+ const roles = [
50087
+ .../* @__PURE__ */ new Set([
50088
+ ...spec?.roles.map((r) => r.role) ?? [],
50089
+ ...snap.workers.map((w) => w.role),
50090
+ ...snap.tasks.map((t) => t.role).filter((r) => !!r)
50091
+ ])
50092
+ ];
50093
+ const since = opts.tasksSince;
50094
+ const tasks = (since === void 0 ? snap.tasks : snap.tasks.filter((t) => t.updated_at >= since - TASKS_SINCE_OVERLAP_MS)).map(clipTask);
50095
+ return {
50096
+ ...snap,
50097
+ tasks,
50098
+ /** Every task of the run, whether or not `tasks` holds them all. */
50099
+ taskCount: snap.tasks.length,
50100
+ /** `tasks` holds only the tasks updated since `tasksSince`. */
50101
+ tasksPartial: since !== void 0,
50102
+ roles,
50103
+ stages: spec?.stages ?? [],
50104
+ maxAgents: spec?.maxAgents ?? null,
50105
+ configured: !!spec,
50106
+ timeline: {
50107
+ start: run3.created_at,
50108
+ end: run3.ended_at ?? (run3.status === "active" ? now : run3.updated_at),
50109
+ cut,
50110
+ /** With `cut`: the time of the oldest event drawn; activity before it is not shown. */
50111
+ from: cut ? all[0]?.ts ?? null : null,
50112
+ workers: snap.workers.map((w) => ({
50113
+ agent_id: w.agent_id,
50114
+ role: w.role,
50115
+ status: w.status,
50116
+ spawned_at: w.spawned_at,
50117
+ ended_at: w.ended_at,
50118
+ segments: busySegments(byAgent2.get(w.agent_id) ?? [], {
50119
+ now,
50120
+ running: w.status === "running" && run3.status === "active"
50121
+ })
50122
+ }))
50123
+ },
50124
+ /** The run's newest event id (the snapshot's own events may stop short of it). */
50125
+ lastEventId: db.swarm.eventLast(run3.id)?.id ?? 0,
50126
+ staleMs: STALE_MS,
50127
+ now
50128
+ };
50129
+ }
50130
+ function runFromId(ctx, id) {
50131
+ if (typeof id !== "string" || !id) return { error: fail(400, "id required") };
50132
+ if (!RUN_ID_RE.test(id)) return { error: fail(400, `invalid run id "${id.slice(0, 40)}"`) };
50133
+ const run3 = getStorage(ctx).swarm.runGet(id);
50134
+ if (!run3) return { error: fail(404, `run ${id} not found`) };
50135
+ return { run: run3 };
50136
+ }
50137
+ function initSwarm(ctx) {
50138
+ const where = swarmFile(ctx.root);
50139
+ if (where.exists) return fail(409, `${where.rel} already exists; edit it instead`);
50140
+ mkdirSync13(dirname12(where.file), { recursive: true });
50141
+ try {
50142
+ writeFileSync11(where.file, STARTER_YAML, { flag: "wx" });
50143
+ } catch (err) {
50144
+ if (err.code === "EEXIST")
50145
+ return fail(409, `${where.rel} already exists; edit it instead`);
50146
+ throw err;
50147
+ }
50148
+ const config2 = activeSwarmConfig(ctx.root);
50149
+ return ok({ created: where.rel, sync: syncAgents(ctx.root, config2) });
50150
+ }
50151
+ function syncSwarm(ctx) {
50152
+ const loaded = loadSwarmConfig(ctx.root);
50153
+ if (loaded.state === "absent")
50154
+ return fail(409, `no ${loaded.rel}; create it first (eminiar swarm init)`);
50155
+ if (loaded.state === "invalid")
50156
+ return fail(400, `${loaded.rel} is invalid; fix it before syncing`, { issues: loaded.issues });
50157
+ return ok(syncAgents(ctx.root, loaded.config));
50158
+ }
50159
+ function finishSwarmRun(ctx, body) {
50160
+ const { run: run3, error: error62 } = runFromId(ctx, body.id);
50161
+ if (error62 || !run3) return error62 ?? fail(400, "id required");
50162
+ const status = body.status;
50163
+ if (typeof status !== "string" || !FINISH_STATUSES.includes(status))
50164
+ return fail(400, `status must be one of ${FINISH_STATUSES.join(", ")}`);
50165
+ if (body.summary !== void 0 && body.summary !== null && typeof body.summary !== "string")
50166
+ return fail(400, "summary must be a string");
50167
+ if (run3.status !== "active") return fail(409, `run ${run3.id} is already ${run3.status}`);
50168
+ const loaded = loadSwarmConfig(ctx.root);
50169
+ const caller = loaded.state === "ok" ? callerOf(loaded.config, {}) : { label: COORDINATOR };
50170
+ try {
50171
+ const r = finishRun(ctx, getStorage(ctx), run3, caller, {
50172
+ status,
50173
+ summary: typeof body.summary === "string" ? body.summary : void 0,
50174
+ remember: body.remember === true
50175
+ });
50176
+ return ok({ run: r.run, memory: r.memory ?? null, memoryError: r.memoryError ?? null });
50177
+ } catch (err) {
50178
+ if (err instanceof SwarmError) return fail(409, err.message);
50179
+ throw err;
50180
+ }
50181
+ }
50182
+ function releaseSwarmWorker(ctx, body) {
50183
+ const { run: run3, error: error62 } = runFromId(ctx, body.id);
50184
+ if (error62 || !run3) return error62 ?? fail(400, "id required");
50185
+ const agent = body.agent;
50186
+ if (typeof agent !== "string" || !agent.trim() || agent.length > 200)
50187
+ return fail(400, "agent must be a worker's agent id");
50188
+ if (run3.status !== "active") return fail(409, `run ${run3.id} is already ${run3.status}`);
50189
+ const loaded = loadSwarmConfig(ctx.root);
50190
+ const caller = loaded.state === "ok" ? callerOf(loaded.config, {}) : { label: COORDINATOR };
50191
+ try {
50192
+ const released = releaseWorkers(getStorage(ctx), run3, caller, { agent });
50193
+ if (!released.length) return fail(404, `no running worker ${agent.slice(0, 80)} in this run`);
50194
+ return ok({ released: released.map((w) => w.agent_id) });
50195
+ } catch (err) {
50196
+ if (err instanceof SwarmError) return fail(409, err.message);
50197
+ throw err;
50198
+ }
50199
+ }
50200
+ async function swarmRoute(ctx, req) {
50201
+ const { path: path3, method, query } = req;
50202
+ const isGet = method === "GET" || method === "HEAD";
50203
+ const readBody = async () => {
50204
+ const b = await req.body();
50205
+ return b && typeof b === "object" && !Array.isArray(b) ? b : {};
50206
+ };
50207
+ if (path3 === "/api/swarm") {
50208
+ if (!isGet) return fail(405, "GET only");
50209
+ return ok(swarmOverview(ctx, Number(query.get("limit")) || RUNS_LIMIT));
50210
+ }
50211
+ if (path3 === "/api/swarm/run") {
50212
+ if (!isGet) return fail(405, "GET only");
50213
+ const { run: run3, error: error62 } = runFromId(ctx, query.get("id"));
50214
+ if (error62 || !run3) return error62 ?? fail(400, "id required");
50215
+ const since = query.get("tasksSince");
50216
+ if (since !== null && !/^\d{1,15}$/.test(since))
50217
+ return fail(400, "tasksSince must be a non-negative integer (ms)");
50218
+ return ok(
50219
+ runDetail(ctx, run3, {
50220
+ tasksSince: since === null ? void 0 : Number(since),
50221
+ events: query.get("events") !== "0"
50222
+ })
50223
+ );
50224
+ }
50225
+ if (path3 === "/api/swarm/task") {
50226
+ if (!isGet) return fail(405, "GET only");
50227
+ const raw = query.get("id") ?? "";
50228
+ if (!/^\d{1,15}$/.test(raw)) return fail(400, "id must be a task id");
50229
+ const task = getStorage(ctx).swarm.taskGet(Number(raw));
50230
+ const runId = query.get("run");
50231
+ if (!task || runId && task.run_id !== runId) return fail(404, `task ${raw} not found`);
50232
+ return ok({ task });
50233
+ }
50234
+ if (path3 === "/api/swarm/events") {
50235
+ if (!isGet) return fail(405, "GET only");
50236
+ const { run: run3, error: error62 } = runFromId(ctx, query.get("id"));
50237
+ if (error62 || !run3) return error62 ?? fail(400, "id required");
50238
+ const raw = query.get("after") ?? "0";
50239
+ if (!/^\d{1,15}$/.test(raw)) return fail(400, "after must be a non-negative integer");
50240
+ const events = getStorage(ctx).swarm.events(run3.id, {
50241
+ after: Number(raw),
50242
+ limit: EVENTS_PAGE
50243
+ });
50244
+ return ok({
50245
+ run: { id: run3.id, status: run3.status, updated_at: run3.updated_at },
50246
+ events,
50247
+ last: events.length ? events[events.length - 1].id : Number(raw),
50248
+ more: events.length === EVENTS_PAGE,
50249
+ now: Date.now()
50250
+ });
50251
+ }
50252
+ if (path3 === "/api/swarm/init") {
50253
+ if (method !== "POST") return fail(405, "POST only");
50254
+ return initSwarm(ctx);
50255
+ }
50256
+ if (path3 === "/api/swarm/sync") {
50257
+ if (method !== "POST") return fail(405, "POST only");
50258
+ return syncSwarm(ctx);
50259
+ }
50260
+ if (path3 === "/api/swarm/finish") {
50261
+ if (method !== "POST") return fail(405, "POST only");
50262
+ let body;
50263
+ try {
50264
+ body = await readBody();
50265
+ } catch {
50266
+ return fail(400, "invalid JSON body");
50267
+ }
50268
+ return finishSwarmRun(ctx, body);
50269
+ }
50270
+ if (path3 === "/api/swarm/release") {
50271
+ if (method !== "POST") return fail(405, "POST only");
50272
+ let body;
50273
+ try {
50274
+ body = await readBody();
50275
+ } catch {
50276
+ return fail(400, "invalid JSON body");
50277
+ }
50278
+ return releaseSwarmWorker(ctx, body);
50279
+ }
50280
+ return fail(404, "not found");
50281
+ }
50282
+ var RUN_ID_RE, SEGMENT_GAP_MS, EVENTS_PAGE, READ_PAGE, TIMELINE_EVENTS_MAX, SNAPSHOT_EVENTS, TASK_TEXT_MAX, TASKS_SINCE_OVERLAP_MS, RUNS_LIMIT, RUNS_LIMIT_MAX, FINISH_STATUSES, ok, fail, ioOf, clipText;
50283
+ var init_swarm_api = __esm({
50284
+ "src/ui/swarm-api.ts"() {
50285
+ "use strict";
50286
+ init_esm_shims();
50287
+ init_storage();
50288
+ init_agents();
50289
+ init_config2();
50290
+ init_runtime();
50291
+ RUN_ID_RE = /^\d{8}-[0-9a-f]{6}$/;
50292
+ SEGMENT_GAP_MS = 6e4;
50293
+ EVENTS_PAGE = 200;
50294
+ READ_PAGE = 1e3;
50295
+ TIMELINE_EVENTS_MAX = 2e4;
50296
+ SNAPSHOT_EVENTS = 200;
50297
+ TASK_TEXT_MAX = 300;
50298
+ TASKS_SINCE_OVERLAP_MS = 5e3;
50299
+ RUNS_LIMIT = 30;
50300
+ RUNS_LIMIT_MAX = 500;
50301
+ FINISH_STATUSES = ["done", "failed", "cancelled"];
50302
+ ok = (body) => ({ status: 200, body });
50303
+ fail = (status, error62, extra = {}) => ({
50304
+ status,
50305
+ body: { error: error62, ...extra }
50306
+ });
50307
+ ioOf = (e) => {
50308
+ const p = e.payload?.io;
50309
+ if (p === "read" || p === "write" || p === "exec" || p === "swarm" || p === "other") return p;
50310
+ const tool = e.payload?.tool;
50311
+ if (typeof tool === "string") return toolIo(tool);
50312
+ return e.kind === "tool" || e.kind === "tool_error" ? "other" : "swarm";
50313
+ };
50314
+ clipText = (s) => s !== null && s.length > TASK_TEXT_MAX ? s.slice(0, TASK_TEXT_MAX) : s;
50315
+ }
50316
+ });
50317
+
48234
50318
  // src/ui/server.ts
48235
50319
  var server_exports = {};
48236
50320
  __export(server_exports, {
@@ -48238,9 +50322,9 @@ __export(server_exports, {
48238
50322
  uiDir: () => uiDir
48239
50323
  });
48240
50324
  import { spawn as spawn2 } from "child_process";
48241
- import { existsSync as existsSync28, readFileSync as readFileSync24, statSync as statSync12 } from "fs";
50325
+ import { existsSync as existsSync30, readFileSync as readFileSync26, statSync as statSync13 } from "fs";
48242
50326
  import { createServer } from "http";
48243
- import { extname as extname3, join as join32, normalize as normalize6 } from "path";
50327
+ import { extname as extname3, join as join35, normalize as normalize7 } from "path";
48244
50328
  import { URL as URL2 } from "url";
48245
50329
  function json2(res, status, body) {
48246
50330
  res.writeHead(status, {
@@ -48262,18 +50346,18 @@ function strList(v) {
48262
50346
  return void 0;
48263
50347
  }
48264
50348
  function uiDir() {
48265
- return process.env.EMINIAR_UI_DIR || join32(pluginRoot(), "dist", "ui");
50349
+ return process.env.EMINIAR_UI_DIR || join35(pluginRoot(), "dist", "ui");
48266
50350
  }
48267
50351
  function originAllowed(req, port) {
48268
50352
  const host = req.headers.host ?? "";
48269
- const ok = (h) => h === `127.0.0.1:${port}` || h === `localhost:${port}` || h === `[::1]:${port}`;
48270
- if (!ok(host)) return false;
50353
+ const ok2 = (h) => h === `127.0.0.1:${port}` || h === `localhost:${port}` || h === `[::1]:${port}`;
50354
+ if (!ok2(host)) return false;
48271
50355
  const method = req.method ?? "GET";
48272
50356
  if (method === "GET" || method === "HEAD") return true;
48273
50357
  const origin = req.headers.origin;
48274
50358
  if (origin) {
48275
50359
  try {
48276
- if (!ok(new URL2(origin).host)) return false;
50360
+ if (!ok2(new URL2(origin).host)) return false;
48277
50361
  } catch {
48278
50362
  return false;
48279
50363
  }
@@ -48286,25 +50370,25 @@ function originAllowed(req, port) {
48286
50370
  function serveStatic(res, p) {
48287
50371
  const dir = uiDir();
48288
50372
  const rel = p === "/" ? "index.html" : p.replace(/^\/+/, "");
48289
- const file3 = normalize6(join32(dir, rel));
48290
- if (!file3.startsWith(normalize6(dir))) return false;
48291
- if (!existsSync28(file3) || !statSync12(file3).isFile()) return false;
50373
+ const file3 = normalize7(join35(dir, rel));
50374
+ if (!file3.startsWith(normalize7(dir))) return false;
50375
+ if (!existsSync30(file3) || !statSync13(file3).isFile()) return false;
48292
50376
  const type = MIME[extname3(file3).toLowerCase()] ?? "application/octet-stream";
48293
50377
  if (rel === "index.html") {
48294
50378
  const stamp = (name) => {
48295
50379
  try {
48296
- return `/${name}?v=${Math.floor(statSync12(join32(dir, name)).mtimeMs)}`;
50380
+ return `/${name}?v=${Math.floor(statSync13(join35(dir, name)).mtimeMs)}`;
48297
50381
  } catch {
48298
50382
  return `/${name}`;
48299
50383
  }
48300
50384
  };
48301
- const html = readFileSync24(file3, "utf8").replace('"/app.global.js"', `"${stamp("app.global.js")}"`).replace('"/styles.css"', `"${stamp("styles.css")}"`);
50385
+ const html = readFileSync26(file3, "utf8").replace('"/app.global.js"', `"${stamp("app.global.js")}"`).replace('"/styles.css"', `"${stamp("styles.css")}"`);
48302
50386
  res.writeHead(200, { "content-type": type, "cache-control": "no-store" });
48303
50387
  res.end(html);
48304
50388
  return true;
48305
50389
  }
48306
50390
  res.writeHead(200, { "content-type": type, "cache-control": "no-cache" });
48307
- res.end(readFileSync24(file3));
50391
+ res.end(readFileSync26(file3));
48308
50392
  return true;
48309
50393
  }
48310
50394
  async function serve(opts) {
@@ -48671,6 +50755,10 @@ async function serve(opts) {
48671
50755
  if (!isUsageSessionId(id)) return json2(res, 400, { error: "invalid session id" });
48672
50756
  return json2(res, 200, usageSessionPayload(ctx(), id, { calls: q.get("calls") === "1" }));
48673
50757
  }
50758
+ if (p === "/api/swarm" || p.startsWith("/api/swarm/")) {
50759
+ const r = await swarmRoute(ctx(), { path: p, method, query: q, body: () => readJson4(req) });
50760
+ return json2(res, r.status, r.body);
50761
+ }
48674
50762
  if (p === "/api/events")
48675
50763
  return json2(
48676
50764
  res,
@@ -48697,9 +50785,9 @@ async function serve(opts) {
48697
50785
  json2(res, 500, { error: err.message });
48698
50786
  }
48699
50787
  });
48700
- await new Promise((resolve9, reject) => {
50788
+ await new Promise((resolve11, reject) => {
48701
50789
  server.once("error", reject);
48702
- server.listen(opts.port, "127.0.0.1", resolve9);
50790
+ server.listen(opts.port, "127.0.0.1", resolve11);
48703
50791
  });
48704
50792
  const url2 = `http://127.0.0.1:${opts.port}/`;
48705
50793
  console.log(`eminiar dashboard at ${url2} (Ctrl-C to stop)`);
@@ -48720,6 +50808,7 @@ var init_server = __esm({
48720
50808
  init_history_api();
48721
50809
  init_history_graph_api();
48722
50810
  init_knowledge_api();
50811
+ init_swarm_api();
48723
50812
  MIME = {
48724
50813
  ".html": "text/html; charset=utf-8",
48725
50814
  ".js": "text/javascript; charset=utf-8",
@@ -48737,13 +50826,832 @@ var init_server = __esm({
48737
50826
  }
48738
50827
  });
48739
50828
 
50829
+ // src/swarm/format.ts
50830
+ function ago2(ts, now = Date.now()) {
50831
+ if (!ts) return "n/a";
50832
+ const s = Math.max(0, Math.round((now - ts) / 1e3));
50833
+ if (s < 90) return `${s}s ago`;
50834
+ if (s < 5400) return `${Math.round(s / 60)}m ago`;
50835
+ if (s < 48 * 3600) return `${Math.round(s / 3600)}h ago`;
50836
+ return `${Math.round(s / 86400)}d ago`;
50837
+ }
50838
+ function capOutput(s, max = OUTPUT_MAX, hint) {
50839
+ if (s.length <= max) return s;
50840
+ const room = max - 120;
50841
+ const cut = s.lastIndexOf("\n", room);
50842
+ const head = s.slice(0, cut > room / 2 ? cut : room);
50843
+ return `${head}
50844
+ \u2026 ${s.length - head.length} more characters left out${hint ? `; ${hint}` : ""}`;
50845
+ }
50846
+ function shortAgent(agentId) {
50847
+ return agentId.replace(/^agent-/, "").slice(0, 8);
50848
+ }
50849
+ function holderName(agentId, workers) {
50850
+ if (!agentId) return "n/a";
50851
+ const w = workers.get(agentId);
50852
+ if (w) return `${w.role}@${shortAgent(agentId)}`;
50853
+ return agentId;
50854
+ }
50855
+ function eventWho(e) {
50856
+ if (e.agent_id) return `${e.role ?? "worker"}@${shortAgent(e.agent_id)}`;
50857
+ return e.role ?? "n/a";
50858
+ }
50859
+ function formatIssues(rel, issues) {
50860
+ return issues.map((i) => `${rel}${i.line ? `:${i.line}` : ""}${i.path ? ` ${i.path}` : ""}: ${i.message}`).join("\n");
50861
+ }
50862
+ function formatInvalid(rel, issues) {
50863
+ return capOutput(
50864
+ `${rel} has ${issues.length} problem(s); swarms stay off until it validates (\`eminiar swarm validate\`):
50865
+ ${formatIssues(rel, issues)}`
50866
+ );
50867
+ }
50868
+ function formatNotOptedIn(rel) {
50869
+ return [
50870
+ `Agent swarms are opt-in and this project has not opted in (no ${rel}).`,
50871
+ "To enable them, ask the user to run `eminiar swarm init` (writes a starter file and generates the worker agents), use \u201CCreate starter swarm.yaml\u201D on the Swarms tab of the dashboard (`eminiar ui`), or write the file by hand (https://github.com/rydn/eminiar/blob/main/docs/swarms.md).",
50872
+ "Until then, do not improvise a swarm: use ordinary subagents or do the work directly."
50873
+ ].join("\n");
50874
+ }
50875
+ function runListItem(run3, tasks, workers) {
50876
+ return {
50877
+ run: run3,
50878
+ done: tasks.filter((t) => t.status === "done").length,
50879
+ total: tasks.length,
50880
+ running: workers.filter((w) => w.status === "running").length
50881
+ };
50882
+ }
50883
+ function runLine(r, now, sessionId) {
50884
+ const { run: run3 } = r;
50885
+ return `${RUN_GLYPH[run3.status]} ${run3.id} ${run3.swarm} ${run3.status}${sessionId && run3.session_id === sessionId ? " (this session)" : ""} \xB7 tasks ${r.done}/${r.total} done \xB7 ${r.running} running \xB7 started ${ago2(run3.created_at, now)}${run3.status === "active" ? ` \xB7 updated ${ago2(run3.updated_at, now)}` : ` \xB7 ended ${ago2(run3.ended_at, now)}`} \u2014 ${clip3(firstLine3(run3.goal), 100)}`;
50886
+ }
50887
+ function runRef(run3) {
50888
+ return `${run3.id} (${run3.swarm}) "${clip3(firstLine3(run3.goal), 80)}"`;
50889
+ }
50890
+ function enforceNote(config2) {
50891
+ if (!config2.enabled || !config2.enforce) return "";
50892
+ return ` \xB7 enforce on${config2.allowedAgents.length ? ` (also allowed: ${config2.allowedAgents.join(", ")})` : ""}`;
50893
+ }
50894
+ function formatSwarmList(config2, plan, active, opts = {}) {
50895
+ const now = opts.now ?? Date.now();
50896
+ const byAgent2 = new Map(plan.map((a) => [a.agentType, a]));
50897
+ const counts = /* @__PURE__ */ new Map();
50898
+ for (const a of plan) counts.set(a.action, (counts.get(a.action) ?? 0) + 1);
50899
+ const fileSummary = [...counts].map(([action, n]) => `${n} ${SYNC_STATE[action]}`).join(", ");
50900
+ const swarms = opts.swarm ? config2.swarms.filter((s) => s.name === opts.swarm) : config2.swarms;
50901
+ const lines = [
50902
+ `eminiar swarms \xB7 ${config2.rel} \xB7 sync ${config2.sync}${enforceNote(config2)} \xB7 agent files: ${fileSummary || "none"}`
50903
+ ];
50904
+ if (opts.swarm && !swarms.length)
50905
+ lines.push(
50906
+ `No swarm "${opts.swarm}"; ${config2.rel} defines: ${config2.swarms.map((s) => s.name).join(", ")}`
50907
+ );
50908
+ for (const s of swarms) {
50909
+ lines.push(
50910
+ "",
50911
+ `${s.name} \xB7 ${s.topology} \xB7 up to ${s.maxAgents} worker(s) at once \u2014 ${clip3(s.description, 160)}`
50912
+ );
50913
+ if (s.topology === "pipeline" && s.stages.length)
50914
+ lines.push(` stages: ${s.stages.join(" \u2192 ")}`);
50915
+ for (const r of s.roles) {
50916
+ const state = byAgent2.get(r.agentType);
50917
+ lines.push(
50918
+ ` ${r.role} \u2192 ${r.agentType} \xB7 ${r.model ?? "inherit"} \xB7 \xD7${r.count}${r.isolation ? " \xB7 worktree" : ""} \xB7 ${state ? SYNC_STATE[state.action] : "n/a"}${state?.action === "conflict" ? ` (${state.reason ?? state.path})` : ""}`
50919
+ );
50920
+ }
50921
+ }
50922
+ const leftover = plan.filter((a) => a.action === "delete");
50923
+ if (leftover.length)
50924
+ lines.push(
50925
+ "",
50926
+ `Left-over agent files: ${leftover.map((a) => a.path).join(", ")} (${leftover[0]?.reason ?? "not in the swarm file"})`
50927
+ );
50928
+ if (plan.some((a) => a.action === "create" || a.action === "update" || a.action === "delete"))
50929
+ lines.push(
50930
+ 'Agent files differ from the YAML: em_swarm {action:"sync"} (or `eminiar swarm sync`).'
50931
+ );
50932
+ if (plan.some((a) => a.action === "conflict"))
50933
+ lines.push(
50934
+ "Sync leaves conflicts alone: rename the role or the hand-written file; for a generated file edited by hand, move the edit into the YAML or overwrite it with sync --force."
50935
+ );
50936
+ lines.push("");
50937
+ if (active.length) {
50938
+ lines.push("Active runs:");
50939
+ for (const r of active.slice(0, 10)) lines.push(` ${runLine(r, now, opts.sessionId)}`);
50940
+ } else lines.push("No active run.");
50941
+ lines.push(
50942
+ 'Start one: em_swarm {action:"start", swarm, goal}; the em-swarm skill has the coordinator playbook.'
50943
+ );
50944
+ return capOutput(lines.join("\n"));
50945
+ }
50946
+ function formatRunList(items2, now = Date.now()) {
50947
+ if (!items2.length) return "No swarm runs yet.";
50948
+ return capOutput(items2.map((r) => runLine(r, now)).join("\n"));
50949
+ }
50950
+ function formatSync2(result, opts = {}) {
50951
+ const word = (a) => SYNC_VERB[a][opts.dryRun ? "dry" : "done"];
50952
+ const tally = /* @__PURE__ */ new Map();
50953
+ for (const a of result.actions) tally.set(a.action, (tally.get(a.action) ?? 0) + 1);
50954
+ const head = `${opts.dryRun ? "dry run \xB7 " : ""}agent files (.claude/agents): ${result.actions.length ? [...tally].map(([a, n]) => `${n} ${word(a)}`).join(", ") : "nothing to do"}${opts.reason ? ` (${opts.reason})` : ""}`;
50955
+ const lines = [head];
50956
+ for (const a of result.actions) {
50957
+ if (a.action === "unchanged") continue;
50958
+ lines.push(
50959
+ ` ${SYNC_VERB[a.action].mark} ${word(a.action)} ${a.path}${a.reason ? `: ${a.reason}` : ""}`
50960
+ );
50961
+ }
50962
+ if (!opts.dryRun && result.actions.some((a) => a.action === "create"))
50963
+ lines.push(
50964
+ "Claude Code reads agent files when a session starts: if a new worker type is not available yet, open /agents or restart the session."
50965
+ );
50966
+ return capOutput(lines.join("\n"));
50967
+ }
50968
+ function quietHolder(t, workers) {
50969
+ if (t.status !== "active" || !t.agent_id) return void 0;
50970
+ const w = workers.get(t.agent_id);
50971
+ return w && (w.status === "stale" || w.status === "done") ? w : void 0;
50972
+ }
50973
+ function taskLine(t, byId, workers, now) {
50974
+ const parts = [`#${t.id} ${clip3(t.title, 120)}${t.role ? ` (${t.role})` : ""}`];
50975
+ if (t.agent_id && t.status !== "pending") parts.push(holderName(t.agent_id, workers));
50976
+ if (t.status === "pending") {
50977
+ const waiting = t.deps.filter((d) => byId.get(d)?.status !== "done");
50978
+ if (waiting.length) parts.push(`waits on ${waiting.map((d) => `#${d}`).join(", ")}`);
50979
+ }
50980
+ if (t.files.length && (t.status === "pending" || t.status === "active"))
50981
+ parts.push(
50982
+ `files ${t.files.slice(0, 3).join(", ")}${t.files.length > 3 ? ` +${t.files.length - 3}` : ""}`
50983
+ );
50984
+ let line = parts.join(" \xB7 ");
50985
+ const quiet = quietHolder(t, workers);
50986
+ if (quiet?.status === "stale")
50987
+ line += ` \xB7 \u26A0 holder quiet ${Math.max(1, Math.round((now - quiet.last_ts) / 6e4))}m \u2014 release with em_swarm {action:"release", agent:"${shortAgent(quiet.agent_id)}"} or update status:pending`;
50988
+ else if (quiet)
50989
+ line += ` \xB7 \u26A0 holder stopped \u2014 reopen with em_swarm {action:"update", task:${t.id}, status:"pending"}`;
50990
+ if (t.auto_closed) line += " \xB7 \u26A0 auto-closed: the worker never reported; check the result";
50991
+ if (t.result && t.status !== "active" && t.status !== "pending")
50992
+ line += ` \u2014 ${clip3(firstLine3(t.result), 140)}`;
50993
+ return line;
50994
+ }
50995
+ function workerLine(w, now) {
50996
+ const when = w.status === "done" ? `ended ${ago2(w.ended_at, now)}` : `last seen ${ago2(w.last_ts, now)}`;
50997
+ return [
50998
+ `${WORKER_GLYPH[w.status]} ${w.status} ${w.role}@${shortAgent(w.agent_id)}`,
50999
+ w.task_id !== null ? `task #${w.task_id}` : void 0,
51000
+ `${w.tool_calls} tool call(s)${w.errors ? `, ${w.errors} failed` : ""}`,
51001
+ `last ${w.last_tool ?? "n/a"}`,
51002
+ when
51003
+ ].filter(Boolean).join(" \xB7 ").concat(w.status === "done" && w.summary ? ` \u2014 ${clip3(firstLine3(w.summary), 120)}` : "");
51004
+ }
51005
+ function eventLine(e, now) {
51006
+ return ` ${ago2(e.ts, now)} ${eventWho(e)} ${e.kind}${e.task_id !== null ? ` #${e.task_id}` : ""}${e.text ? ` ${clip3(firstLine3(e.text), 140)}` : ""}`;
51007
+ }
51008
+ function nextStep(snap) {
51009
+ if (snap.run.status !== "active") return void 0;
51010
+ const t = snap.counts.tasks;
51011
+ const open3 = t.pending + t.active + t.blocked;
51012
+ const running2 = snap.counts.workers.running;
51013
+ const stale = snap.counts.staleHeld;
51014
+ const hints = [];
51015
+ if (stale)
51016
+ hints.push(
51017
+ `${stale} active task(s) are held by a worker that went quiet or stopped: em_swarm {action:"release"} frees every quiet worker (agent or role picks one), or reopen a task with em_swarm {action:"update", task, status:"pending"}.`
51018
+ );
51019
+ if (snap.counts.autoClosed)
51020
+ hints.push(
51021
+ `${snap.counts.autoClosed} task(s) were auto-closed; check them and reopen with em_swarm {action:"update", task, status:"pending"} if the work is not done.`
51022
+ );
51023
+ if (!snap.tasks.length)
51024
+ hints.push('No tasks yet: em_swarm {action:"plan", tasks:[{title, role, files, deps}]}.');
51025
+ else if (!open3)
51026
+ hints.push(
51027
+ 'Every task is closed: review the results, then em_swarm {action:"finish", status:"done", summary, remember:true}.'
51028
+ );
51029
+ else if (!running2 && t.active <= stale && t.pending)
51030
+ hints.push(
51031
+ 'No worker is running: spawn workers for the claimable tasks (Agent tool, a prompt naming "task #N"; the worker claims it).'
51032
+ );
51033
+ if (t.blocked)
51034
+ hints.push(`${t.blocked} task(s) blocked: read their results and unblock or re-plan.`);
51035
+ return hints.length ? `Next: ${hints.join(" ")}` : void 0;
51036
+ }
51037
+ function formatRunStatus(snap, opts = {}) {
51038
+ const now = opts.now ?? Date.now();
51039
+ const perGroup = opts.perGroup ?? PER_GROUP;
51040
+ const maxWorkers = opts.workers ?? 12;
51041
+ const { run: run3 } = snap;
51042
+ const workers = new Map(
51043
+ [...snap.holders ?? [], ...snap.workers].map((w) => [w.agent_id, w])
51044
+ );
51045
+ const byId = new Map(snap.tasks.map((t) => [t.id, t]));
51046
+ const c = snap.counts;
51047
+ const lines = [
51048
+ `Run ${run3.id} \xB7 swarm "${run3.swarm}" (${run3.topology}) \xB7 ${RUN_GLYPH[run3.status]} ${run3.status} \xB7 started ${ago2(run3.created_at, now)} \xB7 ${run3.status === "active" ? `updated ${ago2(run3.updated_at, now)}` : `ended ${ago2(run3.ended_at, now)}`}`,
51049
+ ...opts.lead ?? [],
51050
+ `Goal: ${clip3(run3.goal, 400)}`
51051
+ ];
51052
+ if (run3.summary) lines.push(`Summary: ${clip3(run3.summary, 600)}`);
51053
+ const next = nextStep(snap);
51054
+ if (next) lines.push(next);
51055
+ const taskTally = GROUP_ORDER.filter((s) => c.tasks[s]).map((s) => `${c.tasks[s]} ${s}`);
51056
+ if (c.staleHeld) taskTally.push(`${c.staleHeld} held by a quiet worker`);
51057
+ lines.push("", `Tasks (${snap.tasks.length}): ${taskTally.join(" \xB7 ") || "none planned"}`);
51058
+ for (const status of GROUP_ORDER) {
51059
+ const group = snap.tasks.filter((t) => t.status === status).sort(
51060
+ (a, b) => Number(!!quietHolder(b, workers)) - Number(!!quietHolder(a, workers)) || Number(b.auto_closed) - Number(a.auto_closed) || a.id - b.id
51061
+ );
51062
+ if (!group.length) continue;
51063
+ lines.push(`${TASK_GLYPH[status]} ${status}`);
51064
+ for (const t of group.slice(0, perGroup)) lines.push(` ${taskLine(t, byId, workers, now)}`);
51065
+ if (group.length > perGroup) lines.push(` \u2026 ${group.length - perGroup} more ${status}`);
51066
+ }
51067
+ const wt = ["running", "stale", "done"].filter((s) => c.workers[s]).map((s) => `${c.workers[s]} ${s}`);
51068
+ lines.push(
51069
+ "",
51070
+ `Workers (${snap.workers.length}): ${wt.join(" \xB7 ") || "none spawned"}${snap.workers.length ? ` \xB7 ${c.toolCalls} tool call(s), ${c.errors} failed` : ""}`
51071
+ );
51072
+ const rank = { running: 0, stale: 1, done: 2 };
51073
+ const shown = [...snap.workers].sort(
51074
+ (a, b) => rank[a.status] - rank[b.status] || b.last_ts - a.last_ts
51075
+ );
51076
+ for (const w of shown.slice(0, maxWorkers)) lines.push(` ${workerLine(w, now)}`);
51077
+ if (shown.length > maxWorkers) lines.push(` \u2026 ${shown.length - maxWorkers} more worker(s)`);
51078
+ if (snap.notes.length)
51079
+ lines.push(
51080
+ "",
51081
+ `Blackboard (em_swarm {action:"read", key}): ${snap.notes.slice(0, 16).map((n) => `${n.key} (${n.author ?? "n/a"}, v${n.version}, ${ago2(n.ts, now)})`).join(" \xB7 ")}${snap.notes.length > 16 ? ` \xB7 \u2026 ${snap.notes.length - 16} more` : ""}`
51082
+ );
51083
+ const events = snap.events.slice(-(opts.events ?? 12));
51084
+ if (events.length) {
51085
+ lines.push("", "Recent events (oldest first):");
51086
+ for (const e of events) lines.push(eventLine(e, now));
51087
+ }
51088
+ return capOutput(
51089
+ lines.join("\n"),
51090
+ opts.max ?? OUTPUT_MAX,
51091
+ `\`eminiar swarm status ${run3.id}\` prints all of it`
51092
+ );
51093
+ }
51094
+ function formatReleased(run3, released, running2, opts, now = Date.now()) {
51095
+ if (released.length)
51096
+ return `Released ${released.length} worker(s) of run ${run3.id}: ${released.map(
51097
+ (w) => `${w.role}@${shortAgent(w.agent_id)}${w.task_id !== null ? ` (task #${w.task_id} back to pending)` : ""}`
51098
+ ).join(
51099
+ ", "
51100
+ )}. They no longer count against capacity; spawn a worker for each reopened task (a prompt naming "task #N").`;
51101
+ const why = opts.agent ? `no running worker's id starts with "${opts.agent}"` : opts.role ? `no ${opts.role} worker is running` : `no running worker has been quiet for ${opts.staleMinutes} minutes`;
51102
+ const live2 = running2.length ? ` Running: ${running2.slice(0, 8).map((w) => `${w.role}@${shortAgent(w.agent_id)} (last seen ${ago2(w.last_ts, now)})`).join(
51103
+ ", "
51104
+ )}${running2.length > 8 ? ", \u2026" : ""}; name one with agent:"<id>" or role:"<role>".` : " No worker of the run is running.";
51105
+ return `Nothing released in run ${run3.id}: ${why}.${live2}`;
51106
+ }
51107
+ function watchStep(prevKey, snap, lastEvent) {
51108
+ const last = lastEvent !== void 0 ? lastEvent : [...snap.events].reverse().find((e) => isWatchEvent(e)) ?? null;
51109
+ const body = {
51110
+ run: snap.run.id,
51111
+ swarm: snap.run.swarm,
51112
+ status: snap.run.status,
51113
+ tasks: { ...snap.counts.tasks },
51114
+ workers: { ...snap.counts.workers },
51115
+ auto_closed: snap.counts.autoClosed,
51116
+ stale_held: snap.counts.staleHeld,
51117
+ last_event: last ? {
51118
+ id: last.id,
51119
+ kind: last.kind,
51120
+ who: eventWho(last),
51121
+ task: last.task_id,
51122
+ text: last.text ? clip3(firstLine3(last.text), 160) : null
51123
+ } : null
51124
+ };
51125
+ const key = JSON.stringify({
51126
+ status: body.status,
51127
+ tasks: body.tasks,
51128
+ workers: body.workers,
51129
+ auto_closed: body.auto_closed,
51130
+ stale_held: body.stale_held,
51131
+ event: last?.id ?? null
51132
+ });
51133
+ if (snap.run.status !== "active") return { key, line: { type: "swarm.finished", ...body } };
51134
+ if (prevKey === void 0) return { key, line: { type: "swarm.snapshot", ...body } };
51135
+ if (key === prevKey) return { key };
51136
+ const idle = body.workers.running === 0 && body.tasks.active <= body.stale_held;
51137
+ return { key, line: { type: idle ? "swarm.idle" : "swarm.changed", ...body } };
51138
+ }
51139
+ var OUTPUT_MAX, TASK_GLYPH, WORKER_GLYPH, RUN_GLYPH, firstLine3, SYNC_STATE, SYNC_VERB, GROUP_ORDER, PER_GROUP, WATCH_EVENT_KINDS, isWatchEvent;
51140
+ var init_format3 = __esm({
51141
+ "src/swarm/format.ts"() {
51142
+ "use strict";
51143
+ init_esm_shims();
51144
+ init_runtime();
51145
+ OUTPUT_MAX = 6e3;
51146
+ TASK_GLYPH = {
51147
+ pending: "\u25CB",
51148
+ active: "\u25CF",
51149
+ blocked: "\u25C6",
51150
+ done: "\u2713",
51151
+ failed: "\u2717"
51152
+ };
51153
+ WORKER_GLYPH = {
51154
+ running: "\u25CF",
51155
+ done: "\u2713",
51156
+ stale: "\u25CC"
51157
+ };
51158
+ RUN_GLYPH = {
51159
+ active: "\u25CF",
51160
+ done: "\u2713",
51161
+ failed: "\u2717",
51162
+ cancelled: "\u25CC"
51163
+ };
51164
+ firstLine3 = (s) => s.trim().split("\n")[0] ?? "";
51165
+ SYNC_STATE = {
51166
+ unchanged: "synced",
51167
+ create: "not generated",
51168
+ update: "out of date",
51169
+ delete: "left over",
51170
+ conflict: "conflict"
51171
+ };
51172
+ SYNC_VERB = {
51173
+ create: { done: "created", dry: "to create", mark: "+" },
51174
+ update: { done: "updated", dry: "to update", mark: "~" },
51175
+ delete: { done: "deleted", dry: "to delete", mark: "-" },
51176
+ unchanged: { done: "unchanged", dry: "unchanged", mark: "=" },
51177
+ conflict: { done: "conflict", dry: "conflict", mark: "!" }
51178
+ };
51179
+ GROUP_ORDER = ["active", "blocked", "failed", "pending", "done"];
51180
+ PER_GROUP = 12;
51181
+ WATCH_EVENT_KINDS = /* @__PURE__ */ new Set([
51182
+ "run_start",
51183
+ "plan",
51184
+ "claim",
51185
+ "update",
51186
+ "post",
51187
+ "start",
51188
+ "stop",
51189
+ "auto_close",
51190
+ "deny",
51191
+ "run_finish"
51192
+ ]);
51193
+ isWatchEvent = (e) => WATCH_EVENT_KINDS.has(e.kind);
51194
+ }
51195
+ });
51196
+
51197
+ // src/hooks/log.ts
51198
+ import { appendFileSync } from "fs";
51199
+ var init_log2 = __esm({
51200
+ "src/hooks/log.ts"() {
51201
+ "use strict";
51202
+ init_esm_shims();
51203
+ init_paths();
51204
+ }
51205
+ });
51206
+
51207
+ // src/session/brief.ts
51208
+ import { existsSync as existsSync31, readFileSync as readFileSync27 } from "fs";
51209
+ import { join as join36 } from "path";
51210
+ var init_brief2 = __esm({
51211
+ "src/session/brief.ts"() {
51212
+ "use strict";
51213
+ init_esm_shims();
51214
+ init_manifest();
51215
+ init_model();
51216
+ init_frontmatter();
51217
+ init_store();
51218
+ }
51219
+ });
51220
+
51221
+ // src/swarm/brief.ts
51222
+ var init_brief3 = __esm({
51223
+ "src/swarm/brief.ts"() {
51224
+ "use strict";
51225
+ init_esm_shims();
51226
+ init_model();
51227
+ init_brief2();
51228
+ init_recall();
51229
+ init_glob();
51230
+ init_runtime();
51231
+ }
51232
+ });
51233
+
51234
+ // src/swarm/identity.ts
51235
+ import { createHmac, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
51236
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync28, writeFileSync as writeFileSync12 } from "fs";
51237
+ import { join as join37 } from "path";
51238
+ var init_identity = __esm({
51239
+ "src/swarm/identity.ts"() {
51240
+ "use strict";
51241
+ init_esm_shims();
51242
+ init_paths();
51243
+ }
51244
+ });
51245
+
51246
+ // src/swarm/hooks.ts
51247
+ import { execFileSync } from "child_process";
51248
+ import { readFileSync as readFileSync29, realpathSync as realpathSync4, statSync as statSync14 } from "fs";
51249
+ import { basename as basename3, dirname as dirname13, join as join38, resolve as resolve10 } from "path";
51250
+ function coordinationRoot(root) {
51251
+ try {
51252
+ const dotGit = join38(root, ".git");
51253
+ if (!statSync14(dotGit).isFile()) return root;
51254
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(readFileSync29(dotGit, "utf8"));
51255
+ if (!m) return root;
51256
+ const gitdir = resolve10(root, m[1]);
51257
+ let common;
51258
+ try {
51259
+ common = resolve10(gitdir, readFileSync29(join38(gitdir, "commondir"), "utf8").trim());
51260
+ } catch {
51261
+ return root;
51262
+ }
51263
+ if (basename3(common) === ".git") return realpathSync4(dirname13(common));
51264
+ const out = execFileSync("git", ["worktree", "list", "--porcelain"], {
51265
+ cwd: root,
51266
+ encoding: "utf8",
51267
+ timeout: 2e3,
51268
+ stdio: ["ignore", "pipe", "ignore"]
51269
+ });
51270
+ const first = /^worktree (.+)$/m.exec(out)?.[1];
51271
+ if (!first || /^bare$/m.test(out.split("\n\n")[0] ?? "")) return root;
51272
+ const main = realpathSync4(first);
51273
+ return main !== realpathSync4(common) && statSync14(join38(main, ".git"), { throwIfNoEntry: false }) ? main : root;
51274
+ } catch {
51275
+ return root;
51276
+ }
51277
+ }
51278
+ var init_hooks = __esm({
51279
+ "src/swarm/hooks.ts"() {
51280
+ "use strict";
51281
+ init_esm_shims();
51282
+ init_context();
51283
+ init_log2();
51284
+ init_project();
51285
+ init_storage();
51286
+ init_agents();
51287
+ init_brief3();
51288
+ init_config2();
51289
+ init_identity();
51290
+ init_runtime();
51291
+ }
51292
+ });
51293
+
51294
+ // src/swarm/cli.ts
51295
+ var cli_exports = {};
51296
+ __export(cli_exports, {
51297
+ SWARM_USAGE: () => SWARM_USAGE,
51298
+ swarmCommand: () => swarmCommand
51299
+ });
51300
+ import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "fs";
51301
+ import { dirname as dirname14 } from "path";
51302
+ import { parseArgs } from "util";
51303
+ async function storage(ctx) {
51304
+ const { getStorage: getStorage2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
51305
+ return getStorage2(ctx, { vectors: false });
51306
+ }
51307
+ function items(db, runs) {
51308
+ const now = Date.now();
51309
+ return runs.map(
51310
+ (r) => runListItem(
51311
+ r,
51312
+ db.swarm.tasks(r.id),
51313
+ db.swarm.workers(r.id).map((w) => ({ ...w, status: workerStatus(w, now) }))
51314
+ )
51315
+ );
51316
+ }
51317
+ async function swarmCommand(args, callerCtx, io = defaultIO, opts = {}) {
51318
+ const { values, positionals } = parseArgs({
51319
+ args,
51320
+ options: {
51321
+ force: { type: "boolean" },
51322
+ "dry-run": { type: "boolean" },
51323
+ json: { type: "boolean" },
51324
+ limit: { type: "string" },
51325
+ run: { type: "string" },
51326
+ interval: { type: "string" },
51327
+ agent: { type: "string" },
51328
+ role: { type: "string" }
51329
+ },
51330
+ allowPositionals: true,
51331
+ strict: false
51332
+ });
51333
+ const [sub = "list", arg] = positionals;
51334
+ const json3 = !!values.json;
51335
+ for (const name of VALUE_OPTIONS)
51336
+ if (values[name] === true) {
51337
+ io.err(`--${name} needs a value`);
51338
+ return 2;
51339
+ }
51340
+ const opt = (name) => {
51341
+ const v = values[name];
51342
+ return typeof v === "string" ? v : void 0;
51343
+ };
51344
+ const root = coordinationRoot(callerCtx.root);
51345
+ const ctx = root === callerCtx.root ? callerCtx : getContext(root);
51346
+ const emit = (v) => io.out(JSON.stringify(v, null, 2));
51347
+ switch (sub) {
51348
+ case "init": {
51349
+ const where = swarmFile(root);
51350
+ if (where.exists && !values.force) {
51351
+ io.err(`${where.rel} already exists; pass --force to overwrite it with the starter file`);
51352
+ return 1;
51353
+ }
51354
+ mkdirSync15(dirname14(where.file), { recursive: true });
51355
+ writeFileSync13(where.file, STARTER_YAML);
51356
+ const loaded = loadSwarmConfig(root);
51357
+ if (loaded.state !== "ok") {
51358
+ io.err(`${where.rel} was written but does not validate`);
51359
+ return 1;
51360
+ }
51361
+ const r = syncAgents(root, loaded.config);
51362
+ if (json3) {
51363
+ emit({ file: where.rel, swarms: loaded.config.swarms.map((s) => s.name), sync: r });
51364
+ return r.conflicts ? 1 : 0;
51365
+ }
51366
+ io.out(
51367
+ [
51368
+ `wrote ${where.rel} (swarms: ${loaded.config.swarms.map((s) => s.name).join(", ")})`,
51369
+ formatSync2(r),
51370
+ "Edit the file to fit the project, then in Claude Code: /eminiar:em-swarm feature <goal>",
51371
+ "Docs: https://github.com/rydn/eminiar/blob/main/docs/swarms.md"
51372
+ ].join("\n")
51373
+ );
51374
+ return r.conflicts ? 1 : 0;
51375
+ }
51376
+ case "validate": {
51377
+ const loaded = loadSwarmConfig(root);
51378
+ if (json3) {
51379
+ emit(
51380
+ loaded.state === "ok" ? {
51381
+ state: "ok",
51382
+ file: loaded.config.rel,
51383
+ enabled: loaded.config.enabled,
51384
+ enforce: loaded.config.enforce,
51385
+ allowed_agents: loaded.config.allowedAgents,
51386
+ swarms: loaded.config.swarms.map((s) => s.name)
51387
+ } : loaded.state === "invalid" ? {
51388
+ state: "invalid",
51389
+ file: loaded.rel,
51390
+ enforce: loaded.enforce ?? false,
51391
+ issues: loaded.issues
51392
+ } : { state: "absent", file: loaded.rel }
51393
+ );
51394
+ return loaded.state === "ok" ? 0 : 1;
51395
+ }
51396
+ if (loaded.state === "absent") {
51397
+ io.err(`no ${loaded.rel}: swarms are off in this project (eminiar swarm init creates one)`);
51398
+ return 1;
51399
+ }
51400
+ if (loaded.state === "invalid") {
51401
+ io.err(formatIssues(loaded.rel, loaded.issues));
51402
+ return 1;
51403
+ }
51404
+ const c = loaded.config;
51405
+ io.out(
51406
+ `ok: ${c.rel} \xB7 ${c.swarms.length} swarm(s), ${c.swarms.reduce((n, s) => n + s.roles.length, 0)} role(s)${c.enabled ? "" : " \xB7 disabled (enabled: false)"}${enforceNote(c)}`
51407
+ );
51408
+ return 0;
51409
+ }
51410
+ case "sync": {
51411
+ const loaded = loadSwarmConfig(root);
51412
+ const dryRun = !!values["dry-run"];
51413
+ if (loaded.state === "invalid") {
51414
+ if (json3)
51415
+ emit({
51416
+ dryRun,
51417
+ reason: `${loaded.rel} is invalid: swarms stay off and sync changes nothing`,
51418
+ file: loaded.rel,
51419
+ issues: loaded.issues,
51420
+ actions: [],
51421
+ changed: 0,
51422
+ conflicts: 0
51423
+ });
51424
+ else io.err(formatInvalid(loaded.rel, loaded.issues));
51425
+ return 1;
51426
+ }
51427
+ const config2 = loaded.state === "ok" ? loaded.config : void 0;
51428
+ const syncOpts = { dryRun, force: !!values.force };
51429
+ const r = syncAgents(root, config2, syncOpts);
51430
+ const reason = loaded.state === "absent" ? `no ${loaded.rel}: swarms are off` : config2?.enabled ? void 0 : "swarms are disabled";
51431
+ if (json3) emit({ dryRun, reason: reason ?? null, ...r });
51432
+ else io.out(formatSync2(r, { dryRun, reason }));
51433
+ return r.conflicts ? 1 : 0;
51434
+ }
51435
+ case "list": {
51436
+ const loaded = loadSwarmConfig(root);
51437
+ if (loaded.state === "absent") {
51438
+ if (json3) emit({ state: "absent", file: loaded.rel });
51439
+ else io.out(formatNotOptedIn(loaded.rel));
51440
+ return 0;
51441
+ }
51442
+ if (loaded.state === "invalid") {
51443
+ if (json3) emit({ state: "invalid", file: loaded.rel, issues: loaded.issues });
51444
+ else io.err(formatInvalid(loaded.rel, loaded.issues));
51445
+ return 1;
51446
+ }
51447
+ const c = loaded.config;
51448
+ const plan = planAgentSync(root, c).map(({ content: _c, ...a }) => a);
51449
+ const db = await storage(ctx);
51450
+ const active = items(db, db.swarm.runs({ status: "active", limit: 10 }));
51451
+ if (json3) {
51452
+ emit({
51453
+ state: "ok",
51454
+ file: c.rel,
51455
+ enabled: c.enabled,
51456
+ sync: c.sync,
51457
+ swarms: c.swarms.map((s) => ({
51458
+ name: s.name,
51459
+ description: s.description,
51460
+ topology: s.topology,
51461
+ max_agents: s.maxAgents,
51462
+ stages: s.stages,
51463
+ roles: s.roles.map((r) => ({
51464
+ role: r.role,
51465
+ subagent_type: r.agentType,
51466
+ model: r.model ?? null,
51467
+ count: r.count,
51468
+ file: plan.find((a) => a.agentType === r.agentType)?.action ?? null
51469
+ }))
51470
+ })),
51471
+ agent_files: plan,
51472
+ active_runs: active
51473
+ });
51474
+ return 0;
51475
+ }
51476
+ io.out(
51477
+ `${c.enabled ? "" : "(disabled: enabled is false, workers cannot be spawned)\n"}${formatSwarmList(c, plan, active)}`
51478
+ );
51479
+ return 0;
51480
+ }
51481
+ case "runs": {
51482
+ const limit = opt("limit") === void 0 ? 20 : Number(opt("limit"));
51483
+ if (!Number.isInteger(limit) || limit < 1) {
51484
+ io.err("--limit must be a positive integer");
51485
+ return 2;
51486
+ }
51487
+ const db = await storage(ctx);
51488
+ const list2 = items(db, db.swarm.runs({ limit: Math.min(limit, 500) }));
51489
+ if (json3) emit(list2);
51490
+ else io.out(formatRunList(list2));
51491
+ return 0;
51492
+ }
51493
+ case "status": {
51494
+ const db = await storage(ctx);
51495
+ const id = arg ?? opt("run");
51496
+ const run3 = id ? db.swarm.runGet(id) : db.swarm.runActive() ?? db.swarm.runs({ limit: 1 })[0];
51497
+ if (!run3) {
51498
+ io.err(id ? `unknown run "${id}"` : "no swarm runs yet");
51499
+ return 1;
51500
+ }
51501
+ const snap = runSnapshot(db, run3);
51502
+ if (json3) {
51503
+ emit(snap);
51504
+ return 0;
51505
+ }
51506
+ const others = id ? [] : db.swarm.runs({ status: "active", limit: 50 }).filter((r) => r.id !== run3.id);
51507
+ io.out(
51508
+ formatRunStatus(snap, {
51509
+ max: Number.POSITIVE_INFINITY,
51510
+ perGroup: Number.POSITIVE_INFINITY,
51511
+ workers: Number.POSITIVE_INFINITY,
51512
+ lead: others.length ? [
51513
+ `Also active: ${others.slice(0, 5).map(runRef).join("; ")}${others.length > 5 ? "; \u2026" : ""} (eminiar swarm status <run>)`
51514
+ ] : []
51515
+ })
51516
+ );
51517
+ return 0;
51518
+ }
51519
+ case "release": {
51520
+ const db = await storage(ctx);
51521
+ const id = opt("run") ?? arg;
51522
+ let run3;
51523
+ if (id) {
51524
+ run3 = db.swarm.runGet(id);
51525
+ if (!run3) {
51526
+ io.err(`unknown run "${id}"`);
51527
+ return 1;
51528
+ }
51529
+ } else {
51530
+ const active = db.swarm.runs({ status: "active", limit: 50 });
51531
+ if (active.length > 1) {
51532
+ io.err(
51533
+ `${active.length} runs are active; pass --run: ${active.slice(0, 8).map(runRef).join("; ")}`
51534
+ );
51535
+ return 1;
51536
+ }
51537
+ run3 = active[0];
51538
+ if (!run3) {
51539
+ io.err("no active swarm run; pass --run to release workers of a finished one");
51540
+ return 1;
51541
+ }
51542
+ }
51543
+ const agent = opt("agent")?.trim() || void 0;
51544
+ const role = opt("role")?.trim() || void 0;
51545
+ try {
51546
+ const released = releaseWorkers(db, run3, { label: COORDINATOR }, { agent, role });
51547
+ const running2 = db.swarm.workers(run3.id).filter((w) => w.status === "running");
51548
+ if (json3)
51549
+ emit({
51550
+ run: run3.id,
51551
+ released: released.map((w) => ({
51552
+ agent_id: w.agent_id,
51553
+ role: w.role,
51554
+ task_id: w.task_id
51555
+ }))
51556
+ });
51557
+ else
51558
+ io.out(
51559
+ formatReleased(run3, released, running2, {
51560
+ agent,
51561
+ role,
51562
+ staleMinutes: Math.round(STALE_MS / 6e4)
51563
+ })
51564
+ );
51565
+ return 0;
51566
+ } catch (err) {
51567
+ if (!(err instanceof SwarmError)) throw err;
51568
+ io.err(err.message);
51569
+ return 1;
51570
+ }
51571
+ }
51572
+ case "watch": {
51573
+ const interval = opt("interval") === void 0 ? 2 : Number(opt("interval"));
51574
+ if (!Number.isFinite(interval) || interval <= 0) {
51575
+ io.err("--interval must be a positive number of seconds");
51576
+ return 2;
51577
+ }
51578
+ const db = await storage(ctx);
51579
+ const id = opt("run") ?? arg;
51580
+ const first = id ? db.swarm.runGet(id) : db.swarm.runActive();
51581
+ if (!first) {
51582
+ io.err(
51583
+ id ? `unknown run "${id}"` : 'no active swarm run to watch; start one with em_swarm {action:"start", swarm, goal} or pass --run'
51584
+ );
51585
+ return 1;
51586
+ }
51587
+ if (!id) {
51588
+ const others = db.swarm.runs({ status: "active", limit: 50 }).length - 1;
51589
+ if (others > 0)
51590
+ io.err(
51591
+ `watching run ${first.id}; ${others} other run(s) are active (pass --run to pick)`
51592
+ );
51593
+ }
51594
+ let lastSeen = 0;
51595
+ let lastEvent = null;
51596
+ const scan = (rows) => {
51597
+ for (const e of rows) {
51598
+ lastSeen = Math.max(lastSeen, e.id);
51599
+ if (isWatchEvent(e)) lastEvent = e;
51600
+ }
51601
+ return rows.length;
51602
+ };
51603
+ scan(db.swarm.events(first.id, { limit: WATCH_PAGE }));
51604
+ let key;
51605
+ for (let tick = 0; ; tick++) {
51606
+ const run3 = db.swarm.runGet(first.id);
51607
+ if (!run3) {
51608
+ io.err(`run ${first.id} disappeared`);
51609
+ return 1;
51610
+ }
51611
+ for (let n = WATCH_PAGE; tick > 0 && n === WATCH_PAGE; )
51612
+ n = scan(db.swarm.events(run3.id, { after: lastSeen, limit: WATCH_PAGE }));
51613
+ const step = watchStep(key, runSnapshot(db, run3, 0), lastEvent);
51614
+ key = step.key;
51615
+ if (step.line) io.out(JSON.stringify(step.line));
51616
+ if (run3.status !== "active") return 0;
51617
+ if (opts.maxTicks !== void 0 && tick + 1 >= opts.maxTicks) return 0;
51618
+ await sleep(Math.max(250, interval * 1e3));
51619
+ }
51620
+ }
51621
+ default:
51622
+ io.err(SWARM_USAGE);
51623
+ return 2;
51624
+ }
51625
+ }
51626
+ var defaultIO, SWARM_USAGE, sleep, VALUE_OPTIONS, WATCH_PAGE;
51627
+ var init_cli = __esm({
51628
+ "src/swarm/cli.ts"() {
51629
+ "use strict";
51630
+ init_esm_shims();
51631
+ init_context();
51632
+ init_agents();
51633
+ init_config2();
51634
+ init_format3();
51635
+ init_hooks();
51636
+ init_runtime();
51637
+ defaultIO = {
51638
+ out: (s) => console.log(s),
51639
+ err: (s) => console.error(s)
51640
+ };
51641
+ SWARM_USAGE = "usage: eminiar swarm <init [--force] | validate | sync [--dry-run] [--force] | list | runs [--limit N] | status [run] | release [--run id] [--agent id] [--role r] | watch [--run id] [--interval 2]> [--json]";
51642
+ sleep = (ms) => new Promise((resolve11) => setTimeout(resolve11, ms));
51643
+ VALUE_OPTIONS = ["limit", "run", "interval", "agent", "role"];
51644
+ WATCH_PAGE = 1e3;
51645
+ }
51646
+ });
51647
+
48740
51648
  // src/stats/statusline.ts
48741
51649
  var statusline_exports = {};
48742
51650
  __export(statusline_exports, {
48743
51651
  renderStatusline: () => renderStatusline,
48744
51652
  usageSegments: () => usageSegments
48745
51653
  });
48746
- import { existsSync as existsSync29, readFileSync as readFileSync25, writeFileSync as writeFileSync10 } from "fs";
51654
+ import { existsSync as existsSync32, readFileSync as readFileSync30, writeFileSync as writeFileSync14 } from "fs";
48747
51655
  function epochMs(v) {
48748
51656
  if (typeof v === "number" && Number.isFinite(v)) return v < 1e12 ? v * 1e3 : v;
48749
51657
  if (typeof v === "string") {
@@ -48803,10 +51711,10 @@ function renderStatusline(raw) {
48803
51711
  } catch {
48804
51712
  }
48805
51713
  const key = cacheKey(input2);
48806
- const cache3 = paths().statuslineCache;
51714
+ const cache5 = paths().statuslineCache;
48807
51715
  try {
48808
- if (existsSync29(cache3)) {
48809
- const c = JSON.parse(readFileSync25(cache3, "utf8"));
51716
+ if (existsSync32(cache5)) {
51717
+ const c = JSON.parse(readFileSync30(cache5, "utf8"));
48810
51718
  if (Date.now() - c.at < CACHE_TTL_MS && c.key === key) return c.line;
48811
51719
  }
48812
51720
  } catch {
@@ -48837,7 +51745,7 @@ function renderStatusline(raw) {
48837
51745
  line = `eminiar \xB7 unavailable (${err.message.slice(0, 40)})`;
48838
51746
  }
48839
51747
  try {
48840
- writeFileSync10(cache3, JSON.stringify({ at: Date.now(), line, key }));
51748
+ writeFileSync14(cache5, JSON.stringify({ at: Date.now(), line, key }));
48841
51749
  } catch {
48842
51750
  }
48843
51751
  return line;
@@ -48865,7 +51773,7 @@ init_esm_shims();
48865
51773
  init_context();
48866
51774
  init_doctor();
48867
51775
  init_version();
48868
- import { parseArgs } from "util";
51776
+ import { parseArgs as parseArgs2 } from "util";
48869
51777
  var USAGE = `eminiar v${version2()} \u2014 local-first context, memory & knowledge graph for Claude Code
48870
51778
 
48871
51779
  Usage: eminiar <command> [options]
@@ -48905,13 +51813,24 @@ Use
48905
51813
  statusline Claude Code statusLine command (reads JSON on stdin)
48906
51814
  hook [--event E] [--client claude|cursor] Run the hook handler manually (reads JSON on stdin)
48907
51815
  version | help
51816
+
51817
+ Agent swarms (opt-in: .eminiar/swarm.yaml)
51818
+ swarm init [--force] Write a starter swarm.yaml and generate the worker agents
51819
+ swarm validate Check swarm.yaml; exit 1 with file:line messages on problems
51820
+ swarm sync [--dry-run] [--force] Regenerate .claude/agents/swarm-*.md from swarm.yaml;
51821
+ --force also overwrites generated files edited by hand
51822
+ swarm list | runs [--limit N] | status [run] Swarms and roles, past runs, one run (in full)
51823
+ swarm release [--run id] [--agent id] [--role r] Free stuck workers (default: every worker
51824
+ quiet for 15 min); their tasks go back to pending
51825
+ swarm watch [--run id] [--interval 2] One NDJSON line per change (for the Monitor
51826
+ tool); exits when the run finishes. --json on the others
48908
51827
  `;
48909
51828
  function text2(r) {
48910
51829
  return r.content.map((c) => c.text).join("\n");
48911
51830
  }
48912
51831
  var commands = {
48913
51832
  async doctor(args) {
48914
- const { values } = parseArgs({
51833
+ const { values } = parseArgs2({
48915
51834
  args,
48916
51835
  options: { json: { type: "boolean" }, fix: { type: "boolean" } },
48917
51836
  strict: false
@@ -48923,7 +51842,7 @@ var commands = {
48923
51842
  return checks.every((c) => c.ok) ? 0 : 1;
48924
51843
  },
48925
51844
  async install(args) {
48926
- const { values } = parseArgs({
51845
+ const { values } = parseArgs2({
48927
51846
  args,
48928
51847
  options: {
48929
51848
  "no-model": { type: "boolean" },
@@ -48957,7 +51876,7 @@ var commands = {
48957
51876
  return upgrade2();
48958
51877
  },
48959
51878
  async uninstall(args) {
48960
- const { values } = parseArgs({
51879
+ const { values } = parseArgs2({
48961
51880
  args,
48962
51881
  options: { "purge-data": { type: "boolean" }, yes: { type: "boolean" } },
48963
51882
  strict: false
@@ -48966,7 +51885,7 @@ var commands = {
48966
51885
  return uninstall2({ purgeData: !!values["purge-data"], yes: !!values.yes });
48967
51886
  },
48968
51887
  async "replace-context-mode"(args) {
48969
- const { values } = parseArgs({
51888
+ const { values } = parseArgs2({
48970
51889
  args,
48971
51890
  options: { yes: { type: "boolean" }, "revert-gateway": { type: "boolean" } },
48972
51891
  strict: false
@@ -48975,7 +51894,7 @@ var commands = {
48975
51894
  return replaceContextMode2({ yes: !!values.yes, revertGateway: !!values["revert-gateway"] });
48976
51895
  },
48977
51896
  async index(args) {
48978
- const { values, positionals } = parseArgs({
51897
+ const { values, positionals } = parseArgs2({
48979
51898
  args,
48980
51899
  options: { source: { type: "string" }, glob: { type: "string", multiple: true } },
48981
51900
  allowPositionals: true,
@@ -48995,7 +51914,7 @@ var commands = {
48995
51914
  return r.isError ? 1 : 0;
48996
51915
  },
48997
51916
  async search(args) {
48998
- const { values, positionals } = parseArgs({
51917
+ const { values, positionals } = parseArgs2({
48999
51918
  args,
49000
51919
  options: {
49001
51920
  source: { type: "string" },
@@ -49047,7 +51966,7 @@ var commands = {
49047
51966
  return 0;
49048
51967
  },
49049
51968
  async graph(args) {
49050
- const { values, positionals } = parseArgs({
51969
+ const { values, positionals } = parseArgs2({
49051
51970
  args,
49052
51971
  options: { to: { type: "string" }, depth: { type: "string" }, limit: { type: "string" } },
49053
51972
  allowPositionals: true,
@@ -49107,7 +52026,7 @@ var commands = {
49107
52026
  return r.isError ? 1 : 0;
49108
52027
  },
49109
52028
  async reindex(args) {
49110
- const { values } = parseArgs({
52029
+ const { values } = parseArgs2({
49111
52030
  args,
49112
52031
  options: { scope: { type: "string" }, full: { type: "boolean" } },
49113
52032
  strict: false
@@ -49121,7 +52040,7 @@ var commands = {
49121
52040
  return 0;
49122
52041
  },
49123
52042
  async docs(args) {
49124
- const { values, positionals } = parseArgs({
52043
+ const { values, positionals } = parseArgs2({
49125
52044
  args,
49126
52045
  options: {
49127
52046
  dev: { type: "boolean" },
@@ -49175,7 +52094,7 @@ var commands = {
49175
52094
  return r.isError ? 1 : 0;
49176
52095
  },
49177
52096
  async stats(args) {
49178
- const { values } = parseArgs({
52097
+ const { values } = parseArgs2({
49179
52098
  args,
49180
52099
  options: { lifetime: { type: "boolean" } },
49181
52100
  strict: false
@@ -49186,7 +52105,7 @@ var commands = {
49186
52105
  return 0;
49187
52106
  },
49188
52107
  async usage(args) {
49189
- const { values, positionals } = parseArgs({
52108
+ const { values, positionals } = parseArgs2({
49190
52109
  args,
49191
52110
  options: {
49192
52111
  since: { type: "string" },
@@ -49258,7 +52177,7 @@ var commands = {
49258
52177
  return r.isError ? 1 : 0;
49259
52178
  },
49260
52179
  async recall(args) {
49261
- const { values, positionals } = parseArgs({
52180
+ const { values, positionals } = parseArgs2({
49262
52181
  args,
49263
52182
  options: {
49264
52183
  explain: { type: "boolean" },
@@ -49303,7 +52222,7 @@ var commands = {
49303
52222
  return 0;
49304
52223
  },
49305
52224
  async purge(args) {
49306
- const { values, positionals } = parseArgs({
52225
+ const { values, positionals } = parseArgs2({
49307
52226
  args,
49308
52227
  options: { yes: { type: "boolean" } },
49309
52228
  allowPositionals: true,
@@ -49322,7 +52241,7 @@ var commands = {
49322
52241
  },
49323
52242
  /** `eminiar ui` opens the browser by default; `serve` (the older name) does not. */
49324
52243
  async ui(args) {
49325
- const { values } = parseArgs({
52244
+ const { values } = parseArgs2({
49326
52245
  args,
49327
52246
  options: {
49328
52247
  port: { type: "string" },
@@ -49340,7 +52259,7 @@ var commands = {
49340
52259
  return 0;
49341
52260
  },
49342
52261
  async serve(args) {
49343
- const { values } = parseArgs({
52262
+ const { values } = parseArgs2({
49344
52263
  args,
49345
52264
  options: { port: { type: "string" }, open: { type: "boolean" } },
49346
52265
  strict: false
@@ -49353,6 +52272,10 @@ var commands = {
49353
52272
  });
49354
52273
  return 0;
49355
52274
  },
52275
+ async swarm(args) {
52276
+ const { swarmCommand: swarmCommand2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
52277
+ return swarmCommand2(args, getContext());
52278
+ },
49356
52279
  async statusline() {
49357
52280
  const { renderStatusline: renderStatusline2 } = await Promise.resolve().then(() => (init_statusline(), statusline_exports));
49358
52281
  const chunks = [];