@ra3orblade/swarm 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/swarmd.js CHANGED
@@ -968,8 +968,8 @@ function sinceToIso(since, now = Date.now()) {
968
968
  const m = /^(\d+)([dhm])$/.exec(since.trim());
969
969
  if (m) {
970
970
  const n = Number(m[1]);
971
- const ms2 = m[2] === "d" ? 86400000 : m[2] === "h" ? 3600000 : 60000;
972
- return new Date(now - n * ms2).toISOString();
971
+ const ms = m[2] === "d" ? 86400000 : m[2] === "h" ? 3600000 : 60000;
972
+ return new Date(now - n * ms).toISOString();
973
973
  }
974
974
  const t = Date.parse(since);
975
975
  return Number.isNaN(t) ? null : new Date(t).toISOString();
@@ -1421,22 +1421,22 @@ function contextReport(results, reads, turns, opts = {}) {
1421
1421
  const sessions = [];
1422
1422
  for (const sessionId of ids) {
1423
1423
  const worstAll = rereadWaste(readsBySession.get(sessionId) ?? []);
1424
- const wastedChars2 = worstAll.reduce((a, w) => a + w.wastedChars, 0);
1425
- const toolChars2 = charsBySession.get(sessionId) ?? 0;
1424
+ const wastedChars = worstAll.reduce((a, w) => a + w.wastedChars, 0);
1425
+ const toolChars = charsBySession.get(sessionId) ?? 0;
1426
1426
  const tk = tokensBySession.get(sessionId);
1427
1427
  const cacheable = (tk?.cacheRead ?? 0) + (tk?.input ?? 0);
1428
1428
  sessions.push({
1429
1429
  sessionId,
1430
- toolChars: toolChars2,
1431
- toolTokens: estTokens(toolChars2),
1430
+ toolChars,
1431
+ toolTokens: estTokens(toolChars),
1432
1432
  thinking: tk?.thinking ?? 0,
1433
1433
  cacheRead: tk?.cacheRead ?? 0,
1434
1434
  input: tk?.input ?? 0,
1435
1435
  cacheHit: cacheable ? (tk?.cacheRead ?? 0) / cacheable : 0,
1436
1436
  reads: (readsBySession.get(sessionId) ?? []).length,
1437
1437
  rereadFiles: worstAll.length,
1438
- wastedChars: wastedChars2,
1439
- wasteShare: toolChars2 ? wastedChars2 / toolChars2 : 0,
1438
+ wastedChars,
1439
+ wasteShare: toolChars ? wastedChars / toolChars : 0,
1440
1440
  worst: worstAll.slice(0, o.worstLimit)
1441
1441
  });
1442
1442
  }
@@ -1678,22 +1678,26 @@ function otherLiveInSameTree(current, sessions, now, withinMs = LIVE_WINDOW_MS)
1678
1678
  }
1679
1679
  return null;
1680
1680
  }
1681
+ var GIT_GLOBAL_OPTS = "(?:\\s+(?:-C\\s+\\S+|-c\\s+\\S+|--git-dir(?:=|\\s+)\\S+|--work-tree(?:=|\\s+)\\S+|--namespace(?:=|\\s+)\\S+|--no-pager|--paginate|--bare|--literal-pathspecs|--exec-path(?:=\\S+)?))*";
1682
+ function gitVerb(verb, tail) {
1683
+ return new RegExp(`\\bgit${GIT_GLOBAL_OPTS}\\s+${verb}${tail}`);
1684
+ }
1681
1685
  function isBroadStage(cmd) {
1682
1686
  const c = cmd.trim();
1683
- if (/\bgit\s+add\s+(-A\b|--all\b|\.(\s|$))/.test(c))
1687
+ if (gitVerb("add", "\\s+(-A\\b|--all\\b|\\.(\\s|$))").test(c))
1684
1688
  return true;
1685
- if (/\bgit\s+commit\b[^|&;]*\s-[a-zA-Z]*a/.test(c))
1689
+ if (gitVerb("commit", "\\b[^|&;]*\\s-[a-zA-Z]*a").test(c))
1686
1690
  return true;
1687
- if (/\bgit\s+add\s*$/.test(c))
1691
+ if (gitVerb("add", "\\s*$").test(c))
1688
1692
  return true;
1689
1693
  return false;
1690
1694
  }
1691
1695
  function isDestructiveGit(cmd) {
1692
1696
  const c = cmd.trim();
1693
- return /\bgit\s+reset\s+[^|&;]*--hard\b/.test(c) || /\bgit\s+checkout\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+checkout\s+-f\b/.test(c) || /\bgit\s+restore\s+(--\s+)?\.(\s|$)/.test(c) || /\bgit\s+clean\s+[^|&;]*-[a-zA-Z]*f/.test(c) || /\bgit\s+stash\s+(drop|clear)\b/.test(c) || /\bgit\s+branch\s+[^|&;]*-[a-zA-Z]*D/.test(c);
1697
+ return gitVerb("reset", "\\s+[^|&;]*--hard\\b").test(c) || gitVerb("checkout", "\\s+(--\\s+)?\\.(\\s|$)").test(c) || gitVerb("checkout", "\\s+-f\\b").test(c) || gitVerb("restore", "\\s+(--\\s+)?\\.(\\s|$)").test(c) || gitVerb("clean", "\\s+[^|&;]*-[a-zA-Z]*f").test(c) || gitVerb("stash", "\\s+(drop|clear)\\b").test(c) || gitVerb("branch", "\\s+[^|&;]*-[a-zA-Z]*D").test(c);
1694
1698
  }
