opencode-swarm-plugin 0.20.0 → 0.22.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.
Files changed (41) hide show
  1. package/.beads/issues.jsonl +213 -0
  2. package/INTEGRATION_EXAMPLE.md +66 -0
  3. package/README.md +352 -522
  4. package/dist/index.js +2046 -984
  5. package/dist/plugin.js +2051 -1017
  6. package/docs/analysis/subagent-coordination-patterns.md +2 -0
  7. package/docs/semantic-memory-cli-syntax.md +123 -0
  8. package/docs/swarm-mail-architecture.md +1147 -0
  9. package/evals/README.md +116 -0
  10. package/evals/evalite.config.ts +15 -0
  11. package/evals/example.eval.ts +32 -0
  12. package/evals/fixtures/decomposition-cases.ts +105 -0
  13. package/evals/lib/data-loader.test.ts +288 -0
  14. package/evals/lib/data-loader.ts +111 -0
  15. package/evals/lib/llm.ts +115 -0
  16. package/evals/scorers/index.ts +200 -0
  17. package/evals/scorers/outcome-scorers.test.ts +27 -0
  18. package/evals/scorers/outcome-scorers.ts +349 -0
  19. package/evals/swarm-decomposition.eval.ts +112 -0
  20. package/package.json +8 -1
  21. package/scripts/cleanup-test-memories.ts +346 -0
  22. package/src/beads.ts +49 -0
  23. package/src/eval-capture.ts +487 -0
  24. package/src/index.ts +45 -3
  25. package/src/learning.integration.test.ts +19 -4
  26. package/src/output-guardrails.test.ts +438 -0
  27. package/src/output-guardrails.ts +381 -0
  28. package/src/schemas/index.ts +18 -0
  29. package/src/schemas/swarm-context.ts +115 -0
  30. package/src/storage.ts +117 -5
  31. package/src/streams/events.test.ts +296 -0
  32. package/src/streams/events.ts +93 -0
  33. package/src/streams/migrations.test.ts +24 -20
  34. package/src/streams/migrations.ts +51 -0
  35. package/src/streams/projections.ts +187 -0
  36. package/src/streams/store.ts +275 -0
  37. package/src/swarm-orchestrate.ts +771 -189
  38. package/src/swarm-prompts.ts +84 -12
  39. package/src/swarm.integration.test.ts +124 -0
  40. package/vitest.integration.config.ts +6 -0
  41. package/vitest.integration.setup.ts +48 -0
package/dist/index.js CHANGED
@@ -12675,7 +12675,7 @@ var init_zod = __esm(() => {
12675
12675
  init_external();
12676
12676
  });
12677
12677
 
12678
- // node_modules/.pnpm/@opencode-ai+plugin@1.0.134/node_modules/@opencode-ai/plugin/dist/tool.js
12678
+ // node_modules/.pnpm/@opencode-ai+plugin@1.0.152/node_modules/@opencode-ai/plugin/dist/tool.js
12679
12679
  function tool(input) {
12680
12680
  return input;
12681
12681
  }
@@ -12684,7 +12684,7 @@ var init_tool = __esm(() => {
12684
12684
  tool.schema = exports_external;
12685
12685
  });
12686
12686
 
12687
- // node_modules/.pnpm/@opencode-ai+plugin@1.0.134/node_modules/@opencode-ai/plugin/dist/index.js
12687
+ // node_modules/.pnpm/@opencode-ai+plugin@1.0.152/node_modules/@opencode-ai/plugin/dist/index.js
12688
12688
  var init_dist = __esm(() => {
12689
12689
  init_tool();
12690
12690
  });
