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/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +51 -9
- package/bin/em-hook +75 -2
- package/dist/cli.js +3097 -174
- package/dist/hook.js +6942 -4284
- package/dist/server.js +5020 -1703
- package/dist/ui/app.global.js +67 -60
- package/dist/ui/styles.css +1014 -0
- package/hooks/hooks.json +9 -1
- package/package.json +1 -1
- package/skills/em-swarm/SKILL.md +100 -0
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(
|
|
2605
|
-
for (let i =
|
|
2606
|
-
const omittable = key === "optin" ?
|
|
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,
|
|
2619
|
-
for (let i = 0; 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 &&
|
|
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 (
|
|
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
|
|
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(
|
|
3850
|
-
const optoutStart = getTupleOptStart(
|
|
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 >
|
|
3863
|
+
if (input2.length > items2.length) {
|
|
3864
3864
|
payload.issues.push({
|
|
3865
3865
|
code: "too_big",
|
|
3866
|
-
maximum:
|
|
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(
|
|
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 <
|
|
3878
|
-
const r =
|
|
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 =
|
|
3891
|
-
const rest2 = input2.slice(
|
|
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,
|
|
3909
|
+
return Promise.all(proms).then(() => handleTupleResults(itemResults, payload, items2, input2, optoutStart));
|
|
3910
3910
|
}
|
|
3911
|
-
return handleTupleResults(itemResults, payload,
|
|
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,
|
|
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,
|
|
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,
|
|
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 ?? (
|
|
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
|
|
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(
|
|
14187
|
-
const optoutStart = getTupleOptStart2(
|
|
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 > ${
|
|
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 <
|
|
14196
|
-
const itemSchema =
|
|
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} = ${
|
|
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(
|
|
14249
|
-
for (let i =
|
|
14250
|
-
const omittable = key === "optin" ?
|
|
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,
|
|
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(
|
|
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(
|
|
19368
|
-
return
|
|
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
|
|
19497
|
-
if (!Array.isArray(
|
|
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 <
|
|
19502
|
-
const key = canonicalKey(
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
19838
|
+
} else if (Array.isArray(items2)) {
|
|
19839
19839
|
const minItems = typeof schema.minItems === "number" ? schema.minItems : 0;
|
|
19840
|
-
const tupleItems =
|
|
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 (
|
|
19852
|
-
const element = convertSchema(
|
|
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
|
|
20104
|
+
const cache5 = /* @__PURE__ */ new Map();
|
|
20105
20105
|
function run3(s) {
|
|
20106
|
-
const cached5 =
|
|
20106
|
+
const cached5 = cache5.get(s);
|
|
20107
20107
|
if (cached5 === RESOLVING) {
|
|
20108
20108
|
return new $ZodLazy({
|
|
20109
20109
|
type: "lazy",
|
|
20110
|
-
getter: () =>
|
|
20110
|
+
getter: () => cache5.get(s)
|
|
20111
20111
|
});
|
|
20112
20112
|
}
|
|
20113
20113
|
if (cached5 !== void 0)
|
|
20114
20114
|
return cached5;
|
|
20115
|
-
|
|
20115
|
+
cache5.set(s, RESOLVING);
|
|
20116
20116
|
const inner = mapInner(s);
|
|
20117
20117
|
const mapped = fn(inner, inner !== s);
|
|
20118
|
-
|
|
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
|
|
23312
|
-
if (!
|
|
23313
|
-
if (!existsSync8(
|
|
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(
|
|
23828
|
+
if (fileHash(join11(cache5, rel)) !== fileHash(join11(stable, rel))) return `${rel} differs`;
|
|
23316
23829
|
}
|
|
23317
|
-
if (existsSync8(join11(
|
|
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(
|
|
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")
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
24682
|
-
return
|
|
25197
|
+
function flatten(items2) {
|
|
25198
|
+
return items2.reduce((collection, item) => [].concat(collection, item), []);
|
|
24683
25199
|
}
|
|
24684
25200
|
exports.flatten = flatten;
|
|
24685
|
-
function splitWhen(
|
|
25201
|
+
function splitWhen(items2, predicate) {
|
|
24686
25202
|
const result = [[]];
|
|
24687
25203
|
let groupIndex = 0;
|
|
24688
|
-
for (const item of
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
26013
|
-
var
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
28354
|
+
let items2 = /* @__PURE__ */ new Set();
|
|
27839
28355
|
let negatives = 0;
|
|
27840
28356
|
let onResult = (state) => {
|
|
27841
|
-
|
|
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 ? [...
|
|
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
|
|
28397
|
+
let items2 = [];
|
|
27882
28398
|
let onResult = (state) => {
|
|
27883
28399
|
if (options.onResult) options.onResult(state);
|
|
27884
|
-
|
|
28400
|
+
items2.push(state.output);
|
|
27885
28401
|
};
|
|
27886
28402
|
let matches2 = new Set(micromatch(list2, patterns, { ...options, onResult }));
|
|
27887
|
-
for (let item of
|
|
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
|
|
28437
|
+
let items2 = [].concat(list2);
|
|
27922
28438
|
for (let pattern2 of [].concat(patterns)) {
|
|
27923
28439
|
let isMatch = picomatch(String(pattern2), options);
|
|
27924
|
-
if (
|
|
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
|
|
28447
|
+
let items2 = [].concat(list2);
|
|
27932
28448
|
for (let pattern2 of [].concat(patterns)) {
|
|
27933
28449
|
let isMatch = picomatch(String(pattern2), options);
|
|
27934
|
-
if (!
|
|
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
|
|
28084
|
-
return endsWithSlashGlobStar(pattern2) || isStaticPattern(
|
|
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
|
|
28645
|
+
const relative13 = [];
|
|
28130
28646
|
for (const pattern2 of patterns) {
|
|
28131
|
-
if (
|
|
28647
|
+
if (isAbsolute2(pattern2)) {
|
|
28132
28648
|
absolute.push(pattern2);
|
|
28133
28649
|
} else {
|
|
28134
|
-
|
|
28650
|
+
relative13.push(pattern2);
|
|
28135
28651
|
}
|
|
28136
28652
|
}
|
|
28137
|
-
return [absolute,
|
|
28653
|
+
return [absolute, relative13];
|
|
28138
28654
|
}
|
|
28139
28655
|
exports.partitionAbsoluteAndRelative = partitionAbsoluteAndRelative;
|
|
28140
|
-
function
|
|
28656
|
+
function isAbsolute2(pattern2) {
|
|
28141
28657
|
return path3.isAbsolute(pattern2);
|
|
28142
28658
|
}
|
|
28143
|
-
exports.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
|
|
29084
|
+
function statSync15(path3, optionsOrSettings) {
|
|
28569
29085
|
const settings = getSettings(optionsOrSettings);
|
|
28570
29086
|
return sync.read(path3, settings);
|
|
28571
29087
|
}
|
|
28572
|
-
exports.statSync =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
29795
|
+
var p = new Promise(function(resolve11) {
|
|
29280
29796
|
process.nextTick(function() {
|
|
29281
29797
|
if (queue.idle()) {
|
|
29282
|
-
|
|
29798
|
+
resolve11();
|
|
29283
29799
|
} else {
|
|
29284
29800
|
var previousDrain = queue.drain;
|
|
29285
29801
|
queue.drain = function() {
|
|
29286
29802
|
if (typeof previousDrain === "function") previousDrain();
|
|
29287
|
-
|
|
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((
|
|
30300
|
+
return new Promise((resolve11, reject) => {
|
|
29785
30301
|
this._stat(filepath, this._fsStatSettings, (error62, stats) => {
|
|
29786
|
-
return error62 === null ?
|
|
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((
|
|
30327
|
+
return new Promise((resolve11, reject) => {
|
|
29812
30328
|
this._walkAsync(root, options, (error62, entries) => {
|
|
29813
30329
|
if (error62 === null) {
|
|
29814
|
-
|
|
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((
|
|
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", () =>
|
|
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
|
|
31909
|
-
return { root, edges: [...
|
|
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
|
|
34592
|
-
if (existsSync18(
|
|
34593
|
-
for (const m of readFileSync15(
|
|
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(
|
|
40667
|
-
for (const raw of
|
|
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
|
|
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 =
|
|
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 = (
|
|
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
|
|
47059
|
-
for (const e of [...r.edges, ...among])
|
|
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: [...
|
|
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
|
|
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
|
|
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 ||
|
|
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
|
|
48270
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
48290
|
-
if (!file3.startsWith(
|
|
48291
|
-
if (!
|
|
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(
|
|
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 =
|
|
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(
|
|
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((
|
|
50788
|
+
await new Promise((resolve11, reject) => {
|
|
48701
50789
|
server.once("error", reject);
|
|
48702
|
-
server.listen(opts.port, "127.0.0.1",
|
|
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
|
|
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
|
|
51714
|
+
const cache5 = paths().statuslineCache;
|
|
48807
51715
|
try {
|
|
48808
|
-
if (
|
|
48809
|
-
const c = JSON.parse(
|
|
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
|
-
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 = [];
|