1695
1699
  function isPatternKill(cmd) {
1696
- return /\bpkill\s+-f\b/.test(cmd) || /\bpgrep\s+-f\b[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
1700
+ return /\bpkill\s+(-[a-zA-Z]*f\b|--full\b)/.test(cmd) || /\bkillall\b/.test(cmd) || /\bpgrep\s+(-[a-zA-Z]*f\b|--full\b)[^|]*\|\s*[^|]*\bkill\b/.test(cmd);
1697
1701
  }
1698
1702
  function killedPorts(cmd) {
1699
1703
  const ports = new Set;
@@ -1858,20 +1862,20 @@ function dryRunRules(calls, modes, ctx) {
1858
1862
  if (d.action === "allow")
1859
1863
  continue;
1860
1864
  byRule[d.rule][d.action]++;
1861
- const norm2 = normalizeDisplay(display);
1865
+ const norm = normalizeDisplay(display);
1862
1866
  if (hits.length < maxHits)
1863
1867
  hits.push({
1864
1868
  ts: c.ts,
1865
1869
  sessionId: c.sessionId,
1866
1870
  rule: d.rule,
1867
1871
  action: d.action,
1868
- display: norm2,
1872
+ display: norm,
1869
1873
  completed: c.completed
1870
1874
  });
1871
- const key = `${d.rule} ${norm2}`;
1875
+ const key = `${d.rule} ${norm}`;
1872
1876
  const g = groups.get(key) ?? {
1873
1877
  rule: d.rule,
1874
- display: norm2,
1878
+ display: norm,
1875
1879
  fires: 0,
1876
1880
  completedRatio: 0,
1877
1881
  sessions: 0,
@@ -2362,7 +2366,7 @@ var HYGIENE_DEFAULTS = {
2362
2366
  hungryRssKb: 1024 * 1024,
2363
2367
  heavyKb: 2 * 1024 * 1024
2364
2368
  };
2365
- var days2 = (ms3) => ms3 / 86400000;
2369
+ var days2 = (ms) => ms / 86400000;
2366
2370
  function classifyProcess(p, opts = {}) {
2367
2371
  const o = { ...HYGIENE_DEFAULTS, ...opts };
2368
2372
  if (!p.alive)
@@ -3026,11 +3030,11 @@ function parseTo(to) {
3026
3030
  return { kind: "session", id: t };
3027
3031
  return { kind: "task", task: t };
3028
3032
  }
3029
- function formatMessages(ms3) {
3030
- if (!ms3.length)
3033
+ function formatMessages(ms) {
3034
+ if (!ms.length)
3031
3035
  return null;
3032
- const lines = ms3.map((m) => `- from ${m.from ?? "unknown"}${m.task ? ` (re ${m.task})` : ""}: ${m.text}`);
3033
- return `[swarm] While you were working, message${ms3.length === 1 ? "" : "s"} arrived:
3036
+ const lines = ms.map((m) => `- from ${m.from ?? "unknown"}${m.task ? ` (re ${m.task})` : ""}: ${m.text}`);
3037
+ return `[swarm] While you were working, message${ms.length === 1 ? "" : "s"} arrived:
3034
3038
  ${lines.join(`
3035
3039
  `)}
3036
3040
  Reply with swarm_send if a reply is expected.`;
@@ -3718,8 +3722,8 @@ function commandSignature(command) {
3718
3722
  const more = tokens.length > 2;
3719
3723
  return `${name}${second ? ` ${second}` : ""}${more ? " \u2026" : ""}`;
3720
3724
  }
3721
- function ruleEffect(incidents, changes = [], now = Date.now(), days3 = 30) {
3722
- const since = now - days3 * DAY;
3725
+ function ruleEffect(incidents, changes = [], now = Date.now(), days = 30) {
3726
+ const since = now - days * DAY;
3723
3727
  const rows = incidents.filter((i) => i.rule && Date.parse(i.at) >= since);
3724
3728
  const byRule = new Map;
3725
3729
  for (const i of rows)
@@ -3993,12 +3997,12 @@ function parseMarkdownTasks(text) {
3993
3997
  const cells = splitRow(line);
3994
3998
  if (!cols) {
3995
3999
  const lc = cells.map((c) => c.toLowerCase());
3996
- const id2 = lc.indexOf("id");
4000
+ const id = lc.indexOf("id");
3997
4001
  const task = lc.findIndex((c) => c === "task" || c === "title");
3998
- if (id2 < 0 || task < 0 || !isSeparator(lines[i + 1] ?? ""))
4002
+ if (id < 0 || task < 0 || !isSeparator(lines[i + 1] ?? ""))
3999
4003
  continue;
4000
4004
  cols = {
4001
- id: id2,
4005
+ id,
4002
4006
  task,
4003
4007
  depends: lc.findIndex((c) => c.startsWith("depend")),
4004
4008
  status: lc.indexOf("status")
@@ -4315,10 +4319,10 @@ import { fileURLToPath as fileURLToPath2 } from "url";
4315
4319
 
4316
4320
  // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/compose.js
4317
4321
  var compose = (middleware, onError, onNotFound) => {
4318
- return (context2, next) => {
4322
+ return (context, next) => {
4319
4323
  let index = -1;
4320
- return dispatch2(0);
4321
- async function dispatch2(i) {
4324
+ return dispatch(0);
4325
+ async function dispatch(i) {
4322
4326
  if (i <= index) {
4323
4327
  throw new Error("next() called multiple times");
4324
4328
  }
@@ -4328,31 +4332,31 @@ var compose = (middleware, onError, onNotFound) => {
4328
4332
  let handler;
4329
4333
  if (middleware[i]) {
4330
4334
  handler = middleware[i][0][0];
4331
- context2.req.routeIndex = i;
4335
+ context.req.routeIndex = i;
4332
4336
  } else {
4333
4337
  handler = i === middleware.length && next || undefined;
4334
4338
  }
4335
4339
  if (handler) {
4336
4340
  try {
4337
- res = await handler(context2, () => dispatch2(i + 1));
4341
+ res = await handler(context, () => dispatch(i + 1));
4338
4342
  } catch (err) {
4339
4343
  if (err instanceof Error && onError) {
4340
- context2.error = err;
4341
- res = await onError(err, context2);
4344
+ context.error = err;
4345
+ res = await onError(err, context);
4342
4346
  isError = true;
4343
4347
  } else {
4344
4348
  throw err;
4345
4349
  }
4346
4350
  }
4347
4351
  } else {
4348
- if (context2.finalized === false && onNotFound) {
4349
- res = await onNotFound(context2);
4352
+ if (context.finalized === false && onNotFound) {
4353
+ res = await onNotFound(context);
4350
4354
  }
4351
4355
  }
4352
- if (res && (context2.finalized === false || isError)) {
4353
- context2.res = res;
4356
+ if (res && (context.finalized === false || isError)) {
4357
+ context.res = res;
4354
4358
  }
4355
- return context2;
4359
+ return context;
4356
4360
  }
4357
4361
  };
4358
4362
  };
@@ -4773,7 +4777,7 @@ var raw = (value, callbacks) => {
4773
4777
  escapedString.callbacks = callbacks;
4774
4778
  return escapedString;
4775
4779
  };
4776
- var resolveCallback = async (str, phase, preserveCallbacks, context2, buffer) => {
4780
+ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
4777
4781
  if (typeof str === "object" && !(str instanceof String)) {
4778
4782
  if (!(str instanceof Promise)) {
4779
4783
  str = str.toString();
@@ -4791,7 +4795,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context2, buffer) =>
4791
4795
  } else {
4792
4796
  buffer = [str];
4793
4797
  }
4794
- const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context: context2 }))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context2, buffer))).then(() => buffer[0]));
4798
+ const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))).then(() => buffer[0]));
4795
4799
  if (preserveCallbacks) {
4796
4800
  return raw(await resStr, callbacks);
4797
4801
  } else {
@@ -5190,11 +5194,11 @@ var Hono = class _Hono {
5190
5194
  const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler);
5191
5195
  return (async () => {
5192
5196
  try {
5193
- const context2 = await composed(c);
5194
- if (!context2.finalized) {
5197
+ const context = await composed(c);
5198
+ if (!context.finalized) {
5195
5199
  throw new Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");
5196
5200
  }
5197
- return context2.res;
5201
+ return context.res;
5198
5202
  } catch (err) {
5199
5203
  return this.#handleError(err, c);
5200
5204
  }
@@ -5267,7 +5271,7 @@ var Node = class _Node {
5267
5271
  #index;
5268
5272
  #varIndex;
5269
5273
  #children = /* @__PURE__ */ Object.create(null);
5270
- insert(tokens, index, paramMap, context2, isStatic) {
5274
+ insert(tokens, index, paramMap, context, isStatic) {
5271
5275
  let node = this;
5272
5276
  for (let i = 0, len = tokens.length;i < len; i++) {
5273
5277
  const token = tokens[i];
@@ -5300,7 +5304,7 @@ var Node = class _Node {
5300
5304
  nextNode = node.#children[regexpStr] = new _Node;
5301
5305
  }
5302
5306
  if (name !== "") {
5303
- nextNode.#varIndex ??= context2.varIndex++;
5307
+ nextNode.#varIndex ??= context.varIndex++;
5304
5308
  paramMap.push([name, nextNode.#varIndex]);
5305
5309
  }
5306
5310
  } else {
@@ -5613,7 +5617,7 @@ var SmartRouter = class {
5613
5617
  // node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/trie-router/node.js
5614
5618
  var emptyParams = /* @__PURE__ */ Object.create(null);
5615
5619
  var order = 0;
5616
- var Node2 = class _Node2 {
5620
+ var Node2 = class _Node {
5617
5621
  #methods = [];
5618
5622
  #children = /* @__PURE__ */ Object.create(null);
5619
5623
  #patterns = [];
@@ -5629,7 +5633,7 @@ var Node2 = class _Node2 {
5629
5633
  const pattern = getPattern(p, nextP) || (nextP === undefined && p && p.indexOf("*") === p.length - 1 ? p : null);
5630
5634
  const isParam = Array.isArray(pattern);
5631
5635
  const key = isParam ? pattern[0] : pattern || p;
5632
- const child = curNode.#children[key] ||= new _Node2;
5636
+ const child = curNode.#children[key] ||= new _Node;
5633
5637
  if (pattern && !child.#pattern) {
5634
5638
  child.#pattern = pattern;
5635
5639
  curNode.#patterns.push(child);
@@ -5825,8 +5829,8 @@ var StreamingApi = class {
5825
5829
  `);
5826
5830
  return this;
5827
5831
  }
5828
- sleep(ms4) {
5829
- return new Promise((res) => setTimeout(res, ms4));
5832
+ sleep(ms) {
5833
+ return new Promise((res) => setTimeout(res, ms));
5830
5834
  }
5831
5835
  async close() {
5832
5836
  this.closed = true;
@@ -5937,11 +5941,11 @@ class Dispatcher {
5937
5941
  forge;
5938
5942
  entries = new Map;
5939
5943
  opts = new Map;
5940
- constructor(store, runner, forge2) {
5944
+ constructor(store, runner, forge) {
5941
5945
  this.store = store;
5942
5946
  this.runner = runner;
5943
- this.forge = forge2;
5944
- runner.onEnd((run2) => void this.onRunEnd(run2));
5947
+ this.forge = forge;
5948
+ runner.onEnd((run) => void this.onRunEnd(run));
5945
5949
  }
5946
5950
  project(projectId) {
5947
5951
  let m = this.entries.get(projectId);
@@ -6028,10 +6032,10 @@ class Dispatcher {
6028
6032
  const e = m.get(t.id);
6029
6033
  const opts = this.opts.get(projectId) ?? { owner: "dispatch" };
6030
6034
  const cfg = this.store.config(projectId);
6031
- const gates2 = cfg.gates;
6035
+ const gates = cfg.gates;
6032
6036
  const prompt = taskPrompt(t, {
6033
- requiredGates: gates2.required,
6034
- executableGates: gates2.required.filter((g) => gates2.defs[g]),
6037
+ requiredGates: gates.required,
6038
+ executableGates: gates.required.filter((g) => gates.defs[g]),
6035
6039
  openPr: cfg.dispatch.require_pr
6036
6040
  });
6037
6041
  const r = await this.runner.start({
@@ -6098,76 +6102,76 @@ class Dispatcher {
6098
6102
  await this.startOne(projectId, { id: e.task, title: e.title });
6099
6103
  }
6100
6104
  }
6101
- async onRunEnd(run2) {
6102
- const m = this.entries.get(run2.projectId);
6103
- const e = m?.get(run2.task);
6104
- if (!e || e.runId !== run2.id)
6105
+ async onRunEnd(run) {
6106
+ const m = this.entries.get(run.projectId);
6107
+ const e = m?.get(run.task);
6108
+ if (!e || e.runId !== run.id)
6105
6109
  return;
6106
- const cfg = this.store.config(run2.projectId);
6110
+ const cfg = this.store.config(run.projectId);
6107
6111
  const required = cfg.gates.required;
6108
- let runs = this.store.gateRuns(run2.projectId, run2.task);
6112
+ let runs = this.store.gateRuns(run.projectId, run.task);
6109
6113
  const status = this.store.gateStatusFor(runs, required);
6110
6114
  const missing = required.filter((g) => cfg.gates.defs[g] && status.find((s) => s.gate === g)?.verdict !== "pass");
6111
- if (missing.length && !run2.stopped) {
6112
- await this.store.runGates(run2.projectId, run2.task, missing, {
6113
- sessionId: run2.sessionId,
6115
+ if (missing.length && !run.stopped) {
6116
+ await this.store.runGates(run.projectId, run.task, missing, {
6117
+ sessionId: run.sessionId,
6114
6118
  owner: "dispatch"
6115
6119
  });
6116
- runs = this.store.gateRuns(run2.projectId, run2.task);
6120
+ runs = this.store.gateRuns(run.projectId, run.task);
6117
6121
  }
6118
6122
  const satisfied = gatesSatisfied(runs, required);
6119
6123
  await this.forge.refresh(0).catch(() => {});
6120
- const branch = `task/${run2.task}`;
6121
- const pr = this.forge.prs().find((p) => p.projectId === run2.projectId && p.branch === branch);
6124
+ const branch = `task/${run.task}`;
6125
+ const pr = this.forge.prs().find((p) => p.projectId === run.projectId && p.branch === branch);
6122
6126
  const outcome = dispatchOutcome({
6123
- exitCode: run2.exitCode,
6124
- isError: run2.result?.isError ?? false,
6127
+ exitCode: run.exitCode,
6128
+ isError: run.result?.isError ?? false,
6125
6129
  gatesSatisfied: satisfied,
6126
6130
  prOpen: Boolean(pr),
6127
6131
  requirePr: cfg.dispatch.require_pr,
6128
- stopped: run2.stopped ?? false
6132
+ stopped: run.stopped ?? false
6129
6133
  });
6130
6134
  const verdicts = this.store.gateStatusFor(runs, required).map((s) => `${s.gate} ${s.verdict ?? "\u2014"}`).join(", ");
6131
6135
  const detail = [
6132
- `exit ${run2.exitCode}${run2.result?.isError ? " (error)" : ""}`,
6136
+ `exit ${run.exitCode}${run.result?.isError ? " (error)" : ""}`,
6133
6137
  required.length ? `gates: ${verdicts}` : null,
6134
6138
  pr ? `PR ${pr.url}` : cfg.dispatch.require_pr ? "no PR" : null
6135
6139
  ].filter(Boolean).join(" \xB7 ");
6136
6140
  e.state = "finished";
6137
- e.endedAt = run2.endedAt;
6141
+ e.endedAt = run.endedAt;
6138
6142
  e.outcome = outcome;
6139
6143
  e.detail = detail;
6140
- e.costUsd = run2.result?.costUsd ?? null;
6141
- const ts = run2.endedAt ?? new Date().toISOString();
6144
+ e.costUsd = run.result?.costUsd ?? null;
6145
+ const ts = run.endedAt ?? new Date().toISOString();
6142
6146
  this.store.append({
6143
6147
  ts,
6144
6148
  type: "dispatch.finished",
6145
- projectId: run2.projectId,
6146
- sessionId: run2.sessionId,
6149
+ projectId: run.projectId,
6150
+ sessionId: run.sessionId,
6147
6151
  payload: {
6148
- task: run2.task,
6149
- runId: run2.id,
6152
+ task: run.task,
6153
+ runId: run.id,
6150
6154
  outcome,
6151
6155
  detail,
6152
6156
  costUsd: e.costUsd,
6153
- summary: `dispatch ${run2.task}: ${outcome} \u2014 ${detail}`
6157
+ summary: `dispatch ${run.task}: ${outcome} \u2014 ${detail}`
6154
6158
  }
6155
6159
  });
6156
6160
  if (outcome !== "done" && outcome !== "stopped")
6157
6161
  this.store.append({
6158
6162
  ts,
6159
6163
  type: "incident.opened",
6160
- projectId: run2.projectId,
6161
- sessionId: run2.sessionId,
6164
+ projectId: run.projectId,
6165
+ sessionId: run.sessionId,
6162
6166
  payload: {
6163
6167
  rule: "dispatch_failed",
6164
6168
  action: outcome,
6165
- command: run2.task,
6166
- reason: `dispatched run on ${run2.task} ended ${outcome}: ${detail}. The worktree and claim are kept; resume it from the session page or release it.`
6169
+ command: run.task,
6170
+ reason: `dispatched run on ${run.task} ended ${outcome}: ${detail}. The worktree and claim are kept; resume it from the session page or release it.`
6167
6171
  }
6168
6172
  });
6169
6173
  this.store.touch();
6170
- await this.fill(run2.projectId);
6174
+ await this.fill(run.projectId);
6171
6175
  }
6172
6176
  clear(projectId, task) {
6173
6177
  const m = this.project(projectId);
@@ -6223,7 +6227,7 @@ class ForgeService {
6223
6227
  return all.sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
6224
6228
  }
6225
6229
  async refresh(maxAgeMs = 120000) {
6226
- const projects = this.store.projects();
6230
+ const projects = this.store.liveProjects();
6227
6231
  await Promise.all(projects.map(async (p) => {
6228
6232
  const hit = this.cache.get(p.id);
6229
6233
  if (hit && Date.now() - hit.at < maxAgeMs)
@@ -6246,18 +6250,24 @@ class ForgeService {
6246
6250
  mergedCached(projectId, root) {
6247
6251
  const hit = this.outcomeCache.get(projectId);
6248
6252
  const fresh = !!hit && Date.now() - hit.at < 600000;
6249
- if (!fresh && !this.outcomeInflight.has(projectId)) {
6250
- const run2 = this.merged(projectId, root).finally(() => this.outcomeInflight.delete(projectId));
6251
- this.outcomeInflight.set(projectId, run2);
6252
- }
6253
+ if (!fresh)
6254
+ this.merged(projectId, root).catch(() => {});
6253
6255
  return { merged: hit?.merged ?? [], reverted: hit?.reverted ?? [], fresh };
6254
6256
  }
6255
6257
  async merged(projectId, root) {
6256
6258
  const hit = this.outcomeCache.get(projectId);
6257
6259
  if (hit && Date.now() - hit.at < 600000)
6258
6260
  return hit;
6261
+ const inflight = this.outcomeInflight.get(projectId);
6262
+ if (inflight)
6263
+ return inflight;
6264
+ const run = this.fetchMerged(projectId, root).finally(() => this.outcomeInflight.delete(projectId));
6265
+ this.outcomeInflight.set(projectId, run);
6266
+ return run;
6267
+ }
6268
+ async fetchMerged(projectId, root) {
6259
6269
  let merged = [];
6260
- const remote = this.remote(root);
6270
+ const remote = await this.remote(root);
6261
6271
  if (remote?.forge === "github") {
6262
6272
  const out = await this.run([
6263
6273
  "gh",
@@ -6293,38 +6303,44 @@ class ForgeService {
6293
6303
  mergeSha: (r.merge_commit_sha ?? null)?.toLowerCase() ?? null
6294
6304
  }));
6295
6305
  }
6296
- const log = Bun.spawnSync([
6297
- "git",
6298
- "-C",
6299
- root,
6300
- "log",
6301
- "--grep",
6302
- "This reverts commit",
6303
- "--format=%B",
6304
- "-n",
6305
- "300"
6306
- ]);
6307
- const reverted = log.exitCode === 0 ? [...parseReverts(new TextDecoder().decode(log.stdout))] : [];
6306
+ const log = await this.run(["git", "log", "--grep", "This reverts commit", "--format=%B", "-n", "300"], root);
6307
+ const reverted = log ? [...parseReverts(log)] : [];
6308
6308
  const entry = { at: Date.now(), merged, reverted };
6309
6309
  this.outcomeCache.set(projectId, entry);
6310
6310
  return entry;
6311
6311
  }
6312
- remote(root) {
6313
- const r = Bun.spawnSync(["git", "-C", root, "remote", "get-url", "origin"]);
6314
- if (r.exitCode !== 0)
6315
- return null;
6316
- return parseRemote(new TextDecoder().decode(r.stdout).trim());
6312
+ remoteCache = new Map;
6313
+ async remote(root) {
6314
+ const hit = this.remoteCache.get(root);
6315
+ if (hit && Date.now() - hit.at < (hit.v ? 600000 : 60000))
6316
+ return hit.v;
6317
+ const out = await this.run(["git", "remote", "get-url", "origin"], root);
6318
+ const v = out ? parseRemote(out.trim()) : null;
6319
+ this.remoteCache.set(root, { at: Date.now(), v });
6320
+ return v;
6317
6321
  }
6318
- async run(cmd, cwd) {
6322
+ async run(cmd, cwd, timeoutMs = 20000) {
6319
6323
  const bin = findBin(cmd[0]);
6320
6324
  if (!bin)
6321
6325
  return null;
6322
- const proc = Bun.spawn([bin, ...cmd.slice(1)], { cwd, stdout: "pipe", stderr: "ignore" });
6323
- const out = await new Response(proc.stdout).text();
6324
- return await proc.exited === 0 ? out : null;
6326
+ let proc;
6327
+ try {
6328
+ proc = Bun.spawn([bin, ...cmd.slice(1)], { cwd, stdout: "pipe", stderr: "ignore" });
6329
+ } catch {
6330
+ return null;
6331
+ }
6332
+ const killer = setTimeout(() => proc.kill(), timeoutMs);
6333
+ try {
6334
+ const out = await new Response(proc.stdout).text();
6335
+ return await proc.exited === 0 ? out : null;
6336
+ } catch {
6337
+ return null;
6338
+ } finally {
6339
+ clearTimeout(killer);
6340
+ }
6325
6341
  }
6326
6342
  async poll(projectId, root) {
6327
- const remote = this.remote(root);
6343
+ const remote = await this.remote(root);
6328
6344
  if (!remote)
6329
6345
  return [];
6330
6346
  let prs = [];
@@ -6339,35 +6355,35 @@ class ForgeService {
6339
6355
  }
6340
6356
  return prs.map((pr) => ({ ...pr, projectId, projectRoot: root }));
6341
6357
  }
6342
- async openPR(projectId, worktree2, draft) {
6358
+ async openPR(projectId, worktree, draft) {
6343
6359
  const p = this.store.projects().find((x) => x.id === projectId);
6344
6360
  if (!p)
6345
6361
  return { ok: false, error: "unknown project" };
6346
- if (worktree2.main)
6362
+ if (worktree.main)
6347
6363
  return { ok: false, error: "that is the main checkout \u2014 open the PR from a task worktree" };
6348
- if (!worktree2.branch)
6364
+ if (!worktree.branch)
6349
6365
  return { ok: false, error: "detached HEAD \u2014 check out a branch first" };
6350
- if (worktree2.dirty > 0)
6366
+ if (worktree.dirty > 0)
6351
6367
  return {
6352
6368
  ok: false,
6353
- error: `${worktree2.path} has uncommitted changes \u2014 commit them first (Swarm never commits for you)`
6369
+ error: `${worktree.path} has uncommitted changes \u2014 commit them first (Swarm never commits for you)`
6354
6370
  };
6355
- const remote = this.remote(p.root);
6371
+ const remote = await this.remote(p.root);
6356
6372
  if (!remote)
6357
6373
  return { ok: false, error: "no GitHub/GitLab remote on origin" };
6358
6374
  const cli = remote.forge === "github" ? "gh" : "glab";
6359
6375
  const bin = findBin(cli);
6360
6376
  if (!bin)
6361
6377
  return { ok: false, error: `${cli} is not installed` };
6362
- const sh = async (cmd2, cwd) => {
6363
- const proc = Bun.spawn(cmd2, { cwd, stdout: "pipe", stderr: "pipe" });
6378
+ const sh = async (cmd, cwd) => {
6379
+ const proc = Bun.spawn(cmd, { cwd, stdout: "pipe", stderr: "pipe" });
6364
6380
  const out = await new Response(proc.stdout).text() + await new Response(proc.stderr).text();
6365
6381
  return { ok: await proc.exited === 0, out: out.trim() };
6366
6382
  };
6367
- const push = await sh(["git", "push", "-u", "origin", worktree2.branch], worktree2.path);
6383
+ const push = await sh(["git", "push", "-u", "origin", worktree.branch], worktree.path);
6368
6384
  if (!push.ok)
6369
6385
  return { ok: false, error: `git push failed: ${push.out.slice(0, 400)}` };
6370
- const existing = this.prs().find((x) => x.projectId === projectId && x.branch === worktree2.branch);
6386
+ const existing = this.prs().find((x) => x.projectId === projectId && x.branch === worktree.branch);
6371
6387
  if (existing)
6372
6388
  return { ok: true, url: existing.url, number: existing.number };
6373
6389
  const cmd = remote.forge === "github" ? [
@@ -6375,7 +6391,7 @@ class ForgeService {
6375
6391
  "pr",
6376
6392
  "create",
6377
6393
  "--head",
6378
- worktree2.branch,
6394
+ worktree.branch,
6379
6395
  "--title",
6380
6396
  draft.title,
6381
6397
  "--body",
@@ -6386,7 +6402,7 @@ class ForgeService {
6386
6402
  "mr",
6387
6403
  "create",
6388
6404
  "--source-branch",
6389
- worktree2.branch,
6405
+ worktree.branch,
6390
6406
  "--title",
6391
6407
  draft.title,
6392
6408
  "--description",
@@ -6394,7 +6410,7 @@ class ForgeService {
6394
6410
  "--yes",
6395
6411
  ...draft.isDraft ? ["--draft"] : []
6396
6412
  ];
6397
- const r = await sh(cmd, worktree2.path);
6413
+ const r = await sh(cmd, worktree.path);
6398
6414
  if (!r.ok)
6399
6415
  return { ok: false, error: `${cli} failed: ${r.out.slice(0, 400)}` };
6400
6416
  const url = r.out.match(/https?:\/\/\S+/)?.[0] ?? r.out;
@@ -6406,7 +6422,7 @@ class ForgeService {
6406
6422
  const p = this.store.projects().find((x) => x.id === projectId);
6407
6423
  if (!p)
6408
6424
  return { ok: false, output: "unknown project" };
6409
- const remote = this.remote(p.root);
6425
+ const remote = await this.remote(p.root);
6410
6426
  if (!remote)
6411
6427
  return { ok: false, output: "no forge remote" };
6412
6428
  const cmd = remote.forge === "github" ? ["gh", "pr", "merge", String(number), "--squash"] : ["glab", "mr", "merge", String(number), "--squash", "--yes"];
@@ -6676,9 +6692,9 @@ class Runner {
6676
6692
  return [...this.live.values()].map((x) => x.run).filter((r) => !projectId || r.projectId === projectId);
6677
6693
  }
6678
6694
  get(idOrTask) {
6679
- for (const { run: run2 } of this.live.values())
6680
- if (run2.id === idOrTask || run2.sessionId === idOrTask || run2.task === idOrTask)
6681
- return run2;
6695
+ for (const { run } of this.live.values())
6696
+ if (run.id === idOrTask || run.sessionId === idOrTask || run.task === idOrTask)
6697
+ return run;
6682
6698
  return null;
6683
6699
  }
6684
6700
  async start(input) {
@@ -6706,14 +6722,14 @@ class Runner {
6706
6722
  reason: `a run on ${input.task} is already live \u2014 stop it or send it input`
6707
6723
  };
6708
6724
  const held = this.store.claims(input.projectId).find((c) => c.task === input.task && c.state === "held" && c.owner === input.owner);
6709
- let worktree2 = held?.worktree ?? "";
6710
- if (!worktree2) {
6725
+ let worktree = held?.worktree ?? "";
6726
+ if (!worktree) {
6711
6727
  const c = this.store.claim(input.projectId, input.task, input.owner);
6712
6728
  if (!c.ok)
6713
6729
  return { ok: false, reason: c.error };
6714
- worktree2 = c.worktree;
6730
+ worktree = c.worktree;
6715
6731
  }
6716
- await this.store.awaitBootstrap(worktree2);
6732
+ await this.store.awaitBootstrap(worktree);
6717
6733
  const sessionId = crypto.randomUUID();
6718
6734
  const id = sessionId.slice(0, 8);
6719
6735
  const logDir = join6(this.home, "logs", project.id);
@@ -6750,20 +6766,20 @@ class Runner {
6750
6766
  args.push("--disallowedTools", ...profile.disallowedTools);
6751
6767
  if (input.maxTurns)
6752
6768
  args.push("--max-turns", String(input.maxTurns));
6753
- this.store.preregisterSpawnedSession(sessionId, project.id, worktree2, input.task);
6769
+ this.store.preregisterSpawnedSession(sessionId, project.id, worktree, input.task);
6754
6770
  const proc = Bun.spawn(args, {
6755
- cwd: worktree2,
6771
+ cwd: worktree,
6756
6772
  env: { ...process.env, SWARM_RUN_ID: id, SWARM_OWNER: input.owner },
6757
6773
  stdin: "pipe",
6758
6774
  stdout: "pipe",
6759
6775
  stderr: logFd
6760
6776
  });
6761
- const run2 = {
6777
+ const run = {
6762
6778
  id,
6763
6779
  sessionId,
6764
6780
  projectId: project.id,
6765
6781
  task: input.task,
6766
- worktree: worktree2,
6782
+ worktree,
6767
6783
  pid: proc.pid,
6768
6784
  owner: input.owner,
6769
6785
  model: input.model ?? null,
@@ -6777,20 +6793,20 @@ class Runner {
6777
6793
  result: null,
6778
6794
  pending: []
6779
6795
  };
6780
- this.live.set(id, { run: run2, proc });
6796
+ this.live.set(id, { run, proc });
6781
6797
  this.store.registerProcess({
6782
6798
  pid: proc.pid,
6783
6799
  projectId: project.id,
6784
6800
  sessionId,
6785
6801
  kind: "proc",
6786
6802
  name: `run:${input.task}`,
6787
- cwd: worktree2,
6803
+ cwd: worktree,
6788
6804
  cmd: `claude -p (run ${id})`,
6789
6805
  owner: input.owner,
6790
6806
  log
6791
6807
  });
6792
6808
  this.store.append({
6793
- ts: run2.startedAt,
6809
+ ts: run.startedAt,
6794
6810
  type: "session.started",
6795
6811
  projectId: project.id,
6796
6812
  sessionId,
@@ -6798,7 +6814,7 @@ class Runner {
6798
6814
  });
6799
6815
  this.pump(id, proc);
6800
6816
  this.send(id, input.prompt);
6801
- return { ok: true, run: run2 };
6817
+ return { ok: true, run };
6802
6818
  }
6803
6819
  async pump(id, proc) {
6804
6820
  const entry = this.live.get(id);
@@ -6857,7 +6873,7 @@ class Runner {
6857
6873
  }
6858
6874
  }
6859
6875
  }
6860
- onLine(run2, line) {
6876
+ onLine(run, line) {
6861
6877
  if (!line.startsWith("{"))
6862
6878
  return;
6863
6879
  let j;
@@ -6867,41 +6883,41 @@ class Runner {
6867
6883
  return;
6868
6884
  }
6869
6885
  if (j.type === "control_request" && j.request?.subtype === "can_use_tool") {
6870
- this.onPermissionRequest(run2, j.request_id, j.request.tool_name ?? "", j.request.input ?? {});
6886
+ this.onPermissionRequest(run, j.request_id, j.request.tool_name ?? "", j.request.input ?? {});
6871
6887
  return;
6872
6888
  }
6873
6889
  if (j.type !== "result")
6874
6890
  return;
6875
- run2.result = {
6891
+ run.result = {
6876
6892
  costUsd: Number(j.total_cost_usd ?? 0),
6877
6893
  turns: Number(j.num_turns ?? 0),
6878
6894
  isError: Boolean(j.is_error),
6879
6895
  at: new Date().toISOString()
6880
6896
  };
6881
6897
  this.store.append({
6882
- ts: run2.result.at,
6898
+ ts: run.result.at,
6883
6899
  type: "run.result",
6884
- projectId: run2.projectId,
6885
- sessionId: run2.sessionId,
6900
+ projectId: run.projectId,
6901
+ sessionId: run.sessionId,
6886
6902
  payload: {
6887
- runId: run2.id,
6888
- task: run2.task,
6889
- ...run2.result,
6890
- summary: `turn done \xB7 $${run2.result.costUsd.toFixed(2)} \xB7 ${run2.result.turns} turns${run2.result.isError ? " \xB7 error" : ""}`
6903
+ runId: run.id,
6904
+ task: run.task,
6905
+ ...run.result,
6906
+ summary: `turn done \xB7 $${run.result.costUsd.toFixed(2)} \xB7 ${run.result.turns} turns${run.result.isError ? " \xB7 error" : ""}`
6891
6907
  }
6892
6908
  });
6893
6909
  }
6894
- onPermissionRequest(run2, requestId, tool, input) {
6895
- const { decision, display } = this.store.evaluateTool(tool, input, run2.sessionId, run2.worktree, true);
6910
+ onPermissionRequest(run, requestId, tool, input) {
6911
+ const { decision, display } = this.store.evaluateTool(tool, input, run.sessionId, run.worktree, true);
6896
6912
  if (decision.action === "deny") {
6897
- this.answerPermission(run2.id, requestId, false, `[swarm] ${decision.reason}`);
6913
+ this.answerPermission(run.id, requestId, false, `[swarm] ${decision.reason}`);
6898
6914
  return;
6899
6915
  }
6900
6916
  if (decision.action === "allow") {
6901
- this.answerPermission(run2.id, requestId, true);
6917
+ this.answerPermission(run.id, requestId, true);
6902
6918
  return;
6903
6919
  }
6904
- run2.pending.push({
6920
+ run.pending.push({
6905
6921
  requestId,
6906
6922
  tool,
6907
6923
  input,
@@ -6912,10 +6928,10 @@ class Runner {
6912
6928
  this.store.append({
6913
6929
  ts: new Date().toISOString(),
6914
6930
  type: "permission.requested",
6915
- projectId: run2.projectId,
6916
- sessionId: run2.sessionId,
6931
+ projectId: run.projectId,
6932
+ sessionId: run.sessionId,
6917
6933
  payload: {
6918
- runId: run2.id,
6934
+ runId: run.id,
6919
6935
  requestId,
6920
6936
  tool,
6921
6937
  display: display.slice(0, 300),
@@ -6975,17 +6991,17 @@ class Runner {
6975
6991
  return { ok: true };
6976
6992
  }
6977
6993
  async stop(id) {
6978
- const run2 = this.get(id);
6979
- if (!run2)
6994
+ const run = this.get(id);
6995
+ if (!run)
6980
6996
  return { ok: false, reason: "no live run" };
6981
- const entry = this.live.get(run2.id);
6982
- run2.stopped = true;
6997
+ const entry = this.live.get(run.id);
6998
+ run.stopped = true;
6983
6999
  try {
6984
7000
  const stdin = entry?.proc.stdin;
6985
7001
  if (stdin && typeof stdin !== "number")
6986
7002
  stdin.end();
6987
7003
  } catch {}
6988
- return this.store.stopProcess(run2.pid, run2.projectId, 5000);
7004
+ return this.store.stopProcess(run.pid, run.projectId, 5000);
6989
7005
  }
6990
7006
  async stopAll() {
6991
7007
  await Promise.all([...this.live.keys()].map((id) => this.stop(id)));
@@ -7084,8 +7100,8 @@ class TaskSources {
7084
7100
  const prev = this.cache.get(projectId);
7085
7101
  let entry;
7086
7102
  try {
7087
- const tasks2 = kind === "github" ? await this.github(root, opts.labels) : await this.linear(opts.team);
7088
- entry = { at: Date.now(), tasks: tasks2, error: null };
7103
+ const tasks = kind === "github" ? await this.github(root, opts.labels) : await this.linear(opts.team);
7104
+ entry = { at: Date.now(), tasks, error: null };
7089
7105
  } catch (e) {
7090
7106
  entry = { at: Date.now(), tasks: prev?.tasks ?? [], error: e.message };
7091
7107
  } finally {
@@ -7122,14 +7138,14 @@ class TaskSources {
7122
7138
  `)[0] ?? code}`);
7123
7139
  return normalizeGithubIssues(JSON.parse(out));
7124
7140
  }
7125
- async linear(team2) {
7141
+ async linear(team) {
7126
7142
  const key = this.env.LINEAR_API_KEY;
7127
7143
  if (!key)
7128
7144
  throw new Error("LINEAR_API_KEY not set \u2014 export it in the environment swarmd starts from (never stored)");
7129
7145
  const r = await fetch("https://api.linear.app/graphql", {
7130
7146
  method: "POST",
7131
7147
  headers: { "content-type": "application/json", authorization: key },
7132
- body: JSON.stringify({ query: linearIssuesQuery(team2) })
7148
+ body: JSON.stringify({ query: linearIssuesQuery(team) })
7133
7149
  });
7134
7150
  if (!r.ok)
7135
7151
  throw new Error(`Linear API ${r.status}`);
@@ -7267,14 +7283,14 @@ class Store {
7267
7283
  this.db.transaction(() => {
7268
7284
  for (const r of rows) {
7269
7285
  let payload;
7270
- let raw2;
7286
+ let raw;
7271
7287
  try {
7272
7288
  payload = JSON.parse(r.payload);
7273
- raw2 = r.raw ? JSON.parse(r.raw) : undefined;
7289
+ raw = r.raw ? JSON.parse(r.raw) : undefined;
7274
7290
  } catch {
7275
7291
  continue;
7276
7292
  }
7277
- const slim = slimForStorage({ payload, raw: raw2 });
7293
+ const slim = slimForStorage({ payload, raw });
7278
7294
  upd.run(JSON.stringify(slim.payload ?? null), slim.raw === undefined ? null : JSON.stringify(slim.raw), r.seq);
7279
7295
  }
7280
7296
  this.setMeta("events_slim", "1");
@@ -7477,7 +7493,7 @@ class Store {
7477
7493
  if (!held)
7478
7494
  return null;
7479
7495
  const manual = this.db.query("SELECT id, by FROM handoffs WHERE project_id = ? AND task = ? AND session_id = ?").all(held.projectId, held.task, sessionId);
7480
- if (manual.some((h2) => !isAutoHandoff(h2)))
7496
+ if (manual.some((h) => !isAutoHandoff(h)))
7481
7497
  return null;
7482
7498
  const row = this.db.query("SELECT last_text FROM sessions WHERE id = ?").get(sessionId);
7483
7499
  const h = deriveHandoff(held.task, this.sessionEvents(sessionId, 2000), { lastText: row?.last_text ?? null, sessionId });
@@ -7753,10 +7769,10 @@ class Store {
7753
7769
  return out.length ? out.join(`
7754
7770
  `) : null;
7755
7771
  }
7756
- wfInsert(projectId, task, workflow, steps, actor2) {
7772
+ wfInsert(projectId, task, workflow, steps, actor) {
7757
7773
  const now = new Date().toISOString();
7758
7774
  const r = this.db.query(`INSERT INTO workflow_runs (project_id, task, workflow, step, step_label, steps, state, started_at, updated_at, actor_kind, actor_id)
7759
- VALUES (?, ?, ?, 0, ?, ?, 'running', ?, ?, ?, ?)`).run(projectId, task, workflow, steps[0] ?? "", JSON.stringify(steps), now, now, actor2.kind, actor2.id);
7775
+ VALUES (?, ?, ?, 0, ?, ?, 'running', ?, ?, ?, ?)`).run(projectId, task, workflow, steps[0] ?? "", JSON.stringify(steps), now, now, actor.kind, actor.id);
7760
7776
  this.touch();
7761
7777
  return Number(r.lastInsertRowid);
7762
7778
  }
@@ -7877,10 +7893,10 @@ class Store {
7877
7893
  OR (to_kind = 'task' AND project_id = ? AND task IS ?)
7878
7894
  OR (to_kind = 'lead' AND project_id = ? AND ? = 'interactive'))
7879
7895
  ORDER BY id`).all(sessionId, sessionId, s.project_id, task, s.project_id, s.kind);
7880
- const ms4 = rows.map(rowToMessage);
7881
- if (ms4.length && !opts.peek)
7882
- this.db.query(`UPDATE messages SET delivered_at = ?, session_id = ? WHERE id IN (${ms4.map(() => "?").join(",")})`).run(new Date().toISOString(), sessionId, ...ms4.map((m) => m.id));
7883
- return ms4;
7896
+ const ms = rows.map(rowToMessage);
7897
+ if (ms.length && !opts.peek)
7898
+ this.db.query(`UPDATE messages SET delivered_at = ?, session_id = ? WHERE id IN (${ms.map(() => "?").join(",")})`).run(new Date().toISOString(), sessionId, ...ms.map((m) => m.id));
7899
+ return ms;
7884
7900
  }
7885
7901
  markMessageDelivered(id, sessionId) {
7886
7902
  this.db.query("UPDATE messages SET delivered_at = ?, session_id = COALESCE(?, session_id) WHERE id = ? AND delivered_at IS NULL").run(new Date().toISOString(), sessionId, id);
@@ -7975,13 +7991,13 @@ class Store {
7975
7991
  reason: `gate ${gate} has no command \u2014 add [gates.${gate}] cmd = "\u2026" to .swarm.toml, or record it with swarm gate record`
7976
7992
  };
7977
7993
  const claim = this.claims(projectId).find((c) => c.task === task && c.state === "held");
7978
- const worktree2 = claim?.worktree;
7979
- if (!worktree2 || !existsSync6(worktree2))
7994
+ const worktree = claim?.worktree;
7995
+ if (!worktree || !existsSync6(worktree))
7980
7996
  return {
7981
7997
  ok: false,
7982
7998
  reason: `${task} has no held worktree to run ${gate} in \u2014 claim it first`
7983
7999
  };
7984
- const cwd = def.cwd ? join8(worktree2, def.cwd) : worktree2;
8000
+ const cwd = def.cwd ? join8(worktree, def.cwd) : worktree;
7985
8001
  if (!existsSync6(cwd))
7986
8002
  return { ok: false, reason: `gate cwd ${cwd} does not exist` };
7987
8003
  const key = `${projectId}:${task}:${gate}`;
@@ -7992,7 +8008,7 @@ class Store {
7992
8008
  mkdirSync4(logDir, { recursive: true });
7993
8009
  const log = join8(logDir, `gate-${slug(task)}-${slug(gate)}.log`);
7994
8010
  if (def.builtin === "review")
7995
- return this.runReviewGate(projectId, task, gate, def, { worktree: worktree2, cwd, key, log }, opts);
8011
+ return this.runReviewGate(projectId, task, gate, def, { worktree, cwd, key, log }, opts);
7996
8012
  writeFileSync2(log, `$ ${def.cmd}
7997
8013
  # cwd ${cwd} \xB7 ${new Date().toISOString()}
7998
8014
  `);
@@ -8006,7 +8022,7 @@ class Store {
8006
8022
  stderr: fd,
8007
8023
  env: {
8008
8024
  ...process.env,
8009
- SWARM_WORKTREE: worktree2,
8025
+ SWARM_WORKTREE: worktree,
8010
8026
  SWARM_TASK: task,
8011
8027
  SWARM_GATE: gate,
8012
8028
  CI: process.env.CI ?? "1"
@@ -8014,7 +8030,7 @@ class Store {
8014
8030
  });
8015
8031
  } catch (e) {
8016
8032
  closeSync(fd);
8017
- const run2 = this.recordGate(projectId, {
8033
+ const run = this.recordGate(projectId, {
8018
8034
  ...executedGateInput(task, gate, def.cmd, {
8019
8035
  exitCode: null,
8020
8036
  durationMs: 0,
@@ -8022,7 +8038,7 @@ class Store {
8022
8038
  }),
8023
8039
  sessionId: opts.sessionId ?? null
8024
8040
  });
8025
- return { ok: true, pid: 0, log, done: Promise.resolve(run2.ok ? run2.run : null) };
8041
+ return { ok: true, pid: 0, log, done: Promise.resolve(run.ok ? run.run : null) };
8026
8042
  }
8027
8043
  const started = Date.now();
8028
8044
  const reg = this.registerProcess({
@@ -8061,10 +8077,10 @@ class Store {
8061
8077
  durationMs: Date.now() - started,
8062
8078
  output
8063
8079
  });
8064
- const run2 = this.recordGate(projectId, { ...input, sessionId: opts.sessionId ?? null });
8080
+ const run = this.recordGate(projectId, { ...input, sessionId: opts.sessionId ?? null });
8065
8081
  if (reg.ok)
8066
8082
  this.processes(projectId);
8067
- return run2.ok ? run2.run : null;
8083
+ return run.ok ? run.run : null;
8068
8084
  }).finally(() => {
8069
8085
  this.gateJobs.delete(key);
8070
8086
  this.touch();
@@ -8081,15 +8097,15 @@ class Store {
8081
8097
  return { ok: false, reason: "unknown project" };
8082
8098
  const started = Date.now();
8083
8099
  const record = (input) => {
8084
- const run2 = this.recordGate(projectId, { ...input, sessionId: opts.sessionId ?? null });
8085
- return run2.ok ? run2.run : null;
8100
+ const run = this.recordGate(projectId, { ...input, sessionId: opts.sessionId ?? null });
8101
+ return run.ok ? run.run : null;
8086
8102
  };
8087
8103
  const done = (async () => {
8088
8104
  let diffText = "";
8089
- let stat2 = "";
8105
+ let stat = "";
8090
8106
  try {
8091
8107
  const diff = await worktreeDiff(p.root, where.worktree);
8092
- stat2 = diff.files.map((f) => `${f.status ?? "M"} ${f.path} (+${f.added} -${f.deleted})`).join(`
8108
+ stat = diff.files.map((f) => `${f.status ?? "M"} ${f.path} (+${f.added} -${f.deleted})`).join(`
8093
8109
  `);
8094
8110
  diffText = await worktreePatch(where.worktree, diff.base);
8095
8111
  } catch (e) {
@@ -8111,7 +8127,7 @@ class Store {
8111
8127
  task,
8112
8128
  title: taskRow?.title ?? null,
8113
8129
  branch: w?.branch ?? null,
8114
- stat: stat2,
8130
+ stat,
8115
8131
  patch: diffText
8116
8132
  });
8117
8133
  writeFileSync2(where.log, `$ claude -p <review prompt, ${prompt.length} chars> --output-format json (read-only)
@@ -8200,9 +8216,9 @@ ${err}
8200
8216
  this.gateJobs.set(where.key, done);
8201
8217
  return { ok: true, pid: 0, log: where.log, done };
8202
8218
  }
8203
- async runGates(projectId, task, gates2, opts = {}) {
8219
+ async runGates(projectId, task, gates, opts = {}) {
8204
8220
  const cfg = this.gateDefs(projectId);
8205
- const names = gates2?.length ? gates2 : (cfg?.required ?? []).filter((g) => cfg?.defs[g]);
8221
+ const names = gates?.length ? gates : (cfg?.required ?? []).filter((g) => cfg?.defs[g]);
8206
8222
  const key = `${projectId}:${task}`;
8207
8223
  const batch = (async () => {
8208
8224
  const started = [];
@@ -8215,9 +8231,9 @@ ${err}
8215
8231
  continue;
8216
8232
  }
8217
8233
  started.push(g);
8218
- const run2 = await r.done;
8219
- if (run2)
8220
- runs.push(run2);
8234
+ const run = await r.done;
8235
+ if (run)
8236
+ runs.push(run);
8221
8237
  }
8222
8238
  return { started, skipped, runs };
8223
8239
  })();
@@ -8271,21 +8287,21 @@ ${err}
8271
8287
  const sessionId = this.knownSession(input.sessionId);
8272
8288
  const r = this.db.query(`INSERT INTO gates (project_id, task, gate, verdict, rubric, evidence, session_id, duration_ms, created_at, actor_kind, actor_id)
8273
8289
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(projectId, input.task.trim(), input.gate, input.verdict, input.rubric.trim(), input.evidence?.trim() || null, sessionId, typeof input.durationMs === "number" ? Math.max(0, Math.round(input.durationMs)) : null, createdAt, ...actorCols(this.actorFor(input.sessionId ? null : "daemon", sessionId)));
8274
- const run2 = this.rowToGate(this.db.query("SELECT * FROM gates WHERE id = ?").get(Number(r.lastInsertRowid)));
8275
- this.remember(gateDoc(projectId, run2.id, run2, sessionId));
8290
+ const run = this.rowToGate(this.db.query("SELECT * FROM gates WHERE id = ?").get(Number(r.lastInsertRowid)));
8291
+ this.remember(gateDoc(projectId, run.id, run, sessionId));
8276
8292
  this.append({
8277
8293
  ts: createdAt,
8278
8294
  type: "gate.recorded",
8279
8295
  projectId,
8280
8296
  sessionId,
8281
8297
  payload: {
8282
- task: run2.task,
8283
- gate: run2.gate,
8284
- verdict: run2.verdict,
8285
- summary: `gate ${run2.gate} ${run2.verdict} on ${run2.task}`
8298
+ task: run.task,
8299
+ gate: run.gate,
8300
+ verdict: run.verdict,
8301
+ summary: `gate ${run.gate} ${run.verdict} on ${run.task}`
8286
8302
  }
8287
8303
  });
8288
- if (run2.verdict === "fail")
8304
+ if (run.verdict === "fail")
8289
8305
  this.append({
8290
8306
  ts: createdAt,
8291
8307
  type: "incident.opened",
@@ -8294,12 +8310,12 @@ ${err}
8294
8310
  payload: {
8295
8311
  rule: "gate_failed",
8296
8312
  action: "failed",
8297
- command: `${run2.task} \xB7 ${run2.gate}`,
8298
- reason: `${run2.rubric}${run2.evidence ? ` \u2014 ${run2.evidence.slice(0, 200)}` : ""}`
8313
+ command: `${run.task} \xB7 ${run.gate}`,
8314
+ reason: `${run.rubric}${run.evidence ? ` \u2014 ${run.evidence.slice(0, 200)}` : ""}`
8299
8315
  }
8300
8316
  });
8301
8317
  this.touch();
8302
- return { ok: true, run: run2 };
8318
+ return { ok: true, run };
8303
8319
  }
8304
8320
  taskCache = new Map;
8305
8321
  taskSources = new TaskSources;
@@ -8383,8 +8399,8 @@ ${err}
8383
8399
  const row = this.db.query("SELECT id FROM projects WHERE root = ?").get(root);
8384
8400
  return row?.id ?? null;
8385
8401
  }
8386
- noteRuleChange(key, rules2) {
8387
- const sig = JSON.stringify(Object.entries(rules2).sort());
8402
+ noteRuleChange(key, rules) {
8403
+ const sig = JSON.stringify(Object.entries(rules).sort());
8388
8404
  const metaKey = `rules.sig:${key}`;
8389
8405
  const prev = this.db.query("SELECT value FROM meta WHERE key = ?").get(metaKey)?.value;
8390
8406
  if (prev === sig)
@@ -8393,7 +8409,7 @@ ${err}
8393
8409
  if (prev === undefined)
8394
8410
  return;
8395
8411
  const before = new Map(JSON.parse(prev));
8396
- const after = new Map(Object.entries(rules2));
8412
+ const after = new Map(Object.entries(rules));
8397
8413
  const added = [...after.keys()].filter((r) => !before.has(r)).sort();
8398
8414
  const removed = [...before.keys()].filter((r) => !after.has(r)).sort();
8399
8415
  const retuned = [...after.entries()].filter(([r, mode]) => before.has(r) && before.get(r) !== mode).map(([r, mode]) => `${r}=${mode}`).sort();
@@ -8533,11 +8549,11 @@ ${err}
8533
8549
  state: r.state
8534
8550
  }));
8535
8551
  }
8536
- guardHook(raw2) {
8537
- const tool = typeof raw2.tool_name === "string" ? raw2.tool_name : "";
8538
- const input = raw2.tool_input ?? {};
8539
- const id = typeof raw2.session_id === "string" ? raw2.session_id : "";
8540
- const cwd = typeof raw2.cwd === "string" ? raw2.cwd : "";
8552
+ guardHook(raw) {
8553
+ const tool = typeof raw.tool_name === "string" ? raw.tool_name : "";
8554
+ const input = raw.tool_input ?? {};
8555
+ const id = typeof raw.session_id === "string" ? raw.session_id : "";
8556
+ const cwd = typeof raw.cwd === "string" ? raw.cwd : "";
8541
8557
  const isWrite = WRITE_TOOLS.has(tool) && typeof input.file_path === "string";
8542
8558
  const cmd = tool === "Bash" ? input.command : undefined;
8543
8559
  if (!isWrite && !cmd)
@@ -8682,6 +8698,9 @@ ${err}
8682
8698
  createdAt: r.created_at
8683
8699
  }));
8684
8700
  }
8701
+ liveProjects() {
8702
+ return this.projects().filter((p) => !(p.discovered && isScratchRoot(p.root)));
8703
+ }
8685
8704
  project(id) {
8686
8705
  const r = this.db.query("SELECT * FROM projects WHERE id = ?").get(id);
8687
8706
  if (!r)
@@ -8714,9 +8733,9 @@ ${err}
8714
8733
  if (!explicit) {
8715
8734
  const hit = this.cwdProject.get(path);
8716
8735
  if (hit && Date.now() - hit.t < 60000) {
8717
- const p2 = this.project(hit.id);
8718
- if (p2)
8719
- return p2;
8736
+ const p = this.project(hit.id);
8737
+ if (p)
8738
+ return p;
8720
8739
  }
8721
8740
  }
8722
8741
  const p = this.resolveProjectUncached(path, explicit, name);
@@ -8810,25 +8829,25 @@ ${err}
8810
8829
  e = { ...e, payload: redactValue(e.payload, res), raw: redactValue(e.raw, res) };
8811
8830
  const slim = slimForStorage(e);
8812
8831
  const p = e.payload ?? {};
8813
- const actor2 = e.actor ?? this.actorFor(typeof p.owner === "string" ? p.owner : typeof p.by === "string" ? p.by : null, e.sessionId);
8814
- const r = this.db.query("INSERT INTO events (ts, type, project_id, session_id, payload, raw, actor_kind, actor_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e.ts, e.type, e.projectId, e.sessionId, JSON.stringify(slim.payload ?? null), slim.raw === undefined ? null : JSON.stringify(slim.raw), actor2.kind, actor2.id);
8815
- const stored = { ...e, actor: actor2, seq: Number(r.lastInsertRowid) };
8832
+ const actor = e.actor ?? this.actorFor(typeof p.owner === "string" ? p.owner : typeof p.by === "string" ? p.by : null, e.sessionId);
8833
+ const r = this.db.query("INSERT INTO events (ts, type, project_id, session_id, payload, raw, actor_kind, actor_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(e.ts, e.type, e.projectId, e.sessionId, JSON.stringify(slim.payload ?? null), slim.raw === undefined ? null : JSON.stringify(slim.raw), actor.kind, actor.id);
8834
+ const stored = { ...e, actor, seq: Number(r.lastInsertRowid) };
8816
8835
  if (stored.type === "incident.opened")
8817
8836
  this.remember(incidentDoc(stored.projectId, stored.seq, stored.payload, stored.ts, stored.sessionId));
8818
8837
  this.projectSession(stored);
8819
8838
  if (stored.type === "incident.opened") {
8820
8839
  const webhook = this.policyFor(null).config.notify.webhook;
8821
8840
  if (webhook) {
8822
- const p2 = stored.payload ?? {};
8841
+ const p = stored.payload ?? {};
8823
8842
  const project = this.project(stored.projectId)?.name ?? stored.projectId;
8824
8843
  fetch(webhook, {
8825
8844
  method: "POST",
8826
8845
  headers: { "content-type": "application/json" },
8827
8846
  body: JSON.stringify({
8828
- text: `Swarm incident \xB7 ${p2.rule ?? "?"} \xB7 ${project}
8829
- ${p2.command ?? ""}
8830
- ${p2.reason ?? ""}`.trim(),
8831
- rule: p2.rule,
8847
+ text: `Swarm incident \xB7 ${p.rule ?? "?"} \xB7 ${project}
8848
+ ${p.command ?? ""}
8849
+ ${p.reason ?? ""}`.trim(),
8850
+ rule: p.rule,
8832
8851
  project,
8833
8852
  sessionId: stored.sessionId,
8834
8853
  ts: stored.ts
@@ -8837,15 +8856,15 @@ ${p2.reason ?? ""}`.trim(),
8837
8856
  }).catch(() => {});
8838
8857
  }
8839
8858
  }
8840
- const team2 = this.policyFor(null).config.team;
8841
- if (team2.url && team2.forward.includes("ledger") && isAuditType(stored.type)) {
8859
+ const team = this.policyFor(null).config.team;
8860
+ if (team.url && team.forward.includes("ledger") && isAuditType(stored.type)) {
8842
8861
  this.db.query("INSERT INTO outbox (kind, payload, created_at) VALUES ('event', ?, ?)").run(JSON.stringify({
8843
8862
  seq: stored.seq,
8844
8863
  ts: stored.ts,
8845
8864
  type: stored.type,
8846
8865
  projectId: stored.projectId,
8847
8866
  sessionId: stored.sessionId,
8848
- actor: actor2,
8867
+ actor,
8849
8868
  payload: slim.payload ?? null
8850
8869
  }), stored.ts);
8851
8870
  }
@@ -8898,9 +8917,9 @@ ${p2.reason ?? ""}`.trim(),
8898
8917
  const rows = this.db.query(`SELECT * FROM (SELECT ${WIRE_COLS} FROM events WHERE ${where.join(" AND ")} ORDER BY seq DESC LIMIT ?) ORDER BY seq`).all(...args, limit);
8899
8918
  return rows.map((r) => auditRow(wireRowToEvent(r)));
8900
8919
  }
8901
- prune(days3) {
8920
+ prune(days) {
8902
8921
  const cfg = this.policyFor(null).config;
8903
- const chatter = days3 ?? cfg.events.retain_days;
8922
+ const chatter = days ?? cfg.events.retain_days;
8904
8923
  const cutoff = new Date(Date.now() - chatter * 86400000).toISOString();
8905
8924
  let n = this.db.query(`DELETE FROM events WHERE ts < ? AND type NOT IN (${AUDIT_TYPES_SQL})`).run(cutoff).changes;
8906
8925
  if (cfg.audit.retain_days > 0) {
@@ -8913,12 +8932,12 @@ ${p2.reason ?? ""}`.trim(),
8913
8932
  this.touch();
8914
8933
  return n;
8915
8934
  }
8916
- ingestHook(event, raw2) {
8917
- if (typeof raw2.cwd === "string")
8918
- this.autoRenewFor(typeof raw2.session_id === "string" ? raw2.session_id : null, raw2.cwd);
8919
- const cwd = typeof raw2.cwd === "string" ? raw2.cwd : process.cwd();
8935
+ ingestHook(event, raw) {
8936
+ if (typeof raw.cwd === "string")
8937
+ this.autoRenewFor(typeof raw.session_id === "string" ? raw.session_id : null, raw.cwd);
8938
+ const cwd = typeof raw.cwd === "string" ? raw.cwd : process.cwd();
8920
8939
  const project = existsSync6(cwd) ? this.resolveProject(cwd) : null;
8921
- const e = this.append(normalizeHook(event, raw2, project?.id ?? "p_unknown"));
8940
+ const e = this.append(normalizeHook(event, raw, project?.id ?? "p_unknown"));
8922
8941
  if ((event === "Stop" || event === "SessionEnd") && e.sessionId) {
8923
8942
  if (existsSync6(cwd)) {
8924
8943
  this.autoHandoff(e.sessionId, cwd);
@@ -8926,8 +8945,8 @@ ${p2.reason ?? ""}`.trim(),
8926
8945
  }
8927
8946
  this.rememberSession(e.sessionId);
8928
8947
  }
8929
- if (e.sessionId && typeof raw2.transcript_path === "string") {
8930
- this.db.query("UPDATE sessions SET transcript_path = ? WHERE id = ? AND transcript_path IS NULL").run(raw2.transcript_path, e.sessionId);
8948
+ if (e.sessionId && typeof raw.transcript_path === "string") {
8949
+ this.db.query("UPDATE sessions SET transcript_path = ? WHERE id = ? AND transcript_path IS NULL").run(raw.transcript_path, e.sessionId);
8931
8950
  const last = this.lastTail.get(e.sessionId) ?? 0;
8932
8951
  if (Date.now() - last > 2000) {
8933
8952
  this.lastTail.set(e.sessionId, Date.now());
@@ -9468,11 +9487,11 @@ ${p2.reason ?? ""}`.trim(),
9468
9487
  return { ok: false, error: claimRefusalMessage(decision, task) };
9469
9488
  }
9470
9489
  const branch = `task/${task}`;
9471
- const worktree2 = this.worktreePath(projectId, task);
9472
- if (existsSync6(worktree2))
9473
- return { ok: false, error: `${worktree2} already exists; release ${task} first` };
9474
- mkdirSync4(dirname3(worktree2), { recursive: true });
9475
- const created = worktreeAdd(p.root, worktree2, branch, baseRef);
9490
+ const worktree = this.worktreePath(projectId, task);
9491
+ if (existsSync6(worktree))
9492
+ return { ok: false, error: `${worktree} already exists; release ${task} first` };
9493
+ mkdirSync4(dirname3(worktree), { recursive: true });
9494
+ const created = worktreeAdd(p.root, worktree, branch, baseRef);
9476
9495
  if (!created)
9477
9496
  return { ok: false, error: `git worktree add failed for ${task}` };
9478
9497
  this.invalidateWorktrees(projectId);
@@ -9495,13 +9514,13 @@ ${p2.reason ?? ""}`.trim(),
9495
9514
  return { ok: true, task, owner, worktree: created, branch, expiresAt, bootstrap };
9496
9515
  }
9497
9516
  bootstraps = new Map;
9498
- bootstrapWorktree(projectId, task, repoRoot, worktree2) {
9499
- const plan = planBootstrap(loadConfig({ repoRoot, home: this.home }), repoRoot, worktree2);
9517
+ bootstrapWorktree(projectId, task, repoRoot, worktree) {
9518
+ const plan = planBootstrap(loadConfig({ repoRoot, home: this.home }), repoRoot, worktree);
9500
9519
  if (!needsBootstrap(plan))
9501
9520
  return null;
9502
- const job = runBootstrap(plan, { worktree: worktree2, home: this.home, projectId, task });
9521
+ const job = runBootstrap(plan, { worktree, home: this.home, projectId, task });
9503
9522
  const done = job.done.then((o) => {
9504
- this.bootstraps.delete(worktree2);
9523
+ this.bootstraps.delete(worktree);
9505
9524
  const ts = new Date().toISOString();
9506
9525
  const ok = !o.setup || o.setup.exitCode === 0;
9507
9526
  this.append({
@@ -9511,7 +9530,7 @@ ${p2.reason ?? ""}`.trim(),
9511
9530
  sessionId: null,
9512
9531
  payload: {
9513
9532
  task,
9514
- worktree: worktree2,
9533
+ worktree,
9515
9534
  ok,
9516
9535
  log: job.log,
9517
9536
  ...o,
@@ -9534,11 +9553,11 @@ ${p2.reason ?? ""}`.trim(),
9534
9553
  this.touch();
9535
9554
  return o;
9536
9555
  });
9537
- this.bootstraps.set(worktree2, done);
9556
+ this.bootstraps.set(worktree, done);
9538
9557
  return job.log;
9539
9558
  }
9540
- awaitBootstrap(worktree2) {
9541
- return this.bootstraps.get(worktree2) ?? Promise.resolve();
9559
+ awaitBootstrap(worktree) {
9560
+ return this.bootstraps.get(worktree) ?? Promise.resolve();
9542
9561
  }
9543
9562
  autoRenewAt = new Map;
9544
9563
  autoRenewFor(sessionId, cwd) {
@@ -9574,8 +9593,8 @@ ${p2.reason ?? ""}`.trim(),
9574
9593
  expiresAt: r.expires_at
9575
9594
  }));
9576
9595
  }
9577
- waiting(projectId, days3 = 7) {
9578
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
9596
+ waiting(projectId, days = 7) {
9597
+ const since = new Date(Date.now() - days * 86400000).toISOString();
9579
9598
  const pArgs = projectId ? [projectId] : [];
9580
9599
  const paired = this.db.query(`SELECT type, session_id, project_id, ts,
9581
9600
  COALESCE(json_extract(payload,'$.requestId'), json_extract(payload,'$.id')) AS key,
@@ -9643,8 +9662,8 @@ ${p2.reason ?? ""}`.trim(),
9643
9662
  }))
9644
9663
  };
9645
9664
  }
9646
- gateHealth(projectId, days3 = 30) {
9647
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
9665
+ gateHealth(projectId, days = 30) {
9666
+ const since = new Date(Date.now() - days * 86400000).toISOString();
9648
9667
  const rows = this.db.query(`SELECT project_id, task, gate, verdict, duration_ms, created_at FROM gates
9649
9668
  WHERE created_at >= ?${projectId ? " AND project_id = ?" : ""}`).all(...projectId ? [since, projectId] : [since]);
9650
9669
  return gateHealth(rows.filter((r) => r.verdict === "pass" || r.verdict === "fail").map((r) => ({
@@ -9784,8 +9803,8 @@ ${p2.reason ?? ""}`.trim(),
9784
9803
  this.refreshDisk(trees.map((t) => t.path), diskTtlMs);
9785
9804
  return hygieneReport(procs, trees);
9786
9805
  }
9787
- lineage(projectId, days3 = 14, expanded = []) {
9788
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
9806
+ lineage(projectId, days = 14, expanded = []) {
9807
+ const since = new Date(Date.now() - days * 86400000).toISOString();
9789
9808
  const pArgs = projectId ? [projectId] : [];
9790
9809
  const rows = this.db.query(`SELECT id, project_id, title, agent, kind, state, parent_id, started_at, ended_at
9791
9810
  FROM sessions WHERE last_seen_at >= ?${projectId ? " AND project_id = ?" : ""}`).all(since, ...pArgs);
@@ -9856,8 +9875,8 @@ ${p2.reason ?? ""}`.trim(),
9856
9875
  edges.push(...handoffEdges(holds));
9857
9876
  return lineageGraph(sessions, edges, { expanded });
9858
9877
  }
9859
- mcpHealth(projectId, days3 = 7) {
9860
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
9878
+ mcpHealth(projectId, days = 7) {
9879
+ const since = new Date(Date.now() - days * 86400000).toISOString();
9861
9880
  const rows = this.db.query(`SELECT session_id, seq, ts, type,
9862
9881
  json_extract(payload,'$.tool') AS tool,
9863
9882
  json_extract(payload,'$.toolResponse') AS response
@@ -9897,8 +9916,8 @@ ${p2.reason ?? ""}`.trim(),
9897
9916
  abandon(sessionId, tool, at);
9898
9917
  return mcpHealth(calls);
9899
9918
  }
9900
- context(projectId, days3 = 7) {
9901
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
9919
+ context(projectId, days = 7) {
9920
+ const since = new Date(Date.now() - days * 86400000).toISOString();
9902
9921
  const pArgs = projectId ? [projectId] : [];
9903
9922
  const where = projectId ? " AND project_id = ?" : "";
9904
9923
  const results = this.db.query(`SELECT session_id, json_extract(payload,'$.tool') AS tool,
@@ -9945,10 +9964,10 @@ ${p2.reason ?? ""}`.trim(),
9945
9964
  const projects = projectId ? [projectId] : this.projects().map((p) => p.id);
9946
9965
  const out = [];
9947
9966
  for (const pid of projects) {
9948
- const tasks2 = [
9967
+ const tasks = [
9949
9968
  ...new Set(this.claims(pid).map((c) => splitArmTask(c.task)).filter((x) => x.arm).map((x) => x.task))
9950
9969
  ];
9951
- for (const t of tasks2.sort())
9970
+ for (const t of tasks.sort())
9952
9971
  out.push({ ...this.abTrial(pid, t), projectId: pid });
9953
9972
  }
9954
9973
  const rank = { undecided: 0, "all-failed": 1, winner: 2 };
@@ -10006,8 +10025,8 @@ ${p2.reason ?? ""}`.trim(),
10006
10025
  const sid = sess?.id ?? c.sessionId;
10007
10026
  const g = gateRuns.filter((x) => x.task === c.task);
10008
10027
  const latest = new Map;
10009
- for (const run2 of [...g].sort((a, b) => a.createdAt < b.createdAt ? -1 : 1))
10010
- latest.set(run2.gate, run2);
10028
+ for (const run of [...g].sort((a, b) => a.createdAt < b.createdAt ? -1 : 1))
10029
+ latest.set(run.gate, run);
10011
10030
  const verdicts = [...latest.values()];
10012
10031
  const diff = c.worktree ? this.diffCache.get(c.worktree)?.v : null;
10013
10032
  arms.push({
@@ -10057,8 +10076,8 @@ ${p2.reason ?? ""}`.trim(),
10057
10076
  })
10058
10077
  };
10059
10078
  }
10060
- transitions(projectId, days3 = 7, minWeight = 1) {
10061
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
10079
+ transitions(projectId, days = 7, minWeight = 1) {
10080
+ const since = new Date(Date.now() - days * 86400000).toISOString();
10062
10081
  const rows = this.db.query(`SELECT session_id, json_extract(payload,'$.tool') AS tool
10063
10082
  FROM events
10064
10083
  WHERE type = 'tool.requested' AND ts >= ?
@@ -10066,14 +10085,14 @@ ${p2.reason ?? ""}`.trim(),
10066
10085
  ORDER BY session_id, seq`).all(...projectId ? [since, projectId] : [since]);
10067
10086
  return transitionGraph(rows.map((r) => ({ sessionId: r.session_id ?? "", tool: r.tool ?? "" })), { minWeight });
10068
10087
  }
10069
- resourceHolding(projectId, days3 = 3) {
10070
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
10088
+ resourceHolding(projectId, days = 3) {
10089
+ const since = new Date(Date.now() - days * 86400000).toISOString();
10071
10090
  const p = projectId ? " AND c.project_id = ?" : "";
10072
10091
  const args = projectId ? [projectId] : [];
10073
10092
  const ended = new Map(this.db.query("SELECT id, ended_at FROM sessions WHERE ended_at IS NOT NULL").all().map((r) => [r.id, r.ended_at]));
10074
10093
  const claims = this.db.query(`SELECT c.task AS name, c.owner, c.project_id, c.expires_at, c.actor_id AS session_id
10075
10094
  FROM claims c WHERE c.state = 'held' AND c.released_at IS NULL${p}`).all(...args);
10076
- const resources2 = this.db.query(`SELECT c.name, c.owner, c.project_id, c.expires_at, c.session_id, c.port
10095
+ const resources = this.db.query(`SELECT c.name, c.owner, c.project_id, c.expires_at, c.session_id, c.port
10077
10096
  FROM resources c WHERE c.released = 0${p}`).all(...args);
10078
10097
  const procs = this.db.query(`SELECT c.name, c.owner, c.project_id, c.session_id, c.port
10079
10098
  FROM processes c WHERE c.ended_at IS NULL${p}`).all(...args);
@@ -10090,7 +10109,7 @@ ${p2.reason ?? ""}`.trim(),
10090
10109
  expiresAt: r.expires_at,
10091
10110
  projectId: r.project_id
10092
10111
  })),
10093
- ...resources2.map((r) => ({
10112
+ ...resources.map((r) => ({
10094
10113
  kind: r.port ? "port" : "lease",
10095
10114
  name: r.port ? String(r.port) : r.name,
10096
10115
  owner: r.owner ?? "unknown",
@@ -10119,8 +10138,8 @@ ${p2.reason ?? ""}`.trim(),
10119
10138
  }));
10120
10139
  return resourceGraph(held, wanted);
10121
10140
  }
10122
- fileHeat(projectId, days3 = 14) {
10123
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
10141
+ fileHeat(projectId, days = 14) {
10142
+ const since = new Date(Date.now() - days * 86400000).toISOString();
10124
10143
  const rows = this.db.query(`SELECT session_id, json_extract(payload,'$.tool') AS tool,
10125
10144
  json_extract(payload,'$.toolInput.file_path') AS path
10126
10145
  FROM events
@@ -10128,8 +10147,8 @@ ${p2.reason ?? ""}`.trim(),
10128
10147
  AND json_extract(payload,'$.toolInput.file_path') IS NOT NULL${projectId ? " AND project_id = ?" : ""}`).all(...projectId ? [since, projectId] : [since]);
10129
10148
  return fileHeat(rows.map((r) => ({ sessionId: r.session_id ?? "", tool: r.tool ?? "", path: r.path ?? "" })));
10130
10149
  }
10131
- security(projectId, days3 = 14) {
10132
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
10150
+ security(projectId, days = 14) {
10151
+ const since = new Date(Date.now() - days * 86400000).toISOString();
10133
10152
  const rows = this.db.query(`SELECT session_id, ts, json_extract(payload,'$.tool') AS tool,
10134
10153
  COALESCE(json_extract(payload,'$.toolInput.command'),
10135
10154
  json_extract(payload,'$.toolInput.url'), '') AS command,
@@ -10144,8 +10163,8 @@ ${p2.reason ?? ""}`.trim(),
10144
10163
  at: r.ts
10145
10164
  })));
10146
10165
  }
10147
- ruleEffect(projectId, days3 = 30) {
10148
- const since = new Date(Date.now() - days3 * 86400000).toISOString();
10166
+ ruleEffect(projectId, days = 30) {
10167
+ const since = new Date(Date.now() - days * 86400000).toISOString();
10149
10168
  const rows = this.db.query(`SELECT e.seq, e.ts,
10150
10169
  json_extract(e.payload,'$.rule') AS rule,
10151
10170
  COALESCE(json_extract(e.payload,'$.command'), '') AS command,
@@ -10159,7 +10178,7 @@ ${p2.reason ?? ""}`.trim(),
10159
10178
  command: r.command ?? "",
10160
10179
  at: r.ts,
10161
10180
  acked: Boolean(r.acked)
10162
- })), changes.map((c) => ({ at: c.ts, added: JSON.parse(c.added) })), Date.now(), days3);
10181
+ })), changes.map((c) => ({ at: c.ts, added: JSON.parse(c.added) })), Date.now(), days);
10163
10182
  }
10164
10183
  reclaimBuild(path, { dryRun = false } = {}) {
10165
10184
  const report = this.hygiene();
@@ -10206,6 +10225,7 @@ ${p2.reason ?? ""}`.trim(),
10206
10225
  checkStalls() {
10207
10226
  const live = this.db.query("SELECT id, project_id FROM sessions WHERE state IN ('active','waiting') AND ended_at IS NULL AND last_seen_at >= ?").all(new Date(Date.now() - IDLE_MS).toISOString());
10208
10227
  const liveIds = new Set(live.map((s) => s.id));
10228
+ const before = JSON.stringify([...this.stalls].map(([id, v]) => [id, v.kind, v.reason]));
10209
10229
  for (const id of [...this.stalls.keys()])
10210
10230
  if (!liveIds.has(id))
10211
10231
  this.stalls.delete(id);
@@ -10224,15 +10244,15 @@ ${p2.reason ?? ""}`.trim(),
10224
10244
  ts: ""
10225
10245
  };
10226
10246
  });
10227
- const stall2 = detectStall(calls);
10228
- if (!stall2) {
10247
+ const stall = detectStall(calls);
10248
+ if (!stall) {
10229
10249
  this.stalls.delete(s.id);
10230
10250
  continue;
10231
10251
  }
10232
10252
  flagged++;
10233
10253
  const prev = this.stalls.get(s.id);
10234
- this.stalls.set(s.id, stall2);
10235
- if (prev?.kind === stall2.kind)
10254
+ this.stalls.set(s.id, stall);
10255
+ if (prev?.kind === stall.kind)
10236
10256
  continue;
10237
10257
  this.append({
10238
10258
  ts: new Date().toISOString(),
@@ -10240,13 +10260,16 @@ ${p2.reason ?? ""}`.trim(),
10240
10260
  projectId: s.project_id,
10241
10261
  sessionId: s.id,
10242
10262
  payload: {
10243
- kind: stall2.kind,
10244
- reason: stall2.reason,
10245
- summary: `session looks stuck \u2014 ${stall2.reason}`
10263
+ kind: stall.kind,
10264
+ reason: stall.reason,
10265
+ summary: `session looks stuck \u2014 ${stall.reason}`
10246
10266
  }
10247
10267
  });
10248
10268
  this.touch();
10249
10269
  }
10270
+ const after = JSON.stringify([...this.stalls].map(([id, v]) => [id, v.kind, v.reason]));
10271
+ if (after !== before)
10272
+ this.touch();
10250
10273
  return flagged;
10251
10274
  }
10252
10275
  sweepOrphans() {
@@ -10311,18 +10334,18 @@ ${p2.reason ?? ""}`.trim(),
10311
10334
  const row = this.db.query("SELECT * FROM claims WHERE project_id = ? AND task = ?").get(projectId, task);
10312
10335
  if (!row)
10313
10336
  return { ok: false, error: `no claim on ${task}` };
10314
- const worktree2 = row.worktree ?? "";
10315
- if (worktree2 && existsSync6(worktree2)) {
10316
- const work = heldWork(worktree2);
10337
+ const worktree = row.worktree ?? "";
10338
+ if (worktree && existsSync6(worktree)) {
10339
+ const work = heldWork(worktree);
10317
10340
  const can = canRelease(work, force);
10318
10341
  if (!can.ok)
10319
10342
  return {
10320
10343
  ok: false,
10321
- error: releaseRefusalMessage(can, worktree2),
10344
+ error: releaseRefusalMessage(can, worktree),
10322
10345
  refused: can.reason
10323
10346
  };
10324
- if (p && !worktreeRemove(p.root, worktree2, force))
10325
- return { ok: false, error: `git worktree remove failed for ${worktree2}` };
10347
+ if (p && !worktreeRemove(p.root, worktree, force))
10348
+ return { ok: false, error: `git worktree remove failed for ${worktree}` };
10326
10349
  this.invalidateWorktrees(projectId);
10327
10350
  }
10328
10351
  const releasedAt = new Date().toISOString();
@@ -10431,12 +10454,12 @@ ${p2.reason ?? ""}`.trim(),
10431
10454
  const p = this.project(projectId);
10432
10455
  if (!p)
10433
10456
  return Promise.resolve([]);
10434
- const run2 = listWorktreesAsync(p.root).then((v) => {
10457
+ const run = listWorktreesAsync(p.root).then((v) => {
10435
10458
  this.wtCache.set(projectId, { v, t: Date.now() });
10436
10459
  return v;
10437
10460
  }).finally(() => this.wtInflight.delete(projectId));
10438
- this.wtInflight.set(projectId, run2);
10439
- return run2;
10461
+ this.wtInflight.set(projectId, run);
10462
+ return run;
10440
10463
  }
10441
10464
  createWorktree(projectId, name, baseRef = "HEAD", branch) {
10442
10465
  const p = this.project(projectId);
@@ -10548,7 +10571,7 @@ ${p2.reason ?? ""}`.trim(),
10548
10571
  const taskRow = this.tasks(projectId)?.tasks.find((t) => t.id === task) ?? null;
10549
10572
  const handoff = this.latestHandoff(projectId, task);
10550
10573
  const required = this.requiredGates(projectId);
10551
- const gates2 = required.length ? this.gateStatusFor(this.gateRuns(projectId, task), required).map((g) => ({
10574
+ const gates = required.length ? this.gateStatusFor(this.gateRuns(projectId, task), required).map((g) => ({
10552
10575
  gate: g.gate,
10553
10576
  verdict: g.verdict
10554
10577
  })) : [];
@@ -10557,19 +10580,19 @@ ${p2.reason ?? ""}`.trim(),
10557
10580
  task,
10558
10581
  title: taskRow?.title ?? null,
10559
10582
  handoff,
10560
- gates: gates2,
10583
+ gates,
10561
10584
  files: diff.files,
10562
10585
  commits: diff.commits
10563
10586
  });
10564
10587
  return { ok: true, task, worktree: w, ...d, diff };
10565
10588
  }
10566
- recordPrOpened(projectId, task, worktree2, url) {
10589
+ recordPrOpened(projectId, task, worktree, url) {
10567
10590
  this.append({
10568
10591
  ts: new Date().toISOString(),
10569
10592
  type: "pr.opened",
10570
10593
  projectId,
10571
10594
  sessionId: null,
10572
- payload: { task, worktree: worktree2, url, summary: `PR opened for ${task}: ${url}` }
10595
+ payload: { task, worktree, url, summary: `PR opened for ${task}: ${url}` }
10573
10596
  });
10574
10597
  this.touch();
10575
10598
  }
@@ -10580,22 +10603,31 @@ ${p2.reason ?? ""}`.trim(),
10580
10603
  this.wtCache.clear();
10581
10604
  }
10582
10605
  async refreshAllWorktrees() {
10583
- await Promise.all(this.projects().map((p) => this.refreshWorktrees(p.id)));
10606
+ await Promise.all(this.liveProjects().map((p) => this.refreshWorktrees(p.id)));
10584
10607
  }
10585
10608
  sessions() {
10609
+ return this.memoised("sessions", 2000, () => this.computeSessions());
10610
+ }
10611
+ computeSessions() {
10612
+ const page = this.db.query("SELECT id FROM sessions ORDER BY last_seen_at DESC LIMIT 200").all();
10613
+ if (!page.length)
10614
+ return [];
10615
+ const ids = page.map((r) => r.id);
10616
+ const holes = ids.map(() => "?").join(",");
10586
10617
  const rows = this.db.query(`SELECT s.*, COUNT(t.id) AS turns, COALESCE(SUM(t.input),0) AS input, COALESCE(SUM(t.output),0) AS output,
10587
10618
  COALESCE(SUM(t.cache_write),0) AS cache_write, COALESCE(SUM(t.cache_read),0) AS cache_read, COALESCE(SUM(t.thinking),0) AS thinking,
10588
10619
  SUM(t.cost_usd) AS cost_usd, MAX(t.cost_usd IS NULL AND t.id IS NOT NULL) AS unpriced,
10589
10620
  (SELECT model FROM turns lt WHERE lt.session_id = s.id AND lt.agent_id IS NULL AND lt.sidechain = 0 ORDER BY lt.ts DESC LIMIT 1) AS live_model,
10590
10621
  (SELECT COUNT(DISTINCT model) FROM turns lm WHERE lm.session_id = s.id AND lm.agent_id IS NULL AND lm.sidechain = 0) AS model_count
10591
10622
  FROM sessions s LEFT JOIN turns t ON t.session_id = s.id
10592
- GROUP BY s.id ORDER BY s.last_seen_at DESC LIMIT 200`).all();
10623
+ WHERE s.id IN (${holes})
10624
+ GROUP BY s.id ORDER BY s.last_seen_at DESC`).all(...ids);
10593
10625
  const idleBefore = Date.now() - IDLE_MS;
10594
10626
  const sparkRows = this.db.query(`SELECT session_id, output, cost_usd FROM (
10595
10627
  SELECT t.session_id, t.output, t.cost_usd, t.ts,
10596
10628
  ROW_NUMBER() OVER (PARTITION BY t.session_id ORDER BY t.ts DESC) AS rn
10597
- FROM turns t WHERE t.agent_id IS NULL AND t.sidechain = 0
10598
- ) WHERE rn <= 24 ORDER BY ts`).all();
10629
+ FROM turns t WHERE t.agent_id IS NULL AND t.sidechain = 0 AND t.session_id IN (${holes})
10630
+ ) WHERE rn <= 24 ORDER BY ts`).all(...ids);
10599
10631
  const sparks = new Map;
10600
10632
  for (const x of sparkRows) {
10601
10633
  const a = sparks.get(x.session_id) ?? [];
@@ -10869,10 +10901,10 @@ ${p2.reason ?? ""}`.trim(),
10869
10901
  for (const [k, v] of Object.entries(JSON.parse(r.tc || "{}")))
10870
10902
  tools[k] = (tools[k] ?? 0) + v;
10871
10903
  }
10872
- const sessionRow = (order2) => this.db.query(`SELECT s.id, s.title, s.project_id AS projectId, s.started_at AS startedAt, s.last_seen_at AS lastSeenAt,
10904
+ const sessionRow = (order) => this.db.query(`SELECT s.id, s.title, s.project_id AS projectId, s.started_at AS startedAt, s.last_seen_at AS lastSeenAt,
10873
10905
  COUNT(t.id) AS turns, SUM(t.cost_usd) AS cost, SUM(t.output) AS output, s.tool_calls AS toolCalls
10874
10906
  FROM sessions s JOIN turns t ON t.session_id = s.id WHERE ${scope} AND s.kind != 'subagent'
10875
- GROUP BY s.id ORDER BY ${order2} DESC LIMIT 1`).get(arg);
10907
+ GROUP BY s.id ORDER BY ${order} DESC LIMIT 1`).get(arg);
10876
10908
  const biggestTurn = this.db.query(`SELECT t.session_id AS sessionId, s.title, t.ts, t.output, t.thinking, t.cost_usd AS cost, t.model
10877
10909
  FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope} ORDER BY t.output DESC LIMIT 1`).get(arg);
10878
10910
  const busiestDay = daily.slice().sort((a, b) => (b.cost ?? 0) - (a.cost ?? 0) || b.turns - a.turns)[0] ?? null;
@@ -11012,8 +11044,8 @@ ${p2.reason ?? ""}`.trim(),
11012
11044
  const rows = this.db.query("SELECT * FROM resources WHERE released = 0").all();
11013
11045
  const now = Date.now();
11014
11046
  let n = 0;
11015
- for (const raw2 of rows)
11016
- if (this.reapIfDead(this.rowToResource(raw2), now))
11047
+ for (const raw of rows)
11048
+ if (this.reapIfDead(this.rowToResource(raw), now))
11017
11049
  n++;
11018
11050
  return n;
11019
11051
  }
@@ -11154,11 +11186,11 @@ ${p2.reason ?? ""}`.trim(),
11154
11186
  return { ok: true, process: p };
11155
11187
  }
11156
11188
  async stopProcess(pid, projectId, graceMs = 3000) {
11157
- const raw2 = this.db.query(`SELECT rowid, * FROM processes WHERE pid = ? AND ended_at IS NULL${projectId ? " AND project_id = ?" : ""}`).get(...projectId ? [pid, projectId] : [pid]);
11158
- if (!raw2)
11189
+ const raw = this.db.query(`SELECT rowid, * FROM processes WHERE pid = ? AND ended_at IS NULL${projectId ? " AND project_id = ?" : ""}`).get(...projectId ? [pid, projectId] : [pid]);
11190
+ if (!raw)
11159
11191
  return { ok: false, reason: "not a registered process" };
11160
- const p = this.rowToProcess(raw2);
11161
- const rowid = raw2.rowid;
11192
+ const p = this.rowToProcess(raw);
11193
+ const rowid = raw.rowid;
11162
11194
  if (!this.processIsOurs(p)) {
11163
11195
  this.endProcess(rowid, p, "exited");
11164
11196
  return { ok: true, reason: "already gone" };
@@ -11185,8 +11217,8 @@ ${p2.reason ?? ""}`.trim(),
11185
11217
  }
11186
11218
  acquireResource(input) {
11187
11219
  const key = input.projectId ?? "";
11188
- const raw2 = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ?").get(input.name, key);
11189
- let existing = raw2 ? this.rowToResource(raw2) : null;
11220
+ const raw = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ?").get(input.name, key);
11221
+ let existing = raw ? this.rowToResource(raw) : null;
11190
11222
  if (existing && this.reapIfDead(existing))
11191
11223
  existing = null;
11192
11224
  const d = canAcquire(existing, { owner: input.owner }, Date.now(), Store.pidAlive);
@@ -11228,10 +11260,10 @@ ${p2.reason ?? ""}`.trim(),
11228
11260
  return { ok: true, resource };
11229
11261
  }
11230
11262
  releaseResource(name, projectId, owner, force = false) {
11231
- const raw2 = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ? AND released = 0").get(name, projectId ?? "");
11232
- if (!raw2)
11263
+ const raw = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ? AND released = 0").get(name, projectId ?? "");
11264
+ if (!raw)
11233
11265
  return { ok: false, reason: "not held" };
11234
- const r = this.rowToResource(raw2);
11266
+ const r = this.rowToResource(raw);
11235
11267
  if (!force) {
11236
11268
  if (!owner)
11237
11269
  return { ok: false, reason: `held by ${r.owner}; pass owner or force` };
@@ -11285,14 +11317,14 @@ ${p2.reason ?? ""}`.trim(),
11285
11317
  const from = localDayIso(-13);
11286
11318
  const rows = this.db.query(`SELECT s.project_id AS pid, substr(t.ts, 1, 10) AS day, SUM(t.cost_usd) AS usd
11287
11319
  FROM turns t JOIN sessions s ON s.id = t.session_id WHERE t.ts >= ? GROUP BY pid, day`).all(from);
11288
- const days3 = [];
11320
+ const days = [];
11289
11321
  for (let i = 13;i >= 0; i--)
11290
- days3.push(localDayIso(-i).slice(0, 10));
11322
+ days.push(localDayIso(-i).slice(0, 10));
11291
11323
  const out = {};
11292
11324
  for (const r of rows) {
11293
11325
  out[r.pid] ??= new Array(14).fill(0);
11294
11326
  const arr = out[r.pid];
11295
- const i = days3.indexOf(r.day);
11327
+ const i = days.indexOf(r.day);
11296
11328
  if (i >= 0)
11297
11329
  arr[i] = (arr[i] ?? 0) + (r.usd ?? 0);
11298
11330
  }
@@ -11300,13 +11332,13 @@ ${p2.reason ?? ""}`.trim(),
11300
11332
  }
11301
11333
  snapshot() {
11302
11334
  const worktrees = {};
11303
- const projects = this.projects().filter((p) => !(p.discovered && isScratchRoot(p.root)));
11335
+ const projects = this.liveProjects();
11304
11336
  for (const p of projects)
11305
11337
  worktrees[p.id] = this.worktrees(p.id);
11306
11338
  return {
11307
11339
  projects,
11308
11340
  worktrees,
11309
- sessions: this.memoised("sessions", 2000, () => this.sessions()),
11341
+ sessions: this.sessions(),
11310
11342
  spend: this.memoised("spend", 30000, () => this.spend()),
11311
11343
  spendSparks: this.memoised("spendSparks", 60000, () => this.spendSparks()),
11312
11344
  claims: this.claims(),
@@ -11351,18 +11383,18 @@ function slimForStorage(e) {
11351
11383
  toolResponse: clip(p.toolResponse, TOOL_RESPONSE_MAX)
11352
11384
  };
11353
11385
  }
11354
- let raw2 = e.raw;
11355
- if (raw2 && typeof raw2 === "object") {
11356
- const r = raw2;
11386
+ let raw = e.raw;
11387
+ if (raw && typeof raw === "object") {
11388
+ const r = raw;
11357
11389
  if (RAW_TOOL_KEYS.some((k) => (k in r))) {
11358
11390
  const rest = {};
11359
11391
  for (const k of Object.keys(r))
11360
11392
  if (!RAW_TOOL_KEYS.includes(k))
11361
11393
  rest[k] = r[k];
11362
- raw2 = rest;
11394
+ raw = rest;
11363
11395
  }
11364
11396
  }
11365
- return { payload, raw: raw2 };
11397
+ return { payload, raw };
11366
11398
  }
11367
11399
  function toWire(e) {
11368
11400
  const { raw: _raw, ...rest } = e;
@@ -11475,12 +11507,12 @@ class TeamForwarder {
11475
11507
  return this.store.clusterKeyFor(projectId);
11476
11508
  }
11477
11509
  status() {
11478
- const team2 = this.store.policyFor(null).config.team;
11510
+ const team = this.store.policyFor(null).config.team;
11479
11511
  const box = this.store.outboxStatus();
11480
11512
  return {
11481
- configured: team2.url != null,
11482
- url: team2.url,
11483
- forward: team2.forward,
11513
+ configured: team.url != null,
11514
+ url: team.url,
11515
+ forward: team.forward,
11484
11516
  pending: box.pending,
11485
11517
  oldest: box.oldest,
11486
11518
  lastAckAt: this.store.metaValue("team_last_ack") ?? null,
@@ -11490,14 +11522,14 @@ class TeamForwarder {
11490
11522
  };
11491
11523
  }
11492
11524
  async tick(now = Date.now()) {
11493
- const team2 = this.store.policyFor(null).config.team;
11494
- if (!team2.url)
11525
+ const team = this.store.policyFor(null).config.team;
11526
+ if (!team.url)
11495
11527
  return 0;
11496
- if (now - this.lastTry < team2.interval * 1000 + this.backoffMs)
11528
+ if (now - this.lastTry < team.interval * 1000 + this.backoffMs)
11497
11529
  return 0;
11498
11530
  this.lastTry = now;
11499
11531
  const records = [];
11500
- const events = team2.forward.includes("ledger") ? this.store.outboxPending() : [];
11532
+ const events = team.forward.includes("ledger") ? this.store.outboxPending() : [];
11501
11533
  for (const e of events) {
11502
11534
  const body = JSON.parse(e.payload);
11503
11535
  if (typeof body.projectId === "string")
@@ -11505,7 +11537,7 @@ class TeamForwarder {
11505
11537
  records.push({ seq: e.seq, kind: e.kind, body });
11506
11538
  }
11507
11539
  let spendRows = 0;
11508
- if (team2.forward.includes("cost") && now - this.lastSpend > SPEND_EVERY_MS) {
11540
+ if (team.forward.includes("cost") && now - this.lastSpend > SPEND_EVERY_MS) {
11509
11541
  const day = new Date(now).toISOString().slice(0, 10);
11510
11542
  for (const r of this.store.spendRollup(day)) {
11511
11543
  records.push({
@@ -11612,30 +11644,30 @@ class TeamForwarder {
11612
11644
  });
11613
11645
  if (!res.ok)
11614
11646
  return;
11615
- const { policy: policy2 } = await res.json();
11616
- if (!policy2)
11647
+ const { policy } = await res.json();
11648
+ if (!policy)
11617
11649
  return;
11618
11650
  let pinned = this.store.metaValue("team_policy_pubkey");
11619
11651
  if (!pinned) {
11620
- pinned = policy2.publicKey;
11652
+ pinned = policy.publicKey;
11621
11653
  this.store.setMetaValue("team_policy_pubkey", pinned);
11622
11654
  }
11623
- if (!verifyPolicySignature(policy2.toml, policy2.signature, pinned)) {
11655
+ if (!verifyPolicySignature(policy.toml, policy.signature, pinned)) {
11624
11656
  this.store.setMetaValue("team_last_error", "org policy signature invalid \u2014 not installed");
11625
11657
  return;
11626
11658
  }
11627
11659
  const file = join9(this.store.home, "policy.toml");
11628
11660
  const prev = this.store.metaValue("team_policy_sig");
11629
- if (prev === policy2.signature)
11661
+ if (prev === policy.signature)
11630
11662
  return;
11631
- writeFileSync3(file, policy2.toml, { mode: 384 });
11663
+ writeFileSync3(file, policy.toml, { mode: 384 });
11632
11664
  writeFileSync3(join9(this.store.home, "policy.sig.json"), JSON.stringify({
11633
- signature: policy2.signature,
11665
+ signature: policy.signature,
11634
11666
  publicKey: pinned,
11635
11667
  fetchedAt: new Date(now).toISOString(),
11636
11668
  url: base
11637
11669
  }), { mode: 384 });
11638
- this.store.setMetaValue("team_policy_sig", policy2.signature);
11670
+ this.store.setMetaValue("team_policy_sig", policy.signature);
11639
11671
  } catch {}
11640
11672
  }
11641
11673
  }
@@ -11646,12 +11678,12 @@ class WorkflowEngine {
11646
11678
  runner;
11647
11679
  forge;
11648
11680
  active = new Map;
11649
- constructor(store, runner, forge2) {
11681
+ constructor(store, runner, forge) {
11650
11682
  this.store = store;
11651
11683
  this.runner = runner;
11652
- this.forge = forge2;
11684
+ this.forge = forge;
11653
11685
  store.wfSweepOrphans();
11654
- runner.onEnd((run2) => void this.onRunEnd(run2));
11686
+ runner.onEnd((run) => void this.onRunEnd(run));
11655
11687
  }
11656
11688
  start(projectId, task, workflow, opts = {}) {
11657
11689
  const def = this.store.config(projectId).workflows[workflow];
@@ -11729,11 +11761,11 @@ class WorkflowEngine {
11729
11761
  }
11730
11762
  if (s.kind === "gate") {
11731
11763
  const r = await this.store.runGates(w.projectId, w.task, [s.gate], { owner: w.owner });
11732
- const run2 = r.runs.find((x) => x.gate === s.gate);
11733
- if (!run2)
11764
+ const run = r.runs.find((x) => x.gate === s.gate);
11765
+ if (!run)
11734
11766
  return this.fail(w, `gate ${s.gate} did not run: ${r.skipped[0]?.reason ?? "unknown"}`);
11735
- if (run2.verdict !== "pass")
11736
- return this.fail(w, `gate ${s.gate} failed \u2014 ${run2.rubric}`);
11767
+ if (run.verdict !== "pass")
11768
+ return this.fail(w, `gate ${s.gate} failed \u2014 ${run.rubric}`);
11737
11769
  w.step++;
11738
11770
  continue;
11739
11771
  }
@@ -11753,15 +11785,15 @@ class WorkflowEngine {
11753
11785
  }
11754
11786
  this.finish(w, "done", null);
11755
11787
  }
11756
- async onRunEnd(run2) {
11757
- const w = this.active.get(`${run2.projectId}:${run2.task}`);
11758
- if (!w || w.runId !== run2.id)
11788
+ async onRunEnd(run) {
11789
+ const w = this.active.get(`${run.projectId}:${run.task}`);
11790
+ if (!w || w.runId !== run.id)
11759
11791
  return;
11760
11792
  w.runId = null;
11761
- if (run2.stopped)
11793
+ if (run.stopped)
11762
11794
  return this.finish(w, "stopped", `stopped during ${this.label(w)}`);
11763
- if (run2.exitCode !== 0 || run2.result?.isError)
11764
- return this.fail(w, `${this.label(w)} exited ${run2.exitCode}${run2.result?.isError ? " (error)" : ""} \u2014 log: ${run2.log}`);
11795
+ if (run.exitCode !== 0 || run.result?.isError)
11796
+ return this.fail(w, `${this.label(w)} exited ${run.exitCode}${run.result?.isError ? " (error)" : ""} \u2014 log: ${run.log}`);
11765
11797
  w.step++;
11766
11798
  this.advance(w);
11767
11799
  }
@@ -11831,7 +11863,7 @@ class WorkflowEngine {
11831
11863
  }
11832
11864
 
11833
11865
  // packages/daemon/src/app.ts
11834
- var VERSION = "0.13.0";
11866
+ var VERSION = "0.13.2";
11835
11867
  var WEB_DIR = (() => {
11836
11868
  if (process.env.SWARM_WEB_DIR)
11837
11869
  return process.env.SWARM_WEB_DIR;
@@ -11849,8 +11881,8 @@ function wireJson(e) {
11849
11881
  }
11850
11882
  return s;
11851
11883
  }
11852
- function hookRepoRoot(store, raw2) {
11853
- const cwd = typeof raw2.cwd === "string" ? raw2.cwd : "";
11884
+ function hookRepoRoot(store, raw) {
11885
+ const cwd = typeof raw.cwd === "string" ? raw.cwd : "";
11854
11886
  return cwd && existsSync7(cwd) ? store.resolveProject(cwd)?.root ?? null : null;
11855
11887
  }
11856
11888
  function claudeSettings() {
@@ -11878,17 +11910,24 @@ function diskVersion() {
11878
11910
  return null;
11879
11911
  }
11880
11912
  }
11881
- function createApp(store = new Store, hooks2 = {}) {
11913
+ function expandHome(p, home = homedir4()) {
11914
+ if (p === "~")
11915
+ return home;
11916
+ if (p.startsWith("~/"))
11917
+ return join10(home, p.slice(2));
11918
+ return p;
11919
+ }
11920
+ function createApp(store = new Store, hooks = {}) {
11882
11921
  const app = new Hono2;
11883
- const forge2 = new ForgeService(store);
11922
+ const forge = new ForgeService(store);
11884
11923
  const runner = new Runner(store, store.home);
11885
- const dispatcher = new Dispatcher(store, runner, forge2);
11886
- const workflows2 = new WorkflowEngine(store, runner, forge2);
11887
- const team2 = new TeamForwarder(store, VERSION);
11924
+ const dispatcher = new Dispatcher(store, runner, forge);
11925
+ const workflows = new WorkflowEngine(store, runner, forge);
11926
+ const team = new TeamForwarder(store, VERSION);
11888
11927
  store.onBudgetStop((projectId) => {
11889
11928
  dispatcher.clear(projectId);
11890
- for (const run2 of runner.list(projectId))
11891
- runner.stop(run2.id);
11929
+ for (const run of runner.list(projectId))
11930
+ runner.stop(run.id);
11892
11931
  });
11893
11932
  app.use("/v1/*", async (c, next) => {
11894
11933
  if (c.req.path === "/v1/health")
@@ -11913,13 +11952,13 @@ function createApp(store = new Store, hooks2 = {}) {
11913
11952
  schema: store.schemaVersion(),
11914
11953
  auth: store.policyFor(null).config.daemon.auth
11915
11954
  }));
11916
- app.get("/v1/projects", (c) => c.json(store.snapshot().projects));
11955
+ app.get("/v1/projects", (c) => c.json(store.liveProjects()));
11917
11956
  app.post("/v1/projects", async (c) => {
11918
11957
  const { path, name } = await c.req.json();
11919
11958
  if (!path)
11920
11959
  return c.json({ error: "path required" }, 400);
11921
11960
  try {
11922
- return c.json(store.resolveProject(path, true, name), 201);
11961
+ return c.json(store.resolveProject(expandHome(path), true, name), 201);
11923
11962
  } catch (e) {
11924
11963
  return c.json({ error: e.message }, 400);
11925
11964
  }
@@ -11939,10 +11978,12 @@ function createApp(store = new Store, hooks2 = {}) {
11939
11978
  });
11940
11979
  app.delete("/v1/projects/:id", (c) => store.removeProject(c.req.param("id")) ? c.body(null, 204) : c.json({ error: "not found" }, 404));
11941
11980
  app.get("/v1/fs/ls", (c) => {
11942
- const q = c.req.query("path");
11981
+ const q = expandHome(c.req.query("path") ?? "");
11982
+ if (q && !existsSync7(q))
11983
+ return c.json({ error: "no such folder", path: q }, 404);
11943
11984
  let dir;
11944
11985
  try {
11945
- dir = realpathSync3(q && existsSync7(q) ? q : homedir4());
11986
+ dir = realpathSync3(q || homedir4());
11946
11987
  } catch {
11947
11988
  dir = homedir4();
11948
11989
  }
@@ -11954,7 +11995,14 @@ function createApp(store = new Store, hooks2 = {}) {
11954
11995
  return c.json({ error: e.message, path: dir }, 400);
11955
11996
  }
11956
11997
  });
11957
- app.get("/v1/state", (c) => c.json(store.snapshot()));
11998
+ app.get("/v1/state", (c) => {
11999
+ const body = JSON.stringify(store.snapshot());
12000
+ const etag = `W/"${Bun.hash(body).toString(36)}"`;
12001
+ const head = { etag, "cache-control": "no-cache" };
12002
+ if (c.req.header("if-none-match") === etag)
12003
+ return c.body(null, 304, head);
12004
+ return c.body(body, 200, { ...head, "content-type": "application/json" });
12005
+ });
11958
12006
  app.get("/v1/stats", (c) => c.json(store.stats(c.req.query("project") || undefined)));
11959
12007
  app.get("/v1/graphs/collisions", (c) => c.json(store.collisions(c.req.query("project") || undefined)));
11960
12008
  app.get("/v1/graphs/lineage", (c) => c.json(store.lineage(c.req.query("project") || undefined, Math.max(1, Math.min(90, Number(c.req.query("days") ?? 14) || 14)), c.req.queries("expand") ?? [])));
@@ -11981,7 +12029,7 @@ function createApp(store = new Store, hooks2 = {}) {
11981
12029
  })));
11982
12030
  const outcomesFor = async (project, opts = {}) => {
11983
12031
  const blocking = opts.blocking !== false;
11984
- const sessions = store.snapshot().sessions.filter((s) => !project || s.projectId === project).map((s) => ({
12032
+ const sessions = store.sessions().filter((s) => !project || s.projectId === project).map((s) => ({
11985
12033
  id: s.id,
11986
12034
  branch: s.branch,
11987
12035
  model: s.model,
@@ -11992,8 +12040,9 @@ function createApp(store = new Store, hooks2 = {}) {
11992
12040
  const prs = [];
11993
12041
  const reverted = new Set;
11994
12042
  let stale = false;
11995
- for (const p of store.projects().filter((x) => !project || x.id === project)) {
11996
- const o = blocking ? { ...await forge2.merged(p.id, p.root), fresh: true } : forge2.mergedCached(p.id, p.root);
12043
+ const scope = store.liveProjects().filter((x) => !project || x.id === project);
12044
+ const outcomes = await Promise.all(scope.map(async (p) => blocking ? { ...await forge.merged(p.id, p.root), fresh: true } : forge.mergedCached(p.id, p.root)));
12045
+ for (const o of outcomes) {
11997
12046
  if (!o.fresh)
11998
12047
  stale = true;
11999
12048
  for (const m of o.merged)
@@ -12001,7 +12050,7 @@ function createApp(store = new Store, hooks2 = {}) {
12001
12050
  for (const sha of o.reverted)
12002
12051
  reverted.add(sha);
12003
12052
  }
12004
- for (const pr of forge2.prs())
12053
+ for (const pr of forge.prs())
12005
12054
  if (!project || pr.projectId === project)
12006
12055
  prs.push({
12007
12056
  branch: pr.branch,
@@ -12076,10 +12125,10 @@ function createApp(store = new Store, hooks2 = {}) {
12076
12125
  const offset = Math.max(0, Number(c.req.query("offset") ?? 0) || 0);
12077
12126
  const { branches, stale } = await outcomesFor(project, { blocking: false });
12078
12127
  const projects = store.projects().filter((p) => !project || p.id === project);
12079
- const tasks2 = [];
12128
+ const tasks = [];
12080
12129
  for (const p of projects)
12081
12130
  for (const t of store.tasks(p.id)?.tasks ?? [])
12082
- tasks2.push({ id: t.id, title: t.title, status: t.statusText, url: null });
12131
+ tasks.push({ id: t.id, title: t.title, status: t.statusText, url: null });
12083
12132
  const claims = store.claims(project).map((cl) => ({
12084
12133
  task: cl.task,
12085
12134
  sessionId: cl.sessionId,
@@ -12089,14 +12138,14 @@ function createApp(store = new Store, hooks2 = {}) {
12089
12138
  acquiredAt: cl.acquiredAt,
12090
12139
  state: cl.state
12091
12140
  }));
12092
- const sessions = store.snapshot().sessions.filter((s) => !project || s.projectId === project).map((s) => ({
12141
+ const sessions = store.sessions().filter((s) => !project || s.projectId === project).map((s) => ({
12093
12142
  id: s.id,
12094
12143
  title: s.title,
12095
12144
  agent: s.agent,
12096
12145
  branch: s.branch,
12097
12146
  costUsd: s.costUsd
12098
12147
  }));
12099
- const full = provenance(tasks2, claims, sessions, branches);
12148
+ const full = provenance(tasks, claims, sessions, branches);
12100
12149
  return c.json({
12101
12150
  ...full,
12102
12151
  chains: full.chains.slice(offset, offset + limit),
@@ -12127,7 +12176,7 @@ function createApp(store = new Store, hooks2 = {}) {
12127
12176
  return c.json({ error: e.message }, 500);
12128
12177
  }
12129
12178
  });
12130
- app.get("/v1/team", (c) => c.json(team2.status()));
12179
+ app.get("/v1/team", (c) => c.json(team.status()));
12131
12180
  app.post("/v1/team/credentials", async (c) => {
12132
12181
  const b = await c.req.json().catch(() => ({}));
12133
12182
  if (typeof b.token !== "string" || !b.token)
@@ -12142,8 +12191,8 @@ function createApp(store = new Store, hooks2 = {}) {
12142
12191
  const p = id ? store.project(id) : null;
12143
12192
  if (id && !p)
12144
12193
  return c.json({ error: "unknown project" }, 404);
12145
- const { provenance: provenance3, overridden, policy: policy2 } = store.policyFor(p?.root ?? null);
12146
- return c.json({ ...policy2, provenance: provenance3, overridden });
12194
+ const { provenance, overridden, policy } = store.policyFor(p?.root ?? null);
12195
+ return c.json({ ...policy, provenance, overridden });
12147
12196
  });
12148
12197
  app.get("/v1/audit", (c) => {
12149
12198
  const since = sinceToIso(c.req.query("since"));
@@ -12163,9 +12212,9 @@ function createApp(store = new Store, hooks2 = {}) {
12163
12212
  return c.body(formatAudit(rows, format), 200, { "content-type": ct });
12164
12213
  });
12165
12214
  app.post("/v1/daemon/restart", (c) => {
12166
- if (!hooks2.restart)
12215
+ if (!hooks.restart)
12167
12216
  return c.json({ error: "not restartable in this environment" }, 501);
12168
- setTimeout(() => hooks2.restart?.(), 50);
12217
+ setTimeout(() => hooks.restart?.(), 50);
12169
12218
  return c.json({ ok: true, restarting: true });
12170
12219
  });
12171
12220
  app.get("/v1/rules/dryrun", (c) => {
@@ -12197,12 +12246,12 @@ function createApp(store = new Store, hooks2 = {}) {
12197
12246
  const r = store.acquireResource({ ...b, name: b.name, owner: b.owner });
12198
12247
  return r.ok ? c.json(r, 201) : c.json({ ok: false, error: r.reason }, 409);
12199
12248
  });
12200
- app.get("/v1/prs", (c) => c.json(forge2.prs()));
12249
+ app.get("/v1/prs", (c) => c.json(forge.prs()));
12201
12250
  app.post("/v1/prs/merge", async (c) => {
12202
12251
  const b = await c.req.json().catch(() => ({}));
12203
12252
  if (!b.projectId || !b.number)
12204
12253
  return c.json({ error: "projectId and number are required" }, 400);
12205
- const r = await forge2.merge(b.projectId, b.number);
12254
+ const r = await forge.merge(b.projectId, b.number);
12206
12255
  return r.ok ? c.json(r) : c.json({ error: r.output || "merge failed" }, 409);
12207
12256
  });
12208
12257
  app.delete("/v1/resources/:name", (c) => {
@@ -12292,9 +12341,9 @@ function createApp(store = new Store, hooks2 = {}) {
12292
12341
  const b = await c.req.json().catch(() => ({}));
12293
12342
  const r = store.answer(Number(c.req.param("id")), b.text, b.by ?? null);
12294
12343
  if (r.ok && r.question.sessionId) {
12295
- const run2 = runner.get(r.question.sessionId);
12296
- if (run2 && run2.sessionId === r.question.sessionId) {
12297
- const sent = runner.send(run2.id, `[swarm] answer from ${b.by ?? "a human"} to your question "${r.question.text.slice(0, 200)}": ${r.question.answer}`);
12344
+ const run = runner.get(r.question.sessionId);
12345
+ if (run && run.sessionId === r.question.sessionId) {
12346
+ const sent = runner.send(run.id, `[swarm] answer from ${b.by ?? "a human"} to your question "${r.question.text.slice(0, 200)}": ${r.question.answer}`);
12298
12347
  if (sent.ok)
12299
12348
  store.inbox(r.question.sessionId);
12300
12349
  }
@@ -12306,13 +12355,13 @@ function createApp(store = new Store, hooks2 = {}) {
12306
12355
  const project = c.req.query("project");
12307
12356
  if (!project)
12308
12357
  return c.json({ error: "project required" }, 400);
12309
- return c.json({ defs: store.config(project).workflows, runs: workflows2.status(project) });
12358
+ return c.json({ defs: store.config(project).workflows, runs: workflows.status(project) });
12310
12359
  });
12311
12360
  app.post("/v1/workflows", async (c) => {
12312
12361
  const b = await c.req.json().catch(() => ({}));
12313
12362
  if (!b.projectId || !b.task || !b.workflow)
12314
12363
  return c.json({ ok: false, error: "projectId, task, workflow required" }, 400);
12315
- const r = workflows2.start(b.projectId, b.task, b.workflow, {
12364
+ const r = workflows.start(b.projectId, b.task, b.workflow, {
12316
12365
  ...b.owner ? { owner: b.owner } : {},
12317
12366
  sessionId: b.sessionId ?? null
12318
12367
  });
@@ -12322,7 +12371,7 @@ function createApp(store = new Store, hooks2 = {}) {
12322
12371
  const b = await c.req.json().catch(() => ({}));
12323
12372
  if (!b.projectId || !b.task)
12324
12373
  return c.json({ ok: false, error: "projectId and task required" }, 400);
12325
- const r = workflows2.stop(b.projectId, b.task);
12374
+ const r = workflows.stop(b.projectId, b.task);
12326
12375
  return c.json(r, r.ok ? 200 : 404);
12327
12376
  });
12328
12377
  app.get("/v1/timeline", (c) => c.json(store.timelineDetail(Number(c.req.query("hours")) || 12, c.req.query("project") || null)));
@@ -12346,11 +12395,11 @@ function createApp(store = new Store, hooks2 = {}) {
12346
12395
  if (!r.ok)
12347
12396
  return c.json(r, 400);
12348
12397
  const m = r.message;
12349
- const run2 = m.task ? runner.get(m.task) : m.sessionId ? runner.get(m.sessionId) : null;
12350
- if (run2 && !run2.endedAt) {
12351
- const sent = runner.send(run2.id, `[swarm] message from ${m.from ?? "unknown"}: ${m.text}`);
12398
+ const run = m.task ? runner.get(m.task) : m.sessionId ? runner.get(m.sessionId) : null;
12399
+ if (run && !run.endedAt) {
12400
+ const sent = runner.send(run.id, `[swarm] message from ${m.from ?? "unknown"}: ${m.text}`);
12352
12401
  if (sent.ok)
12353
- store.markMessageDelivered(m.id, run2.sessionId);
12402
+ store.markMessageDelivered(m.id, run.sessionId);
12354
12403
  }
12355
12404
  return c.json({ ok: true, message: store.message(m.id) }, 201);
12356
12405
  });
@@ -12558,7 +12607,7 @@ function createApp(store = new Store, hooks2 = {}) {
12558
12607
  const d = await store.prDraftFor(b.projectId, ref);
12559
12608
  if (!d.ok)
12560
12609
  return c.json(d, 404);
12561
- const r = await forge2.openPR(b.projectId, d.worktree, {
12610
+ const r = await forge.openPR(b.projectId, d.worktree, {
12562
12611
  title: b.title?.trim() || d.title,
12563
12612
  body: b.body ?? d.body,
12564
12613
  isDraft: b.draft ?? false
@@ -12629,21 +12678,21 @@ function createApp(store = new Store, hooks2 = {}) {
12629
12678
  app.post("/v1/sessions/:id/tail", (c) => c.json({ turns: store.tailSession(c.req.param("id")) }));
12630
12679
  app.post("/v1/hook/:event", async (c) => {
12631
12680
  const event = c.req.param("event");
12632
- const raw2 = await c.req.json().catch(() => ({}));
12633
- store.ingestHook(event, raw2);
12634
- if (event === "SessionStart" && typeof raw2.cwd === "string") {
12635
- store.checkPolicy(raw2.cwd, typeof raw2.session_id === "string" ? raw2.session_id : null);
12636
- const ctx = store.sessionContext(raw2.cwd);
12681
+ const raw = await c.req.json().catch(() => ({}));
12682
+ store.ingestHook(event, raw);
12683
+ if (event === "SessionStart" && typeof raw.cwd === "string") {
12684
+ store.checkPolicy(raw.cwd, typeof raw.session_id === "string" ? raw.session_id : null);
12685
+ const ctx = store.sessionContext(raw.cwd);
12637
12686
  if (ctx)
12638
12687
  return c.json({
12639
12688
  additionalContext: ctx,
12640
12689
  hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: ctx }
12641
12690
  });
12642
12691
  }
12643
- const sid = typeof raw2.session_id === "string" ? raw2.session_id : null;
12692
+ const sid = typeof raw.session_id === "string" ? raw.session_id : null;
12644
12693
  const answers = event === "UserPromptSubmit" || event === "PreToolUse" || event === "PostToolUse" ? store.answerContext(sid) : null;
12645
- if (event === "PreToolUse" && !store.guardDisabled(hookRepoRoot(store, raw2))) {
12646
- const guard = store.guardHook(raw2);
12694
+ if (event === "PreToolUse" && !store.guardDisabled(hookRepoRoot(store, raw))) {
12695
+ const guard = store.guardHook(raw);
12647
12696
  if (guard) {
12648
12697
  return c.json({
12649
12698
  hookSpecificOutput: {
@@ -12668,22 +12717,22 @@ function createApp(store = new Store, hooks2 = {}) {
12668
12717
  });
12669
12718
  app.get("/v1/events", (c) => {
12670
12719
  const full = c.req.query("full") === "1";
12671
- const raw2 = Number(c.req.query("since") ?? 0);
12672
- const since = raw2 > 0 ? raw2 : Math.max(0, store.seq() - REPLAY_TAIL);
12673
- return streamSSE(c, async (stream2) => {
12720
+ const raw = Number(c.req.query("since") ?? 0);
12721
+ const since = raw > 0 ? raw : Math.max(0, store.seq() - REPLAY_TAIL);
12722
+ return streamSSE(c, async (stream) => {
12674
12723
  for (const e of store.since(since, 5000, full)) {
12675
- await stream2.writeSSE({ id: String(e.seq), event: e.type, data: JSON.stringify(e) });
12724
+ await stream.writeSSE({ id: String(e.seq), event: e.type, data: JSON.stringify(e) });
12676
12725
  }
12677
- await stream2.writeSSE({ event: "ping", data: "" });
12678
- await new Promise((resolve2) => {
12726
+ await stream.writeSSE({ event: "ping", data: "" });
12727
+ await new Promise((resolve) => {
12679
12728
  const off = store.subscribe((e) => {
12680
- stream2.writeSSE({ id: String(e.seq), event: e.type, data: wireJson(e) });
12729
+ stream.writeSSE({ id: String(e.seq), event: e.type, data: wireJson(e) });
12681
12730
  });
12682
- const beat = setInterval(() => void stream2.writeSSE({ event: "ping", data: "" }), 15000);
12683
- stream2.onAbort(() => {
12731
+ const beat = setInterval(() => void stream.writeSSE({ event: "ping", data: "" }), 15000);
12732
+ stream.onAbort(() => {
12684
12733
  clearInterval(beat);
12685
12734
  off();
12686
- resolve2();
12735
+ resolve();
12687
12736
  });
12688
12737
  });
12689
12738
  });
@@ -12714,7 +12763,7 @@ function createApp(store = new Store, hooks2 = {}) {
12714
12763
  etag
12715
12764
  });
12716
12765
  });
12717
- return { app, store, forge: forge2, runner, dispatcher, workflows: workflows2, team: team2 };
12766
+ return { app, store, forge, runner, dispatcher, workflows, team };
12718
12767
  }
12719
12768
 
12720
12769
  // packages/daemon/src/demo.ts
@@ -12751,8 +12800,8 @@ function seedDemo(store) {
12751
12800
  VALUES ('p_demo1', 'checkout', 'demo-s1', '/work/acme-app-wt/checkout', 'task/checkout', ?, ?, NULL, 'held', 'agent', 'demo-s1')`).run(iso(5 * H), iso(-30 * 60000));
12752
12801
  db.query(`INSERT OR IGNORE INTO claims (project_id, task, owner, worktree, branch, acquired_at, expires_at, released_at, state, actor_kind, actor_id)
12753
12802
  VALUES ('p_demo1', 'webhooks', 'alice', '/work/acme-app-wt/webhooks', 'task/webhooks', ?, ?, NULL, 'orphaned', 'human', 'alice')`).run(iso(26 * H), iso(20 * H));
12754
- const gate = (task, gate2, verdict, rubric, ago, sid) => db.query(`INSERT OR IGNORE INTO gates (project_id, task, gate, verdict, rubric, evidence, session_id, created_at, actor_kind, actor_id)
12755
- VALUES ('p_demo1', ?, ?, ?, ?, NULL, ?, ?, 'daemon', 'daemon')`).run(task, gate2, verdict, rubric, sid, iso(ago));
12803
+ const gate = (task, gate, verdict, rubric, ago, sid) => db.query(`INSERT OR IGNORE INTO gates (project_id, task, gate, verdict, rubric, evidence, session_id, created_at, actor_kind, actor_id)
12804
+ VALUES ('p_demo1', ?, ?, ?, ?, NULL, ?, ?, 'daemon', 'daemon')`).run(task, gate, verdict, rubric, sid, iso(ago));
12756
12805
  gate("checkout", "tests", "fail", "ran `bun test` \u2014 exit 1 in 41s", 3 * H, "demo-s1");
12757
12806
  gate("checkout", "tests", "pass", "ran `bun test` \u2014 exit 0 in 39s", 1 * H, "demo-s1");
12758
12807
  gate("checkout", "review", "pass", "review: no blocker/major findings", 40 * 60000, null);