@@ -12705,7 +12705,7 @@ function createEvent(type, data) {
12705
12705
  function isEventType(event, type) {
12706
12706
  return event.type === type;
12707
12707
  }
12708
- var BaseEventSchema, AgentRegisteredEventSchema, AgentActiveEventSchema, MessageSentEventSchema, MessageReadEventSchema, MessageAckedEventSchema, FileReservedEventSchema, FileReleasedEventSchema, TaskStartedEventSchema, TaskProgressEventSchema, TaskCompletedEventSchema, TaskBlockedEventSchema, AgentEventSchema;
12708
+ var BaseEventSchema, AgentRegisteredEventSchema, AgentActiveEventSchema, MessageSentEventSchema, MessageReadEventSchema, MessageAckedEventSchema, FileReservedEventSchema, FileReleasedEventSchema, TaskStartedEventSchema, TaskProgressEventSchema, TaskCompletedEventSchema, TaskBlockedEventSchema, DecompositionGeneratedEventSchema, SubtaskOutcomeEventSchema, HumanFeedbackEventSchema, SwarmCheckpointedEventSchema, SwarmRecoveredEventSchema, AgentEventSchema;
12709
12709
  var init_events = __esm(() => {
12710
12710
  init_zod();
12711
12711
  BaseEventSchema = exports_external.object({
@@ -12791,6 +12791,68 @@ var init_events = __esm(() => {
12791
12791
  bead_id: exports_external.string(),
12792
12792
  reason: exports_external.string()
12793
12793
  });
12794
+ DecompositionGeneratedEventSchema = BaseEventSchema.extend({
12795
+ type: exports_external.literal("decomposition_generated"),
12796
+ epic_id: exports_external.string(),
12797
+ task: exports_external.string(),
12798
+ context: exports_external.string().optional(),
12799
+ strategy: exports_external.enum(["file-based", "feature-based", "risk-based"]),
12800
+ epic_title: exports_external.string(),
12801
+ subtasks: exports_external.array(exports_external.object({
12802
+ title: exports_external.string(),
12803
+ files: exports_external.array(exports_external.string()),
12804
+ priority: exports_external.number().min(0).max(3).optional()
12805
+ })),
12806
+ recovery_context: exports_external.object({
12807
+ shared_context: exports_external.string().optional(),
12808
+ skills_to_load: exports_external.array(exports_external.string()).optional(),
12809
+ coordinator_notes: exports_external.string().optional()
12810
+ }).optional()
12811
+ });
12812
+ SubtaskOutcomeEventSchema = BaseEventSchema.extend({
12813
+ type: exports_external.literal("subtask_outcome"),
12814
+ epic_id: exports_external.string(),
12815
+ bead_id: exports_external.string(),
12816
+ planned_files: exports_external.array(exports_external.string()),
12817
+ actual_files: exports_external.array(exports_external.string()),
12818
+ duration_ms: exports_external.number().min(0),
12819
+ error_count: exports_external.number().min(0).default(0),
12820
+ retry_count: exports_external.number().min(0).default(0),
12821
+ success: exports_external.boolean()
12822
+ });
12823
+ HumanFeedbackEventSchema = BaseEventSchema.extend({
12824
+ type: exports_external.literal("human_feedback"),
12825
+ epic_id: exports_external.string(),
12826
+ accepted: exports_external.boolean(),
12827
+ modified: exports_external.boolean().default(false),
12828
+ notes: exports_external.string().optional()
12829
+ });
12830
+ SwarmCheckpointedEventSchema = BaseEventSchema.extend({
12831
+ type: exports_external.literal("swarm_checkpointed"),
12832
+ epic_id: exports_external.string(),
12833
+ bead_id: exports_external.string(),
12834
+ strategy: exports_external.enum(["file-based", "feature-based", "risk-based"]),
12835
+ files: exports_external.array(exports_external.string()),
12836
+ dependencies: exports_external.array(exports_external.string()),
12837
+ directives: exports_external.object({
12838
+ shared_context: exports_external.string().optional(),
12839
+ skills_to_load: exports_external.array(exports_external.string()).optional(),
12840
+ coordinator_notes: exports_external.string().optional()
12841
+ }),
12842
+ recovery: exports_external.object({
12843
+ last_checkpoint: exports_external.number(),
12844
+ files_modified: exports_external.array(exports_external.string()),
12845
+ progress_percent: exports_external.number().min(0).max(100),
12846
+ last_message: exports_external.string().optional(),
12847
+ error_context: exports_external.string().optional()
12848
+ })
12849
+ });
12850
+ SwarmRecoveredEventSchema = BaseEventSchema.extend({
12851
+ type: exports_external.literal("swarm_recovered"),
12852
+ epic_id: exports_external.string(),
12853
+ bead_id: exports_external.string(),
12854
+ recovered_from_checkpoint: exports_external.number()
12855
+ });
12794
12856
  AgentEventSchema = exports_external.discriminatedUnion("type", [
12795
12857
  AgentRegisteredEventSchema,
12796
12858
  AgentActiveEventSchema,
@@ -12802,609 +12864,206 @@ var init_events = __esm(() => {
12802
12864
  TaskStartedEventSchema,
12803
12865
  TaskProgressEventSchema,
12804
12866
  TaskCompletedEventSchema,
12805
- TaskBlockedEventSchema
12867
+ TaskBlockedEventSchema,
12868
+ DecompositionGeneratedEventSchema,
12869
+ SubtaskOutcomeEventSchema,
12870
+ HumanFeedbackEventSchema,
12871
+ SwarmCheckpointedEventSchema,
12872
+ SwarmRecoveredEventSchema
12806
12873
  ]);
12807
12874
  });
12808
12875
 
12809
- // src/streams/store.ts
12810
- function parseTimestamp(timestamp) {
12811
- const ts = parseInt(timestamp, 10);
12812
- if (Number.isNaN(ts)) {
12813
- throw new Error(`[SwarmMail] Invalid timestamp: ${timestamp}`);
12876
+ // node_modules/.pnpm/@isaacs+balanced-match@4.0.1/node_modules/@isaacs/balanced-match/dist/esm/index.js
12877
+ var balanced = (a, b, str2) => {
12878
+ const ma = a instanceof RegExp ? maybeMatch(a, str2) : a;
12879
+ const mb = b instanceof RegExp ? maybeMatch(b, str2) : b;
12880
+ const r = ma !== null && mb != null && range(ma, mb, str2);
12881
+ return r && {
12882
+ start: r[0],
12883
+ end: r[1],
12884
+ pre: str2.slice(0, r[0]),
12885
+ body: str2.slice(r[0] + ma.length, r[1]),
12886
+ post: str2.slice(r[1] + mb.length)
12887
+ };
12888
+ }, maybeMatch = (reg, str2) => {
12889
+ const m = str2.match(reg);
12890
+ return m ? m[0] : null;
12891
+ }, range = (a, b, str2) => {
12892
+ let begs, beg, left, right = undefined, result;
12893
+ let ai = str2.indexOf(a);
12894
+ let bi = str2.indexOf(b, ai + 1);
12895
+ let i = ai;
12896
+ if (ai >= 0 && bi > 0) {
12897
+ if (a === b) {
12898
+ return [ai, bi];
12899
+ }
12900
+ begs = [];
12901
+ left = str2.length;
12902
+ while (i >= 0 && !result) {
12903
+ if (i === ai) {
12904
+ begs.push(i);
12905
+ ai = str2.indexOf(a, i + 1);
12906
+ } else if (begs.length === 1) {
12907
+ const r = begs.pop();
12908
+ if (r !== undefined)
12909
+ result = [r, bi];
12910
+ } else {
12911
+ beg = begs.pop();
12912
+ if (beg !== undefined && beg < left) {
12913
+ left = beg;
12914
+ right = bi;
12915
+ }
12916
+ bi = str2.indexOf(b, i + 1);
12917
+ }
12918
+ i = ai < bi && ai >= 0 ? ai : bi;
12919
+ }
12920
+ if (begs.length && right !== undefined) {
12921
+ result = [left, right];
12922
+ }
12814
12923
  }
12815
- if (ts > Number.MAX_SAFE_INTEGER) {
12816
- console.warn(`[SwarmMail] Timestamp ${timestamp} exceeds MAX_SAFE_INTEGER (year 2286+), precision may be lost`);
12924
+ return result;
12925
+ };
12926
+
12927
+ // node_modules/.pnpm/@isaacs+brace-expansion@5.0.0/node_modules/@isaacs/brace-expansion/dist/esm/index.js
12928
+ function numeric(str2) {
12929
+ return !isNaN(str2) ? parseInt(str2, 10) : str2.charCodeAt(0);
12930
+ }
12931
+ function escapeBraces(str2) {
12932
+ return str2.replace(slashPattern, escSlash).replace(openPattern, escOpen).replace(closePattern, escClose).replace(commaPattern, escComma).replace(periodPattern, escPeriod);
12933
+ }
12934
+ function unescapeBraces(str2) {
12935
+ return str2.replace(escSlashPattern, "\\").replace(escOpenPattern, "{").replace(escClosePattern, "}").replace(escCommaPattern, ",").replace(escPeriodPattern, ".");
12936
+ }
12937
+ function parseCommaParts(str2) {
12938
+ if (!str2) {
12939
+ return [""];
12817
12940
  }
12818
- return ts;
12941
+ const parts = [];
12942
+ const m = balanced("{", "}", str2);
12943
+ if (!m) {
12944
+ return str2.split(",");
12945
+ }
12946
+ const { pre, body, post } = m;
12947
+ const p = pre.split(",");
12948
+ p[p.length - 1] += "{" + body + "}";
12949
+ const postParts = parseCommaParts(post);
12950
+ if (post.length) {
12951
+ p[p.length - 1] += postParts.shift();
12952
+ p.push.apply(p, postParts);
12953
+ }
12954
+ parts.push.apply(parts, p);
12955
+ return parts;
12819
12956
  }
12820
- async function appendEvent(event, projectPath) {
12821
- const db = await getDatabase(projectPath);
12822
- const { type, project_key, timestamp, ...rest } = event;
12823
- console.log("[SwarmMail] Appending event", {
12824
- type,
12825
- projectKey: project_key,
12826
- timestamp
12827
- });
12828
- const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
12829
- VALUES ($1, $2, $3, $4)
12830
- RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
12831
- const row = result.rows[0];
12832
- if (!row) {
12833
- throw new Error("Failed to insert event - no row returned");
12957
+ function expand(str2) {
12958
+ if (!str2) {
12959
+ return [];
12834
12960
  }
12835
- const { id, sequence } = row;
12836
- console.log("[SwarmMail] Event appended", {
12837
- type,
12838
- id,
12839
- sequence,
12840
- projectKey: project_key
12841
- });
12842
- console.debug("[SwarmMail] Updating materialized views", { type, id });
12843
- await updateMaterializedViews(db, { ...event, id, sequence });
12844
- return { ...event, id, sequence };
12961
+ if (str2.slice(0, 2) === "{}") {
12962
+ str2 = "\\{\\}" + str2.slice(2);
12963
+ }
12964
+ return expand_(escapeBraces(str2), true).map(unescapeBraces);
12845
12965
  }
12846
- async function appendEvents(events, projectPath) {
12847
- return withTiming("appendEvents", async () => {
12848
- const db = await getDatabase(projectPath);
12849
- const results = [];
12850
- await db.exec("BEGIN");
12851
- try {
12852
- for (const event of events) {
12853
- const { type, project_key, timestamp, ...rest } = event;
12854
- const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
12855
- VALUES ($1, $2, $3, $4)
12856
- RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
12857
- const row = result.rows[0];
12858
- if (!row) {
12859
- throw new Error("Failed to insert event - no row returned");
12966
+ function embrace(str2) {
12967
+ return "{" + str2 + "}";
12968
+ }
12969
+ function isPadded(el) {
12970
+ return /^-?0\d/.test(el);
12971
+ }
12972
+ function lte(i, y) {
12973
+ return i <= y;
12974
+ }
12975
+ function gte(i, y) {
12976
+ return i >= y;
12977
+ }
12978
+ function expand_(str2, isTop) {
12979
+ const expansions = [];
12980
+ const m = balanced("{", "}", str2);
12981
+ if (!m)
12982
+ return [str2];
12983
+ const pre = m.pre;
12984
+ const post = m.post.length ? expand_(m.post, false) : [""];
12985
+ if (/\$$/.test(m.pre)) {
12986
+ for (let k = 0;k < post.length; k++) {
12987
+ const expansion = pre + "{" + m.body + "}" + post[k];
12988
+ expansions.push(expansion);
12989
+ }
12990
+ } else {
12991
+ const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
12992
+ const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
12993
+ const isSequence = isNumericSequence || isAlphaSequence;
12994
+ const isOptions = m.body.indexOf(",") >= 0;
12995
+ if (!isSequence && !isOptions) {
12996
+ if (m.post.match(/,(?!,).*\}/)) {
12997
+ str2 = m.pre + "{" + m.body + escClose + m.post;
12998
+ return expand_(str2);
12999
+ }
13000
+ return [str2];
13001
+ }
13002
+ let n;
13003
+ if (isSequence) {
13004
+ n = m.body.split(/\.\./);
13005
+ } else {
13006
+ n = parseCommaParts(m.body);
13007
+ if (n.length === 1 && n[0] !== undefined) {
13008
+ n = expand_(n[0], false).map(embrace);
13009
+ if (n.length === 1) {
13010
+ return post.map((p) => m.pre + n[0] + p);
12860
13011
  }
12861
- const { id, sequence } = row;
12862
- const enrichedEvent = { ...event, id, sequence };
12863
- await updateMaterializedViews(db, enrichedEvent);
12864
- results.push(enrichedEvent);
12865
13012
  }
12866
- await db.exec("COMMIT");
12867
- } catch (e) {
12868
- let rollbackError = null;
12869
- try {
12870
- await db.exec("ROLLBACK");
12871
- } catch (rbErr) {
12872
- rollbackError = rbErr;
12873
- console.error("[SwarmMail] ROLLBACK failed:", rbErr);
13013
+ }
13014
+ let N;
13015
+ if (isSequence && n[0] !== undefined && n[1] !== undefined) {
13016
+ const x = numeric(n[0]);
13017
+ const y = numeric(n[1]);
13018
+ const width = Math.max(n[0].length, n[1].length);
13019
+ let incr = n.length === 3 && n[2] !== undefined ? Math.abs(numeric(n[2])) : 1;
13020
+ let test = lte;
13021
+ const reverse = y < x;
13022
+ if (reverse) {
13023
+ incr *= -1;
13024
+ test = gte;
12874
13025
  }
12875
- if (rollbackError) {
12876
- const compositeError = new Error(`Transaction failed: ${e instanceof Error ? e.message : String(e)}. ` + `ROLLBACK also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}. ` + `Database may be in inconsistent state.`);
12877
- compositeError.originalError = e;
12878
- compositeError.rollbackError = rollbackError;
12879
- throw compositeError;
13026
+ const pad = n.some(isPadded);
13027
+ N = [];
13028
+ for (let i = x;test(i, y); i += incr) {
13029
+ let c;
13030
+ if (isAlphaSequence) {
13031
+ c = String.fromCharCode(i);
13032
+ if (c === "\\") {
13033
+ c = "";
13034
+ }
13035
+ } else {
13036
+ c = String(i);
13037
+ if (pad) {
13038
+ const need = width - c.length;
13039
+ if (need > 0) {
13040
+ const z = new Array(need + 1).join("0");
13041
+ if (i < 0) {
13042
+ c = "-" + z + c.slice(1);
13043
+ } else {
13044
+ c = z + c;
13045
+ }
13046
+ }
13047
+ }
13048
+ }
13049
+ N.push(c);
13050
+ }
13051
+ } else {
13052
+ N = [];
13053
+ for (let j = 0;j < n.length; j++) {
13054
+ N.push.apply(N, expand_(n[j], false));
12880
13055
  }
12881
- throw e;
12882
13056
  }
12883
- return results;
12884
- });
12885
- }
12886
- async function readEvents(options2 = {}, projectPath) {
12887
- return withTiming("readEvents", async () => {
12888
- const db = await getDatabase(projectPath);
12889
- const conditions = [];
12890
- const params = [];
12891
- let paramIndex = 1;
12892
- if (options2.projectKey) {
12893
- conditions.push(`project_key = $${paramIndex++}`);
12894
- params.push(options2.projectKey);
12895
- }
12896
- if (options2.types && options2.types.length > 0) {
12897
- conditions.push(`type = ANY($${paramIndex++})`);
12898
- params.push(options2.types);
12899
- }
12900
- if (options2.since !== undefined) {
12901
- conditions.push(`timestamp >= $${paramIndex++}`);
12902
- params.push(options2.since);
12903
- }
12904
- if (options2.until !== undefined) {
12905
- conditions.push(`timestamp <= $${paramIndex++}`);
12906
- params.push(options2.until);
12907
- }
12908
- if (options2.afterSequence !== undefined) {
12909
- conditions.push(`sequence > $${paramIndex++}`);
12910
- params.push(options2.afterSequence);
12911
- }
12912
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
12913
- let query = `
12914
- SELECT id, type, project_key, timestamp, sequence, data
12915
- FROM events
12916
- ${whereClause}
12917
- ORDER BY sequence ASC
12918
- `;
12919
- if (options2.limit) {
12920
- query += ` LIMIT $${paramIndex++}`;
12921
- params.push(options2.limit);
12922
- }
12923
- if (options2.offset) {
12924
- query += ` OFFSET $${paramIndex++}`;
12925
- params.push(options2.offset);
12926
- }
12927
- const result = await db.query(query, params);
12928
- return result.rows.map((row) => {
12929
- const data = typeof row.data === "string" ? JSON.parse(row.data) : row.data;
12930
- return {
12931
- id: row.id,
12932
- type: row.type,
12933
- project_key: row.project_key,
12934
- timestamp: parseTimestamp(row.timestamp),
12935
- sequence: row.sequence,
12936
- ...data
12937
- };
12938
- });
12939
- });
12940
- }
12941
- async function getLatestSequence(projectKey, projectPath) {
12942
- const db = await getDatabase(projectPath);
12943
- const query = projectKey ? "SELECT MAX(sequence) as seq FROM events WHERE project_key = $1" : "SELECT MAX(sequence) as seq FROM events";
12944
- const params = projectKey ? [projectKey] : [];
12945
- const result = await db.query(query, params);
12946
- return result.rows[0]?.seq ?? 0;
12947
- }
12948
- async function replayEvents(options2 = {}, projectPath) {
12949
- return withTiming("replayEvents", async () => {
12950
- const startTime = Date.now();
12951
- const db = await getDatabase(projectPath);
12952
- if (options2.clearViews) {
12953
- if (options2.projectKey) {
12954
- await db.query(`DELETE FROM message_recipients WHERE message_id IN (
12955
- SELECT id FROM messages WHERE project_key = $1
12956
- )`, [options2.projectKey]);
12957
- await db.query(`DELETE FROM messages WHERE project_key = $1`, [
12958
- options2.projectKey
12959
- ]);
12960
- await db.query(`DELETE FROM reservations WHERE project_key = $1`, [
12961
- options2.projectKey
12962
- ]);
12963
- await db.query(`DELETE FROM agents WHERE project_key = $1`, [
12964
- options2.projectKey
12965
- ]);
12966
- } else {
12967
- await db.exec(`
12968
- DELETE FROM message_recipients;
12969
- DELETE FROM messages;
12970
- DELETE FROM reservations;
12971
- DELETE FROM agents;
12972
- `);
12973
- }
12974
- }
12975
- const events = await readEvents({
12976
- projectKey: options2.projectKey,
12977
- afterSequence: options2.fromSequence
12978
- }, projectPath);
12979
- for (const event of events) {
12980
- await updateMaterializedViews(db, event);
12981
- }
12982
- return {
12983
- eventsReplayed: events.length,
12984
- duration: Date.now() - startTime
12985
- };
12986
- });
12987
- }
12988
- async function replayEventsBatched(projectKey, onBatch, options2 = {}, projectPath) {
12989
- return withTiming("replayEventsBatched", async () => {
12990
- const startTime = Date.now();
12991
- const batchSize = options2.batchSize ?? 1000;
12992
- const fromSequence = options2.fromSequence ?? 0;
12993
- const db = await getDatabase(projectPath);
12994
- if (options2.clearViews) {
12995
- await db.query(`DELETE FROM message_recipients WHERE message_id IN (
12996
- SELECT id FROM messages WHERE project_key = $1
12997
- )`, [projectKey]);
12998
- await db.query(`DELETE FROM messages WHERE project_key = $1`, [
12999
- projectKey
13000
- ]);
13001
- await db.query(`DELETE FROM reservations WHERE project_key = $1`, [
13002
- projectKey
13003
- ]);
13004
- await db.query(`DELETE FROM agents WHERE project_key = $1`, [projectKey]);
13005
- }
13006
- const countResult = await db.query(`SELECT COUNT(*) as count FROM events WHERE project_key = $1 AND sequence > $2`, [projectKey, fromSequence]);
13007
- const total = parseInt(countResult.rows[0]?.count ?? "0");
13008
- if (total === 0) {
13009
- return { eventsReplayed: 0, duration: Date.now() - startTime };
13010
- }
13011
- let processed = 0;
13012
- let offset = 0;
13013
- while (processed < total) {
13014
- const events = await readEvents({
13015
- projectKey,
13016
- afterSequence: fromSequence,
13017
- limit: batchSize,
13018
- offset
13019
- }, projectPath);
13020
- if (events.length === 0)
13021
- break;
13022
- for (const event of events) {
13023
- await updateMaterializedViews(db, event);
13024
- }
13025
- processed += events.length;
13026
- const percent = Math.round(processed / total * 100);
13027
- await onBatch(events, { processed, total, percent });
13028
- console.log(`[SwarmMail] Replaying events: ${processed}/${total} (${percent}%)`);
13029
- offset += batchSize;
13030
- }
13031
- return {
13032
- eventsReplayed: processed,
13033
- duration: Date.now() - startTime
13034
- };
13035
- });
13036
- }
13037
- async function updateMaterializedViews(db, event) {
13038
- try {
13039
- switch (event.type) {
13040
- case "agent_registered":
13041
- await handleAgentRegistered(db, event);
13042
- break;
13043
- case "agent_active":
13044
- await db.query(`UPDATE agents SET last_active_at = $1 WHERE project_key = $2 AND name = $3`, [event.timestamp, event.project_key, event.agent_name]);
13045
- break;
13046
- case "message_sent":
13047
- await handleMessageSent(db, event);
13048
- break;
13049
- case "message_read":
13050
- await db.query(`UPDATE message_recipients SET read_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
13051
- break;
13052
- case "message_acked":
13053
- await db.query(`UPDATE message_recipients SET acked_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
13054
- break;
13055
- case "file_reserved":
13056
- await handleFileReserved(db, event);
13057
- break;
13058
- case "file_released":
13059
- await handleFileReleased(db, event);
13060
- break;
13061
- case "task_started":
13062
- case "task_progress":
13063
- case "task_completed":
13064
- case "task_blocked":
13065
- break;
13066
- }
13067
- } catch (error45) {
13068
- console.error("[SwarmMail] Failed to update materialized views", {
13069
- eventType: event.type,
13070
- eventId: event.id,
13071
- error: error45
13072
- });
13073
- throw error45;
13074
- }
13075
- }
13076
- async function handleAgentRegistered(db, event) {
13077
- await db.query(`INSERT INTO agents (project_key, name, program, model, task_description, registered_at, last_active_at)
13078
- VALUES ($1, $2, $3, $4, $5, $6, $6)
13079
- ON CONFLICT (project_key, name) DO UPDATE SET
13080
- program = EXCLUDED.program,
13081
- model = EXCLUDED.model,
13082
- task_description = EXCLUDED.task_description,
13083
- last_active_at = EXCLUDED.last_active_at`, [
13084
- event.project_key,
13085
- event.agent_name,
13086
- event.program,
13087
- event.model,
13088
- event.task_description || null,
13089
- event.timestamp
13090
- ]);
13091
- }
13092
- async function handleMessageSent(db, event) {
13093
- console.log("[SwarmMail] Handling message sent event", {
13094
- from: event.from_agent,
13095
- to: event.to_agents,
13096
- subject: event.subject,
13097
- projectKey: event.project_key
13098
- });
13099
- const result = await db.query(`INSERT INTO messages (project_key, from_agent, subject, body, thread_id, importance, ack_required, created_at)
13100
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
13101
- RETURNING id`, [
13102
- event.project_key,
13103
- event.from_agent,
13104
- event.subject,
13105
- event.body,
13106
- event.thread_id || null,
13107
- event.importance,
13108
- event.ack_required,
13109
- event.timestamp
13110
- ]);
13111
- const msgRow = result.rows[0];
13112
- if (!msgRow) {
13113
- throw new Error("Failed to insert message - no row returned");
13114
- }
13115
- const messageId = msgRow.id;
13116
- if (event.to_agents.length > 0) {
13117
- const values = event.to_agents.map((_, i) => `($1, $${i + 2})`).join(", ");
13118
- const params = [messageId, ...event.to_agents];
13119
- await db.query(`INSERT INTO message_recipients (message_id, agent_name)
13120
- VALUES ${values}
13121
- ON CONFLICT DO NOTHING`, params);
13122
- console.log("[SwarmMail] Message recipients inserted", {
13123
- messageId,
13124
- recipientCount: event.to_agents.length
13125
- });
13126
- }
13127
- }
13128
- async function handleFileReserved(db, event) {
13129
- console.log("[SwarmMail] Handling file reservation event", {
13130
- agent: event.agent_name,
13131
- paths: event.paths,
13132
- exclusive: event.exclusive,
13133
- projectKey: event.project_key
13134
- });
13135
- if (event.paths.length > 0) {
13136
- const values = event.paths.map((_, i) => `($1, $2, $${i + 3}, $${event.paths.length + 3}, $${event.paths.length + 4}, $${event.paths.length + 5}, $${event.paths.length + 6})`).join(", ");
13137
- const params = [
13138
- event.project_key,
13139
- event.agent_name,
13140
- ...event.paths,
13141
- event.exclusive,
13142
- event.reason || null,
13143
- event.timestamp,
13144
- event.expires_at
13145
- ];
13146
- if (event.paths.length > 0) {
13147
- await db.query(`DELETE FROM reservations
13148
- WHERE project_key = $1
13149
- AND agent_name = $2
13150
- AND path_pattern = ANY($3)
13151
- AND released_at IS NULL`, [event.project_key, event.agent_name, event.paths]);
13152
- }
13153
- await db.query(`INSERT INTO reservations (project_key, agent_name, path_pattern, exclusive, reason, created_at, expires_at)
13154
- VALUES ${values}`, params);
13155
- console.log("[SwarmMail] File reservations inserted", {
13156
- agent: event.agent_name,
13157
- reservationCount: event.paths.length
13158
- });
13159
- }
13160
- }
13161
- async function handleFileReleased(db, event) {
13162
- if (event.type !== "file_released")
13163
- return;
13164
- if (event.reservation_ids && event.reservation_ids.length > 0) {
13165
- await db.query(`UPDATE reservations SET released_at = $1 WHERE id = ANY($2)`, [event.timestamp, event.reservation_ids]);
13166
- } else if (event.paths && event.paths.length > 0) {
13167
- await db.query(`UPDATE reservations SET released_at = $1
13168
- WHERE project_key = $2 AND agent_name = $3 AND path_pattern = ANY($4) AND released_at IS NULL`, [event.timestamp, event.project_key, event.agent_name, event.paths]);
13169
- } else {
13170
- await db.query(`UPDATE reservations SET released_at = $1
13171
- WHERE project_key = $2 AND agent_name = $3 AND released_at IS NULL`, [event.timestamp, event.project_key, event.agent_name]);
13172
- }
13173
- }
13174
- async function registerAgent(projectKey, agentName, options2 = {}, projectPath) {
13175
- const event = createEvent("agent_registered", {
13176
- project_key: projectKey,
13177
- agent_name: agentName,
13178
- program: options2.program || "opencode",
13179
- model: options2.model || "unknown",
13180
- task_description: options2.taskDescription
13181
- });
13182
- return appendEvent(event, projectPath);
13183
- }
13184
- async function sendMessage(projectKey, fromAgent, toAgents, subject, body, options2 = {}, projectPath) {
13185
- const event = createEvent("message_sent", {
13186
- project_key: projectKey,
13187
- from_agent: fromAgent,
13188
- to_agents: toAgents,
13189
- subject,
13190
- body,
13191
- thread_id: options2.threadId,
13192
- importance: options2.importance || "normal",
13193
- ack_required: options2.ackRequired || false
13194
- });
13195
- return appendEvent(event, projectPath);
13196
- }
13197
- async function reserveFiles(projectKey, agentName, paths, options2 = {}, projectPath) {
13198
- const ttlSeconds = options2.ttlSeconds || 3600;
13199
- const event = createEvent("file_reserved", {
13200
- project_key: projectKey,
13201
- agent_name: agentName,
13202
- paths,
13203
- reason: options2.reason,
13204
- exclusive: options2.exclusive ?? true,
13205
- ttl_seconds: ttlSeconds,
13206
- expires_at: Date.now() + ttlSeconds * 1000
13207
- });
13208
- return appendEvent(event, projectPath);
13209
- }
13210
- var TIMESTAMP_SAFE_UNTIL;
13211
- var init_store = __esm(() => {
13212
- init_streams();
13213
- init_events();
13214
- TIMESTAMP_SAFE_UNTIL = new Date("2286-01-01").getTime();
13215
- });
13216
-
13217
- // node_modules/.pnpm/@isaacs+balanced-match@4.0.1/node_modules/@isaacs/balanced-match/dist/esm/index.js
13218
- var balanced = (a, b, str2) => {
13219
- const ma = a instanceof RegExp ? maybeMatch(a, str2) : a;
13220
- const mb = b instanceof RegExp ? maybeMatch(b, str2) : b;
13221
- const r = ma !== null && mb != null && range(ma, mb, str2);
13222
- return r && {
13223
- start: r[0],
13224
- end: r[1],
13225
- pre: str2.slice(0, r[0]),
13226
- body: str2.slice(r[0] + ma.length, r[1]),
13227
- post: str2.slice(r[1] + mb.length)
13228
- };
13229
- }, maybeMatch = (reg, str2) => {
13230
- const m = str2.match(reg);
13231
- return m ? m[0] : null;
13232
- }, range = (a, b, str2) => {
13233
- let begs, beg, left, right = undefined, result;
13234
- let ai = str2.indexOf(a);
13235
- let bi = str2.indexOf(b, ai + 1);
13236
- let i = ai;
13237
- if (ai >= 0 && bi > 0) {
13238
- if (a === b) {
13239
- return [ai, bi];
13240
- }
13241
- begs = [];
13242
- left = str2.length;
13243
- while (i >= 0 && !result) {
13244
- if (i === ai) {
13245
- begs.push(i);
13246
- ai = str2.indexOf(a, i + 1);
13247
- } else if (begs.length === 1) {
13248
- const r = begs.pop();
13249
- if (r !== undefined)
13250
- result = [r, bi];
13251
- } else {
13252
- beg = begs.pop();
13253
- if (beg !== undefined && beg < left) {
13254
- left = beg;
13255
- right = bi;
13256
- }
13257
- bi = str2.indexOf(b, i + 1);
13258
- }
13259
- i = ai < bi && ai >= 0 ? ai : bi;
13260
- }
13261
- if (begs.length && right !== undefined) {
13262
- result = [left, right];
13263
- }
13264
- }
13265
- return result;
13266
- };
13267
-
13268
- // node_modules/.pnpm/@isaacs+brace-expansion@5.0.0/node_modules/@isaacs/brace-expansion/dist/esm/index.js
13269
- function numeric(str2) {
13270
- return !isNaN(str2) ? parseInt(str2, 10) : str2.charCodeAt(0);
13271
- }
13272
- function escapeBraces(str2) {
13273
- return str2.replace(slashPattern, escSlash).replace(openPattern, escOpen).replace(closePattern, escClose).replace(commaPattern, escComma).replace(periodPattern, escPeriod);
13274
- }
13275
- function unescapeBraces(str2) {
13276
- return str2.replace(escSlashPattern, "\\").replace(escOpenPattern, "{").replace(escClosePattern, "}").replace(escCommaPattern, ",").replace(escPeriodPattern, ".");
13277
- }
13278
- function parseCommaParts(str2) {
13279
- if (!str2) {
13280
- return [""];
13281
- }
13282
- const parts = [];
13283
- const m = balanced("{", "}", str2);
13284
- if (!m) {
13285
- return str2.split(",");
13286
- }
13287
- const { pre, body, post } = m;
13288
- const p = pre.split(",");
13289
- p[p.length - 1] += "{" + body + "}";
13290
- const postParts = parseCommaParts(post);
13291
- if (post.length) {
13292
- p[p.length - 1] += postParts.shift();
13293
- p.push.apply(p, postParts);
13294
- }
13295
- parts.push.apply(parts, p);
13296
- return parts;
13297
- }
13298
- function expand(str2) {
13299
- if (!str2) {
13300
- return [];
13301
- }
13302
- if (str2.slice(0, 2) === "{}") {
13303
- str2 = "\\{\\}" + str2.slice(2);
13304
- }
13305
- return expand_(escapeBraces(str2), true).map(unescapeBraces);
13306
- }
13307
- function embrace(str2) {
13308
- return "{" + str2 + "}";
13309
- }
13310
- function isPadded(el) {
13311
- return /^-?0\d/.test(el);
13312
- }
13313
- function lte(i, y) {
13314
- return i <= y;
13315
- }
13316
- function gte(i, y) {
13317
- return i >= y;
13318
- }
13319
- function expand_(str2, isTop) {
13320
- const expansions = [];
13321
- const m = balanced("{", "}", str2);
13322
- if (!m)
13323
- return [str2];
13324
- const pre = m.pre;
13325
- const post = m.post.length ? expand_(m.post, false) : [""];
13326
- if (/\$$/.test(m.pre)) {
13327
- for (let k = 0;k < post.length; k++) {
13328
- const expansion = pre + "{" + m.body + "}" + post[k];
13329
- expansions.push(expansion);
13330
- }
13331
- } else {
13332
- const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
13333
- const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
13334
- const isSequence = isNumericSequence || isAlphaSequence;
13335
- const isOptions = m.body.indexOf(",") >= 0;
13336
- if (!isSequence && !isOptions) {
13337
- if (m.post.match(/,(?!,).*\}/)) {
13338
- str2 = m.pre + "{" + m.body + escClose + m.post;
13339
- return expand_(str2);
13340
- }
13341
- return [str2];
13342
- }
13343
- let n;
13344
- if (isSequence) {
13345
- n = m.body.split(/\.\./);
13346
- } else {
13347
- n = parseCommaParts(m.body);
13348
- if (n.length === 1 && n[0] !== undefined) {
13349
- n = expand_(n[0], false).map(embrace);
13350
- if (n.length === 1) {
13351
- return post.map((p) => m.pre + n[0] + p);
13352
- }
13353
- }
13354
- }
13355
- let N;
13356
- if (isSequence && n[0] !== undefined && n[1] !== undefined) {
13357
- const x = numeric(n[0]);
13358
- const y = numeric(n[1]);
13359
- const width = Math.max(n[0].length, n[1].length);
13360
- let incr = n.length === 3 && n[2] !== undefined ? Math.abs(numeric(n[2])) : 1;
13361
- let test = lte;
13362
- const reverse = y < x;
13363
- if (reverse) {
13364
- incr *= -1;
13365
- test = gte;
13366
- }
13367
- const pad = n.some(isPadded);
13368
- N = [];
13369
- for (let i = x;test(i, y); i += incr) {
13370
- let c;
13371
- if (isAlphaSequence) {
13372
- c = String.fromCharCode(i);
13373
- if (c === "\\") {
13374
- c = "";
13375
- }
13376
- } else {
13377
- c = String(i);
13378
- if (pad) {
13379
- const need = width - c.length;
13380
- if (need > 0) {
13381
- const z = new Array(need + 1).join("0");
13382
- if (i < 0) {
13383
- c = "-" + z + c.slice(1);
13384
- } else {
13385
- c = z + c;
13386
- }
13387
- }
13388
- }
13389
- }
13390
- N.push(c);
13391
- }
13392
- } else {
13393
- N = [];
13394
- for (let j = 0;j < n.length; j++) {
13395
- N.push.apply(N, expand_(n[j], false));
13396
- }
13397
- }
13398
- for (let j = 0;j < N.length; j++) {
13399
- for (let k = 0;k < post.length; k++) {
13400
- const expansion = pre + N[j] + post[k];
13401
- if (!isTop || isSequence || expansion) {
13402
- expansions.push(expansion);
13403
- }
13404
- }
13405
- }
13406
- }
13407
- return expansions;
13057
+ for (let j = 0;j < N.length; j++) {
13058
+ for (let k = 0;k < post.length; k++) {
13059
+ const expansion = pre + N[j] + post[k];
13060
+ if (!isTop || isSequence || expansion) {
13061
+ expansions.push(expansion);
13062
+ }
13063
+ }
13064
+ }
13065
+ }
13066
+ return expansions;
13408
13067
  }
13409
13068
  var escSlash, escOpen, escClose, escComma, escPeriod, escSlashPattern, escOpenPattern, escClosePattern, escCommaPattern, escPeriodPattern, slashPattern, openPattern, closePattern, commaPattern, periodPattern;
13410
13069
  var init_esm = __esm(() => {
@@ -14753,6 +14412,79 @@ function pathMatches(path2, pattern) {
14753
14412
  }
14754
14413
  return minimatch(path2, pattern);
14755
14414
  }
14415
+ async function getEvalRecords(projectKey, options2, projectPath) {
14416
+ const db = await getDatabase(projectPath);
14417
+ const conditions = ["project_key = $1"];
14418
+ const params = [projectKey];
14419
+ let paramIndex = 2;
14420
+ if (options2?.strategy) {
14421
+ conditions.push(`strategy = $${paramIndex++}`);
14422
+ params.push(options2.strategy);
14423
+ }
14424
+ const whereClause = conditions.join(" AND ");
14425
+ let query = `
14426
+ SELECT id, project_key, task, context, strategy, epic_title, subtasks,
14427
+ outcomes, overall_success, total_duration_ms, total_errors,
14428
+ human_accepted, human_modified, human_notes,
14429
+ file_overlap_count, scope_accuracy, time_balance_ratio,
14430
+ created_at, updated_at
14431
+ FROM eval_records
14432
+ WHERE ${whereClause}
14433
+ ORDER BY created_at DESC
14434
+ `;
14435
+ if (options2?.limit) {
14436
+ query += ` LIMIT $${paramIndex}`;
14437
+ params.push(options2.limit);
14438
+ }
14439
+ const result = await db.query(query, params);
14440
+ return result.rows.map((row) => ({
14441
+ id: row.id,
14442
+ project_key: row.project_key,
14443
+ task: row.task,
14444
+ context: row.context,
14445
+ strategy: row.strategy,
14446
+ epic_title: row.epic_title,
14447
+ subtasks: typeof row.subtasks === "string" ? JSON.parse(row.subtasks) : row.subtasks,
14448
+ outcomes: row.outcomes ? typeof row.outcomes === "string" ? JSON.parse(row.outcomes) : row.outcomes : undefined,
14449
+ overall_success: row.overall_success,
14450
+ total_duration_ms: row.total_duration_ms,
14451
+ total_errors: row.total_errors,
14452
+ human_accepted: row.human_accepted,
14453
+ human_modified: row.human_modified,
14454
+ human_notes: row.human_notes,
14455
+ file_overlap_count: row.file_overlap_count,
14456
+ scope_accuracy: row.scope_accuracy,
14457
+ time_balance_ratio: row.time_balance_ratio,
14458
+ created_at: parseInt(row.created_at),
14459
+ updated_at: parseInt(row.updated_at)
14460
+ }));
14461
+ }
14462
+ async function getEvalStats(projectKey, projectPath) {
14463
+ const db = await getDatabase(projectPath);
14464
+ const overallResult = await db.query(`SELECT
14465
+ COUNT(*) as total_records,
14466
+ COUNT(*) FILTER (WHERE overall_success = true) as success_count,
14467
+ AVG(total_duration_ms) as avg_duration
14468
+ FROM eval_records
14469
+ WHERE project_key = $1`, [projectKey]);
14470
+ const totalRecords = parseInt(overallResult.rows[0]?.total_records || "0");
14471
+ const successCount = parseInt(overallResult.rows[0]?.success_count || "0");
14472
+ const avgDurationMs = parseFloat(overallResult.rows[0]?.avg_duration || "0");
14473
+ const strategyResult = await db.query(`SELECT strategy, COUNT(*) as count
14474
+ FROM eval_records
14475
+ WHERE project_key = $1
14476
+ GROUP BY strategy`, [projectKey]);
14477
+ const byStrategy = {};
14478
+ for (const row of strategyResult.rows) {
14479
+ byStrategy[row.strategy] = parseInt(row.count);
14480
+ }
14481
+ return {
14482
+ totalRecords,
14483
+ successRate: totalRecords > 0 ? successCount / totalRecords : 0,
14484
+ avgDurationMs,
14485
+ byStrategy
14486
+ };
14487
+ }
14756
14488
  var init_projections = __esm(() => {
14757
14489
  init_streams();
14758
14490
  init_esm2();
@@ -15476,10 +15208,279 @@ var init_migrations = __esm(() => {
15476
15208
  CREATE INDEX IF NOT EXISTS idx_deferred_resolved ON deferred(resolved);
15477
15209
  `,
15478
15210
  down: `DROP TABLE IF EXISTS deferred;`
15211
+ },
15212
+ {
15213
+ version: 3,
15214
+ description: "Add eval_records table for learning system",
15215
+ up: `
15216
+ CREATE TABLE IF NOT EXISTS eval_records (
15217
+ id TEXT PRIMARY KEY,
15218
+ project_key TEXT NOT NULL,
15219
+ task TEXT NOT NULL,
15220
+ context TEXT,
15221
+ strategy TEXT NOT NULL,
15222
+ epic_title TEXT NOT NULL,
15223
+ subtasks JSONB NOT NULL,
15224
+ outcomes JSONB,
15225
+ overall_success BOOLEAN,
15226
+ total_duration_ms INTEGER,
15227
+ total_errors INTEGER,
15228
+ human_accepted BOOLEAN,
15229
+ human_modified BOOLEAN,
15230
+ human_notes TEXT,
15231
+ file_overlap_count INTEGER,
15232
+ scope_accuracy REAL,
15233
+ time_balance_ratio REAL,
15234
+ created_at BIGINT NOT NULL,
15235
+ updated_at BIGINT NOT NULL
15236
+ );
15237
+ CREATE INDEX IF NOT EXISTS idx_eval_records_project ON eval_records(project_key);
15238
+ CREATE INDEX IF NOT EXISTS idx_eval_records_strategy ON eval_records(strategy);
15239
+ `,
15240
+ down: `DROP TABLE IF EXISTS eval_records;`
15241
+ },
15242
+ {
15243
+ version: 4,
15244
+ description: "Add swarm_contexts table for context recovery",
15245
+ up: `
15246
+ CREATE TABLE IF NOT EXISTS swarm_contexts (
15247
+ id TEXT PRIMARY KEY,
15248
+ epic_id TEXT NOT NULL,
15249
+ bead_id TEXT NOT NULL,
15250
+ strategy TEXT NOT NULL,
15251
+ files JSONB NOT NULL,
15252
+ dependencies JSONB NOT NULL,
15253
+ directives JSONB NOT NULL,
15254
+ recovery JSONB NOT NULL,
15255
+ created_at BIGINT NOT NULL,
15256
+ updated_at BIGINT NOT NULL
15257
+ );
15258
+ CREATE INDEX IF NOT EXISTS idx_swarm_contexts_epic ON swarm_contexts(epic_id);
15259
+ CREATE INDEX IF NOT EXISTS idx_swarm_contexts_bead ON swarm_contexts(bead_id);
15260
+ `,
15261
+ down: `DROP TABLE IF EXISTS swarm_contexts;`
15479
15262
  }
15480
15263
  ];
15481
15264
  });
15482
15265
 
15266
+ // src/streams/swarm-mail.ts
15267
+ function generateSwarmAgentName() {
15268
+ const adj = ADJECTIVES2[Math.floor(Math.random() * ADJECTIVES2.length)];
15269
+ const noun = NOUNS2[Math.floor(Math.random() * NOUNS2.length)];
15270
+ return `${adj}${noun}`;
15271
+ }
15272
+ async function initSwarmAgent(options2) {
15273
+ const {
15274
+ projectPath,
15275
+ agentName = generateSwarmAgentName(),
15276
+ program = "opencode",
15277
+ model = "unknown",
15278
+ taskDescription
15279
+ } = options2;
15280
+ await registerAgent(projectPath, agentName, { program, model, taskDescription }, projectPath);
15281
+ return {
15282
+ projectKey: projectPath,
15283
+ agentName
15284
+ };
15285
+ }
15286
+ async function sendSwarmMessage(options2) {
15287
+ const {
15288
+ projectPath,
15289
+ fromAgent,
15290
+ toAgents,
15291
+ subject,
15292
+ body,
15293
+ threadId,
15294
+ importance = "normal",
15295
+ ackRequired = false
15296
+ } = options2;
15297
+ await sendMessage(projectPath, fromAgent, toAgents, subject, body, { threadId, importance, ackRequired }, projectPath);
15298
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_streams(), exports_streams));
15299
+ const db = await getDatabase2(projectPath);
15300
+ const result = await db.query(`SELECT id FROM messages
15301
+ WHERE project_key = $1 AND from_agent = $2 AND subject = $3
15302
+ ORDER BY created_at DESC LIMIT 1`, [projectPath, fromAgent, subject]);
15303
+ const messageId = result.rows[0]?.id ?? 0;
15304
+ return {
15305
+ success: true,
15306
+ messageId,
15307
+ threadId,
15308
+ recipientCount: toAgents.length
15309
+ };
15310
+ }
15311
+ async function getSwarmInbox(options2) {
15312
+ const {
15313
+ projectPath,
15314
+ agentName,
15315
+ limit = MAX_INBOX_LIMIT2,
15316
+ urgentOnly = false,
15317
+ unreadOnly = false,
15318
+ includeBodies = false
15319
+ } = options2;
15320
+ const effectiveLimit = Math.min(limit, MAX_INBOX_LIMIT2);
15321
+ const messages = await getInbox(projectPath, agentName, {
15322
+ limit: effectiveLimit,
15323
+ urgentOnly,
15324
+ unreadOnly,
15325
+ includeBodies
15326
+ }, projectPath);
15327
+ return {
15328
+ messages: messages.map((m) => ({
15329
+ id: m.id,
15330
+ from_agent: m.from_agent,
15331
+ subject: m.subject,
15332
+ body: includeBodies ? m.body : undefined,
15333
+ thread_id: m.thread_id,
15334
+ importance: m.importance,
15335
+ created_at: m.created_at
15336
+ })),
15337
+ total: messages.length
15338
+ };
15339
+ }
15340
+ async function readSwarmMessage(options2) {
15341
+ const { projectPath, messageId, agentName, markAsRead = false } = options2;
15342
+ const message = await getMessage(projectPath, messageId, projectPath);
15343
+ if (!message) {
15344
+ return null;
15345
+ }
15346
+ if (markAsRead && agentName) {
15347
+ await appendEvent(createEvent("message_read", {
15348
+ project_key: projectPath,
15349
+ message_id: messageId,
15350
+ agent_name: agentName
15351
+ }), projectPath);
15352
+ }
15353
+ return {
15354
+ id: message.id,
15355
+ from_agent: message.from_agent,
15356
+ subject: message.subject,
15357
+ body: message.body,
15358
+ thread_id: message.thread_id,
15359
+ importance: message.importance,
15360
+ created_at: message.created_at
15361
+ };
15362
+ }
15363
+ async function reserveSwarmFiles(options2) {
15364
+ const {
15365
+ projectPath,
15366
+ agentName,
15367
+ paths,
15368
+ reason,
15369
+ exclusive = true,
15370
+ ttlSeconds = DEFAULT_TTL_SECONDS2
15371
+ } = options2;
15372
+ const conflicts = await checkConflicts(projectPath, agentName, paths, projectPath);
15373
+ await reserveFiles(projectPath, agentName, paths, { reason, exclusive, ttlSeconds }, projectPath);
15374
+ const reservations = await getActiveReservations(projectPath, projectPath, agentName);
15375
+ const granted = reservations.filter((r) => paths.includes(r.path_pattern)).map((r) => ({
15376
+ id: r.id,
15377
+ path_pattern: r.path_pattern,
15378
+ exclusive: r.exclusive,
15379
+ expiresAt: r.expires_at
15380
+ }));
15381
+ return {
15382
+ granted,
15383
+ conflicts: conflicts.map((c) => ({
15384
+ path: c.path,
15385
+ holder: c.holder,
15386
+ pattern: c.pattern
15387
+ }))
15388
+ };
15389
+ }
15390
+ async function releaseSwarmFiles(options2) {
15391
+ const { projectPath, agentName, paths, reservationIds } = options2;
15392
+ const currentReservations = await getActiveReservations(projectPath, projectPath, agentName);
15393
+ let releaseCount = 0;
15394
+ if (paths && paths.length > 0) {
15395
+ releaseCount = currentReservations.filter((r) => paths.includes(r.path_pattern)).length;
15396
+ } else if (reservationIds && reservationIds.length > 0) {
15397
+ releaseCount = currentReservations.filter((r) => reservationIds.includes(r.id)).length;
15398
+ } else {
15399
+ releaseCount = currentReservations.length;
15400
+ }
15401
+ await appendEvent(createEvent("file_released", {
15402
+ project_key: projectPath,
15403
+ agent_name: agentName,
15404
+ paths,
15405
+ reservation_ids: reservationIds
15406
+ }), projectPath);
15407
+ return {
15408
+ released: releaseCount,
15409
+ releasedAt: Date.now()
15410
+ };
15411
+ }
15412
+ async function acknowledgeSwarmMessage(options2) {
15413
+ const { projectPath, messageId, agentName } = options2;
15414
+ const timestamp = Date.now();
15415
+ await appendEvent(createEvent("message_acked", {
15416
+ project_key: projectPath,
15417
+ message_id: messageId,
15418
+ agent_name: agentName
15419
+ }), projectPath);
15420
+ return {
15421
+ acknowledged: true,
15422
+ acknowledgedAt: new Date(timestamp).toISOString()
15423
+ };
15424
+ }
15425
+ async function checkSwarmHealth(projectPath) {
15426
+ const healthy = await isDatabaseHealthy(projectPath);
15427
+ if (!healthy) {
15428
+ return {
15429
+ healthy: false,
15430
+ database: "disconnected"
15431
+ };
15432
+ }
15433
+ const stats = await getDatabaseStats(projectPath);
15434
+ return {
15435
+ healthy: true,
15436
+ database: "connected",
15437
+ stats
15438
+ };
15439
+ }
15440
+ var MAX_INBOX_LIMIT2 = 5, DEFAULT_TTL_SECONDS2 = 3600, ADJECTIVES2, NOUNS2;
15441
+ var init_swarm_mail = __esm(() => {
15442
+ init_events();
15443
+ init_streams();
15444
+ init_projections();
15445
+ init_store();
15446
+ ADJECTIVES2 = [
15447
+ "Blue",
15448
+ "Red",
15449
+ "Green",
15450
+ "Gold",
15451
+ "Silver",
15452
+ "Swift",
15453
+ "Bright",
15454
+ "Dark",
15455
+ "Calm",
15456
+ "Bold",
15457
+ "Wise",
15458
+ "Quick",
15459
+ "Warm",
15460
+ "Cool",
15461
+ "Pure",
15462
+ "Wild"
15463
+ ];
15464
+ NOUNS2 = [
15465
+ "Lake",
15466
+ "Stone",
15467
+ "River",
15468
+ "Mountain",
15469
+ "Forest",
15470
+ "Ocean",
15471
+ "Star",
15472
+ "Moon",
15473
+ "Wind",
15474
+ "Fire",
15475
+ "Cloud",
15476
+ "Storm",
15477
+ "Dawn",
15478
+ "Dusk",
15479
+ "Hawk",
15480
+ "Wolf"
15481
+ ];
15482
+ });
15483
+
15483
15484
  // src/streams/index.ts
15484
15485
  var exports_streams = {};
15485
15486
  __export(exports_streams, {
@@ -15494,7 +15495,7 @@ __export(exports_streams, {
15494
15495
  reserveSwarmFiles: () => reserveSwarmFiles,
15495
15496
  reserveFiles: () => reserveFiles,
15496
15497
  reserveAgentFiles: () => reserveAgentFiles,
15497
- replayEventsBatched: () => replayEventsBatched,
15498
+ replayEventsBatched: () => replayEventsBatched2,
15498
15499
  replayEvents: () => replayEvents,
15499
15500
  releaseSwarmFiles: () => releaseSwarmFiles,
15500
15501
  releaseAgentFiles: () => releaseAgentFiles,
@@ -15516,6 +15517,8 @@ __export(exports_streams, {
15516
15517
  getLatestSequence: () => getLatestSequence,
15517
15518
  getInbox: () => getInbox,
15518
15519
  getEventTimeline: () => getEventTimeline,
15520
+ getEvalStats: () => getEvalStats,
15521
+ getEvalRecords: () => getEvalRecords,
15519
15522
  getDatabaseStats: () => getDatabaseStats,
15520
15523
  getDatabasePath: () => getDatabasePath,
15521
15524
  getDatabase: () => getDatabase,
@@ -15543,12 +15546,17 @@ __export(exports_streams, {
15543
15546
  TaskProgressEventSchema: () => TaskProgressEventSchema,
15544
15547
  TaskCompletedEventSchema: () => TaskCompletedEventSchema,
15545
15548
  TaskBlockedEventSchema: () => TaskBlockedEventSchema,
15549
+ SwarmRecoveredEventSchema: () => SwarmRecoveredEventSchema,
15550
+ SwarmCheckpointedEventSchema: () => SwarmCheckpointedEventSchema,
15551
+ SubtaskOutcomeEventSchema: () => SubtaskOutcomeEventSchema,
15546
15552
  PGlite: () => PGlite,
15547
15553
  MessageSentEventSchema: () => MessageSentEventSchema,
15548
15554
  MessageReadEventSchema: () => MessageReadEventSchema,
15549
15555
  MessageAckedEventSchema: () => MessageAckedEventSchema,
15556
+ HumanFeedbackEventSchema: () => HumanFeedbackEventSchema,
15550
15557
  FileReservedEventSchema: () => FileReservedEventSchema,
15551
15558
  FileReleasedEventSchema: () => FileReleasedEventSchema,
15559
+ DecompositionGeneratedEventSchema: () => DecompositionGeneratedEventSchema,
15552
15560
  BaseEventSchema: () => BaseEventSchema,
15553
15561
  AgentRegisteredEventSchema: () => AgentRegisteredEventSchema,
15554
15562
  AgentEventSchema: () => AgentEventSchema,
@@ -15829,271 +15837,627 @@ async function isDatabaseHealthy(projectPath) {
15829
15837
  return false;
15830
15838
  }
15831
15839
  }
15832
- async function getDatabaseStats(projectPath) {
15840
+ async function getDatabaseStats(projectPath) {
15841
+ const db = await getDatabase(projectPath);
15842
+ const [events2, agents, messages, reservations] = await Promise.all([
15843
+ db.query("SELECT COUNT(*) as count FROM events"),
15844
+ db.query("SELECT COUNT(*) as count FROM agents"),
15845
+ db.query("SELECT COUNT(*) as count FROM messages"),
15846
+ db.query("SELECT COUNT(*) as count FROM reservations WHERE released_at IS NULL")
15847
+ ]);
15848
+ return {
15849
+ events: parseInt(events2.rows[0]?.count || "0"),
15850
+ agents: parseInt(agents.rows[0]?.count || "0"),
15851
+ messages: parseInt(messages.rows[0]?.count || "0"),
15852
+ reservations: parseInt(reservations.rows[0]?.count || "0")
15853
+ };
15854
+ }
15855
+ function handleExit() {
15856
+ const dbsToClose = Array.from(instances.values());
15857
+ for (const db of dbsToClose) {
15858
+ try {
15859
+ db.close().catch(() => {});
15860
+ } catch {}
15861
+ }
15862
+ }
15863
+ var SLOW_QUERY_THRESHOLD_MS = 100, DEBUG_LOG_PATH, instances, pendingInstances, schemaInitialized, degradedInstances, lastAccess, MAX_CACHE_SIZE = 10;
15864
+ var init_streams = __esm(() => {
15865
+ init_agent_mail();
15866
+ init_debug();
15867
+ init_events();
15868
+ init_migrations();
15869
+ init_projections();
15870
+ init_store();
15871
+ init_swarm_mail();
15872
+ DEBUG_LOG_PATH = join(homedir(), ".opencode", "streams-debug.log");
15873
+ instances = new Map;
15874
+ pendingInstances = new Map;
15875
+ schemaInitialized = new Map;
15876
+ degradedInstances = new Map;
15877
+ lastAccess = new Map;
15878
+ process.on("exit", handleExit);
15879
+ process.on("SIGINT", () => {
15880
+ handleExit();
15881
+ process.exit(0);
15882
+ });
15883
+ process.on("SIGTERM", () => {
15884
+ handleExit();
15885
+ process.exit(0);
15886
+ });
15887
+ });
15888
+
15889
+ // src/streams/store.ts
15890
+ function parseTimestamp(timestamp) {
15891
+ const ts = parseInt(timestamp, 10);
15892
+ if (Number.isNaN(ts)) {
15893
+ throw new Error(`[SwarmMail] Invalid timestamp: ${timestamp}`);
15894
+ }
15895
+ if (ts > Number.MAX_SAFE_INTEGER) {
15896
+ console.warn(`[SwarmMail] Timestamp ${timestamp} exceeds MAX_SAFE_INTEGER (year 2286+), precision may be lost`);
15897
+ }
15898
+ return ts;
15899
+ }
15900
+ async function appendEvent(event, projectPath) {
15901
+ const db = await getDatabase(projectPath);
15902
+ const { type, project_key, timestamp, ...rest } = event;
15903
+ console.log("[SwarmMail] Appending event", {
15904
+ type,
15905
+ projectKey: project_key,
15906
+ timestamp
15907
+ });
15908
+ const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
15909
+ VALUES ($1, $2, $3, $4)
15910
+ RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
15911
+ const row = result.rows[0];
15912
+ if (!row) {
15913
+ throw new Error("Failed to insert event - no row returned");
15914
+ }
15915
+ const { id, sequence } = row;
15916
+ console.log("[SwarmMail] Event appended", {
15917
+ type,
15918
+ id,
15919
+ sequence,
15920
+ projectKey: project_key
15921
+ });
15922
+ console.debug("[SwarmMail] Updating materialized views", { type, id });
15923
+ await updateMaterializedViews(db, { ...event, id, sequence });
15924
+ return { ...event, id, sequence };
15925
+ }
15926
+ async function appendEvents(events2, projectPath) {
15927
+ return withTiming("appendEvents", async () => {
15928
+ const db = await getDatabase(projectPath);
15929
+ const results = [];
15930
+ await db.exec("BEGIN");
15931
+ try {
15932
+ for (const event of events2) {
15933
+ const { type, project_key, timestamp, ...rest } = event;
15934
+ const result = await db.query(`INSERT INTO events (type, project_key, timestamp, data)
15935
+ VALUES ($1, $2, $3, $4)
15936
+ RETURNING id, sequence`, [type, project_key, timestamp, JSON.stringify(rest)]);
15937
+ const row = result.rows[0];
15938
+ if (!row) {
15939
+ throw new Error("Failed to insert event - no row returned");
15940
+ }
15941
+ const { id, sequence } = row;
15942
+ const enrichedEvent = { ...event, id, sequence };
15943
+ await updateMaterializedViews(db, enrichedEvent);
15944
+ results.push(enrichedEvent);
15945
+ }
15946
+ await db.exec("COMMIT");
15947
+ } catch (e) {
15948
+ let rollbackError = null;
15949
+ try {
15950
+ await db.exec("ROLLBACK");
15951
+ } catch (rbErr) {
15952
+ rollbackError = rbErr;
15953
+ console.error("[SwarmMail] ROLLBACK failed:", rbErr);
15954
+ }
15955
+ if (rollbackError) {
15956
+ const compositeError = new Error(`Transaction failed: ${e instanceof Error ? e.message : String(e)}. ` + `ROLLBACK also failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}. ` + `Database may be in inconsistent state.`);
15957
+ compositeError.originalError = e;
15958
+ compositeError.rollbackError = rollbackError;
15959
+ throw compositeError;
15960
+ }
15961
+ throw e;
15962
+ }
15963
+ return results;
15964
+ });
15965
+ }
15966
+ async function readEvents(options2 = {}, projectPath) {
15967
+ return withTiming("readEvents", async () => {
15968
+ const db = await getDatabase(projectPath);
15969
+ const conditions = [];
15970
+ const params = [];
15971
+ let paramIndex = 1;
15972
+ if (options2.projectKey) {
15973
+ conditions.push(`project_key = $${paramIndex++}`);
15974
+ params.push(options2.projectKey);
15975
+ }
15976
+ if (options2.types && options2.types.length > 0) {
15977
+ conditions.push(`type = ANY($${paramIndex++})`);
15978
+ params.push(options2.types);
15979
+ }
15980
+ if (options2.since !== undefined) {
15981
+ conditions.push(`timestamp >= $${paramIndex++}`);
15982
+ params.push(options2.since);
15983
+ }
15984
+ if (options2.until !== undefined) {
15985
+ conditions.push(`timestamp <= $${paramIndex++}`);
15986
+ params.push(options2.until);
15987
+ }
15988
+ if (options2.afterSequence !== undefined) {
15989
+ conditions.push(`sequence > $${paramIndex++}`);
15990
+ params.push(options2.afterSequence);
15991
+ }
15992
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
15993
+ let query = `
15994
+ SELECT id, type, project_key, timestamp, sequence, data
15995
+ FROM events
15996
+ ${whereClause}
15997
+ ORDER BY sequence ASC
15998
+ `;
15999
+ if (options2.limit) {
16000
+ query += ` LIMIT $${paramIndex++}`;
16001
+ params.push(options2.limit);
16002
+ }
16003
+ if (options2.offset) {
16004
+ query += ` OFFSET $${paramIndex++}`;
16005
+ params.push(options2.offset);
16006
+ }
16007
+ const result = await db.query(query, params);
16008
+ return result.rows.map((row) => {
16009
+ const data = typeof row.data === "string" ? JSON.parse(row.data) : row.data;
16010
+ return {
16011
+ id: row.id,
16012
+ type: row.type,
16013
+ project_key: row.project_key,
16014
+ timestamp: parseTimestamp(row.timestamp),
16015
+ sequence: row.sequence,
16016
+ ...data
16017
+ };
16018
+ });
16019
+ });
16020
+ }
16021
+ async function getLatestSequence(projectKey, projectPath) {
15833
16022
  const db = await getDatabase(projectPath);
15834
- const [events2, agents, messages, reservations] = await Promise.all([
15835
- db.query("SELECT COUNT(*) as count FROM events"),
15836
- db.query("SELECT COUNT(*) as count FROM agents"),
15837
- db.query("SELECT COUNT(*) as count FROM messages"),
15838
- db.query("SELECT COUNT(*) as count FROM reservations WHERE released_at IS NULL")
15839
- ]);
15840
- return {
15841
- events: parseInt(events2.rows[0]?.count || "0"),
15842
- agents: parseInt(agents.rows[0]?.count || "0"),
15843
- messages: parseInt(messages.rows[0]?.count || "0"),
15844
- reservations: parseInt(reservations.rows[0]?.count || "0")
15845
- };
15846
- }
15847
- function handleExit() {
15848
- const dbsToClose = Array.from(instances.values());
15849
- for (const db of dbsToClose) {
15850
- try {
15851
- db.close().catch(() => {});
15852
- } catch {}
15853
- }
16023
+ const query = projectKey ? "SELECT MAX(sequence) as seq FROM events WHERE project_key = $1" : "SELECT MAX(sequence) as seq FROM events";
16024
+ const params = projectKey ? [projectKey] : [];
16025
+ const result = await db.query(query, params);
16026
+ return result.rows[0]?.seq ?? 0;
15854
16027
  }
15855
- var SLOW_QUERY_THRESHOLD_MS = 100, DEBUG_LOG_PATH, instances, pendingInstances, schemaInitialized, degradedInstances, lastAccess, MAX_CACHE_SIZE = 10;
15856
- var init_streams = __esm(() => {
15857
- init_agent_mail();
15858
- init_debug();
15859
- init_events();
15860
- init_migrations();
15861
- init_projections();
15862
- init_store();
15863
- init_swarm_mail();
15864
- DEBUG_LOG_PATH = join(homedir(), ".opencode", "streams-debug.log");
15865
- instances = new Map;
15866
- pendingInstances = new Map;
15867
- schemaInitialized = new Map;
15868
- degradedInstances = new Map;
15869
- lastAccess = new Map;
15870
- process.on("exit", handleExit);
15871
- process.on("SIGINT", () => {
15872
- handleExit();
15873
- process.exit(0);
15874
- });
15875
- process.on("SIGTERM", () => {
15876
- handleExit();
15877
- process.exit(0);
16028
+ async function replayEvents(options2 = {}, projectPath) {
16029
+ return withTiming("replayEvents", async () => {
16030
+ const startTime = Date.now();
16031
+ const db = await getDatabase(projectPath);
16032
+ if (options2.clearViews) {
16033
+ if (options2.projectKey) {
16034
+ await db.query(`DELETE FROM message_recipients WHERE message_id IN (
16035
+ SELECT id FROM messages WHERE project_key = $1
16036
+ )`, [options2.projectKey]);
16037
+ await db.query(`DELETE FROM messages WHERE project_key = $1`, [
16038
+ options2.projectKey
16039
+ ]);
16040
+ await db.query(`DELETE FROM reservations WHERE project_key = $1`, [
16041
+ options2.projectKey
16042
+ ]);
16043
+ await db.query(`DELETE FROM agents WHERE project_key = $1`, [
16044
+ options2.projectKey
16045
+ ]);
16046
+ } else {
16047
+ await db.exec(`
16048
+ DELETE FROM message_recipients;
16049
+ DELETE FROM messages;
16050
+ DELETE FROM reservations;
16051
+ DELETE FROM agents;
16052
+ `);
16053
+ }
16054
+ }
16055
+ const events2 = await readEvents({
16056
+ projectKey: options2.projectKey,
16057
+ afterSequence: options2.fromSequence
16058
+ }, projectPath);
16059
+ for (const event of events2) {
16060
+ await updateMaterializedViews(db, event);
16061
+ }
16062
+ return {
16063
+ eventsReplayed: events2.length,
16064
+ duration: Date.now() - startTime
16065
+ };
15878
16066
  });
15879
- });
15880
-
15881
- // src/streams/swarm-mail.ts
15882
- function generateSwarmAgentName() {
15883
- const adj = ADJECTIVES2[Math.floor(Math.random() * ADJECTIVES2.length)];
15884
- const noun = NOUNS2[Math.floor(Math.random() * NOUNS2.length)];
15885
- return `${adj}${noun}`;
15886
16067
  }
15887
- async function initSwarmAgent(options2) {
15888
- const {
15889
- projectPath,
15890
- agentName = generateSwarmAgentName(),
15891
- program = "opencode",
15892
- model = "unknown",
15893
- taskDescription
15894
- } = options2;
15895
- await registerAgent(projectPath, agentName, { program, model, taskDescription }, projectPath);
15896
- return {
15897
- projectKey: projectPath,
15898
- agentName
15899
- };
16068
+ async function replayEventsBatched2(projectKey, onBatch, options2 = {}, projectPath) {
16069
+ return withTiming("replayEventsBatched", async () => {
16070
+ const startTime = Date.now();
16071
+ const batchSize = options2.batchSize ?? 1000;
16072
+ const fromSequence = options2.fromSequence ?? 0;
16073
+ const db = await getDatabase(projectPath);
16074
+ if (options2.clearViews) {
16075
+ await db.query(`DELETE FROM message_recipients WHERE message_id IN (
16076
+ SELECT id FROM messages WHERE project_key = $1
16077
+ )`, [projectKey]);
16078
+ await db.query(`DELETE FROM messages WHERE project_key = $1`, [
16079
+ projectKey
16080
+ ]);
16081
+ await db.query(`DELETE FROM reservations WHERE project_key = $1`, [
16082
+ projectKey
16083
+ ]);
16084
+ await db.query(`DELETE FROM agents WHERE project_key = $1`, [projectKey]);
16085
+ }
16086
+ const countResult = await db.query(`SELECT COUNT(*) as count FROM events WHERE project_key = $1 AND sequence > $2`, [projectKey, fromSequence]);
16087
+ const total = parseInt(countResult.rows[0]?.count ?? "0");
16088
+ if (total === 0) {
16089
+ return { eventsReplayed: 0, duration: Date.now() - startTime };
16090
+ }
16091
+ let processed = 0;
16092
+ let offset = 0;
16093
+ while (processed < total) {
16094
+ const events2 = await readEvents({
16095
+ projectKey,
16096
+ afterSequence: fromSequence,
16097
+ limit: batchSize,
16098
+ offset
16099
+ }, projectPath);
16100
+ if (events2.length === 0)
16101
+ break;
16102
+ for (const event of events2) {
16103
+ await updateMaterializedViews(db, event);
16104
+ }
16105
+ processed += events2.length;
16106
+ const percent = Math.round(processed / total * 100);
16107
+ await onBatch(events2, { processed, total, percent });
16108
+ console.log(`[SwarmMail] Replaying events: ${processed}/${total} (${percent}%)`);
16109
+ offset += batchSize;
16110
+ }
16111
+ return {
16112
+ eventsReplayed: processed,
16113
+ duration: Date.now() - startTime
16114
+ };
16115
+ });
15900
16116
  }
15901
- async function sendSwarmMessage(options2) {
15902
- const {
15903
- projectPath,
15904
- fromAgent,
15905
- toAgents,
15906
- subject,
15907
- body,
15908
- threadId,
15909
- importance = "normal",
15910
- ackRequired = false
15911
- } = options2;
15912
- await sendMessage(projectPath, fromAgent, toAgents, subject, body, { threadId, importance, ackRequired }, projectPath);
15913
- const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_streams(), exports_streams));
15914
- const db = await getDatabase2(projectPath);
15915
- const result = await db.query(`SELECT id FROM messages
15916
- WHERE project_key = $1 AND from_agent = $2 AND subject = $3
15917
- ORDER BY created_at DESC LIMIT 1`, [projectPath, fromAgent, subject]);
15918
- const messageId = result.rows[0]?.id ?? 0;
15919
- return {
15920
- success: true,
15921
- messageId,
15922
- threadId,
15923
- recipientCount: toAgents.length
15924
- };
16117
+ async function updateMaterializedViews(db, event) {
16118
+ try {
16119
+ switch (event.type) {
16120
+ case "agent_registered":
16121
+ await handleAgentRegistered(db, event);
16122
+ break;
16123
+ case "agent_active":
16124
+ await db.query(`UPDATE agents SET last_active_at = $1 WHERE project_key = $2 AND name = $3`, [event.timestamp, event.project_key, event.agent_name]);
16125
+ break;
16126
+ case "message_sent":
16127
+ await handleMessageSent(db, event);
16128
+ break;
16129
+ case "message_read":
16130
+ await db.query(`UPDATE message_recipients SET read_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
16131
+ break;
16132
+ case "message_acked":
16133
+ await db.query(`UPDATE message_recipients SET acked_at = $1 WHERE message_id = $2 AND agent_name = $3`, [event.timestamp, event.message_id, event.agent_name]);
16134
+ break;
16135
+ case "file_reserved":
16136
+ await handleFileReserved(db, event);
16137
+ break;
16138
+ case "file_released":
16139
+ await handleFileReleased(db, event);
16140
+ break;
16141
+ case "task_started":
16142
+ case "task_progress":
16143
+ case "task_completed":
16144
+ case "task_blocked":
16145
+ break;
16146
+ case "decomposition_generated":
16147
+ await handleDecompositionGenerated(db, event);
16148
+ break;
16149
+ case "subtask_outcome":
16150
+ await handleSubtaskOutcome(db, event);
16151
+ break;
16152
+ case "human_feedback":
16153
+ await handleHumanFeedback(db, event);
16154
+ break;
16155
+ case "swarm_checkpointed":
16156
+ await handleSwarmCheckpointed(db, event);
16157
+ break;
16158
+ case "swarm_recovered":
16159
+ await handleSwarmRecovered(db, event);
16160
+ break;
16161
+ }
16162
+ } catch (error45) {
16163
+ console.error("[SwarmMail] Failed to update materialized views", {
16164
+ eventType: event.type,
16165
+ eventId: event.id,
16166
+ error: error45
16167
+ });
16168
+ throw error45;
16169
+ }
15925
16170
  }
15926
- async function getSwarmInbox(options2) {
15927
- const {
15928
- projectPath,
15929
- agentName,
15930
- limit = MAX_INBOX_LIMIT2,
15931
- urgentOnly = false,
15932
- unreadOnly = false,
15933
- includeBodies = false
15934
- } = options2;
15935
- const effectiveLimit = Math.min(limit, MAX_INBOX_LIMIT2);
15936
- const messages = await getInbox(projectPath, agentName, {
15937
- limit: effectiveLimit,
15938
- urgentOnly,
15939
- unreadOnly,
15940
- includeBodies
15941
- }, projectPath);
15942
- return {
15943
- messages: messages.map((m) => ({
15944
- id: m.id,
15945
- from_agent: m.from_agent,
15946
- subject: m.subject,
15947
- body: includeBodies ? m.body : undefined,
15948
- thread_id: m.thread_id,
15949
- importance: m.importance,
15950
- created_at: m.created_at
15951
- })),
15952
- total: messages.length
15953
- };
16171
+ async function handleAgentRegistered(db, event) {
16172
+ await db.query(`INSERT INTO agents (project_key, name, program, model, task_description, registered_at, last_active_at)
16173
+ VALUES ($1, $2, $3, $4, $5, $6, $6)
16174
+ ON CONFLICT (project_key, name) DO UPDATE SET
16175
+ program = EXCLUDED.program,
16176
+ model = EXCLUDED.model,
16177
+ task_description = EXCLUDED.task_description,
16178
+ last_active_at = EXCLUDED.last_active_at`, [
16179
+ event.project_key,
16180
+ event.agent_name,
16181
+ event.program,
16182
+ event.model,
16183
+ event.task_description || null,
16184
+ event.timestamp
16185
+ ]);
15954
16186
  }
15955
- async function readSwarmMessage(options2) {
15956
- const { projectPath, messageId, agentName, markAsRead = false } = options2;
15957
- const message = await getMessage(projectPath, messageId, projectPath);
15958
- if (!message) {
15959
- return null;
16187
+ async function handleMessageSent(db, event) {
16188
+ console.log("[SwarmMail] Handling message sent event", {
16189
+ from: event.from_agent,
16190
+ to: event.to_agents,
16191
+ subject: event.subject,
16192
+ projectKey: event.project_key
16193
+ });
16194
+ const result = await db.query(`INSERT INTO messages (project_key, from_agent, subject, body, thread_id, importance, ack_required, created_at)
16195
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
16196
+ RETURNING id`, [
16197
+ event.project_key,
16198
+ event.from_agent,
16199
+ event.subject,
16200
+ event.body,
16201
+ event.thread_id || null,
16202
+ event.importance,
16203
+ event.ack_required,
16204
+ event.timestamp
16205
+ ]);
16206
+ const msgRow = result.rows[0];
16207
+ if (!msgRow) {
16208
+ throw new Error("Failed to insert message - no row returned");
15960
16209
  }
15961
- if (markAsRead && agentName) {
15962
- await appendEvent(createEvent("message_read", {
15963
- project_key: projectPath,
15964
- message_id: messageId,
15965
- agent_name: agentName
15966
- }), projectPath);
16210
+ const messageId = msgRow.id;
16211
+ if (event.to_agents.length > 0) {
16212
+ const values = event.to_agents.map((_, i) => `($1, $${i + 2})`).join(", ");
16213
+ const params = [messageId, ...event.to_agents];
16214
+ await db.query(`INSERT INTO message_recipients (message_id, agent_name)
16215
+ VALUES ${values}
16216
+ ON CONFLICT DO NOTHING`, params);
16217
+ console.log("[SwarmMail] Message recipients inserted", {
16218
+ messageId,
16219
+ recipientCount: event.to_agents.length
16220
+ });
15967
16221
  }
15968
- return {
15969
- id: message.id,
15970
- from_agent: message.from_agent,
15971
- subject: message.subject,
15972
- body: message.body,
15973
- thread_id: message.thread_id,
15974
- importance: message.importance,
15975
- created_at: message.created_at
15976
- };
15977
16222
  }
15978
- async function reserveSwarmFiles(options2) {
15979
- const {
15980
- projectPath,
15981
- agentName,
15982
- paths,
15983
- reason,
15984
- exclusive = true,
15985
- ttlSeconds = DEFAULT_TTL_SECONDS2
15986
- } = options2;
15987
- const conflicts = await checkConflicts(projectPath, agentName, paths, projectPath);
15988
- await reserveFiles(projectPath, agentName, paths, { reason, exclusive, ttlSeconds }, projectPath);
15989
- const reservations = await getActiveReservations(projectPath, projectPath, agentName);
15990
- const granted = reservations.filter((r) => paths.includes(r.path_pattern)).map((r) => ({
15991
- id: r.id,
15992
- path_pattern: r.path_pattern,
15993
- exclusive: r.exclusive,
15994
- expiresAt: r.expires_at
15995
- }));
15996
- return {
15997
- granted,
15998
- conflicts: conflicts.map((c) => ({
15999
- path: c.path,
16000
- holder: c.holder,
16001
- pattern: c.pattern
16002
- }))
16003
- };
16223
+ async function handleFileReserved(db, event) {
16224
+ console.log("[SwarmMail] Handling file reservation event", {
16225
+ agent: event.agent_name,
16226
+ paths: event.paths,
16227
+ exclusive: event.exclusive,
16228
+ projectKey: event.project_key
16229
+ });
16230
+ if (event.paths.length > 0) {
16231
+ const values = event.paths.map((_, i) => `($1, $2, $${i + 3}, $${event.paths.length + 3}, $${event.paths.length + 4}, $${event.paths.length + 5}, $${event.paths.length + 6})`).join(", ");
16232
+ const params = [
16233
+ event.project_key,
16234
+ event.agent_name,
16235
+ ...event.paths,
16236
+ event.exclusive,
16237
+ event.reason || null,
16238
+ event.timestamp,
16239
+ event.expires_at
16240
+ ];
16241
+ if (event.paths.length > 0) {
16242
+ await db.query(`DELETE FROM reservations
16243
+ WHERE project_key = $1
16244
+ AND agent_name = $2
16245
+ AND path_pattern = ANY($3)
16246
+ AND released_at IS NULL`, [event.project_key, event.agent_name, event.paths]);
16247
+ }
16248
+ await db.query(`INSERT INTO reservations (project_key, agent_name, path_pattern, exclusive, reason, created_at, expires_at)
16249
+ VALUES ${values}`, params);
16250
+ console.log("[SwarmMail] File reservations inserted", {
16251
+ agent: event.agent_name,
16252
+ reservationCount: event.paths.length
16253
+ });
16254
+ }
16004
16255
  }
16005
- async function releaseSwarmFiles(options2) {
16006
- const { projectPath, agentName, paths, reservationIds } = options2;
16007
- const currentReservations = await getActiveReservations(projectPath, projectPath, agentName);
16008
- let releaseCount = 0;
16009
- if (paths && paths.length > 0) {
16010
- releaseCount = currentReservations.filter((r) => paths.includes(r.path_pattern)).length;
16011
- } else if (reservationIds && reservationIds.length > 0) {
16012
- releaseCount = currentReservations.filter((r) => reservationIds.includes(r.id)).length;
16256
+ async function handleFileReleased(db, event) {
16257
+ if (event.type !== "file_released")
16258
+ return;
16259
+ if (event.reservation_ids && event.reservation_ids.length > 0) {
16260
+ await db.query(`UPDATE reservations SET released_at = $1 WHERE id = ANY($2)`, [event.timestamp, event.reservation_ids]);
16261
+ } else if (event.paths && event.paths.length > 0) {
16262
+ await db.query(`UPDATE reservations SET released_at = $1
16263
+ WHERE project_key = $2 AND agent_name = $3 AND path_pattern = ANY($4) AND released_at IS NULL`, [event.timestamp, event.project_key, event.agent_name, event.paths]);
16013
16264
  } else {
16014
- releaseCount = currentReservations.length;
16265
+ await db.query(`UPDATE reservations SET released_at = $1
16266
+ WHERE project_key = $2 AND agent_name = $3 AND released_at IS NULL`, [event.timestamp, event.project_key, event.agent_name]);
16015
16267
  }
16016
- await appendEvent(createEvent("file_released", {
16017
- project_key: projectPath,
16018
- agent_name: agentName,
16019
- paths,
16020
- reservation_ids: reservationIds
16021
- }), projectPath);
16022
- return {
16023
- released: releaseCount,
16024
- releasedAt: Date.now()
16025
- };
16026
16268
  }
16027
- async function acknowledgeSwarmMessage(options2) {
16028
- const { projectPath, messageId, agentName } = options2;
16029
- const timestamp = Date.now();
16030
- await appendEvent(createEvent("message_acked", {
16031
- project_key: projectPath,
16032
- message_id: messageId,
16033
- agent_name: agentName
16034
- }), projectPath);
16035
- return {
16036
- acknowledged: true,
16037
- acknowledgedAt: new Date(timestamp).toISOString()
16038
- };
16269
+ async function handleDecompositionGenerated(db, event) {
16270
+ if (event.type !== "decomposition_generated")
16271
+ return;
16272
+ await db.query(`INSERT INTO eval_records (
16273
+ id, project_key, task, context, strategy, epic_title, subtasks,
16274
+ created_at, updated_at
16275
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $8)
16276
+ ON CONFLICT (id) DO NOTHING`, [
16277
+ event.epic_id,
16278
+ event.project_key,
16279
+ event.task,
16280
+ event.context || null,
16281
+ event.strategy,
16282
+ event.epic_title,
16283
+ JSON.stringify(event.subtasks),
16284
+ event.timestamp
16285
+ ]);
16039
16286
  }
16040
- async function checkSwarmHealth(projectPath) {
16041
- const healthy = await isDatabaseHealthy(projectPath);
16042
- if (!healthy) {
16043
- return {
16044
- healthy: false,
16045
- database: "disconnected"
16046
- };
16287
+ async function handleSubtaskOutcome(db, event) {
16288
+ if (event.type !== "subtask_outcome")
16289
+ return;
16290
+ const result = await db.query(`SELECT outcomes, subtasks FROM eval_records WHERE id = $1`, [
16291
+ event.epic_id
16292
+ ]);
16293
+ if (!result.rows[0]) {
16294
+ console.warn(`[SwarmMail] No eval_record found for epic_id ${event.epic_id}`);
16295
+ return;
16047
16296
  }
16048
- const stats = await getDatabaseStats(projectPath);
16049
- return {
16050
- healthy: true,
16051
- database: "connected",
16052
- stats
16053
- };
16297
+ const row = result.rows[0];
16298
+ const subtasks = typeof row.subtasks === "string" ? JSON.parse(row.subtasks) : row.subtasks;
16299
+ const outcomes = row.outcomes ? typeof row.outcomes === "string" ? JSON.parse(row.outcomes) : row.outcomes : [];
16300
+ const newOutcome = {
16301
+ bead_id: event.bead_id,
16302
+ planned_files: event.planned_files,
16303
+ actual_files: event.actual_files,
16304
+ duration_ms: event.duration_ms,
16305
+ error_count: event.error_count,
16306
+ retry_count: event.retry_count,
16307
+ success: event.success
16308
+ };
16309
+ const updatedOutcomes = [...outcomes, newOutcome];
16310
+ const fileOverlapCount = computeFileOverlap(subtasks);
16311
+ const scopeAccuracy = computeScopeAccuracy(event.planned_files, event.actual_files);
16312
+ const timeBalanceRatio = computeTimeBalanceRatio(updatedOutcomes);
16313
+ const overallSuccess = updatedOutcomes.every((o) => o.success);
16314
+ const totalDurationMs = updatedOutcomes.reduce((sum, o) => sum + o.duration_ms, 0);
16315
+ const totalErrors = updatedOutcomes.reduce((sum, o) => sum + o.error_count, 0);
16316
+ await db.query(`UPDATE eval_records SET
16317
+ outcomes = $1,
16318
+ file_overlap_count = $2,
16319
+ scope_accuracy = $3,
16320
+ time_balance_ratio = $4,
16321
+ overall_success = $5,
16322
+ total_duration_ms = $6,
16323
+ total_errors = $7,
16324
+ updated_at = $8
16325
+ WHERE id = $9`, [
16326
+ JSON.stringify(updatedOutcomes),
16327
+ fileOverlapCount,
16328
+ scopeAccuracy,
16329
+ timeBalanceRatio,
16330
+ overallSuccess,
16331
+ totalDurationMs,
16332
+ totalErrors,
16333
+ event.timestamp,
16334
+ event.epic_id
16335
+ ]);
16054
16336
  }
16055
- var MAX_INBOX_LIMIT2 = 5, DEFAULT_TTL_SECONDS2 = 3600, ADJECTIVES2, NOUNS2;
16056
- var init_swarm_mail = __esm(() => {
16057
- init_events();
16058
- init_streams();
16059
- init_projections();
16060
- init_store();
16061
- ADJECTIVES2 = [
16062
- "Blue",
16063
- "Red",
16064
- "Green",
16065
- "Gold",
16066
- "Silver",
16067
- "Swift",
16068
- "Bright",
16069
- "Dark",
16070
- "Calm",
16071
- "Bold",
16072
- "Wise",
16073
- "Quick",
16074
- "Warm",
16075
- "Cool",
16076
- "Pure",
16077
- "Wild"
16078
- ];
16079
- NOUNS2 = [
16080
- "Lake",
16081
- "Stone",
16082
- "River",
16083
- "Mountain",
16084
- "Forest",
16085
- "Ocean",
16086
- "Star",
16087
- "Moon",
16088
- "Wind",
16089
- "Fire",
16090
- "Cloud",
16091
- "Storm",
16092
- "Dawn",
16093
- "Dusk",
16094
- "Hawk",
16095
- "Wolf"
16096
- ];
16337
+ async function handleHumanFeedback(db, event) {
16338
+ if (event.type !== "human_feedback")
16339
+ return;
16340
+ await db.query(`UPDATE eval_records SET
16341
+ human_accepted = $1,
16342
+ human_modified = $2,
16343
+ human_notes = $3,
16344
+ updated_at = $4
16345
+ WHERE id = $5`, [
16346
+ event.accepted,
16347
+ event.modified,
16348
+ event.notes || null,
16349
+ event.timestamp,
16350
+ event.epic_id
16351
+ ]);
16352
+ }
16353
+ async function handleSwarmCheckpointed(db, event) {
16354
+ if (event.type !== "swarm_checkpointed")
16355
+ return;
16356
+ await db.query(`INSERT INTO swarm_contexts (
16357
+ project_key, epic_id, bead_id, strategy, files, dependencies,
16358
+ directives, recovery, checkpointed_at, updated_at
16359
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $9)
16360
+ ON CONFLICT (project_key, epic_id, bead_id) DO UPDATE SET
16361
+ strategy = EXCLUDED.strategy,
16362
+ files = EXCLUDED.files,
16363
+ dependencies = EXCLUDED.dependencies,
16364
+ directives = EXCLUDED.directives,
16365
+ recovery = EXCLUDED.recovery,
16366
+ checkpointed_at = EXCLUDED.checkpointed_at,
16367
+ updated_at = EXCLUDED.updated_at`, [
16368
+ event.project_key,
16369
+ event.epic_id,
16370
+ event.bead_id,
16371
+ event.strategy,
16372
+ JSON.stringify(event.files),
16373
+ JSON.stringify(event.dependencies),
16374
+ JSON.stringify(event.directives),
16375
+ JSON.stringify(event.recovery),
16376
+ event.timestamp
16377
+ ]);
16378
+ }
16379
+ async function handleSwarmRecovered(db, event) {
16380
+ if (event.type !== "swarm_recovered")
16381
+ return;
16382
+ await db.query(`UPDATE swarm_contexts SET
16383
+ recovered_at = $1,
16384
+ recovered_from_checkpoint = $2,
16385
+ updated_at = $1
16386
+ WHERE project_key = $3 AND epic_id = $4 AND bead_id = $5`, [
16387
+ event.timestamp,
16388
+ event.recovered_from_checkpoint,
16389
+ event.project_key,
16390
+ event.epic_id,
16391
+ event.bead_id
16392
+ ]);
16393
+ }
16394
+ function computeFileOverlap(subtasks) {
16395
+ const fileCount = new Map;
16396
+ for (const subtask of subtasks) {
16397
+ for (const file2 of subtask.files) {
16398
+ fileCount.set(file2, (fileCount.get(file2) || 0) + 1);
16399
+ }
16400
+ }
16401
+ return Array.from(fileCount.values()).filter((count) => count > 1).length;
16402
+ }
16403
+ function computeScopeAccuracy(planned, actual) {
16404
+ if (planned.length === 0)
16405
+ return 1;
16406
+ const plannedSet = new Set(planned);
16407
+ const intersection2 = actual.filter((file2) => plannedSet.has(file2));
16408
+ return intersection2.length / planned.length;
16409
+ }
16410
+ function computeTimeBalanceRatio(outcomes) {
16411
+ if (outcomes.length === 0)
16412
+ return null;
16413
+ const durations = outcomes.map((o) => o.duration_ms);
16414
+ const max = Math.max(...durations);
16415
+ const min = Math.min(...durations);
16416
+ if (min === 0)
16417
+ return null;
16418
+ return max / min;
16419
+ }
16420
+ async function registerAgent(projectKey, agentName, options2 = {}, projectPath) {
16421
+ const event = createEvent("agent_registered", {
16422
+ project_key: projectKey,
16423
+ agent_name: agentName,
16424
+ program: options2.program || "opencode",
16425
+ model: options2.model || "unknown",
16426
+ task_description: options2.taskDescription
16427
+ });
16428
+ return appendEvent(event, projectPath);
16429
+ }
16430
+ async function sendMessage(projectKey, fromAgent, toAgents, subject, body, options2 = {}, projectPath) {
16431
+ const event = createEvent("message_sent", {
16432
+ project_key: projectKey,
16433
+ from_agent: fromAgent,
16434
+ to_agents: toAgents,
16435
+ subject,
16436
+ body,
16437
+ thread_id: options2.threadId,
16438
+ importance: options2.importance || "normal",
16439
+ ack_required: options2.ackRequired || false
16440
+ });
16441
+ return appendEvent(event, projectPath);
16442
+ }
16443
+ async function reserveFiles(projectKey, agentName, paths, options2 = {}, projectPath) {
16444
+ const ttlSeconds = options2.ttlSeconds || 3600;
16445
+ const event = createEvent("file_reserved", {
16446
+ project_key: projectKey,
16447
+ agent_name: agentName,
16448
+ paths,
16449
+ reason: options2.reason,
16450
+ exclusive: options2.exclusive ?? true,
16451
+ ttl_seconds: ttlSeconds,
16452
+ expires_at: Date.now() + ttlSeconds * 1000
16453
+ });
16454
+ return appendEvent(event, projectPath);
16455
+ }
16456
+ var TIMESTAMP_SAFE_UNTIL;
16457
+ var init_store = __esm(() => {
16458
+ init_streams();
16459
+ init_events();
16460
+ TIMESTAMP_SAFE_UNTIL = new Date("2286-01-01").getTime();
16097
16461
  });
16098
16462
 
16099
16463
  // node_modules/.pnpm/@ioredis+commands@1.4.0/node_modules/@ioredis/commands/built/commands.json
@@ -30780,7 +31144,58 @@ var mandateSchemas = {
30780
31144
  QueryMandatesArgsSchema,
30781
31145
  ScoreCalculationResultSchema
30782
31146
  };
31147
+ // src/schemas/swarm-context.ts
31148
+ init_zod();
31149
+ var SwarmStrategySchema = exports_external.enum([
31150
+ "file-based",
31151
+ "feature-based",
31152
+ "risk-based"
31153
+ ]);
31154
+ var SwarmDirectivesSchema = exports_external.object({
31155
+ shared_context: exports_external.string(),
31156
+ skills_to_load: exports_external.array(exports_external.string()).default([]),
31157
+ coordinator_notes: exports_external.string().default("")
31158
+ });
31159
+ var SwarmRecoverySchema = exports_external.object({
31160
+ last_checkpoint: exports_external.string(),
31161
+ files_modified: exports_external.array(exports_external.string()).default([]),
31162
+ progress_percent: exports_external.number().min(0).max(100).default(0),
31163
+ last_message: exports_external.string().default(""),
31164
+ error_context: exports_external.string().optional()
31165
+ });
31166
+ var SwarmBeadContextSchema = exports_external.object({
31167
+ id: exports_external.string(),
31168
+ epic_id: exports_external.string(),
31169
+ bead_id: exports_external.string(),
31170
+ strategy: SwarmStrategySchema,
31171
+ files: exports_external.array(exports_external.string()),
31172
+ dependencies: exports_external.array(exports_external.string()).default([]),
31173
+ directives: SwarmDirectivesSchema,
31174
+ recovery: SwarmRecoverySchema,
31175
+ created_at: exports_external.number().int().positive(),
31176
+ updated_at: exports_external.number().int().positive()
31177
+ });
31178
+ var CreateSwarmContextArgsSchema = SwarmBeadContextSchema.omit({
31179
+ id: true,
31180
+ created_at: true,
31181
+ updated_at: true
31182
+ });
31183
+ var UpdateSwarmContextArgsSchema = exports_external.object({
31184
+ id: exports_external.string(),
31185
+ recovery: SwarmRecoverySchema.partial().optional(),
31186
+ files: exports_external.array(exports_external.string()).optional(),
31187
+ dependencies: exports_external.array(exports_external.string()).optional(),
31188
+ directives: SwarmDirectivesSchema.partial().optional()
31189
+ });
31190
+ var QuerySwarmContextsArgsSchema = exports_external.object({
31191
+ epic_id: exports_external.string().optional(),
31192
+ bead_id: exports_external.string().optional(),
31193
+ strategy: SwarmStrategySchema.optional(),
31194
+ has_errors: exports_external.boolean().optional()
31195
+ });
30783
31196
  // src/beads.ts
31197
+ init_events();
31198
+ init_store();
30784
31199
  var beadsWorkingDirectory = null;
30785
31200
  function setBeadsWorkingDirectory(directory) {
30786
31201
  beadsWorkingDirectory = directory;
@@ -30925,7 +31340,15 @@ var beads_create_epic = tool({
30925
31340
  priority: tool.schema.number().min(0).max(3).optional(),
30926
31341
  files: tool.schema.array(tool.schema.string()).optional(),
30927
31342
  id_suffix: tool.schema.string().optional().describe("Custom ID suffix (e.g., 'e2e-test' becomes 'phase-0.e2e-test')")
30928
- })).describe("Subtasks to create under the epic")
31343
+ })).describe("Subtasks to create under the epic"),
31344
+ strategy: tool.schema.enum(["file-based", "feature-based", "risk-based"]).optional().describe("Decomposition strategy used (default: feature-based)"),
31345
+ task: tool.schema.string().optional().describe("Original task description that was decomposed"),
31346
+ project_key: tool.schema.string().optional().describe("Project path for event emission"),
31347
+ recovery_context: tool.schema.object({
31348
+ shared_context: tool.schema.string().optional(),
31349
+ skills_to_load: tool.schema.array(tool.schema.string()).optional(),
31350
+ coordinator_notes: tool.schema.string().optional()
31351
+ }).optional().describe("Recovery context from checkpoint compaction")
30929
31352
  },
30930
31353
  async execute(args, ctx) {
30931
31354
  const validated = EpicCreateArgsSchema.parse(args);
@@ -30968,6 +31391,27 @@ var beads_create_epic = tool({
30968
31391
  epic,
30969
31392
  subtasks: created.slice(1)
30970
31393
  };
31394
+ if (args.project_key) {
31395
+ try {
31396
+ const event = createEvent("decomposition_generated", {
31397
+ project_key: args.project_key,
31398
+ epic_id: epic.id,
31399
+ task: args.task || validated.epic_title,
31400
+ context: validated.epic_description,
31401
+ strategy: args.strategy || "feature-based",
31402
+ epic_title: validated.epic_title,
31403
+ subtasks: validated.subtasks.map((st) => ({
31404
+ title: st.title,
31405
+ files: st.files || [],
31406
+ priority: st.priority
31407
+ })),
31408
+ recovery_context: args.recovery_context
31409
+ });
31410
+ await appendEvent(event, args.project_key);
31411
+ } catch (error45) {
31412
+ console.warn("[beads_create_epic] Failed to emit DecompositionGeneratedEvent:", error45);
31413
+ }
31414
+ }
30971
31415
  return JSON.stringify(result, null, 2);
30972
31416
  } catch (error45) {
30973
31417
  const rollbackCommands = [];
@@ -31146,7 +31590,7 @@ var beads_sync = tool({
31146
31590
  async execute(args, ctx) {
31147
31591
  const autoPull = args.auto_pull ?? true;
31148
31592
  const TIMEOUT_MS = 30000;
31149
- const withTimeout = async (promise2, timeoutMs, operation) => {
31593
+ const withTimeout2 = async (promise2, timeoutMs, operation) => {
31150
31594
  let timeoutId;
31151
31595
  const timeoutPromise = new Promise((_, reject) => {
31152
31596
  timeoutId = setTimeout(() => reject(new BeadError(`Operation timed out after ${timeoutMs}ms`, operation)), timeoutMs);
@@ -31159,7 +31603,7 @@ var beads_sync = tool({
31159
31603
  }
31160
31604
  }
31161
31605
  };
31162
- const flushResult = await withTimeout(runBdCommand(["sync", "--flush-only"]), TIMEOUT_MS, "bd sync --flush-only");
31606
+ const flushResult = await withTimeout2(runBdCommand(["sync", "--flush-only"]), TIMEOUT_MS, "bd sync --flush-only");
31163
31607
  if (flushResult.exitCode !== 0) {
31164
31608
  throw new BeadError(`Failed to flush beads because bd sync failed: ${flushResult.stderr}. Try: Check if .beads/ directory is writable, verify no corrupted JSONL files, or run 'bd list' to test basic beads functionality.`, "bd sync --flush-only", flushResult.exitCode);
31165
31609
  }
@@ -31174,7 +31618,7 @@ var beads_sync = tool({
31174
31618
  if (addResult.exitCode !== 0) {
31175
31619
  throw new BeadError(`Failed to stage beads because git add failed: ${addResult.stderr}. Try: Check if .beads/ directory exists, verify git is initialized with 'git status', or check for .gitignore patterns blocking .beads/.`, "git add .beads/", addResult.exitCode);
31176
31620
  }
31177
- const commitResult = await withTimeout(runGitCommand(["commit", "-m", "chore: sync beads"]), TIMEOUT_MS, "git commit");
31621
+ const commitResult = await withTimeout2(runGitCommand(["commit", "-m", "chore: sync beads"]), TIMEOUT_MS, "git commit");
31178
31622
  if (commitResult.exitCode !== 0 && !commitResult.stdout.includes("nothing to commit")) {
31179
31623
  throw new BeadError(`Failed to commit beads because git commit failed: ${commitResult.stderr}. Try: Check git config (user.name, user.email) with 'git config --list', verify working tree is clean, or check for pre-commit hooks blocking commit.`, "git commit", commitResult.exitCode);
31180
31624
  }
@@ -31203,7 +31647,7 @@ var beads_sync = tool({
31203
31647
  console.warn(`[beads] Stash failed (${stashResult.stderr}), attempting pull anyway...`);
31204
31648
  }
31205
31649
  }
31206
- const pullResult = await withTimeout(runGitCommand(["pull", "--rebase"]), TIMEOUT_MS, "git pull --rebase");
31650
+ const pullResult = await withTimeout2(runGitCommand(["pull", "--rebase"]), TIMEOUT_MS, "git pull --rebase");
31207
31651
  if (didStash) {
31208
31652
  console.warn("[beads] Restoring stashed changes...");
31209
31653
  const unstashResult = await runGitCommand(["stash", "pop"]);
@@ -31217,12 +31661,12 @@ var beads_sync = tool({
31217
31661
  if (pullResult.exitCode !== 0) {
31218
31662
  throw new BeadError(`Failed to pull because git pull --rebase failed: ${pullResult.stderr}. Try: Resolve merge conflicts manually with 'git status', check if remote is accessible with 'git remote -v', or use skip_verification to bypass automatic pull.`, "git pull --rebase", pullResult.exitCode);
31219
31663
  }
31220
- const importResult = await withTimeout(runBdCommand(["sync", "--import-only"]), TIMEOUT_MS, "bd sync --import-only");
31664
+ const importResult = await withTimeout2(runBdCommand(["sync", "--import-only"]), TIMEOUT_MS, "bd sync --import-only");
31221
31665
  if (importResult.exitCode !== 0) {
31222
31666
  console.warn(`[beads] Import warning: ${importResult.stderr}`);
31223
31667
  }
31224
31668
  }
31225
- const pushResult = await withTimeout(runGitCommand(["push"]), TIMEOUT_MS, "git push");
31669
+ const pushResult = await withTimeout2(runGitCommand(["push"]), TIMEOUT_MS, "git push");
31226
31670
  if (pushResult.exitCode !== 0) {
31227
31671
  throw new BeadError(`Failed to push because git push failed: ${pushResult.stderr}. Try: Check if remote branch is up to date with 'git pull --rebase', verify push permissions, check remote URL with 'git remote -v', or force push with 'git push --force-with-lease' if safe.`, "git push", pushResult.exitCode);
31228
31672
  }
@@ -34182,20 +34626,29 @@ Only modify these files. Need others? Message the coordinator.
34182
34626
 
34183
34627
  {error_context}
34184
34628
 
34185
- ## [MANDATORY: SWARM MAIL]
34629
+ ## [MANDATORY: SWARM MAIL INITIALIZATION]
34186
34630
 
34187
- **YOU MUST USE SWARM MAIL FOR ALL COORDINATION.** This is non-negotiable.
34631
+ **CRITICAL: YOU MUST INITIALIZE SWARM MAIL BEFORE DOING ANY WORK.**
34188
34632
 
34189
- ### Initialize FIRST (before any work)
34190
- \`\`\`
34191
- swarmmail_init(project_path="$PWD", task_description="{subtask_title}")
34192
- \`\`\`
34633
+ This is your FIRST step - before reading files, before planning, before ANY other action.
34193
34634
 
34194
- ### Reserve Files (if not already reserved by coordinator)
34635
+ ### Step 1: Initialize (REQUIRED - DO THIS FIRST)
34195
34636
  \`\`\`
34196
- swarmmail_reserve(paths=[...files...], reason="{bead_id}: {subtask_title}")
34637
+ swarmmail_init(project_path="{project_path}", task_description="{bead_id}: {subtask_title}")
34197
34638
  \`\`\`
34198
34639
 
34640
+ **This registers you with the coordination system and enables:**
34641
+ - File reservation tracking
34642
+ - Inter-agent communication
34643
+ - Progress monitoring
34644
+ - Conflict detection
34645
+
34646
+ **If you skip this step, your work will not be tracked and swarm_complete will fail.**
34647
+
34648
+ ## [SWARM MAIL USAGE]
34649
+
34650
+ After initialization, use Swarm Mail for coordination:
34651
+
34199
34652
  ### Check Inbox Regularly
34200
34653
  \`\`\`
34201
34654
  swarmmail_inbox() # Check for coordinator messages
@@ -34249,14 +34702,17 @@ As you work, note reusable patterns, best practices, or domain insights:
34249
34702
  - Skills make swarms smarter over time
34250
34703
 
34251
34704
  ## [WORKFLOW]
34252
- 1. **swarmmail_init** - Initialize session FIRST
34705
+ 1. **swarmmail_init** - Initialize session (MANDATORY FIRST STEP)
34253
34706
  2. Read assigned files
34254
34707
  3. Implement changes
34255
34708
  4. **swarmmail_send** - Report progress to coordinator
34256
34709
  5. Verify (typecheck)
34257
34710
  6. **swarm_complete** - Mark done, release reservations
34258
34711
 
34259
- **CRITICAL: Never work silently. Send progress updates via swarmmail_send every significant milestone.**
34712
+ **CRITICAL REQUIREMENTS:**
34713
+ - Step 1 (swarmmail_init) is NON-NEGOTIABLE - do it before anything else
34714
+ - Never work silently - send progress updates via swarmmail_send every significant milestone
34715
+ - If you complete without initializing, swarm_complete will detect this and warn/fail
34260
34716
 
34261
34717
  Begin now.`;
34262
34718
  var EVALUATION_PROMPT = `Evaluate the work completed for this subtask.
@@ -34300,7 +34756,33 @@ function formatSubtaskPromptV2(params) {
34300
34756
  `) : "(no specific files - use judgment)";
34301
34757
  const compressedSection = params.compressed_context ? params.compressed_context : "";
34302
34758
  const errorSection = params.error_context ? params.error_context : "";
34303
- return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id).replace(/{epic_id}/g, params.epic_id).replace("{subtask_title}", params.subtask_title).replace("{subtask_description}", params.subtask_description || "(see title)").replace("{file_list}", fileList).replace("{shared_context}", params.shared_context || "(none)").replace("{compressed_context}", compressedSection).replace("{error_context}", errorSection);
34759
+ let recoverySection = "";
34760
+ if (params.recovery_context) {
34761
+ const sections = [];
34762
+ if (params.recovery_context.shared_context) {
34763
+ sections.push(`### Recovery Context
34764
+ ${params.recovery_context.shared_context}`);
34765
+ }
34766
+ if (params.recovery_context.skills_to_load && params.recovery_context.skills_to_load.length > 0) {
34767
+ sections.push(`### Skills to Load
34768
+ Before starting work, load these skills for specialized guidance:
34769
+ ${params.recovery_context.skills_to_load.map((s) => `- skills_use(name="${s}")`).join(`
34770
+ `)}`);
34771
+ }
34772
+ if (params.recovery_context.coordinator_notes) {
34773
+ sections.push(`### Coordinator Notes
34774
+ ${params.recovery_context.coordinator_notes}`);
34775
+ }
34776
+ if (sections.length > 0) {
34777
+ recoverySection = `
34778
+ ## [RECOVERY CONTEXT]
34779
+
34780
+ ${sections.join(`
34781
+
34782
+ `)}`;
34783
+ }
34784
+ }
34785
+ return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id).replace(/{epic_id}/g, params.epic_id).replace(/{project_path}/g, params.project_path || "$PWD").replace("{subtask_title}", params.subtask_title).replace("{subtask_description}", params.subtask_description || "(see title)").replace("{file_list}", fileList).replace("{shared_context}", params.shared_context || "(none)").replace("{compressed_context}", compressedSection).replace("{error_context}", errorSection + recoverySection);
34304
34786
  }
34305
34787
  function formatSubtaskPrompt(params) {
34306
34788
  const fileList = params.files.map((f) => `- \`${f}\``).join(`
@@ -34321,7 +34803,8 @@ var swarm_subtask_prompt = tool({
34321
34803
  subtask_title: tool.schema.string().describe("Subtask title"),
34322
34804
  subtask_description: tool.schema.string().optional().describe("Detailed subtask instructions"),
34323
34805
  files: tool.schema.array(tool.schema.string()).describe("Files assigned to this subtask"),
34324
- shared_context: tool.schema.string().optional().describe("Context shared across all agents")
34806
+ shared_context: tool.schema.string().optional().describe("Context shared across all agents"),
34807
+ project_path: tool.schema.string().optional().describe("Absolute project path for swarmmail_init")
34325
34808
  },
34326
34809
  async execute(args) {
34327
34810
  const prompt = formatSubtaskPrompt({
@@ -34337,14 +34820,20 @@ var swarm_subtask_prompt = tool({
34337
34820
  }
34338
34821
  });
34339
34822
  var swarm_spawn_subtask = tool({
34340
- description: "Prepare a subtask for spawning. Returns prompt with Agent Mail/beads instructions.",
34823
+ description: "Prepare a subtask for spawning. Returns prompt with Agent Mail/beads instructions. IMPORTANT: Pass project_path for swarmmail_init.",
34341
34824
  args: {
34342
34825
  bead_id: tool.schema.string().describe("Subtask bead ID"),
34343
34826
  epic_id: tool.schema.string().describe("Parent epic bead ID"),
34344
34827
  subtask_title: tool.schema.string().describe("Subtask title"),
34345
34828
  subtask_description: tool.schema.string().optional().describe("Detailed subtask instructions"),
34346
34829
  files: tool.schema.array(tool.schema.string()).describe("Files assigned to this subtask"),
34347
- shared_context: tool.schema.string().optional().describe("Context shared across all agents")
34830
+ shared_context: tool.schema.string().optional().describe("Context shared across all agents"),
34831
+ project_path: tool.schema.string().optional().describe("Absolute project path for swarmmail_init (REQUIRED for tracking)"),
34832
+ recovery_context: tool.schema.object({
34833
+ shared_context: tool.schema.string().optional(),
34834
+ skills_to_load: tool.schema.array(tool.schema.string()).optional(),
34835
+ coordinator_notes: tool.schema.string().optional()
34836
+ }).optional().describe("Recovery context from checkpoint compaction")
34348
34837
  },
34349
34838
  async execute(args) {
34350
34839
  const prompt = formatSubtaskPromptV2({
@@ -34353,13 +34842,17 @@ var swarm_spawn_subtask = tool({
34353
34842
  subtask_title: args.subtask_title,
34354
34843
  subtask_description: args.subtask_description || "",
34355
34844
  files: args.files,
34356
- shared_context: args.shared_context
34845
+ shared_context: args.shared_context,
34846
+ project_path: args.project_path,
34847
+ recovery_context: args.recovery_context
34357
34848
  });
34358
34849
  return JSON.stringify({
34359
34850
  prompt,
34360
34851
  bead_id: args.bead_id,
34361
34852
  epic_id: args.epic_id,
34362
- files: args.files
34853
+ files: args.files,
34854
+ project_path: args.project_path,
34855
+ recovery_context: args.recovery_context
34363
34856
  }, null, 2);
34364
34857
  }
34365
34858
  });
@@ -34481,6 +34974,9 @@ var promptTools = {
34481
34974
  init_dist();
34482
34975
  init_zod();
34483
34976
  init_swarm_mail();
34977
+ init_projections();
34978
+ init_events();
34979
+ init_store();
34484
34980
  init_learning();
34485
34981
  init_skills();
34486
34982
  async function queryEpicSubtasks(epicId) {
@@ -34910,7 +35406,57 @@ var swarm_progress = tool({
34910
35406
  threadId: epicId,
34911
35407
  importance: args.status === "blocked" ? "high" : "normal"
34912
35408
  });
34913
- return `Progress reported: ${args.status}${args.progress_percent !== undefined ? ` (${args.progress_percent}%)` : ""}`;
35409
+ let checkpointCreated = false;
35410
+ if (args.progress_percent !== undefined && args.files_touched && args.files_touched.length > 0) {
35411
+ const milestones = [25, 50, 75];
35412
+ if (milestones.includes(args.progress_percent)) {
35413
+ try {
35414
+ const checkpoint = {
35415
+ epic_id: epicId,
35416
+ bead_id: args.bead_id,
35417
+ strategy: "file-based",
35418
+ files: args.files_touched,
35419
+ dependencies: [],
35420
+ directives: {},
35421
+ recovery: {
35422
+ last_checkpoint: Date.now(),
35423
+ files_modified: args.files_touched,
35424
+ progress_percent: args.progress_percent,
35425
+ last_message: args.message
35426
+ }
35427
+ };
35428
+ const event = createEvent("swarm_checkpointed", {
35429
+ project_key: args.project_key,
35430
+ ...checkpoint
35431
+ });
35432
+ await appendEvent(event, args.project_key);
35433
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_streams(), exports_streams));
35434
+ const db = await getDatabase2(args.project_key);
35435
+ const now = Date.now();
35436
+ await db.query(`INSERT INTO swarm_contexts (id, epic_id, bead_id, strategy, files, dependencies, directives, recovery, created_at, updated_at)
35437
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
35438
+ ON CONFLICT (id) DO UPDATE SET
35439
+ files = EXCLUDED.files,
35440
+ recovery = EXCLUDED.recovery,
35441
+ updated_at = EXCLUDED.updated_at`, [
35442
+ args.bead_id,
35443
+ epicId,
35444
+ args.bead_id,
35445
+ checkpoint.strategy,
35446
+ JSON.stringify(checkpoint.files),
35447
+ JSON.stringify(checkpoint.dependencies),
35448
+ JSON.stringify(checkpoint.directives),
35449
+ JSON.stringify(checkpoint.recovery),
35450
+ now,
35451
+ now
35452
+ ]);
35453
+ checkpointCreated = true;
35454
+ } catch (error45) {
35455
+ console.warn(`[swarm_progress] Auto-checkpoint failed at ${args.progress_percent}%:`, error45);
35456
+ }
35457
+ }
35458
+ }
35459
+ return `Progress reported: ${args.status}${args.progress_percent !== undefined ? ` (${args.progress_percent}%)` : ""}${checkpointCreated ? " [checkpoint created]" : ""}`;
34914
35460
  }
34915
35461
  });
34916
35462
  var swarm_broadcast = tool({
@@ -34970,125 +35516,205 @@ var swarm_complete = tool({
34970
35516
  evaluation: tool.schema.string().optional().describe("Self-evaluation JSON (Evaluation schema)"),
34971
35517
  files_touched: tool.schema.array(tool.schema.string()).optional().describe("Files modified - will be verified (UBS, typecheck, tests)"),
34972
35518
  skip_ubs_scan: tool.schema.boolean().optional().describe("Skip UBS bug scan (default: false)"),
34973
- skip_verification: tool.schema.boolean().optional().describe("Skip ALL verification (UBS, typecheck, tests). Use sparingly! (default: false)")
35519
+ skip_verification: tool.schema.boolean().optional().describe("Skip ALL verification (UBS, typecheck, tests). Use sparingly! (default: false)"),
35520
+ planned_files: tool.schema.array(tool.schema.string()).optional().describe("Files that were originally planned to be modified"),
35521
+ start_time: tool.schema.number().optional().describe("Task start timestamp (Unix ms) for duration calculation"),
35522
+ error_count: tool.schema.number().optional().describe("Number of errors encountered during task"),
35523
+ retry_count: tool.schema.number().optional().describe("Number of retry attempts during task")
34974
35524
  },
34975
35525
  async execute(args) {
34976
- let verificationResult = null;
34977
- if (!args.skip_verification && args.files_touched?.length) {
34978
- verificationResult = await runVerificationGate(args.files_touched, args.skip_ubs_scan ?? false);
34979
- if (!verificationResult.passed) {
34980
- return JSON.stringify({
34981
- success: false,
34982
- error: "Verification Gate FAILED - fix issues before completing",
34983
- verification: {
34984
- passed: false,
34985
- summary: verificationResult.summary,
34986
- blockers: verificationResult.blockers,
34987
- steps: verificationResult.steps.map((s) => ({
34988
- name: s.name,
34989
- passed: s.passed,
34990
- skipped: s.skipped,
34991
- skipReason: s.skipReason,
34992
- error: s.error?.slice(0, 200)
34993
- }))
34994
- },
34995
- hint: verificationResult.blockers.length > 0 ? `Fix these issues: ${verificationResult.blockers.map((b, i) => `${i + 1}. ${b}`).join(", ")}. Use skip_verification=true only as last resort.` : "Fix the failing checks and try again. Use skip_verification=true only as last resort.",
34996
- gate_function: "IDENTIFY → RUN → READ → VERIFY → CLAIM (you are at VERIFY, claim blocked)"
34997
- }, null, 2);
35526
+ const epicId = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
35527
+ try {
35528
+ const projectKey = args.project_key.replace(/\//g, "-").replace(/\\/g, "-");
35529
+ let agentRegistered = false;
35530
+ let registrationWarning = "";
35531
+ try {
35532
+ const agent = await getAgent(projectKey, args.agent_name, args.project_key);
35533
+ agentRegistered = agent !== null;
35534
+ if (!agentRegistered) {
35535
+ registrationWarning = `⚠️ WARNING: Agent '${args.agent_name}' was NOT registered in swarm-mail for project '${projectKey}'.
35536
+
35537
+ This usually means you skipped the MANDATORY swarmmail_init step.
35538
+
35539
+ **Impact:**
35540
+ - Your work was not tracked in the coordination system
35541
+ - File reservations may not have been managed
35542
+ - Other agents couldn't coordinate with you
35543
+ - Learning/eval data may be incomplete
35544
+
35545
+ **Next time:** Run swarmmail_init(project_path="${args.project_key}", task_description="<task>") FIRST, before any other work.
35546
+
35547
+ Continuing with completion, but this should be fixed for future subtasks.`;
35548
+ console.warn(`[swarm_complete] ${registrationWarning}`);
35549
+ }
35550
+ } catch (error45) {
35551
+ console.warn(`[swarm_complete] Could not verify agent registration:`, error45);
35552
+ registrationWarning = `ℹ️ Could not verify swarm-mail registration (database may not be available). Consider running swarmmail_init next time.`;
34998
35553
  }
34999
- }
35000
- let ubsResult = null;
35001
- if (!args.skip_verification && !verificationResult && args.files_touched?.length && !args.skip_ubs_scan) {
35002
- ubsResult = await runUbsScan(args.files_touched);
35003
- if (ubsResult && ubsResult.summary.critical > 0) {
35004
- return JSON.stringify({
35005
- success: false,
35006
- error: `UBS found ${ubsResult.summary.critical} critical bug(s) that must be fixed before completing`,
35007
- ubs_scan: {
35008
- critical_count: ubsResult.summary.critical,
35009
- bugs: ubsResult.bugs.filter((b) => b.severity === "critical")
35010
- },
35011
- hint: `Fix these critical bugs: ${ubsResult.bugs.filter((b) => b.severity === "critical").map((b) => `${b.file}:${b.line} - ${b.message}`).slice(0, 3).join("; ")}. Try: Run 'ubs scan ${args.files_touched?.join(" ") || "."} --json' for full report, fix reported issues, or use skip_ubs_scan=true to bypass (not recommended).`
35012
- }, null, 2);
35554
+ let verificationResult = null;
35555
+ if (!args.skip_verification && args.files_touched?.length) {
35556
+ verificationResult = await runVerificationGate(args.files_touched, args.skip_ubs_scan ?? false);
35557
+ if (!verificationResult.passed) {
35558
+ return JSON.stringify({
35559
+ success: false,
35560
+ error: "Verification Gate FAILED - fix issues before completing",
35561
+ verification: {
35562
+ passed: false,
35563
+ summary: verificationResult.summary,
35564
+ blockers: verificationResult.blockers,
35565
+ steps: verificationResult.steps.map((s) => ({
35566
+ name: s.name,
35567
+ passed: s.passed,
35568
+ skipped: s.skipped,
35569
+ skipReason: s.skipReason,
35570
+ error: s.error?.slice(0, 200)
35571
+ }))
35572
+ },
35573
+ hint: verificationResult.blockers.length > 0 ? `Fix these issues: ${verificationResult.blockers.map((b, i) => `${i + 1}. ${b}`).join(", ")}. Use skip_verification=true only as last resort.` : "Fix the failing checks and try again. Use skip_verification=true only as last resort.",
35574
+ gate_function: "IDENTIFY → RUN → READ → VERIFY → CLAIM (you are at VERIFY, claim blocked)"
35575
+ }, null, 2);
35576
+ }
35577
+ }
35578
+ let ubsResult = null;
35579
+ if (!args.skip_verification && !verificationResult && args.files_touched?.length && !args.skip_ubs_scan) {
35580
+ ubsResult = await runUbsScan(args.files_touched);
35581
+ if (ubsResult && ubsResult.summary.critical > 0) {
35582
+ return JSON.stringify({
35583
+ success: false,
35584
+ error: `UBS found ${ubsResult.summary.critical} critical bug(s) that must be fixed before completing`,
35585
+ ubs_scan: {
35586
+ critical_count: ubsResult.summary.critical,
35587
+ bugs: ubsResult.bugs.filter((b) => b.severity === "critical")
35588
+ },
35589
+ hint: `Fix these critical bugs: ${ubsResult.bugs.filter((b) => b.severity === "critical").map((b) => `${b.file}:${b.line} - ${b.message}`).slice(0, 3).join("; ")}. Try: Run 'ubs scan ${args.files_touched?.join(" ") || "."} --json' for full report, fix reported issues, or use skip_ubs_scan=true to bypass (not recommended).`
35590
+ }, null, 2);
35591
+ }
35592
+ }
35593
+ let parsedEvaluation;
35594
+ if (args.evaluation) {
35595
+ try {
35596
+ parsedEvaluation = EvaluationSchema.parse(JSON.parse(args.evaluation));
35597
+ } catch (error45) {
35598
+ return JSON.stringify({
35599
+ success: false,
35600
+ error: "Invalid evaluation format",
35601
+ details: error45 instanceof exports_external.ZodError ? error45.issues : String(error45)
35602
+ }, null, 2);
35603
+ }
35604
+ if (!parsedEvaluation.passed) {
35605
+ return JSON.stringify({
35606
+ success: false,
35607
+ error: "Self-evaluation failed",
35608
+ retry_suggestion: parsedEvaluation.retry_suggestion,
35609
+ feedback: parsedEvaluation.overall_feedback
35610
+ }, null, 2);
35611
+ }
35612
+ }
35613
+ const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.quiet().nothrow();
35614
+ if (closeResult.exitCode !== 0) {
35615
+ throw new Error(`Failed to close bead because bd close command failed: ${closeResult.stderr.toString()}. Try: Verify bead exists and is not already closed with 'bd show ${args.bead_id}', check if bead ID is correct with 'beads_query()', or use beads_close tool directly.`);
35013
35616
  }
35014
- }
35015
- let parsedEvaluation;
35016
- if (args.evaluation) {
35017
35617
  try {
35018
- parsedEvaluation = EvaluationSchema.parse(JSON.parse(args.evaluation));
35618
+ const epicId3 = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
35619
+ const durationMs2 = args.start_time ? Date.now() - args.start_time : 0;
35620
+ const event = createEvent("subtask_outcome", {
35621
+ project_key: args.project_key,
35622
+ epic_id: epicId3,
35623
+ bead_id: args.bead_id,
35624
+ planned_files: args.planned_files || [],
35625
+ actual_files: args.files_touched || [],
35626
+ duration_ms: durationMs2,
35627
+ error_count: args.error_count || 0,
35628
+ retry_count: args.retry_count || 0,
35629
+ success: true
35630
+ });
35631
+ await appendEvent(event, args.project_key);
35019
35632
  } catch (error45) {
35020
- return JSON.stringify({
35021
- success: false,
35022
- error: "Invalid evaluation format",
35023
- details: error45 instanceof exports_external.ZodError ? error45.issues : String(error45)
35024
- }, null, 2);
35633
+ console.warn("[swarm_complete] Failed to emit SubtaskOutcomeEvent:", error45);
35025
35634
  }
35026
- if (!parsedEvaluation.passed) {
35027
- return JSON.stringify({
35028
- success: false,
35029
- error: "Self-evaluation failed",
35030
- retry_suggestion: parsedEvaluation.retry_suggestion,
35031
- feedback: parsedEvaluation.overall_feedback
35032
- }, null, 2);
35635
+ let capturedStrategy;
35636
+ const durationMs = args.start_time ? Date.now() - args.start_time : 0;
35637
+ const memoryInfo = formatMemoryStoreOnSuccess(args.bead_id, args.summary, args.files_touched || [], capturedStrategy);
35638
+ let memoryStored = false;
35639
+ let memoryError;
35640
+ try {
35641
+ const memoryAvailable = await isToolAvailable("semantic-memory");
35642
+ if (memoryAvailable) {
35643
+ const storeResult = await Bun.$`semantic-memory store ${memoryInfo.information} --metadata ${memoryInfo.metadata}`.quiet().nothrow();
35644
+ if (storeResult.exitCode === 0) {
35645
+ memoryStored = true;
35646
+ console.log(`[swarm_complete] Stored learning for ${args.bead_id} in semantic-memory`);
35647
+ } else {
35648
+ memoryError = `semantic-memory store failed: ${storeResult.stderr.toString().slice(0, 200)}`;
35649
+ console.warn(`[swarm_complete] ${memoryError}`);
35650
+ }
35651
+ } else {
35652
+ memoryError = "semantic-memory not available - learning stored in-memory only";
35653
+ warnMissingTool("semantic-memory");
35654
+ }
35655
+ } catch (error45) {
35656
+ memoryError = `Failed to store memory: ${error45 instanceof Error ? error45.message : String(error45)}`;
35657
+ console.warn(`[swarm_complete] ${memoryError}`);
35033
35658
  }
35034
- }
35035
- const closeResult = await Bun.$`bd close ${args.bead_id} --reason ${args.summary} --json`.quiet().nothrow();
35036
- if (closeResult.exitCode !== 0) {
35037
- throw new Error(`Failed to close bead because bd close command failed: ${closeResult.stderr.toString()}. Try: Verify bead exists and is not already closed with 'bd show ${args.bead_id}', check if bead ID is correct with 'beads_query()', or use beads_close tool directly.`);
35038
- }
35039
- try {
35040
- await releaseSwarmFiles({
35659
+ try {
35660
+ await releaseSwarmFiles({
35661
+ projectPath: args.project_key,
35662
+ agentName: args.agent_name
35663
+ });
35664
+ } catch (error45) {
35665
+ console.warn(`[swarm] Failed to release file reservations for ${args.agent_name}:`, error45);
35666
+ }
35667
+ const epicId2 = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
35668
+ const completionBody = [
35669
+ `## Subtask Complete: ${args.bead_id}`,
35670
+ "",
35671
+ `**Summary**: ${args.summary}`,
35672
+ "",
35673
+ parsedEvaluation ? `**Self-Evaluation**: ${parsedEvaluation.passed ? "PASSED" : "FAILED"}` : "",
35674
+ parsedEvaluation?.overall_feedback ? `**Feedback**: ${parsedEvaluation.overall_feedback}` : "",
35675
+ "",
35676
+ `**Memory Capture**: ${memoryStored ? "✓ Stored in semantic-memory" : `✗ ${memoryError || "Failed"}`}`
35677
+ ].filter(Boolean).join(`
35678
+ `);
35679
+ await sendSwarmMessage({
35041
35680
  projectPath: args.project_key,
35042
- agentName: args.agent_name
35681
+ fromAgent: args.agent_name,
35682
+ toAgents: [],
35683
+ subject: `Complete: ${args.bead_id}`,
35684
+ body: completionBody,
35685
+ threadId: epicId2,
35686
+ importance: "normal"
35043
35687
  });
35044
- } catch (error45) {
35045
- console.warn(`[swarm] Failed to release file reservations for ${args.agent_name}:`, error45);
35046
- }
35047
- const epicId = args.bead_id.includes(".") ? args.bead_id.split(".")[0] : args.bead_id;
35048
- const completionBody = [
35049
- `## Subtask Complete: ${args.bead_id}`,
35050
- "",
35051
- `**Summary**: ${args.summary}`,
35052
- "",
35053
- parsedEvaluation ? `**Self-Evaluation**: ${parsedEvaluation.passed ? "PASSED" : "FAILED"}` : "",
35054
- parsedEvaluation?.overall_feedback ? `**Feedback**: ${parsedEvaluation.overall_feedback}` : ""
35055
- ].filter(Boolean).join(`
35056
- `);
35057
- await sendSwarmMessage({
35058
- projectPath: args.project_key,
35059
- fromAgent: args.agent_name,
35060
- toAgents: [],
35061
- subject: `Complete: ${args.bead_id}`,
35062
- body: completionBody,
35063
- threadId: epicId,
35064
- importance: "normal"
35065
- });
35066
- const response = {
35067
- success: true,
35068
- bead_id: args.bead_id,
35069
- closed: true,
35070
- reservations_released: true,
35071
- message_sent: true,
35072
- verification_gate: verificationResult ? {
35073
- passed: true,
35074
- summary: verificationResult.summary,
35075
- steps: verificationResult.steps.map((s) => ({
35076
- name: s.name,
35077
- passed: s.passed,
35078
- skipped: s.skipped,
35079
- skipReason: s.skipReason
35080
- }))
35081
- } : args.skip_verification ? { skipped: true, reason: "skip_verification=true" } : { skipped: true, reason: "no files_touched provided" },
35082
- ubs_scan: ubsResult ? {
35083
- ran: true,
35084
- bugs_found: ubsResult.summary.total,
35085
- summary: ubsResult.summary,
35086
- warnings: ubsResult.bugs.filter((b) => b.severity !== "critical")
35087
- } : verificationResult ? { ran: true, included_in_verification_gate: true } : {
35088
- ran: false,
35089
- reason: args.skip_ubs_scan ? "skipped" : "no files or ubs unavailable"
35090
- },
35091
- learning_prompt: `## Reflection
35688
+ const response = {
35689
+ success: true,
35690
+ bead_id: args.bead_id,
35691
+ closed: true,
35692
+ reservations_released: true,
35693
+ message_sent: true,
35694
+ agent_registration: {
35695
+ verified: agentRegistered,
35696
+ warning: registrationWarning || undefined
35697
+ },
35698
+ verification_gate: verificationResult ? {
35699
+ passed: true,
35700
+ summary: verificationResult.summary,
35701
+ steps: verificationResult.steps.map((s) => ({
35702
+ name: s.name,
35703
+ passed: s.passed,
35704
+ skipped: s.skipped,
35705
+ skipReason: s.skipReason
35706
+ }))
35707
+ } : args.skip_verification ? { skipped: true, reason: "skip_verification=true" } : { skipped: true, reason: "no files_touched provided" },
35708
+ ubs_scan: ubsResult ? {
35709
+ ran: true,
35710
+ bugs_found: ubsResult.summary.total,
35711
+ summary: ubsResult.summary,
35712
+ warnings: ubsResult.bugs.filter((b) => b.severity !== "critical")
35713
+ } : verificationResult ? { ran: true, included_in_verification_gate: true } : {
35714
+ ran: false,
35715
+ reason: args.skip_ubs_scan ? "skipped" : "no files or ubs unavailable"
35716
+ },
35717
+ learning_prompt: `## Reflection
35092
35718
 
35093
35719
  Did you learn anything reusable during this subtask? Consider:
35094
35720
 
@@ -35100,9 +35726,81 @@ Did you learn anything reusable during this subtask? Consider:
35100
35726
  If you discovered something valuable, use \`swarm_learn\` or \`skills_create\` to preserve it as a skill for future swarms.
35101
35727
 
35102
35728
  Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
35103
- memory_store: formatMemoryStoreOnSuccess(args.bead_id, args.summary, args.files_touched || [])
35104
- };
35105
- return JSON.stringify(response, null, 2);
35729
+ memory_capture: {
35730
+ attempted: true,
35731
+ stored: memoryStored,
35732
+ error: memoryError,
35733
+ information: memoryInfo.information,
35734
+ metadata: memoryInfo.metadata,
35735
+ note: memoryStored ? "Learning automatically stored in semantic-memory" : `Failed to store: ${memoryError}. Learning lost unless semantic-memory is available.`
35736
+ }
35737
+ };
35738
+ return JSON.stringify(response, null, 2);
35739
+ } catch (error45) {
35740
+ const errorMessage = error45 instanceof Error ? error45.message : String(error45);
35741
+ const errorStack = error45 instanceof Error ? error45.stack : undefined;
35742
+ let failedStep = "unknown";
35743
+ if (errorMessage.includes("verification")) {
35744
+ failedStep = "Verification Gate (UBS/typecheck/tests)";
35745
+ } else if (errorMessage.includes("UBS") || errorMessage.includes("ubs")) {
35746
+ failedStep = "UBS scan";
35747
+ } else if (errorMessage.includes("evaluation")) {
35748
+ failedStep = "Self-evaluation parsing";
35749
+ } else if (errorMessage.includes("bead") || errorMessage.includes("close")) {
35750
+ failedStep = "Bead close";
35751
+ } else if (errorMessage.includes("memory") || errorMessage.includes("semantic")) {
35752
+ failedStep = "Memory storage (non-fatal)";
35753
+ } else if (errorMessage.includes("reservation") || errorMessage.includes("release")) {
35754
+ failedStep = "File reservation release";
35755
+ } else if (errorMessage.includes("message") || errorMessage.includes("mail")) {
35756
+ failedStep = "Swarm mail notification";
35757
+ }
35758
+ const errorBody = [
35759
+ `## ⚠️ SWARM_COMPLETE FAILED`,
35760
+ "",
35761
+ `**Bead**: ${args.bead_id}`,
35762
+ `**Agent**: ${args.agent_name}`,
35763
+ `**Failed Step**: ${failedStep}`,
35764
+ "",
35765
+ `### Error Message`,
35766
+ "```",
35767
+ errorMessage,
35768
+ "```",
35769
+ "",
35770
+ errorStack ? `### Stack Trace
35771
+ \`\`\`
35772
+ ${errorStack.slice(0, 1000)}
35773
+ \`\`\`
35774
+ ` : "",
35775
+ `### Context`,
35776
+ `- **Summary**: ${args.summary}`,
35777
+ `- **Files touched**: ${args.files_touched?.length ? args.files_touched.join(", ") : "none"}`,
35778
+ `- **Skip UBS**: ${args.skip_ubs_scan ?? false}`,
35779
+ `- **Skip verification**: ${args.skip_verification ?? false}`,
35780
+ "",
35781
+ `### Recovery Actions`,
35782
+ "1. Check error message for specific issue",
35783
+ "2. Review failed step (UBS scan, typecheck, bead close, etc.)",
35784
+ "3. Fix underlying issue or use skip flags if appropriate",
35785
+ "4. Retry swarm_complete after fixing"
35786
+ ].filter(Boolean).join(`
35787
+ `);
35788
+ try {
35789
+ await sendSwarmMessage({
35790
+ projectPath: args.project_key,
35791
+ fromAgent: args.agent_name,
35792
+ toAgents: [],
35793
+ subject: `FAILED: swarm_complete for ${args.bead_id}`,
35794
+ body: errorBody,
35795
+ threadId: epicId,
35796
+ importance: "urgent"
35797
+ });
35798
+ } catch (mailError) {
35799
+ console.error(`[swarm_complete] CRITICAL: Failed to notify coordinator of failure for ${args.bead_id}:`, mailError);
35800
+ console.error(`[swarm_complete] Original error:`, error45);
35801
+ }
35802
+ throw error45;
35803
+ }
35106
35804
  }
35107
35805
  });
35108
35806
  var swarm_record_outcome = tool({
@@ -35330,6 +36028,147 @@ var swarm_check_strikes = tool({
35330
36028
  }
35331
36029
  }
35332
36030
  });
36031
+ var swarm_checkpoint = tool({
36032
+ description: "Checkpoint swarm context for recovery. Records current state for crash recovery. Non-fatal errors.",
36033
+ args: {
36034
+ project_key: tool.schema.string().describe("Project path"),
36035
+ agent_name: tool.schema.string().describe("Agent name"),
36036
+ bead_id: tool.schema.string().describe("Subtask bead ID"),
36037
+ epic_id: tool.schema.string().describe("Epic bead ID"),
36038
+ files_modified: tool.schema.array(tool.schema.string()).describe("Files modified so far"),
36039
+ progress_percent: tool.schema.number().min(0).max(100).describe("Current progress"),
36040
+ directives: tool.schema.object({
36041
+ shared_context: tool.schema.string().optional(),
36042
+ skills_to_load: tool.schema.array(tool.schema.string()).optional(),
36043
+ coordinator_notes: tool.schema.string().optional()
36044
+ }).optional().describe("Coordinator directives for this subtask"),
36045
+ error_context: tool.schema.string().optional().describe("Error context if checkpoint is during error handling")
36046
+ },
36047
+ async execute(args) {
36048
+ try {
36049
+ const checkpoint = {
36050
+ epic_id: args.epic_id,
36051
+ bead_id: args.bead_id,
36052
+ strategy: "file-based",
36053
+ files: args.files_modified,
36054
+ dependencies: [],
36055
+ directives: args.directives || {},
36056
+ recovery: {
36057
+ last_checkpoint: Date.now(),
36058
+ files_modified: args.files_modified,
36059
+ progress_percent: args.progress_percent,
36060
+ error_context: args.error_context
36061
+ }
36062
+ };
36063
+ const event = createEvent("swarm_checkpointed", {
36064
+ project_key: args.project_key,
36065
+ epic_id: args.epic_id,
36066
+ bead_id: args.bead_id,
36067
+ strategy: checkpoint.strategy,
36068
+ files: checkpoint.files,
36069
+ dependencies: checkpoint.dependencies,
36070
+ directives: checkpoint.directives,
36071
+ recovery: checkpoint.recovery
36072
+ });
36073
+ await appendEvent(event, args.project_key);
36074
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_streams(), exports_streams));
36075
+ const db = await getDatabase2(args.project_key);
36076
+ const now = Date.now();
36077
+ await db.query(`INSERT INTO swarm_contexts (id, epic_id, bead_id, strategy, files, dependencies, directives, recovery, created_at, updated_at)
36078
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
36079
+ ON CONFLICT (id) DO UPDATE SET
36080
+ files = EXCLUDED.files,
36081
+ recovery = EXCLUDED.recovery,
36082
+ updated_at = EXCLUDED.updated_at`, [
36083
+ args.bead_id,
36084
+ args.epic_id,
36085
+ args.bead_id,
36086
+ checkpoint.strategy,
36087
+ JSON.stringify(checkpoint.files),
36088
+ JSON.stringify(checkpoint.dependencies),
36089
+ JSON.stringify(checkpoint.directives),
36090
+ JSON.stringify(checkpoint.recovery),
36091
+ now,
36092
+ now
36093
+ ]);
36094
+ return JSON.stringify({
36095
+ success: true,
36096
+ checkpoint_timestamp: now,
36097
+ summary: `Checkpoint saved for ${args.bead_id} at ${args.progress_percent}%`,
36098
+ bead_id: args.bead_id,
36099
+ epic_id: args.epic_id,
36100
+ files_tracked: args.files_modified.length
36101
+ }, null, 2);
36102
+ } catch (error45) {
36103
+ console.warn(`[swarm_checkpoint] Failed to checkpoint ${args.bead_id}:`, error45);
36104
+ return JSON.stringify({
36105
+ success: false,
36106
+ warning: "Checkpoint failed but continuing",
36107
+ error: error45 instanceof Error ? error45.message : String(error45),
36108
+ bead_id: args.bead_id,
36109
+ note: "This is non-fatal. Work can continue without checkpoint."
36110
+ }, null, 2);
36111
+ }
36112
+ }
36113
+ });
36114
+ var swarm_recover = tool({
36115
+ description: "Recover swarm context from last checkpoint. Returns context or null if not found.",
36116
+ args: {
36117
+ project_key: tool.schema.string().describe("Project path"),
36118
+ epic_id: tool.schema.string().describe("Epic bead ID to recover")
36119
+ },
36120
+ async execute(args) {
36121
+ try {
36122
+ const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_streams(), exports_streams));
36123
+ const db = await getDatabase2(args.project_key);
36124
+ const result = await db.query(`SELECT * FROM swarm_contexts
36125
+ WHERE epic_id = $1
36126
+ ORDER BY updated_at DESC
36127
+ LIMIT 1`, [args.epic_id]);
36128
+ if (result.rows.length === 0) {
36129
+ return JSON.stringify({
36130
+ found: false,
36131
+ message: `No checkpoint found for epic ${args.epic_id}`,
36132
+ epic_id: args.epic_id
36133
+ }, null, 2);
36134
+ }
36135
+ const row = result.rows[0];
36136
+ const context = {
36137
+ id: row.id,
36138
+ epic_id: row.epic_id,
36139
+ bead_id: row.bead_id,
36140
+ strategy: row.strategy,
36141
+ files: JSON.parse(row.files),
36142
+ dependencies: JSON.parse(row.dependencies),
36143
+ directives: JSON.parse(row.directives),
36144
+ recovery: JSON.parse(row.recovery),
36145
+ created_at: row.created_at,
36146
+ updated_at: row.updated_at
36147
+ };
36148
+ const event = createEvent("swarm_recovered", {
36149
+ project_key: args.project_key,
36150
+ epic_id: args.epic_id,
36151
+ bead_id: context.bead_id,
36152
+ recovered_from_checkpoint: context.recovery.last_checkpoint
36153
+ });
36154
+ await appendEvent(event, args.project_key);
36155
+ return JSON.stringify({
36156
+ found: true,
36157
+ context,
36158
+ summary: `Recovered checkpoint from ${new Date(context.updated_at).toISOString()}`,
36159
+ age_seconds: Math.round((Date.now() - context.updated_at) / 1000)
36160
+ }, null, 2);
36161
+ } catch (error45) {
36162
+ console.warn(`[swarm_recover] Failed to recover context for ${args.epic_id}:`, error45);
36163
+ return JSON.stringify({
36164
+ found: false,
36165
+ error: error45 instanceof Error ? error45.message : String(error45),
36166
+ message: `Recovery failed for epic ${args.epic_id}`,
36167
+ epic_id: args.epic_id
36168
+ }, null, 2);
36169
+ }
36170
+ }
36171
+ });
35333
36172
  var swarm_learn = tool({
35334
36173
  description: `Analyze completed work and optionally create a skill from learned patterns.
35335
36174
 
@@ -35470,6 +36309,8 @@ var orchestrateTools = {
35470
36309
  swarm_get_error_context,
35471
36310
  swarm_resolve_error,
35472
36311
  swarm_check_strikes,
36312
+ swarm_checkpoint,
36313
+ swarm_recover,
35473
36314
  swarm_learn
35474
36315
  };
35475
36316
 
@@ -36564,6 +37405,172 @@ var mandateTools = {
36564
37405
  mandate_list,
36565
37406
  mandate_stats
36566
37407
  };
37408
+
37409
+ // src/output-guardrails.ts
37410
+ var DEFAULT_GUARDRAIL_CONFIG = {
37411
+ defaultMaxChars: 32000,
37412
+ toolLimits: {
37413
+ "repo-autopsy_file": 64000,
37414
+ "repo-autopsy_search": 64000,
37415
+ "repo-autopsy_exports_map": 64000,
37416
+ "context7_get-library-docs": 64000,
37417
+ cass_view: 64000,
37418
+ cass_search: 48000,
37419
+ skills_read: 48000,
37420
+ "repo-autopsy_structure": 24000,
37421
+ "repo-autopsy_stats": 16000,
37422
+ cass_stats: 8000
37423
+ },
37424
+ skipTools: [
37425
+ "beads_create",
37426
+ "beads_create_epic",
37427
+ "beads_query",
37428
+ "beads_update",
37429
+ "beads_close",
37430
+ "beads_start",
37431
+ "beads_ready",
37432
+ "beads_sync",
37433
+ "agentmail_init",
37434
+ "agentmail_send",
37435
+ "agentmail_inbox",
37436
+ "agentmail_read_message",
37437
+ "agentmail_summarize_thread",
37438
+ "agentmail_reserve",
37439
+ "agentmail_release",
37440
+ "agentmail_ack",
37441
+ "swarmmail_init",
37442
+ "swarmmail_send",
37443
+ "swarmmail_inbox",
37444
+ "swarmmail_read_message",
37445
+ "swarmmail_reserve",
37446
+ "swarmmail_release",
37447
+ "swarmmail_ack",
37448
+ "structured_extract_json",
37449
+ "structured_validate",
37450
+ "structured_parse_evaluation",
37451
+ "structured_parse_decomposition",
37452
+ "structured_parse_bead_tree",
37453
+ "swarm_select_strategy",
37454
+ "swarm_plan_prompt",
37455
+ "swarm_decompose",
37456
+ "swarm_validate_decomposition",
37457
+ "swarm_status",
37458
+ "swarm_progress",
37459
+ "swarm_complete",
37460
+ "swarm_record_outcome",
37461
+ "swarm_subtask_prompt",
37462
+ "swarm_spawn_subtask",
37463
+ "swarm_complete_subtask",
37464
+ "swarm_evaluation_prompt",
37465
+ "mandate_file",
37466
+ "mandate_vote",
37467
+ "mandate_query",
37468
+ "mandate_list",
37469
+ "mandate_stats"
37470
+ ]
37471
+ };
37472
+ function findMatchingBrace(text, startIdx) {
37473
+ const openChar = text[startIdx];
37474
+ const closeChar = openChar === "{" ? "}" : "]";
37475
+ let depth = 1;
37476
+ for (let i = startIdx + 1;i < text.length; i++) {
37477
+ if (text[i] === openChar) {
37478
+ depth++;
37479
+ } else if (text[i] === closeChar) {
37480
+ depth--;
37481
+ if (depth === 0) {
37482
+ return i;
37483
+ }
37484
+ }
37485
+ }
37486
+ return -1;
37487
+ }
37488
+ function truncateWithBoundaries(text, maxChars) {
37489
+ if (text.length <= maxChars) {
37490
+ return text;
37491
+ }
37492
+ let truncateAt = maxChars;
37493
+ const beforeTruncate = text.slice(0, maxChars);
37494
+ const lastOpenBrace = Math.max(beforeTruncate.lastIndexOf("{"), beforeTruncate.lastIndexOf("["));
37495
+ const lastCloseBrace = Math.max(beforeTruncate.lastIndexOf("}"), beforeTruncate.lastIndexOf("]"));
37496
+ if (lastOpenBrace > lastCloseBrace) {
37497
+ const matchingClose = findMatchingBrace(text, lastOpenBrace);
37498
+ if (matchingClose !== -1 && matchingClose < maxChars * 1.2) {
37499
+ truncateAt = matchingClose + 1;
37500
+ } else {
37501
+ truncateAt = lastOpenBrace;
37502
+ }
37503
+ }
37504
+ const codeBlockMarker = "```";
37505
+ const beforeTruncateForCode = text.slice(0, truncateAt);
37506
+ const codeBlockCount = (beforeTruncateForCode.match(/```/g) || []).length;
37507
+ if (codeBlockCount % 2 === 1) {
37508
+ const closeMarkerIdx = text.indexOf(codeBlockMarker, truncateAt);
37509
+ if (closeMarkerIdx !== -1 && closeMarkerIdx < maxChars * 1.2) {
37510
+ truncateAt = closeMarkerIdx + codeBlockMarker.length;
37511
+ } else {
37512
+ const lastOpenMarker = beforeTruncateForCode.lastIndexOf(codeBlockMarker);
37513
+ if (lastOpenMarker !== -1) {
37514
+ truncateAt = lastOpenMarker;
37515
+ }
37516
+ }
37517
+ }
37518
+ const headerMatch = text.slice(0, truncateAt).match(/\n#{1,6}\s/g);
37519
+ if (headerMatch && headerMatch.length > 0) {
37520
+ const lastHeaderIdx = beforeTruncateForCode.lastIndexOf(`
37521
+ ##`);
37522
+ if (lastHeaderIdx !== -1 && lastHeaderIdx > maxChars * 0.8) {
37523
+ truncateAt = lastHeaderIdx;
37524
+ }
37525
+ }
37526
+ while (truncateAt > 0 && !/\s/.test(text[truncateAt])) {
37527
+ truncateAt--;
37528
+ }
37529
+ const truncated = text.slice(0, truncateAt).trimEnd();
37530
+ const charsRemoved = text.length - truncated.length;
37531
+ return `${truncated}
37532
+
37533
+ [TRUNCATED - ${charsRemoved.toLocaleString()} chars removed]`;
37534
+ }
37535
+ function getToolLimit(toolName, config2 = DEFAULT_GUARDRAIL_CONFIG) {
37536
+ return config2.toolLimits[toolName] ?? config2.defaultMaxChars;
37537
+ }
37538
+ function guardrailOutput(toolName, output, config2 = DEFAULT_GUARDRAIL_CONFIG) {
37539
+ const originalLength = output.length;
37540
+ if (config2.skipTools.includes(toolName)) {
37541
+ return {
37542
+ output,
37543
+ truncated: false,
37544
+ originalLength,
37545
+ truncatedLength: originalLength
37546
+ };
37547
+ }
37548
+ const limit = getToolLimit(toolName, config2);
37549
+ if (originalLength <= limit) {
37550
+ return {
37551
+ output,
37552
+ truncated: false,
37553
+ originalLength,
37554
+ truncatedLength: originalLength
37555
+ };
37556
+ }
37557
+ const truncatedOutput = truncateWithBoundaries(output, limit);
37558
+ const truncatedLength = truncatedOutput.length;
37559
+ return {
37560
+ output: truncatedOutput,
37561
+ truncated: true,
37562
+ originalLength,
37563
+ truncatedLength
37564
+ };
37565
+ }
37566
+ function createMetrics(result, toolName) {
37567
+ return {
37568
+ toolName,
37569
+ originalLength: result.originalLength,
37570
+ truncatedLength: result.truncatedLength,
37571
+ timestamp: Date.now()
37572
+ };
37573
+ }
36567
37574
  // src/storage.ts
36568
37575
  init_learning();
36569
37576
 
@@ -36696,20 +37703,47 @@ async function execSemanticMemory2(args) {
36696
37703
  };
36697
37704
  }
36698
37705
  }
36699
- var DEFAULT_STORAGE_CONFIG = {
36700
- backend: "semantic-memory",
36701
- collections: {
37706
+ function getCollectionNames() {
37707
+ const base = {
36702
37708
  feedback: "swarm-feedback",
36703
37709
  patterns: "swarm-patterns",
36704
37710
  maturity: "swarm-maturity"
36705
- },
37711
+ };
37712
+ if (process.env.TEST_MEMORY_COLLECTIONS === "true") {
37713
+ return {
37714
+ feedback: `${base.feedback}-test`,
37715
+ patterns: `${base.patterns}-test`,
37716
+ maturity: `${base.maturity}-test`
37717
+ };
37718
+ }
37719
+ return base;
37720
+ }
37721
+ var DEFAULT_STORAGE_CONFIG = {
37722
+ backend: "semantic-memory",
37723
+ collections: getCollectionNames(),
36706
37724
  useSemanticSearch: true
36707
37725
  };
36708
-
37726
+ var sessionStats = {
37727
+ storesCount: 0,
37728
+ queriesCount: 0,
37729
+ sessionStart: Date.now(),
37730
+ lastAlertCheck: Date.now()
37731
+ };
36709
37732
  class SemanticMemoryStorage {
36710
37733
  config;
36711
37734
  constructor(config2 = {}) {
36712
37735
  this.config = { ...DEFAULT_STORAGE_CONFIG, ...config2 };
37736
+ console.log(`[storage] SemanticMemoryStorage initialized with collections:`, this.config.collections);
37737
+ }
37738
+ async checkLowUsageAlert() {
37739
+ const TEN_MINUTES = 10 * 60 * 1000;
37740
+ const now = Date.now();
37741
+ const sessionDuration = now - sessionStats.sessionStart;
37742
+ const timeSinceLastAlert = now - sessionStats.lastAlertCheck;
37743
+ if (sessionDuration >= TEN_MINUTES && sessionStats.storesCount < 1 && timeSinceLastAlert >= TEN_MINUTES) {
37744
+ console.warn(`[storage] LOW USAGE ALERT: ${sessionStats.storesCount} stores after ${Math.floor(sessionDuration / 60000)} minutes`);
37745
+ sessionStats.lastAlertCheck = now;
37746
+ }
36713
37747
  }
36714
37748
  async store(collection, data, metadata) {
36715
37749
  const content = typeof data === "string" ? data : JSON.stringify(data);
@@ -36717,7 +37751,13 @@ class SemanticMemoryStorage {
36717
37751
  if (metadata) {
36718
37752
  args.push("--metadata", JSON.stringify(metadata));
36719
37753
  }
36720
- await execSemanticMemory2(args);
37754
+ console.log(`[storage] store() -> collection="${collection}"`);
37755
+ sessionStats.storesCount++;
37756
+ const result = await execSemanticMemory2(args);
37757
+ if (result.exitCode !== 0) {
37758
+ console.warn(`[storage] semantic-memory store() failed with exit code ${result.exitCode}: ${result.stderr.toString().trim()}`);
37759
+ }
37760
+ await this.checkLowUsageAlert();
36721
37761
  }
36722
37762
  async find(collection, query, limit = 10, useFts = false) {
36723
37763
  const args = [
@@ -36732,6 +37772,8 @@ class SemanticMemoryStorage {
36732
37772
  if (useFts) {
36733
37773
  args.push("--fts");
36734
37774
  }
37775
+ console.log(`[storage] find() -> collection="${collection}", query="${query.slice(0, 50)}${query.length > 50 ? "..." : ""}", limit=${limit}, fts=${useFts}`);
37776
+ sessionStats.queriesCount++;
36735
37777
  const result = await execSemanticMemory2(args);
36736
37778
  if (result.exitCode !== 0) {
36737
37779
  console.warn(`[storage] semantic-memory find() failed with exit code ${result.exitCode}: ${result.stderr.toString().trim()}`);
@@ -36757,6 +37799,8 @@ class SemanticMemoryStorage {
36757
37799
  }
36758
37800
  }
36759
37801
  async list(collection) {
37802
+ console.log(`[storage] list() -> collection="${collection}"`);
37803
+ sessionStats.queriesCount++;
36760
37804
  const result = await execSemanticMemory2([
36761
37805
  "list",
36762
37806
  "--collection",
@@ -37037,6 +38081,13 @@ var SwarmPlugin = async (input) => {
37037
38081
  },
37038
38082
  "tool.execute.after": async (input2, output) => {
37039
38083
  const toolName = input2.tool;
38084
+ if (output.output && typeof output.output === "string") {
38085
+ const guardrailResult = guardrailOutput(toolName, output.output);
38086
+ if (guardrailResult.truncated) {
38087
+ output.output = guardrailResult.output;
38088
+ console.log(`[swarm-plugin] Guardrail truncated ${toolName}: ${guardrailResult.originalLength} → ${guardrailResult.truncatedLength} chars`);
38089
+ }
38090
+ }
37040
38091
  if (toolName === "agentmail_init" && output.output) {
37041
38092
  try {
37042
38093
  const result = JSON.parse(output.output);
@@ -37083,6 +38134,7 @@ export {
37083
38134
  warnMissingTool,
37084
38135
  updateMandateStatus,
37085
38136
  updateAllMandateStatuses,
38137
+ truncateWithBoundaries,
37086
38138
  swarmTools,
37087
38139
  swarmMailTools,
37088
38140
  structuredTools,
@@ -37111,6 +38163,7 @@ export {
37111
38163
  isAgentNotFoundError,
37112
38164
  invalidateSkillsCache,
37113
38165
  ifToolAvailable,
38166
+ guardrailOutput,
37114
38167
  groupByTransition,
37115
38168
  getToolAvailability,
37116
38169
  getSwarmMailProjectDirectory,
@@ -37137,6 +38190,7 @@ export {
37137
38190
  src_default as default,
37138
38191
  createStorageWithFallback,
37139
38192
  createStorage,
38193
+ createMetrics,
37140
38194
  createMandateStorage,
37141
38195
  createAgentMailError,
37142
38196
  clearSessionState,
@@ -37159,12 +38213,17 @@ export {
37159
38213
  VoteTypeSchema,
37160
38214
  VoteSchema,
37161
38215
  ValidationResultSchema,
38216
+ UpdateSwarmContextArgsSchema,
37162
38217
  TaskDecompositionSchema,
38218
+ SwarmStrategySchema,
37163
38219
  SwarmStatusSchema,
37164
38220
  SwarmSpawnResultSchema,
38221
+ SwarmRecoverySchema,
37165
38222
  SwarmPlugin,
37166
38223
  SwarmEvaluationResultSchema,
37167
38224
  SwarmError,
38225
+ SwarmDirectivesSchema,
38226
+ SwarmBeadContextSchema,
37168
38227
  SubtaskSpecSchema,
37169
38228
  SubtaskDependencySchema,
37170
38229
  SpawnedAgentSchema,
@@ -37174,6 +38233,7 @@ export {
37174
38233
  SUBTASK_PROMPT_V2,
37175
38234
  STRATEGIES,
37176
38235
  RepoCrawlError,
38236
+ QuerySwarmContextsArgsSchema,
37177
38237
  QueryMandatesArgsSchema,
37178
38238
  MandateStatusSchema,
37179
38239
  MandateScoreSchema,
@@ -37195,8 +38255,10 @@ export {
37195
38255
  DEFAULT_STORAGE_CONFIG,
37196
38256
  DEFAULT_MANDATE_STORAGE_CONFIG,
37197
38257
  DEFAULT_MANDATE_DECAY_CONFIG,
38258
+ DEFAULT_GUARDRAIL_CONFIG,
37198
38259
  DEFAULT_CRITERIA,
37199
38260
  CriterionEvaluationSchema,
38261
+ CreateSwarmContextArgsSchema,
37200
38262
  CreateMandateArgsSchema,
37201
38263
  CastVoteArgsSchema,
37202
38264
  BeadValidationError,