@ra3orblade/swarm 0.13.1 → 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/swarm-mcp.js +994 -1376
- package/dist/swarm.js +47 -47
- package/dist/swarmd.js +373 -373
- package/package.json +1 -1
- package/web/dashboard.css +1 -1
- package/web/dashboard.js +16 -16
- package/web/menus.js +7 -7
- package/web/release-notes.js +1 -1
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
|
|
972
|
-
return new Date(now - n *
|
|
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
|
|
1425
|
-
const
|
|
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
|
|
1431
|
-
toolTokens: estTokens(
|
|
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
|
|
1439
|
-
wasteShare:
|
|
1438
|
+
wastedChars,
|
|
1439
|
+
wasteShare: toolChars ? wastedChars / toolChars : 0,
|
|
1440
1440
|
worst: worstAll.slice(0, o.worstLimit)
|
|
1441
1441
|
});
|
|
1442
1442
|
}
|
|
@@ -1862,20 +1862,20 @@ function dryRunRules(calls, modes, ctx) {
|
|
|
1862
1862
|
if (d.action === "allow")
|
|
1863
1863
|
continue;
|
|
1864
1864
|
byRule[d.rule][d.action]++;
|
|
1865
|
-
const
|
|
1865
|
+
const norm = normalizeDisplay(display);
|
|
1866
1866
|
if (hits.length < maxHits)
|
|
1867
1867
|
hits.push({
|
|
1868
1868
|
ts: c.ts,
|
|
1869
1869
|
sessionId: c.sessionId,
|
|
1870
1870
|
rule: d.rule,
|
|
1871
1871
|
action: d.action,
|
|
1872
|
-
display:
|
|
1872
|
+
display: norm,
|
|
1873
1873
|
completed: c.completed
|
|
1874
1874
|
});
|
|
1875
|
-
const key = `${d.rule} ${
|
|
1875
|
+
const key = `${d.rule} ${norm}`;
|
|
1876
1876
|
const g = groups.get(key) ?? {
|
|
1877
1877
|
rule: d.rule,
|
|
1878
|
-
display:
|
|
1878
|
+
display: norm,
|
|
1879
1879
|
fires: 0,
|
|
1880
1880
|
completedRatio: 0,
|
|
1881
1881
|
sessions: 0,
|
|
@@ -2366,7 +2366,7 @@ var HYGIENE_DEFAULTS = {
|
|
|
2366
2366
|
hungryRssKb: 1024 * 1024,
|
|
2367
2367
|
heavyKb: 2 * 1024 * 1024
|
|
2368
2368
|
};
|
|
2369
|
-
var days2 = (
|
|
2369
|
+
var days2 = (ms) => ms / 86400000;
|
|
2370
2370
|
function classifyProcess(p, opts = {}) {
|
|
2371
2371
|
const o = { ...HYGIENE_DEFAULTS, ...opts };
|
|
2372
2372
|
if (!p.alive)
|
|
@@ -3030,11 +3030,11 @@ function parseTo(to) {
|
|
|
3030
3030
|
return { kind: "session", id: t };
|
|
3031
3031
|
return { kind: "task", task: t };
|
|
3032
3032
|
}
|
|
3033
|
-
function formatMessages(
|
|
3034
|
-
if (!
|
|
3033
|
+
function formatMessages(ms) {
|
|
3034
|
+
if (!ms.length)
|
|
3035
3035
|
return null;
|
|
3036
|
-
const lines =
|
|
3037
|
-
return `[swarm] While you were working, message${
|
|
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:
|
|
3038
3038
|
${lines.join(`
|
|
3039
3039
|
`)}
|
|
3040
3040
|
Reply with swarm_send if a reply is expected.`;
|
|
@@ -3722,8 +3722,8 @@ function commandSignature(command) {
|
|
|
3722
3722
|
const more = tokens.length > 2;
|
|
3723
3723
|
return `${name}${second ? ` ${second}` : ""}${more ? " \u2026" : ""}`;
|
|
3724
3724
|
}
|
|
3725
|
-
function ruleEffect(incidents, changes = [], now = Date.now(),
|
|
3726
|
-
const since = now -
|
|
3725
|
+
function ruleEffect(incidents, changes = [], now = Date.now(), days = 30) {
|
|
3726
|
+
const since = now - days * DAY;
|
|
3727
3727
|
const rows = incidents.filter((i) => i.rule && Date.parse(i.at) >= since);
|
|
3728
3728
|
const byRule = new Map;
|
|
3729
3729
|
for (const i of rows)
|
|
@@ -3997,12 +3997,12 @@ function parseMarkdownTasks(text) {
|
|
|
3997
3997
|
const cells = splitRow(line);
|
|
3998
3998
|
if (!cols) {
|
|
3999
3999
|
const lc = cells.map((c) => c.toLowerCase());
|
|
4000
|
-
const
|
|
4000
|
+
const id = lc.indexOf("id");
|
|
4001
4001
|
const task = lc.findIndex((c) => c === "task" || c === "title");
|
|
4002
|
-
if (
|
|
4002
|
+
if (id < 0 || task < 0 || !isSeparator(lines[i + 1] ?? ""))
|
|
4003
4003
|
continue;
|
|
4004
4004
|
cols = {
|
|
4005
|
-
id
|
|
4005
|
+
id,
|
|
4006
4006
|
task,
|
|
4007
4007
|
depends: lc.findIndex((c) => c.startsWith("depend")),
|
|
4008
4008
|
status: lc.indexOf("status")
|
|
@@ -4319,10 +4319,10 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
4319
4319
|
|
|
4320
4320
|
// node_modules/.bun/hono@4.13.3/node_modules/hono/dist/compose.js
|
|
4321
4321
|
var compose = (middleware, onError, onNotFound) => {
|
|
4322
|
-
return (
|
|
4322
|
+
return (context, next) => {
|
|
4323
4323
|
let index = -1;
|
|
4324
|
-
return
|
|
4325
|
-
async function
|
|
4324
|
+
return dispatch(0);
|
|
4325
|
+
async function dispatch(i) {
|
|
4326
4326
|
if (i <= index) {
|
|
4327
4327
|
throw new Error("next() called multiple times");
|
|
4328
4328
|
}
|
|
@@ -4332,31 +4332,31 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
4332
4332
|
let handler;
|
|
4333
4333
|
if (middleware[i]) {
|
|
4334
4334
|
handler = middleware[i][0][0];
|
|
4335
|
-
|
|
4335
|
+
context.req.routeIndex = i;
|
|
4336
4336
|
} else {
|
|
4337
4337
|
handler = i === middleware.length && next || undefined;
|
|
4338
4338
|
}
|
|
4339
4339
|
if (handler) {
|
|
4340
4340
|
try {
|
|
4341
|
-
res = await handler(
|
|
4341
|
+
res = await handler(context, () => dispatch(i + 1));
|
|
4342
4342
|
} catch (err) {
|
|
4343
4343
|
if (err instanceof Error && onError) {
|
|
4344
|
-
|
|
4345
|
-
res = await onError(err,
|
|
4344
|
+
context.error = err;
|
|
4345
|
+
res = await onError(err, context);
|
|
4346
4346
|
isError = true;
|
|
4347
4347
|
} else {
|
|
4348
4348
|
throw err;
|
|
4349
4349
|
}
|
|
4350
4350
|
}
|
|
4351
4351
|
} else {
|
|
4352
|
-
if (
|
|
4353
|
-
res = await onNotFound(
|
|
4352
|
+
if (context.finalized === false && onNotFound) {
|
|
4353
|
+
res = await onNotFound(context);
|
|
4354
4354
|
}
|
|
4355
4355
|
}
|
|
4356
|
-
if (res && (
|
|
4357
|
-
|
|
4356
|
+
if (res && (context.finalized === false || isError)) {
|
|
4357
|
+
context.res = res;
|
|
4358
4358
|
}
|
|
4359
|
-
return
|
|
4359
|
+
return context;
|
|
4360
4360
|
}
|
|
4361
4361
|
};
|
|
4362
4362
|
};
|
|
@@ -4777,7 +4777,7 @@ var raw = (value, callbacks) => {
|
|
|
4777
4777
|
escapedString.callbacks = callbacks;
|
|
4778
4778
|
return escapedString;
|
|
4779
4779
|
};
|
|
4780
|
-
var resolveCallback = async (str, phase, preserveCallbacks,
|
|
4780
|
+
var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
|
|
4781
4781
|
if (typeof str === "object" && !(str instanceof String)) {
|
|
4782
4782
|
if (!(str instanceof Promise)) {
|
|
4783
4783
|
str = str.toString();
|
|
@@ -4795,7 +4795,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context2, buffer) =>
|
|
|
4795
4795
|
} else {
|
|
4796
4796
|
buffer = [str];
|
|
4797
4797
|
}
|
|
4798
|
-
const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context
|
|
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]));
|
|
4799
4799
|
if (preserveCallbacks) {
|
|
4800
4800
|
return raw(await resStr, callbacks);
|
|
4801
4801
|
} else {
|
|
@@ -5194,11 +5194,11 @@ var Hono = class _Hono {
|
|
|
5194
5194
|
const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler);
|
|
5195
5195
|
return (async () => {
|
|
5196
5196
|
try {
|
|
5197
|
-
const
|
|
5198
|
-
if (!
|
|
5197
|
+
const context = await composed(c);
|
|
5198
|
+
if (!context.finalized) {
|
|
5199
5199
|
throw new Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");
|
|
5200
5200
|
}
|
|
5201
|
-
return
|
|
5201
|
+
return context.res;
|
|
5202
5202
|
} catch (err) {
|
|
5203
5203
|
return this.#handleError(err, c);
|
|
5204
5204
|
}
|
|
@@ -5271,7 +5271,7 @@ var Node = class _Node {
|
|
|
5271
5271
|
#index;
|
|
5272
5272
|
#varIndex;
|
|
5273
5273
|
#children = /* @__PURE__ */ Object.create(null);
|
|
5274
|
-
insert(tokens, index, paramMap,
|
|
5274
|
+
insert(tokens, index, paramMap, context, isStatic) {
|
|
5275
5275
|
let node = this;
|
|
5276
5276
|
for (let i = 0, len = tokens.length;i < len; i++) {
|
|
5277
5277
|
const token = tokens[i];
|
|
@@ -5304,7 +5304,7 @@ var Node = class _Node {
|
|
|
5304
5304
|
nextNode = node.#children[regexpStr] = new _Node;
|
|
5305
5305
|
}
|
|
5306
5306
|
if (name !== "") {
|
|
5307
|
-
nextNode.#varIndex ??=
|
|
5307
|
+
nextNode.#varIndex ??= context.varIndex++;
|
|
5308
5308
|
paramMap.push([name, nextNode.#varIndex]);
|
|
5309
5309
|
}
|
|
5310
5310
|
} else {
|
|
@@ -5617,7 +5617,7 @@ var SmartRouter = class {
|
|
|
5617
5617
|
// node_modules/.bun/hono@4.13.3/node_modules/hono/dist/router/trie-router/node.js
|
|
5618
5618
|
var emptyParams = /* @__PURE__ */ Object.create(null);
|
|
5619
5619
|
var order = 0;
|
|
5620
|
-
var Node2 = class
|
|
5620
|
+
var Node2 = class _Node {
|
|
5621
5621
|
#methods = [];
|
|
5622
5622
|
#children = /* @__PURE__ */ Object.create(null);
|
|
5623
5623
|
#patterns = [];
|
|
@@ -5633,7 +5633,7 @@ var Node2 = class _Node2 {
|
|
|
5633
5633
|
const pattern = getPattern(p, nextP) || (nextP === undefined && p && p.indexOf("*") === p.length - 1 ? p : null);
|
|
5634
5634
|
const isParam = Array.isArray(pattern);
|
|
5635
5635
|
const key = isParam ? pattern[0] : pattern || p;
|
|
5636
|
-
const child = curNode.#children[key] ||= new
|
|
5636
|
+
const child = curNode.#children[key] ||= new _Node;
|
|
5637
5637
|
if (pattern && !child.#pattern) {
|
|
5638
5638
|
child.#pattern = pattern;
|
|
5639
5639
|
curNode.#patterns.push(child);
|
|
@@ -5829,8 +5829,8 @@ var StreamingApi = class {
|
|
|
5829
5829
|
`);
|
|
5830
5830
|
return this;
|
|
5831
5831
|
}
|
|
5832
|
-
sleep(
|
|
5833
|
-
return new Promise((res) => setTimeout(res,
|
|
5832
|
+
sleep(ms) {
|
|
5833
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
5834
5834
|
}
|
|
5835
5835
|
async close() {
|
|
5836
5836
|
this.closed = true;
|
|
@@ -5941,11 +5941,11 @@ class Dispatcher {
|
|
|
5941
5941
|
forge;
|
|
5942
5942
|
entries = new Map;
|
|
5943
5943
|
opts = new Map;
|
|
5944
|
-
constructor(store, runner,
|
|
5944
|
+
constructor(store, runner, forge) {
|
|
5945
5945
|
this.store = store;
|
|
5946
5946
|
this.runner = runner;
|
|
5947
|
-
this.forge =
|
|
5948
|
-
runner.onEnd((
|
|
5947
|
+
this.forge = forge;
|
|
5948
|
+
runner.onEnd((run) => void this.onRunEnd(run));
|
|
5949
5949
|
}
|
|
5950
5950
|
project(projectId) {
|
|
5951
5951
|
let m = this.entries.get(projectId);
|
|
@@ -6032,10 +6032,10 @@ class Dispatcher {
|
|
|
6032
6032
|
const e = m.get(t.id);
|
|
6033
6033
|
const opts = this.opts.get(projectId) ?? { owner: "dispatch" };
|
|
6034
6034
|
const cfg = this.store.config(projectId);
|
|
6035
|
-
const
|
|
6035
|
+
const gates = cfg.gates;
|
|
6036
6036
|
const prompt = taskPrompt(t, {
|
|
6037
|
-
requiredGates:
|
|
6038
|
-
executableGates:
|
|
6037
|
+
requiredGates: gates.required,
|
|
6038
|
+
executableGates: gates.required.filter((g) => gates.defs[g]),
|
|
6039
6039
|
openPr: cfg.dispatch.require_pr
|
|
6040
6040
|
});
|
|
6041
6041
|
const r = await this.runner.start({
|
|
@@ -6102,76 +6102,76 @@ class Dispatcher {
|
|
|
6102
6102
|
await this.startOne(projectId, { id: e.task, title: e.title });
|
|
6103
6103
|
}
|
|
6104
6104
|
}
|
|
6105
|
-
async onRunEnd(
|
|
6106
|
-
const m = this.entries.get(
|
|
6107
|
-
const e = m?.get(
|
|
6108
|
-
if (!e || e.runId !==
|
|
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)
|
|
6109
6109
|
return;
|
|
6110
|
-
const cfg = this.store.config(
|
|
6110
|
+
const cfg = this.store.config(run.projectId);
|
|
6111
6111
|
const required = cfg.gates.required;
|
|
6112
|
-
let runs = this.store.gateRuns(
|
|
6112
|
+
let runs = this.store.gateRuns(run.projectId, run.task);
|
|
6113
6113
|
const status = this.store.gateStatusFor(runs, required);
|
|
6114
6114
|
const missing = required.filter((g) => cfg.gates.defs[g] && status.find((s) => s.gate === g)?.verdict !== "pass");
|
|
6115
|
-
if (missing.length && !
|
|
6116
|
-
await this.store.runGates(
|
|
6117
|
-
sessionId:
|
|
6115
|
+
if (missing.length && !run.stopped) {
|
|
6116
|
+
await this.store.runGates(run.projectId, run.task, missing, {
|
|
6117
|
+
sessionId: run.sessionId,
|
|
6118
6118
|
owner: "dispatch"
|
|
6119
6119
|
});
|
|
6120
|
-
runs = this.store.gateRuns(
|
|
6120
|
+
runs = this.store.gateRuns(run.projectId, run.task);
|
|
6121
6121
|
}
|
|
6122
6122
|
const satisfied = gatesSatisfied(runs, required);
|
|
6123
6123
|
await this.forge.refresh(0).catch(() => {});
|
|
6124
|
-
const branch = `task/${
|
|
6125
|
-
const pr = this.forge.prs().find((p) => p.projectId ===
|
|
6124
|
+
const branch = `task/${run.task}`;
|
|
6125
|
+
const pr = this.forge.prs().find((p) => p.projectId === run.projectId && p.branch === branch);
|
|
6126
6126
|
const outcome = dispatchOutcome({
|
|
6127
|
-
exitCode:
|
|
6128
|
-
isError:
|
|
6127
|
+
exitCode: run.exitCode,
|
|
6128
|
+
isError: run.result?.isError ?? false,
|
|
6129
6129
|
gatesSatisfied: satisfied,
|
|
6130
6130
|
prOpen: Boolean(pr),
|
|
6131
6131
|
requirePr: cfg.dispatch.require_pr,
|
|
6132
|
-
stopped:
|
|
6132
|
+
stopped: run.stopped ?? false
|
|
6133
6133
|
});
|
|
6134
6134
|
const verdicts = this.store.gateStatusFor(runs, required).map((s) => `${s.gate} ${s.verdict ?? "\u2014"}`).join(", ");
|
|
6135
6135
|
const detail = [
|
|
6136
|
-
`exit ${
|
|
6136
|
+
`exit ${run.exitCode}${run.result?.isError ? " (error)" : ""}`,
|
|
6137
6137
|
required.length ? `gates: ${verdicts}` : null,
|
|
6138
6138
|
pr ? `PR ${pr.url}` : cfg.dispatch.require_pr ? "no PR" : null
|
|
6139
6139
|
].filter(Boolean).join(" \xB7 ");
|
|
6140
6140
|
e.state = "finished";
|
|
6141
|
-
e.endedAt =
|
|
6141
|
+
e.endedAt = run.endedAt;
|
|
6142
6142
|
e.outcome = outcome;
|
|
6143
6143
|
e.detail = detail;
|
|
6144
|
-
e.costUsd =
|
|
6145
|
-
const ts =
|
|
6144
|
+
e.costUsd = run.result?.costUsd ?? null;
|
|
6145
|
+
const ts = run.endedAt ?? new Date().toISOString();
|
|
6146
6146
|
this.store.append({
|
|
6147
6147
|
ts,
|
|
6148
6148
|
type: "dispatch.finished",
|
|
6149
|
-
projectId:
|
|
6150
|
-
sessionId:
|
|
6149
|
+
projectId: run.projectId,
|
|
6150
|
+
sessionId: run.sessionId,
|
|
6151
6151
|
payload: {
|
|
6152
|
-
task:
|
|
6153
|
-
runId:
|
|
6152
|
+
task: run.task,
|
|
6153
|
+
runId: run.id,
|
|
6154
6154
|
outcome,
|
|
6155
6155
|
detail,
|
|
6156
6156
|
costUsd: e.costUsd,
|
|
6157
|
-
summary: `dispatch ${
|
|
6157
|
+
summary: `dispatch ${run.task}: ${outcome} \u2014 ${detail}`
|
|
6158
6158
|
}
|
|
6159
6159
|
});
|
|
6160
6160
|
if (outcome !== "done" && outcome !== "stopped")
|
|
6161
6161
|
this.store.append({
|
|
6162
6162
|
ts,
|
|
6163
6163
|
type: "incident.opened",
|
|
6164
|
-
projectId:
|
|
6165
|
-
sessionId:
|
|
6164
|
+
projectId: run.projectId,
|
|
6165
|
+
sessionId: run.sessionId,
|
|
6166
6166
|
payload: {
|
|
6167
6167
|
rule: "dispatch_failed",
|
|
6168
6168
|
action: outcome,
|
|
6169
|
-
command:
|
|
6170
|
-
reason: `dispatched run on ${
|
|
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.`
|
|
6171
6171
|
}
|
|
6172
6172
|
});
|
|
6173
6173
|
this.store.touch();
|
|
6174
|
-
await this.fill(
|
|
6174
|
+
await this.fill(run.projectId);
|
|
6175
6175
|
}
|
|
6176
6176
|
clear(projectId, task) {
|
|
6177
6177
|
const m = this.project(projectId);
|
|
@@ -6261,9 +6261,9 @@ class ForgeService {
|
|
|
6261
6261
|
const inflight = this.outcomeInflight.get(projectId);
|
|
6262
6262
|
if (inflight)
|
|
6263
6263
|
return inflight;
|
|
6264
|
-
const
|
|
6265
|
-
this.outcomeInflight.set(projectId,
|
|
6266
|
-
return
|
|
6264
|
+
const run = this.fetchMerged(projectId, root).finally(() => this.outcomeInflight.delete(projectId));
|
|
6265
|
+
this.outcomeInflight.set(projectId, run);
|
|
6266
|
+
return run;
|
|
6267
6267
|
}
|
|
6268
6268
|
async fetchMerged(projectId, root) {
|
|
6269
6269
|
let merged = [];
|
|
@@ -6355,18 +6355,18 @@ class ForgeService {
|
|
|
6355
6355
|
}
|
|
6356
6356
|
return prs.map((pr) => ({ ...pr, projectId, projectRoot: root }));
|
|
6357
6357
|
}
|
|
6358
|
-
async openPR(projectId,
|
|
6358
|
+
async openPR(projectId, worktree, draft) {
|
|
6359
6359
|
const p = this.store.projects().find((x) => x.id === projectId);
|
|
6360
6360
|
if (!p)
|
|
6361
6361
|
return { ok: false, error: "unknown project" };
|
|
6362
|
-
if (
|
|
6362
|
+
if (worktree.main)
|
|
6363
6363
|
return { ok: false, error: "that is the main checkout \u2014 open the PR from a task worktree" };
|
|
6364
|
-
if (!
|
|
6364
|
+
if (!worktree.branch)
|
|
6365
6365
|
return { ok: false, error: "detached HEAD \u2014 check out a branch first" };
|
|
6366
|
-
if (
|
|
6366
|
+
if (worktree.dirty > 0)
|
|
6367
6367
|
return {
|
|
6368
6368
|
ok: false,
|
|
6369
|
-
error: `${
|
|
6369
|
+
error: `${worktree.path} has uncommitted changes \u2014 commit them first (Swarm never commits for you)`
|
|
6370
6370
|
};
|
|
6371
6371
|
const remote = await this.remote(p.root);
|
|
6372
6372
|
if (!remote)
|
|
@@ -6375,15 +6375,15 @@ class ForgeService {
|
|
|
6375
6375
|
const bin = findBin(cli);
|
|
6376
6376
|
if (!bin)
|
|
6377
6377
|
return { ok: false, error: `${cli} is not installed` };
|
|
6378
|
-
const sh = async (
|
|
6379
|
-
const proc = Bun.spawn(
|
|
6378
|
+
const sh = async (cmd, cwd) => {
|
|
6379
|
+
const proc = Bun.spawn(cmd, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
6380
6380
|
const out = await new Response(proc.stdout).text() + await new Response(proc.stderr).text();
|
|
6381
6381
|
return { ok: await proc.exited === 0, out: out.trim() };
|
|
6382
6382
|
};
|
|
6383
|
-
const push = await sh(["git", "push", "-u", "origin",
|
|
6383
|
+
const push = await sh(["git", "push", "-u", "origin", worktree.branch], worktree.path);
|
|
6384
6384
|
if (!push.ok)
|
|
6385
6385
|
return { ok: false, error: `git push failed: ${push.out.slice(0, 400)}` };
|
|
6386
|
-
const existing = this.prs().find((x) => x.projectId === projectId && x.branch ===
|
|
6386
|
+
const existing = this.prs().find((x) => x.projectId === projectId && x.branch === worktree.branch);
|
|
6387
6387
|
if (existing)
|
|
6388
6388
|
return { ok: true, url: existing.url, number: existing.number };
|
|
6389
6389
|
const cmd = remote.forge === "github" ? [
|
|
@@ -6391,7 +6391,7 @@ class ForgeService {
|
|
|
6391
6391
|
"pr",
|
|
6392
6392
|
"create",
|
|
6393
6393
|
"--head",
|
|
6394
|
-
|
|
6394
|
+
worktree.branch,
|
|
6395
6395
|
"--title",
|
|
6396
6396
|
draft.title,
|
|
6397
6397
|
"--body",
|
|
@@ -6402,7 +6402,7 @@ class ForgeService {
|
|
|
6402
6402
|
"mr",
|
|
6403
6403
|
"create",
|
|
6404
6404
|
"--source-branch",
|
|
6405
|
-
|
|
6405
|
+
worktree.branch,
|
|
6406
6406
|
"--title",
|
|
6407
6407
|
draft.title,
|
|
6408
6408
|
"--description",
|
|
@@ -6410,7 +6410,7 @@ class ForgeService {
|
|
|
6410
6410
|
"--yes",
|
|
6411
6411
|
...draft.isDraft ? ["--draft"] : []
|
|
6412
6412
|
];
|
|
6413
|
-
const r = await sh(cmd,
|
|
6413
|
+
const r = await sh(cmd, worktree.path);
|
|
6414
6414
|
if (!r.ok)
|
|
6415
6415
|
return { ok: false, error: `${cli} failed: ${r.out.slice(0, 400)}` };
|
|
6416
6416
|
const url = r.out.match(/https?:\/\/\S+/)?.[0] ?? r.out;
|
|
@@ -6692,9 +6692,9 @@ class Runner {
|
|
|
6692
6692
|
return [...this.live.values()].map((x) => x.run).filter((r) => !projectId || r.projectId === projectId);
|
|
6693
6693
|
}
|
|
6694
6694
|
get(idOrTask) {
|
|
6695
|
-
for (const { run
|
|
6696
|
-
if (
|
|
6697
|
-
return
|
|
6695
|
+
for (const { run } of this.live.values())
|
|
6696
|
+
if (run.id === idOrTask || run.sessionId === idOrTask || run.task === idOrTask)
|
|
6697
|
+
return run;
|
|
6698
6698
|
return null;
|
|
6699
6699
|
}
|
|
6700
6700
|
async start(input) {
|
|
@@ -6722,14 +6722,14 @@ class Runner {
|
|
|
6722
6722
|
reason: `a run on ${input.task} is already live \u2014 stop it or send it input`
|
|
6723
6723
|
};
|
|
6724
6724
|
const held = this.store.claims(input.projectId).find((c) => c.task === input.task && c.state === "held" && c.owner === input.owner);
|
|
6725
|
-
let
|
|
6726
|
-
if (!
|
|
6725
|
+
let worktree = held?.worktree ?? "";
|
|
6726
|
+
if (!worktree) {
|
|
6727
6727
|
const c = this.store.claim(input.projectId, input.task, input.owner);
|
|
6728
6728
|
if (!c.ok)
|
|
6729
6729
|
return { ok: false, reason: c.error };
|
|
6730
|
-
|
|
6730
|
+
worktree = c.worktree;
|
|
6731
6731
|
}
|
|
6732
|
-
await this.store.awaitBootstrap(
|
|
6732
|
+
await this.store.awaitBootstrap(worktree);
|
|
6733
6733
|
const sessionId = crypto.randomUUID();
|
|
6734
6734
|
const id = sessionId.slice(0, 8);
|
|
6735
6735
|
const logDir = join6(this.home, "logs", project.id);
|
|
@@ -6766,20 +6766,20 @@ class Runner {
|
|
|
6766
6766
|
args.push("--disallowedTools", ...profile.disallowedTools);
|
|
6767
6767
|
if (input.maxTurns)
|
|
6768
6768
|
args.push("--max-turns", String(input.maxTurns));
|
|
6769
|
-
this.store.preregisterSpawnedSession(sessionId, project.id,
|
|
6769
|
+
this.store.preregisterSpawnedSession(sessionId, project.id, worktree, input.task);
|
|
6770
6770
|
const proc = Bun.spawn(args, {
|
|
6771
|
-
cwd:
|
|
6771
|
+
cwd: worktree,
|
|
6772
6772
|
env: { ...process.env, SWARM_RUN_ID: id, SWARM_OWNER: input.owner },
|
|
6773
6773
|
stdin: "pipe",
|
|
6774
6774
|
stdout: "pipe",
|
|
6775
6775
|
stderr: logFd
|
|
6776
6776
|
});
|
|
6777
|
-
const
|
|
6777
|
+
const run = {
|
|
6778
6778
|
id,
|
|
6779
6779
|
sessionId,
|
|
6780
6780
|
projectId: project.id,
|
|
6781
6781
|
task: input.task,
|
|
6782
|
-
worktree
|
|
6782
|
+
worktree,
|
|
6783
6783
|
pid: proc.pid,
|
|
6784
6784
|
owner: input.owner,
|
|
6785
6785
|
model: input.model ?? null,
|
|
@@ -6793,20 +6793,20 @@ class Runner {
|
|
|
6793
6793
|
result: null,
|
|
6794
6794
|
pending: []
|
|
6795
6795
|
};
|
|
6796
|
-
this.live.set(id, { run
|
|
6796
|
+
this.live.set(id, { run, proc });
|
|
6797
6797
|
this.store.registerProcess({
|
|
6798
6798
|
pid: proc.pid,
|
|
6799
6799
|
projectId: project.id,
|
|
6800
6800
|
sessionId,
|
|
6801
6801
|
kind: "proc",
|
|
6802
6802
|
name: `run:${input.task}`,
|
|
6803
|
-
cwd:
|
|
6803
|
+
cwd: worktree,
|
|
6804
6804
|
cmd: `claude -p (run ${id})`,
|
|
6805
6805
|
owner: input.owner,
|
|
6806
6806
|
log
|
|
6807
6807
|
});
|
|
6808
6808
|
this.store.append({
|
|
6809
|
-
ts:
|
|
6809
|
+
ts: run.startedAt,
|
|
6810
6810
|
type: "session.started",
|
|
6811
6811
|
projectId: project.id,
|
|
6812
6812
|
sessionId,
|
|
@@ -6814,7 +6814,7 @@ class Runner {
|
|
|
6814
6814
|
});
|
|
6815
6815
|
this.pump(id, proc);
|
|
6816
6816
|
this.send(id, input.prompt);
|
|
6817
|
-
return { ok: true, run
|
|
6817
|
+
return { ok: true, run };
|
|
6818
6818
|
}
|
|
6819
6819
|
async pump(id, proc) {
|
|
6820
6820
|
const entry = this.live.get(id);
|
|
@@ -6873,7 +6873,7 @@ class Runner {
|
|
|
6873
6873
|
}
|
|
6874
6874
|
}
|
|
6875
6875
|
}
|
|
6876
|
-
onLine(
|
|
6876
|
+
onLine(run, line) {
|
|
6877
6877
|
if (!line.startsWith("{"))
|
|
6878
6878
|
return;
|
|
6879
6879
|
let j;
|
|
@@ -6883,41 +6883,41 @@ class Runner {
|
|
|
6883
6883
|
return;
|
|
6884
6884
|
}
|
|
6885
6885
|
if (j.type === "control_request" && j.request?.subtype === "can_use_tool") {
|
|
6886
|
-
this.onPermissionRequest(
|
|
6886
|
+
this.onPermissionRequest(run, j.request_id, j.request.tool_name ?? "", j.request.input ?? {});
|
|
6887
6887
|
return;
|
|
6888
6888
|
}
|
|
6889
6889
|
if (j.type !== "result")
|
|
6890
6890
|
return;
|
|
6891
|
-
|
|
6891
|
+
run.result = {
|
|
6892
6892
|
costUsd: Number(j.total_cost_usd ?? 0),
|
|
6893
6893
|
turns: Number(j.num_turns ?? 0),
|
|
6894
6894
|
isError: Boolean(j.is_error),
|
|
6895
6895
|
at: new Date().toISOString()
|
|
6896
6896
|
};
|
|
6897
6897
|
this.store.append({
|
|
6898
|
-
ts:
|
|
6898
|
+
ts: run.result.at,
|
|
6899
6899
|
type: "run.result",
|
|
6900
|
-
projectId:
|
|
6901
|
-
sessionId:
|
|
6900
|
+
projectId: run.projectId,
|
|
6901
|
+
sessionId: run.sessionId,
|
|
6902
6902
|
payload: {
|
|
6903
|
-
runId:
|
|
6904
|
-
task:
|
|
6905
|
-
...
|
|
6906
|
-
summary: `turn done \xB7 $${
|
|
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" : ""}`
|
|
6907
6907
|
}
|
|
6908
6908
|
});
|
|
6909
6909
|
}
|
|
6910
|
-
onPermissionRequest(
|
|
6911
|
-
const { decision, display } = this.store.evaluateTool(tool, input,
|
|
6910
|
+
onPermissionRequest(run, requestId, tool, input) {
|
|
6911
|
+
const { decision, display } = this.store.evaluateTool(tool, input, run.sessionId, run.worktree, true);
|
|
6912
6912
|
if (decision.action === "deny") {
|
|
6913
|
-
this.answerPermission(
|
|
6913
|
+
this.answerPermission(run.id, requestId, false, `[swarm] ${decision.reason}`);
|
|
6914
6914
|
return;
|
|
6915
6915
|
}
|
|
6916
6916
|
if (decision.action === "allow") {
|
|
6917
|
-
this.answerPermission(
|
|
6917
|
+
this.answerPermission(run.id, requestId, true);
|
|
6918
6918
|
return;
|
|
6919
6919
|
}
|
|
6920
|
-
|
|
6920
|
+
run.pending.push({
|
|
6921
6921
|
requestId,
|
|
6922
6922
|
tool,
|
|
6923
6923
|
input,
|
|
@@ -6928,10 +6928,10 @@ class Runner {
|
|
|
6928
6928
|
this.store.append({
|
|
6929
6929
|
ts: new Date().toISOString(),
|
|
6930
6930
|
type: "permission.requested",
|
|
6931
|
-
projectId:
|
|
6932
|
-
sessionId:
|
|
6931
|
+
projectId: run.projectId,
|
|
6932
|
+
sessionId: run.sessionId,
|
|
6933
6933
|
payload: {
|
|
6934
|
-
runId:
|
|
6934
|
+
runId: run.id,
|
|
6935
6935
|
requestId,
|
|
6936
6936
|
tool,
|
|
6937
6937
|
display: display.slice(0, 300),
|
|
@@ -6991,17 +6991,17 @@ class Runner {
|
|
|
6991
6991
|
return { ok: true };
|
|
6992
6992
|
}
|
|
6993
6993
|
async stop(id) {
|
|
6994
|
-
const
|
|
6995
|
-
if (!
|
|
6994
|
+
const run = this.get(id);
|
|
6995
|
+
if (!run)
|
|
6996
6996
|
return { ok: false, reason: "no live run" };
|
|
6997
|
-
const entry = this.live.get(
|
|
6998
|
-
|
|
6997
|
+
const entry = this.live.get(run.id);
|
|
6998
|
+
run.stopped = true;
|
|
6999
6999
|
try {
|
|
7000
7000
|
const stdin = entry?.proc.stdin;
|
|
7001
7001
|
if (stdin && typeof stdin !== "number")
|
|
7002
7002
|
stdin.end();
|
|
7003
7003
|
} catch {}
|
|
7004
|
-
return this.store.stopProcess(
|
|
7004
|
+
return this.store.stopProcess(run.pid, run.projectId, 5000);
|
|
7005
7005
|
}
|
|
7006
7006
|
async stopAll() {
|
|
7007
7007
|
await Promise.all([...this.live.keys()].map((id) => this.stop(id)));
|
|
@@ -7100,8 +7100,8 @@ class TaskSources {
|
|
|
7100
7100
|
const prev = this.cache.get(projectId);
|
|
7101
7101
|
let entry;
|
|
7102
7102
|
try {
|
|
7103
|
-
const
|
|
7104
|
-
entry = { at: Date.now(), tasks
|
|
7103
|
+
const tasks = kind === "github" ? await this.github(root, opts.labels) : await this.linear(opts.team);
|
|
7104
|
+
entry = { at: Date.now(), tasks, error: null };
|
|
7105
7105
|
} catch (e) {
|
|
7106
7106
|
entry = { at: Date.now(), tasks: prev?.tasks ?? [], error: e.message };
|
|
7107
7107
|
} finally {
|
|
@@ -7138,14 +7138,14 @@ class TaskSources {
|
|
|
7138
7138
|
`)[0] ?? code}`);
|
|
7139
7139
|
return normalizeGithubIssues(JSON.parse(out));
|
|
7140
7140
|
}
|
|
7141
|
-
async linear(
|
|
7141
|
+
async linear(team) {
|
|
7142
7142
|
const key = this.env.LINEAR_API_KEY;
|
|
7143
7143
|
if (!key)
|
|
7144
7144
|
throw new Error("LINEAR_API_KEY not set \u2014 export it in the environment swarmd starts from (never stored)");
|
|
7145
7145
|
const r = await fetch("https://api.linear.app/graphql", {
|
|
7146
7146
|
method: "POST",
|
|
7147
7147
|
headers: { "content-type": "application/json", authorization: key },
|
|
7148
|
-
body: JSON.stringify({ query: linearIssuesQuery(
|
|
7148
|
+
body: JSON.stringify({ query: linearIssuesQuery(team) })
|
|
7149
7149
|
});
|
|
7150
7150
|
if (!r.ok)
|
|
7151
7151
|
throw new Error(`Linear API ${r.status}`);
|
|
@@ -7283,14 +7283,14 @@ class Store {
|
|
|
7283
7283
|
this.db.transaction(() => {
|
|
7284
7284
|
for (const r of rows) {
|
|
7285
7285
|
let payload;
|
|
7286
|
-
let
|
|
7286
|
+
let raw;
|
|
7287
7287
|
try {
|
|
7288
7288
|
payload = JSON.parse(r.payload);
|
|
7289
|
-
|
|
7289
|
+
raw = r.raw ? JSON.parse(r.raw) : undefined;
|
|
7290
7290
|
} catch {
|
|
7291
7291
|
continue;
|
|
7292
7292
|
}
|
|
7293
|
-
const slim = slimForStorage({ payload, raw
|
|
7293
|
+
const slim = slimForStorage({ payload, raw });
|
|
7294
7294
|
upd.run(JSON.stringify(slim.payload ?? null), slim.raw === undefined ? null : JSON.stringify(slim.raw), r.seq);
|
|
7295
7295
|
}
|
|
7296
7296
|
this.setMeta("events_slim", "1");
|
|
@@ -7493,7 +7493,7 @@ class Store {
|
|
|
7493
7493
|
if (!held)
|
|
7494
7494
|
return null;
|
|
7495
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);
|
|
7496
|
-
if (manual.some((
|
|
7496
|
+
if (manual.some((h) => !isAutoHandoff(h)))
|
|
7497
7497
|
return null;
|
|
7498
7498
|
const row = this.db.query("SELECT last_text FROM sessions WHERE id = ?").get(sessionId);
|
|
7499
7499
|
const h = deriveHandoff(held.task, this.sessionEvents(sessionId, 2000), { lastText: row?.last_text ?? null, sessionId });
|
|
@@ -7769,10 +7769,10 @@ class Store {
|
|
|
7769
7769
|
return out.length ? out.join(`
|
|
7770
7770
|
`) : null;
|
|
7771
7771
|
}
|
|
7772
|
-
wfInsert(projectId, task, workflow, steps,
|
|
7772
|
+
wfInsert(projectId, task, workflow, steps, actor) {
|
|
7773
7773
|
const now = new Date().toISOString();
|
|
7774
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)
|
|
7775
|
-
VALUES (?, ?, ?, 0, ?, ?, 'running', ?, ?, ?, ?)`).run(projectId, task, workflow, steps[0] ?? "", JSON.stringify(steps), now, now,
|
|
7775
|
+
VALUES (?, ?, ?, 0, ?, ?, 'running', ?, ?, ?, ?)`).run(projectId, task, workflow, steps[0] ?? "", JSON.stringify(steps), now, now, actor.kind, actor.id);
|
|
7776
7776
|
this.touch();
|
|
7777
7777
|
return Number(r.lastInsertRowid);
|
|
7778
7778
|
}
|
|
@@ -7893,10 +7893,10 @@ class Store {
|
|
|
7893
7893
|
OR (to_kind = 'task' AND project_id = ? AND task IS ?)
|
|
7894
7894
|
OR (to_kind = 'lead' AND project_id = ? AND ? = 'interactive'))
|
|
7895
7895
|
ORDER BY id`).all(sessionId, sessionId, s.project_id, task, s.project_id, s.kind);
|
|
7896
|
-
const
|
|
7897
|
-
if (
|
|
7898
|
-
this.db.query(`UPDATE messages SET delivered_at = ?, session_id = ? WHERE id IN (${
|
|
7899
|
-
return
|
|
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;
|
|
7900
7900
|
}
|
|
7901
7901
|
markMessageDelivered(id, sessionId) {
|
|
7902
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);
|
|
@@ -7991,13 +7991,13 @@ class Store {
|
|
|
7991
7991
|
reason: `gate ${gate} has no command \u2014 add [gates.${gate}] cmd = "\u2026" to .swarm.toml, or record it with swarm gate record`
|
|
7992
7992
|
};
|
|
7993
7993
|
const claim = this.claims(projectId).find((c) => c.task === task && c.state === "held");
|
|
7994
|
-
const
|
|
7995
|
-
if (!
|
|
7994
|
+
const worktree = claim?.worktree;
|
|
7995
|
+
if (!worktree || !existsSync6(worktree))
|
|
7996
7996
|
return {
|
|
7997
7997
|
ok: false,
|
|
7998
7998
|
reason: `${task} has no held worktree to run ${gate} in \u2014 claim it first`
|
|
7999
7999
|
};
|
|
8000
|
-
const cwd = def.cwd ? join8(
|
|
8000
|
+
const cwd = def.cwd ? join8(worktree, def.cwd) : worktree;
|
|
8001
8001
|
if (!existsSync6(cwd))
|
|
8002
8002
|
return { ok: false, reason: `gate cwd ${cwd} does not exist` };
|
|
8003
8003
|
const key = `${projectId}:${task}:${gate}`;
|
|
@@ -8008,7 +8008,7 @@ class Store {
|
|
|
8008
8008
|
mkdirSync4(logDir, { recursive: true });
|
|
8009
8009
|
const log = join8(logDir, `gate-${slug(task)}-${slug(gate)}.log`);
|
|
8010
8010
|
if (def.builtin === "review")
|
|
8011
|
-
return this.runReviewGate(projectId, task, gate, def, { worktree
|
|
8011
|
+
return this.runReviewGate(projectId, task, gate, def, { worktree, cwd, key, log }, opts);
|
|
8012
8012
|
writeFileSync2(log, `$ ${def.cmd}
|
|
8013
8013
|
# cwd ${cwd} \xB7 ${new Date().toISOString()}
|
|
8014
8014
|
`);
|
|
@@ -8022,7 +8022,7 @@ class Store {
|
|
|
8022
8022
|
stderr: fd,
|
|
8023
8023
|
env: {
|
|
8024
8024
|
...process.env,
|
|
8025
|
-
SWARM_WORKTREE:
|
|
8025
|
+
SWARM_WORKTREE: worktree,
|
|
8026
8026
|
SWARM_TASK: task,
|
|
8027
8027
|
SWARM_GATE: gate,
|
|
8028
8028
|
CI: process.env.CI ?? "1"
|
|
@@ -8030,7 +8030,7 @@ class Store {
|
|
|
8030
8030
|
});
|
|
8031
8031
|
} catch (e) {
|
|
8032
8032
|
closeSync(fd);
|
|
8033
|
-
const
|
|
8033
|
+
const run = this.recordGate(projectId, {
|
|
8034
8034
|
...executedGateInput(task, gate, def.cmd, {
|
|
8035
8035
|
exitCode: null,
|
|
8036
8036
|
durationMs: 0,
|
|
@@ -8038,7 +8038,7 @@ class Store {
|
|
|
8038
8038
|
}),
|
|
8039
8039
|
sessionId: opts.sessionId ?? null
|
|
8040
8040
|
});
|
|
8041
|
-
return { ok: true, pid: 0, log, done: Promise.resolve(
|
|
8041
|
+
return { ok: true, pid: 0, log, done: Promise.resolve(run.ok ? run.run : null) };
|
|
8042
8042
|
}
|
|
8043
8043
|
const started = Date.now();
|
|
8044
8044
|
const reg = this.registerProcess({
|
|
@@ -8077,10 +8077,10 @@ class Store {
|
|
|
8077
8077
|
durationMs: Date.now() - started,
|
|
8078
8078
|
output
|
|
8079
8079
|
});
|
|
8080
|
-
const
|
|
8080
|
+
const run = this.recordGate(projectId, { ...input, sessionId: opts.sessionId ?? null });
|
|
8081
8081
|
if (reg.ok)
|
|
8082
8082
|
this.processes(projectId);
|
|
8083
|
-
return
|
|
8083
|
+
return run.ok ? run.run : null;
|
|
8084
8084
|
}).finally(() => {
|
|
8085
8085
|
this.gateJobs.delete(key);
|
|
8086
8086
|
this.touch();
|
|
@@ -8097,15 +8097,15 @@ class Store {
|
|
|
8097
8097
|
return { ok: false, reason: "unknown project" };
|
|
8098
8098
|
const started = Date.now();
|
|
8099
8099
|
const record = (input) => {
|
|
8100
|
-
const
|
|
8101
|
-
return
|
|
8100
|
+
const run = this.recordGate(projectId, { ...input, sessionId: opts.sessionId ?? null });
|
|
8101
|
+
return run.ok ? run.run : null;
|
|
8102
8102
|
};
|
|
8103
8103
|
const done = (async () => {
|
|
8104
8104
|
let diffText = "";
|
|
8105
|
-
let
|
|
8105
|
+
let stat = "";
|
|
8106
8106
|
try {
|
|
8107
8107
|
const diff = await worktreeDiff(p.root, where.worktree);
|
|
8108
|
-
|
|
8108
|
+
stat = diff.files.map((f) => `${f.status ?? "M"} ${f.path} (+${f.added} -${f.deleted})`).join(`
|
|
8109
8109
|
`);
|
|
8110
8110
|
diffText = await worktreePatch(where.worktree, diff.base);
|
|
8111
8111
|
} catch (e) {
|
|
@@ -8127,7 +8127,7 @@ class Store {
|
|
|
8127
8127
|
task,
|
|
8128
8128
|
title: taskRow?.title ?? null,
|
|
8129
8129
|
branch: w?.branch ?? null,
|
|
8130
|
-
stat
|
|
8130
|
+
stat,
|
|
8131
8131
|
patch: diffText
|
|
8132
8132
|
});
|
|
8133
8133
|
writeFileSync2(where.log, `$ claude -p <review prompt, ${prompt.length} chars> --output-format json (read-only)
|
|
@@ -8216,9 +8216,9 @@ ${err}
|
|
|
8216
8216
|
this.gateJobs.set(where.key, done);
|
|
8217
8217
|
return { ok: true, pid: 0, log: where.log, done };
|
|
8218
8218
|
}
|
|
8219
|
-
async runGates(projectId, task,
|
|
8219
|
+
async runGates(projectId, task, gates, opts = {}) {
|
|
8220
8220
|
const cfg = this.gateDefs(projectId);
|
|
8221
|
-
const names =
|
|
8221
|
+
const names = gates?.length ? gates : (cfg?.required ?? []).filter((g) => cfg?.defs[g]);
|
|
8222
8222
|
const key = `${projectId}:${task}`;
|
|
8223
8223
|
const batch = (async () => {
|
|
8224
8224
|
const started = [];
|
|
@@ -8231,9 +8231,9 @@ ${err}
|
|
|
8231
8231
|
continue;
|
|
8232
8232
|
}
|
|
8233
8233
|
started.push(g);
|
|
8234
|
-
const
|
|
8235
|
-
if (
|
|
8236
|
-
runs.push(
|
|
8234
|
+
const run = await r.done;
|
|
8235
|
+
if (run)
|
|
8236
|
+
runs.push(run);
|
|
8237
8237
|
}
|
|
8238
8238
|
return { started, skipped, runs };
|
|
8239
8239
|
})();
|
|
@@ -8287,21 +8287,21 @@ ${err}
|
|
|
8287
8287
|
const sessionId = this.knownSession(input.sessionId);
|
|
8288
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)
|
|
8289
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)));
|
|
8290
|
-
const
|
|
8291
|
-
this.remember(gateDoc(projectId,
|
|
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));
|
|
8292
8292
|
this.append({
|
|
8293
8293
|
ts: createdAt,
|
|
8294
8294
|
type: "gate.recorded",
|
|
8295
8295
|
projectId,
|
|
8296
8296
|
sessionId,
|
|
8297
8297
|
payload: {
|
|
8298
|
-
task:
|
|
8299
|
-
gate:
|
|
8300
|
-
verdict:
|
|
8301
|
-
summary: `gate ${
|
|
8298
|
+
task: run.task,
|
|
8299
|
+
gate: run.gate,
|
|
8300
|
+
verdict: run.verdict,
|
|
8301
|
+
summary: `gate ${run.gate} ${run.verdict} on ${run.task}`
|
|
8302
8302
|
}
|
|
8303
8303
|
});
|
|
8304
|
-
if (
|
|
8304
|
+
if (run.verdict === "fail")
|
|
8305
8305
|
this.append({
|
|
8306
8306
|
ts: createdAt,
|
|
8307
8307
|
type: "incident.opened",
|
|
@@ -8310,12 +8310,12 @@ ${err}
|
|
|
8310
8310
|
payload: {
|
|
8311
8311
|
rule: "gate_failed",
|
|
8312
8312
|
action: "failed",
|
|
8313
|
-
command: `${
|
|
8314
|
-
reason: `${
|
|
8313
|
+
command: `${run.task} \xB7 ${run.gate}`,
|
|
8314
|
+
reason: `${run.rubric}${run.evidence ? ` \u2014 ${run.evidence.slice(0, 200)}` : ""}`
|
|
8315
8315
|
}
|
|
8316
8316
|
});
|
|
8317
8317
|
this.touch();
|
|
8318
|
-
return { ok: true, run
|
|
8318
|
+
return { ok: true, run };
|
|
8319
8319
|
}
|
|
8320
8320
|
taskCache = new Map;
|
|
8321
8321
|
taskSources = new TaskSources;
|
|
@@ -8399,8 +8399,8 @@ ${err}
|
|
|
8399
8399
|
const row = this.db.query("SELECT id FROM projects WHERE root = ?").get(root);
|
|
8400
8400
|
return row?.id ?? null;
|
|
8401
8401
|
}
|
|
8402
|
-
noteRuleChange(key,
|
|
8403
|
-
const sig = JSON.stringify(Object.entries(
|
|
8402
|
+
noteRuleChange(key, rules) {
|
|
8403
|
+
const sig = JSON.stringify(Object.entries(rules).sort());
|
|
8404
8404
|
const metaKey = `rules.sig:${key}`;
|
|
8405
8405
|
const prev = this.db.query("SELECT value FROM meta WHERE key = ?").get(metaKey)?.value;
|
|
8406
8406
|
if (prev === sig)
|
|
@@ -8409,7 +8409,7 @@ ${err}
|
|
|
8409
8409
|
if (prev === undefined)
|
|
8410
8410
|
return;
|
|
8411
8411
|
const before = new Map(JSON.parse(prev));
|
|
8412
|
-
const after = new Map(Object.entries(
|
|
8412
|
+
const after = new Map(Object.entries(rules));
|
|
8413
8413
|
const added = [...after.keys()].filter((r) => !before.has(r)).sort();
|
|
8414
8414
|
const removed = [...before.keys()].filter((r) => !after.has(r)).sort();
|
|
8415
8415
|
const retuned = [...after.entries()].filter(([r, mode]) => before.has(r) && before.get(r) !== mode).map(([r, mode]) => `${r}=${mode}`).sort();
|
|
@@ -8549,11 +8549,11 @@ ${err}
|
|
|
8549
8549
|
state: r.state
|
|
8550
8550
|
}));
|
|
8551
8551
|
}
|
|
8552
|
-
guardHook(
|
|
8553
|
-
const tool = typeof
|
|
8554
|
-
const input =
|
|
8555
|
-
const id = typeof
|
|
8556
|
-
const cwd = typeof
|
|
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 : "";
|
|
8557
8557
|
const isWrite = WRITE_TOOLS.has(tool) && typeof input.file_path === "string";
|
|
8558
8558
|
const cmd = tool === "Bash" ? input.command : undefined;
|
|
8559
8559
|
if (!isWrite && !cmd)
|
|
@@ -8733,9 +8733,9 @@ ${err}
|
|
|
8733
8733
|
if (!explicit) {
|
|
8734
8734
|
const hit = this.cwdProject.get(path);
|
|
8735
8735
|
if (hit && Date.now() - hit.t < 60000) {
|
|
8736
|
-
const
|
|
8737
|
-
if (
|
|
8738
|
-
return
|
|
8736
|
+
const p = this.project(hit.id);
|
|
8737
|
+
if (p)
|
|
8738
|
+
return p;
|
|
8739
8739
|
}
|
|
8740
8740
|
}
|
|
8741
8741
|
const p = this.resolveProjectUncached(path, explicit, name);
|
|
@@ -8829,25 +8829,25 @@ ${err}
|
|
|
8829
8829
|
e = { ...e, payload: redactValue(e.payload, res), raw: redactValue(e.raw, res) };
|
|
8830
8830
|
const slim = slimForStorage(e);
|
|
8831
8831
|
const p = e.payload ?? {};
|
|
8832
|
-
const
|
|
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),
|
|
8834
|
-
const stored = { ...e, actor
|
|
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) };
|
|
8835
8835
|
if (stored.type === "incident.opened")
|
|
8836
8836
|
this.remember(incidentDoc(stored.projectId, stored.seq, stored.payload, stored.ts, stored.sessionId));
|
|
8837
8837
|
this.projectSession(stored);
|
|
8838
8838
|
if (stored.type === "incident.opened") {
|
|
8839
8839
|
const webhook = this.policyFor(null).config.notify.webhook;
|
|
8840
8840
|
if (webhook) {
|
|
8841
|
-
const
|
|
8841
|
+
const p = stored.payload ?? {};
|
|
8842
8842
|
const project = this.project(stored.projectId)?.name ?? stored.projectId;
|
|
8843
8843
|
fetch(webhook, {
|
|
8844
8844
|
method: "POST",
|
|
8845
8845
|
headers: { "content-type": "application/json" },
|
|
8846
8846
|
body: JSON.stringify({
|
|
8847
|
-
text: `Swarm incident \xB7 ${
|
|
8848
|
-
${
|
|
8849
|
-
${
|
|
8850
|
-
rule:
|
|
8847
|
+
text: `Swarm incident \xB7 ${p.rule ?? "?"} \xB7 ${project}
|
|
8848
|
+
${p.command ?? ""}
|
|
8849
|
+
${p.reason ?? ""}`.trim(),
|
|
8850
|
+
rule: p.rule,
|
|
8851
8851
|
project,
|
|
8852
8852
|
sessionId: stored.sessionId,
|
|
8853
8853
|
ts: stored.ts
|
|
@@ -8856,15 +8856,15 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
8856
8856
|
}).catch(() => {});
|
|
8857
8857
|
}
|
|
8858
8858
|
}
|
|
8859
|
-
const
|
|
8860
|
-
if (
|
|
8859
|
+
const team = this.policyFor(null).config.team;
|
|
8860
|
+
if (team.url && team.forward.includes("ledger") && isAuditType(stored.type)) {
|
|
8861
8861
|
this.db.query("INSERT INTO outbox (kind, payload, created_at) VALUES ('event', ?, ?)").run(JSON.stringify({
|
|
8862
8862
|
seq: stored.seq,
|
|
8863
8863
|
ts: stored.ts,
|
|
8864
8864
|
type: stored.type,
|
|
8865
8865
|
projectId: stored.projectId,
|
|
8866
8866
|
sessionId: stored.sessionId,
|
|
8867
|
-
actor
|
|
8867
|
+
actor,
|
|
8868
8868
|
payload: slim.payload ?? null
|
|
8869
8869
|
}), stored.ts);
|
|
8870
8870
|
}
|
|
@@ -8917,9 +8917,9 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
8917
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);
|
|
8918
8918
|
return rows.map((r) => auditRow(wireRowToEvent(r)));
|
|
8919
8919
|
}
|
|
8920
|
-
prune(
|
|
8920
|
+
prune(days) {
|
|
8921
8921
|
const cfg = this.policyFor(null).config;
|
|
8922
|
-
const chatter =
|
|
8922
|
+
const chatter = days ?? cfg.events.retain_days;
|
|
8923
8923
|
const cutoff = new Date(Date.now() - chatter * 86400000).toISOString();
|
|
8924
8924
|
let n = this.db.query(`DELETE FROM events WHERE ts < ? AND type NOT IN (${AUDIT_TYPES_SQL})`).run(cutoff).changes;
|
|
8925
8925
|
if (cfg.audit.retain_days > 0) {
|
|
@@ -8932,12 +8932,12 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
8932
8932
|
this.touch();
|
|
8933
8933
|
return n;
|
|
8934
8934
|
}
|
|
8935
|
-
ingestHook(event,
|
|
8936
|
-
if (typeof
|
|
8937
|
-
this.autoRenewFor(typeof
|
|
8938
|
-
const cwd = typeof
|
|
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();
|
|
8939
8939
|
const project = existsSync6(cwd) ? this.resolveProject(cwd) : null;
|
|
8940
|
-
const e = this.append(normalizeHook(event,
|
|
8940
|
+
const e = this.append(normalizeHook(event, raw, project?.id ?? "p_unknown"));
|
|
8941
8941
|
if ((event === "Stop" || event === "SessionEnd") && e.sessionId) {
|
|
8942
8942
|
if (existsSync6(cwd)) {
|
|
8943
8943
|
this.autoHandoff(e.sessionId, cwd);
|
|
@@ -8945,8 +8945,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
8945
8945
|
}
|
|
8946
8946
|
this.rememberSession(e.sessionId);
|
|
8947
8947
|
}
|
|
8948
|
-
if (e.sessionId && typeof
|
|
8949
|
-
this.db.query("UPDATE sessions SET transcript_path = ? WHERE id = ? AND transcript_path IS NULL").run(
|
|
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);
|
|
8950
8950
|
const last = this.lastTail.get(e.sessionId) ?? 0;
|
|
8951
8951
|
if (Date.now() - last > 2000) {
|
|
8952
8952
|
this.lastTail.set(e.sessionId, Date.now());
|
|
@@ -9487,11 +9487,11 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9487
9487
|
return { ok: false, error: claimRefusalMessage(decision, task) };
|
|
9488
9488
|
}
|
|
9489
9489
|
const branch = `task/${task}`;
|
|
9490
|
-
const
|
|
9491
|
-
if (existsSync6(
|
|
9492
|
-
return { ok: false, error: `${
|
|
9493
|
-
mkdirSync4(dirname3(
|
|
9494
|
-
const created = worktreeAdd(p.root,
|
|
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);
|
|
9495
9495
|
if (!created)
|
|
9496
9496
|
return { ok: false, error: `git worktree add failed for ${task}` };
|
|
9497
9497
|
this.invalidateWorktrees(projectId);
|
|
@@ -9514,13 +9514,13 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9514
9514
|
return { ok: true, task, owner, worktree: created, branch, expiresAt, bootstrap };
|
|
9515
9515
|
}
|
|
9516
9516
|
bootstraps = new Map;
|
|
9517
|
-
bootstrapWorktree(projectId, task, repoRoot,
|
|
9518
|
-
const plan = planBootstrap(loadConfig({ repoRoot, home: this.home }), repoRoot,
|
|
9517
|
+
bootstrapWorktree(projectId, task, repoRoot, worktree) {
|
|
9518
|
+
const plan = planBootstrap(loadConfig({ repoRoot, home: this.home }), repoRoot, worktree);
|
|
9519
9519
|
if (!needsBootstrap(plan))
|
|
9520
9520
|
return null;
|
|
9521
|
-
const job = runBootstrap(plan, { worktree
|
|
9521
|
+
const job = runBootstrap(plan, { worktree, home: this.home, projectId, task });
|
|
9522
9522
|
const done = job.done.then((o) => {
|
|
9523
|
-
this.bootstraps.delete(
|
|
9523
|
+
this.bootstraps.delete(worktree);
|
|
9524
9524
|
const ts = new Date().toISOString();
|
|
9525
9525
|
const ok = !o.setup || o.setup.exitCode === 0;
|
|
9526
9526
|
this.append({
|
|
@@ -9530,7 +9530,7 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9530
9530
|
sessionId: null,
|
|
9531
9531
|
payload: {
|
|
9532
9532
|
task,
|
|
9533
|
-
worktree
|
|
9533
|
+
worktree,
|
|
9534
9534
|
ok,
|
|
9535
9535
|
log: job.log,
|
|
9536
9536
|
...o,
|
|
@@ -9553,11 +9553,11 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9553
9553
|
this.touch();
|
|
9554
9554
|
return o;
|
|
9555
9555
|
});
|
|
9556
|
-
this.bootstraps.set(
|
|
9556
|
+
this.bootstraps.set(worktree, done);
|
|
9557
9557
|
return job.log;
|
|
9558
9558
|
}
|
|
9559
|
-
awaitBootstrap(
|
|
9560
|
-
return this.bootstraps.get(
|
|
9559
|
+
awaitBootstrap(worktree) {
|
|
9560
|
+
return this.bootstraps.get(worktree) ?? Promise.resolve();
|
|
9561
9561
|
}
|
|
9562
9562
|
autoRenewAt = new Map;
|
|
9563
9563
|
autoRenewFor(sessionId, cwd) {
|
|
@@ -9593,8 +9593,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9593
9593
|
expiresAt: r.expires_at
|
|
9594
9594
|
}));
|
|
9595
9595
|
}
|
|
9596
|
-
waiting(projectId,
|
|
9597
|
-
const since = new Date(Date.now() -
|
|
9596
|
+
waiting(projectId, days = 7) {
|
|
9597
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
9598
9598
|
const pArgs = projectId ? [projectId] : [];
|
|
9599
9599
|
const paired = this.db.query(`SELECT type, session_id, project_id, ts,
|
|
9600
9600
|
COALESCE(json_extract(payload,'$.requestId'), json_extract(payload,'$.id')) AS key,
|
|
@@ -9662,8 +9662,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9662
9662
|
}))
|
|
9663
9663
|
};
|
|
9664
9664
|
}
|
|
9665
|
-
gateHealth(projectId,
|
|
9666
|
-
const since = new Date(Date.now() -
|
|
9665
|
+
gateHealth(projectId, days = 30) {
|
|
9666
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
9667
9667
|
const rows = this.db.query(`SELECT project_id, task, gate, verdict, duration_ms, created_at FROM gates
|
|
9668
9668
|
WHERE created_at >= ?${projectId ? " AND project_id = ?" : ""}`).all(...projectId ? [since, projectId] : [since]);
|
|
9669
9669
|
return gateHealth(rows.filter((r) => r.verdict === "pass" || r.verdict === "fail").map((r) => ({
|
|
@@ -9803,8 +9803,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9803
9803
|
this.refreshDisk(trees.map((t) => t.path), diskTtlMs);
|
|
9804
9804
|
return hygieneReport(procs, trees);
|
|
9805
9805
|
}
|
|
9806
|
-
lineage(projectId,
|
|
9807
|
-
const since = new Date(Date.now() -
|
|
9806
|
+
lineage(projectId, days = 14, expanded = []) {
|
|
9807
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
9808
9808
|
const pArgs = projectId ? [projectId] : [];
|
|
9809
9809
|
const rows = this.db.query(`SELECT id, project_id, title, agent, kind, state, parent_id, started_at, ended_at
|
|
9810
9810
|
FROM sessions WHERE last_seen_at >= ?${projectId ? " AND project_id = ?" : ""}`).all(since, ...pArgs);
|
|
@@ -9875,8 +9875,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9875
9875
|
edges.push(...handoffEdges(holds));
|
|
9876
9876
|
return lineageGraph(sessions, edges, { expanded });
|
|
9877
9877
|
}
|
|
9878
|
-
mcpHealth(projectId,
|
|
9879
|
-
const since = new Date(Date.now() -
|
|
9878
|
+
mcpHealth(projectId, days = 7) {
|
|
9879
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
9880
9880
|
const rows = this.db.query(`SELECT session_id, seq, ts, type,
|
|
9881
9881
|
json_extract(payload,'$.tool') AS tool,
|
|
9882
9882
|
json_extract(payload,'$.toolResponse') AS response
|
|
@@ -9916,8 +9916,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9916
9916
|
abandon(sessionId, tool, at);
|
|
9917
9917
|
return mcpHealth(calls);
|
|
9918
9918
|
}
|
|
9919
|
-
context(projectId,
|
|
9920
|
-
const since = new Date(Date.now() -
|
|
9919
|
+
context(projectId, days = 7) {
|
|
9920
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
9921
9921
|
const pArgs = projectId ? [projectId] : [];
|
|
9922
9922
|
const where = projectId ? " AND project_id = ?" : "";
|
|
9923
9923
|
const results = this.db.query(`SELECT session_id, json_extract(payload,'$.tool') AS tool,
|
|
@@ -9964,10 +9964,10 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
9964
9964
|
const projects = projectId ? [projectId] : this.projects().map((p) => p.id);
|
|
9965
9965
|
const out = [];
|
|
9966
9966
|
for (const pid of projects) {
|
|
9967
|
-
const
|
|
9967
|
+
const tasks = [
|
|
9968
9968
|
...new Set(this.claims(pid).map((c) => splitArmTask(c.task)).filter((x) => x.arm).map((x) => x.task))
|
|
9969
9969
|
];
|
|
9970
|
-
for (const t of
|
|
9970
|
+
for (const t of tasks.sort())
|
|
9971
9971
|
out.push({ ...this.abTrial(pid, t), projectId: pid });
|
|
9972
9972
|
}
|
|
9973
9973
|
const rank = { undecided: 0, "all-failed": 1, winner: 2 };
|
|
@@ -10025,8 +10025,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10025
10025
|
const sid = sess?.id ?? c.sessionId;
|
|
10026
10026
|
const g = gateRuns.filter((x) => x.task === c.task);
|
|
10027
10027
|
const latest = new Map;
|
|
10028
|
-
for (const
|
|
10029
|
-
latest.set(
|
|
10028
|
+
for (const run of [...g].sort((a, b) => a.createdAt < b.createdAt ? -1 : 1))
|
|
10029
|
+
latest.set(run.gate, run);
|
|
10030
10030
|
const verdicts = [...latest.values()];
|
|
10031
10031
|
const diff = c.worktree ? this.diffCache.get(c.worktree)?.v : null;
|
|
10032
10032
|
arms.push({
|
|
@@ -10076,8 +10076,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10076
10076
|
})
|
|
10077
10077
|
};
|
|
10078
10078
|
}
|
|
10079
|
-
transitions(projectId,
|
|
10080
|
-
const since = new Date(Date.now() -
|
|
10079
|
+
transitions(projectId, days = 7, minWeight = 1) {
|
|
10080
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
10081
10081
|
const rows = this.db.query(`SELECT session_id, json_extract(payload,'$.tool') AS tool
|
|
10082
10082
|
FROM events
|
|
10083
10083
|
WHERE type = 'tool.requested' AND ts >= ?
|
|
@@ -10085,14 +10085,14 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10085
10085
|
ORDER BY session_id, seq`).all(...projectId ? [since, projectId] : [since]);
|
|
10086
10086
|
return transitionGraph(rows.map((r) => ({ sessionId: r.session_id ?? "", tool: r.tool ?? "" })), { minWeight });
|
|
10087
10087
|
}
|
|
10088
|
-
resourceHolding(projectId,
|
|
10089
|
-
const since = new Date(Date.now() -
|
|
10088
|
+
resourceHolding(projectId, days = 3) {
|
|
10089
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
10090
10090
|
const p = projectId ? " AND c.project_id = ?" : "";
|
|
10091
10091
|
const args = projectId ? [projectId] : [];
|
|
10092
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]));
|
|
10093
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
|
|
10094
10094
|
FROM claims c WHERE c.state = 'held' AND c.released_at IS NULL${p}`).all(...args);
|
|
10095
|
-
const
|
|
10095
|
+
const resources = this.db.query(`SELECT c.name, c.owner, c.project_id, c.expires_at, c.session_id, c.port
|
|
10096
10096
|
FROM resources c WHERE c.released = 0${p}`).all(...args);
|
|
10097
10097
|
const procs = this.db.query(`SELECT c.name, c.owner, c.project_id, c.session_id, c.port
|
|
10098
10098
|
FROM processes c WHERE c.ended_at IS NULL${p}`).all(...args);
|
|
@@ -10109,7 +10109,7 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10109
10109
|
expiresAt: r.expires_at,
|
|
10110
10110
|
projectId: r.project_id
|
|
10111
10111
|
})),
|
|
10112
|
-
...
|
|
10112
|
+
...resources.map((r) => ({
|
|
10113
10113
|
kind: r.port ? "port" : "lease",
|
|
10114
10114
|
name: r.port ? String(r.port) : r.name,
|
|
10115
10115
|
owner: r.owner ?? "unknown",
|
|
@@ -10138,8 +10138,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10138
10138
|
}));
|
|
10139
10139
|
return resourceGraph(held, wanted);
|
|
10140
10140
|
}
|
|
10141
|
-
fileHeat(projectId,
|
|
10142
|
-
const since = new Date(Date.now() -
|
|
10141
|
+
fileHeat(projectId, days = 14) {
|
|
10142
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
10143
10143
|
const rows = this.db.query(`SELECT session_id, json_extract(payload,'$.tool') AS tool,
|
|
10144
10144
|
json_extract(payload,'$.toolInput.file_path') AS path
|
|
10145
10145
|
FROM events
|
|
@@ -10147,8 +10147,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10147
10147
|
AND json_extract(payload,'$.toolInput.file_path') IS NOT NULL${projectId ? " AND project_id = ?" : ""}`).all(...projectId ? [since, projectId] : [since]);
|
|
10148
10148
|
return fileHeat(rows.map((r) => ({ sessionId: r.session_id ?? "", tool: r.tool ?? "", path: r.path ?? "" })));
|
|
10149
10149
|
}
|
|
10150
|
-
security(projectId,
|
|
10151
|
-
const since = new Date(Date.now() -
|
|
10150
|
+
security(projectId, days = 14) {
|
|
10151
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
10152
10152
|
const rows = this.db.query(`SELECT session_id, ts, json_extract(payload,'$.tool') AS tool,
|
|
10153
10153
|
COALESCE(json_extract(payload,'$.toolInput.command'),
|
|
10154
10154
|
json_extract(payload,'$.toolInput.url'), '') AS command,
|
|
@@ -10163,8 +10163,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10163
10163
|
at: r.ts
|
|
10164
10164
|
})));
|
|
10165
10165
|
}
|
|
10166
|
-
ruleEffect(projectId,
|
|
10167
|
-
const since = new Date(Date.now() -
|
|
10166
|
+
ruleEffect(projectId, days = 30) {
|
|
10167
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
10168
10168
|
const rows = this.db.query(`SELECT e.seq, e.ts,
|
|
10169
10169
|
json_extract(e.payload,'$.rule') AS rule,
|
|
10170
10170
|
COALESCE(json_extract(e.payload,'$.command'), '') AS command,
|
|
@@ -10178,7 +10178,7 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10178
10178
|
command: r.command ?? "",
|
|
10179
10179
|
at: r.ts,
|
|
10180
10180
|
acked: Boolean(r.acked)
|
|
10181
|
-
})), changes.map((c) => ({ at: c.ts, added: JSON.parse(c.added) })), Date.now(),
|
|
10181
|
+
})), changes.map((c) => ({ at: c.ts, added: JSON.parse(c.added) })), Date.now(), days);
|
|
10182
10182
|
}
|
|
10183
10183
|
reclaimBuild(path, { dryRun = false } = {}) {
|
|
10184
10184
|
const report = this.hygiene();
|
|
@@ -10244,15 +10244,15 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10244
10244
|
ts: ""
|
|
10245
10245
|
};
|
|
10246
10246
|
});
|
|
10247
|
-
const
|
|
10248
|
-
if (!
|
|
10247
|
+
const stall = detectStall(calls);
|
|
10248
|
+
if (!stall) {
|
|
10249
10249
|
this.stalls.delete(s.id);
|
|
10250
10250
|
continue;
|
|
10251
10251
|
}
|
|
10252
10252
|
flagged++;
|
|
10253
10253
|
const prev = this.stalls.get(s.id);
|
|
10254
|
-
this.stalls.set(s.id,
|
|
10255
|
-
if (prev?.kind ===
|
|
10254
|
+
this.stalls.set(s.id, stall);
|
|
10255
|
+
if (prev?.kind === stall.kind)
|
|
10256
10256
|
continue;
|
|
10257
10257
|
this.append({
|
|
10258
10258
|
ts: new Date().toISOString(),
|
|
@@ -10260,9 +10260,9 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10260
10260
|
projectId: s.project_id,
|
|
10261
10261
|
sessionId: s.id,
|
|
10262
10262
|
payload: {
|
|
10263
|
-
kind:
|
|
10264
|
-
reason:
|
|
10265
|
-
summary: `session looks stuck \u2014 ${
|
|
10263
|
+
kind: stall.kind,
|
|
10264
|
+
reason: stall.reason,
|
|
10265
|
+
summary: `session looks stuck \u2014 ${stall.reason}`
|
|
10266
10266
|
}
|
|
10267
10267
|
});
|
|
10268
10268
|
this.touch();
|
|
@@ -10334,18 +10334,18 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10334
10334
|
const row = this.db.query("SELECT * FROM claims WHERE project_id = ? AND task = ?").get(projectId, task);
|
|
10335
10335
|
if (!row)
|
|
10336
10336
|
return { ok: false, error: `no claim on ${task}` };
|
|
10337
|
-
const
|
|
10338
|
-
if (
|
|
10339
|
-
const work = heldWork(
|
|
10337
|
+
const worktree = row.worktree ?? "";
|
|
10338
|
+
if (worktree && existsSync6(worktree)) {
|
|
10339
|
+
const work = heldWork(worktree);
|
|
10340
10340
|
const can = canRelease(work, force);
|
|
10341
10341
|
if (!can.ok)
|
|
10342
10342
|
return {
|
|
10343
10343
|
ok: false,
|
|
10344
|
-
error: releaseRefusalMessage(can,
|
|
10344
|
+
error: releaseRefusalMessage(can, worktree),
|
|
10345
10345
|
refused: can.reason
|
|
10346
10346
|
};
|
|
10347
|
-
if (p && !worktreeRemove(p.root,
|
|
10348
|
-
return { ok: false, error: `git worktree remove failed for ${
|
|
10347
|
+
if (p && !worktreeRemove(p.root, worktree, force))
|
|
10348
|
+
return { ok: false, error: `git worktree remove failed for ${worktree}` };
|
|
10349
10349
|
this.invalidateWorktrees(projectId);
|
|
10350
10350
|
}
|
|
10351
10351
|
const releasedAt = new Date().toISOString();
|
|
@@ -10454,12 +10454,12 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10454
10454
|
const p = this.project(projectId);
|
|
10455
10455
|
if (!p)
|
|
10456
10456
|
return Promise.resolve([]);
|
|
10457
|
-
const
|
|
10457
|
+
const run = listWorktreesAsync(p.root).then((v) => {
|
|
10458
10458
|
this.wtCache.set(projectId, { v, t: Date.now() });
|
|
10459
10459
|
return v;
|
|
10460
10460
|
}).finally(() => this.wtInflight.delete(projectId));
|
|
10461
|
-
this.wtInflight.set(projectId,
|
|
10462
|
-
return
|
|
10461
|
+
this.wtInflight.set(projectId, run);
|
|
10462
|
+
return run;
|
|
10463
10463
|
}
|
|
10464
10464
|
createWorktree(projectId, name, baseRef = "HEAD", branch) {
|
|
10465
10465
|
const p = this.project(projectId);
|
|
@@ -10571,7 +10571,7 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10571
10571
|
const taskRow = this.tasks(projectId)?.tasks.find((t) => t.id === task) ?? null;
|
|
10572
10572
|
const handoff = this.latestHandoff(projectId, task);
|
|
10573
10573
|
const required = this.requiredGates(projectId);
|
|
10574
|
-
const
|
|
10574
|
+
const gates = required.length ? this.gateStatusFor(this.gateRuns(projectId, task), required).map((g) => ({
|
|
10575
10575
|
gate: g.gate,
|
|
10576
10576
|
verdict: g.verdict
|
|
10577
10577
|
})) : [];
|
|
@@ -10580,19 +10580,19 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10580
10580
|
task,
|
|
10581
10581
|
title: taskRow?.title ?? null,
|
|
10582
10582
|
handoff,
|
|
10583
|
-
gates
|
|
10583
|
+
gates,
|
|
10584
10584
|
files: diff.files,
|
|
10585
10585
|
commits: diff.commits
|
|
10586
10586
|
});
|
|
10587
10587
|
return { ok: true, task, worktree: w, ...d, diff };
|
|
10588
10588
|
}
|
|
10589
|
-
recordPrOpened(projectId, task,
|
|
10589
|
+
recordPrOpened(projectId, task, worktree, url) {
|
|
10590
10590
|
this.append({
|
|
10591
10591
|
ts: new Date().toISOString(),
|
|
10592
10592
|
type: "pr.opened",
|
|
10593
10593
|
projectId,
|
|
10594
10594
|
sessionId: null,
|
|
10595
|
-
payload: { task, worktree
|
|
10595
|
+
payload: { task, worktree, url, summary: `PR opened for ${task}: ${url}` }
|
|
10596
10596
|
});
|
|
10597
10597
|
this.touch();
|
|
10598
10598
|
}
|
|
@@ -10901,10 +10901,10 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10901
10901
|
for (const [k, v] of Object.entries(JSON.parse(r.tc || "{}")))
|
|
10902
10902
|
tools[k] = (tools[k] ?? 0) + v;
|
|
10903
10903
|
}
|
|
10904
|
-
const sessionRow = (
|
|
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,
|
|
10905
10905
|
COUNT(t.id) AS turns, SUM(t.cost_usd) AS cost, SUM(t.output) AS output, s.tool_calls AS toolCalls
|
|
10906
10906
|
FROM sessions s JOIN turns t ON t.session_id = s.id WHERE ${scope} AND s.kind != 'subagent'
|
|
10907
|
-
GROUP BY s.id ORDER BY ${
|
|
10907
|
+
GROUP BY s.id ORDER BY ${order} DESC LIMIT 1`).get(arg);
|
|
10908
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
|
|
10909
10909
|
FROM turns t JOIN sessions s ON s.id = t.session_id WHERE ${scope} ORDER BY t.output DESC LIMIT 1`).get(arg);
|
|
10910
10910
|
const busiestDay = daily.slice().sort((a, b) => (b.cost ?? 0) - (a.cost ?? 0) || b.turns - a.turns)[0] ?? null;
|
|
@@ -11044,8 +11044,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
11044
11044
|
const rows = this.db.query("SELECT * FROM resources WHERE released = 0").all();
|
|
11045
11045
|
const now = Date.now();
|
|
11046
11046
|
let n = 0;
|
|
11047
|
-
for (const
|
|
11048
|
-
if (this.reapIfDead(this.rowToResource(
|
|
11047
|
+
for (const raw of rows)
|
|
11048
|
+
if (this.reapIfDead(this.rowToResource(raw), now))
|
|
11049
11049
|
n++;
|
|
11050
11050
|
return n;
|
|
11051
11051
|
}
|
|
@@ -11186,11 +11186,11 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
11186
11186
|
return { ok: true, process: p };
|
|
11187
11187
|
}
|
|
11188
11188
|
async stopProcess(pid, projectId, graceMs = 3000) {
|
|
11189
|
-
const
|
|
11190
|
-
if (!
|
|
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)
|
|
11191
11191
|
return { ok: false, reason: "not a registered process" };
|
|
11192
|
-
const p = this.rowToProcess(
|
|
11193
|
-
const rowid =
|
|
11192
|
+
const p = this.rowToProcess(raw);
|
|
11193
|
+
const rowid = raw.rowid;
|
|
11194
11194
|
if (!this.processIsOurs(p)) {
|
|
11195
11195
|
this.endProcess(rowid, p, "exited");
|
|
11196
11196
|
return { ok: true, reason: "already gone" };
|
|
@@ -11217,8 +11217,8 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
11217
11217
|
}
|
|
11218
11218
|
acquireResource(input) {
|
|
11219
11219
|
const key = input.projectId ?? "";
|
|
11220
|
-
const
|
|
11221
|
-
let existing =
|
|
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;
|
|
11222
11222
|
if (existing && this.reapIfDead(existing))
|
|
11223
11223
|
existing = null;
|
|
11224
11224
|
const d = canAcquire(existing, { owner: input.owner }, Date.now(), Store.pidAlive);
|
|
@@ -11260,10 +11260,10 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
11260
11260
|
return { ok: true, resource };
|
|
11261
11261
|
}
|
|
11262
11262
|
releaseResource(name, projectId, owner, force = false) {
|
|
11263
|
-
const
|
|
11264
|
-
if (!
|
|
11263
|
+
const raw = this.db.query("SELECT * FROM resources WHERE name = ? AND project_id = ? AND released = 0").get(name, projectId ?? "");
|
|
11264
|
+
if (!raw)
|
|
11265
11265
|
return { ok: false, reason: "not held" };
|
|
11266
|
-
const r = this.rowToResource(
|
|
11266
|
+
const r = this.rowToResource(raw);
|
|
11267
11267
|
if (!force) {
|
|
11268
11268
|
if (!owner)
|
|
11269
11269
|
return { ok: false, reason: `held by ${r.owner}; pass owner or force` };
|
|
@@ -11317,14 +11317,14 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
11317
11317
|
const from = localDayIso(-13);
|
|
11318
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
|
|
11319
11319
|
FROM turns t JOIN sessions s ON s.id = t.session_id WHERE t.ts >= ? GROUP BY pid, day`).all(from);
|
|
11320
|
-
const
|
|
11320
|
+
const days = [];
|
|
11321
11321
|
for (let i = 13;i >= 0; i--)
|
|
11322
|
-
|
|
11322
|
+
days.push(localDayIso(-i).slice(0, 10));
|
|
11323
11323
|
const out = {};
|
|
11324
11324
|
for (const r of rows) {
|
|
11325
11325
|
out[r.pid] ??= new Array(14).fill(0);
|
|
11326
11326
|
const arr = out[r.pid];
|
|
11327
|
-
const i =
|
|
11327
|
+
const i = days.indexOf(r.day);
|
|
11328
11328
|
if (i >= 0)
|
|
11329
11329
|
arr[i] = (arr[i] ?? 0) + (r.usd ?? 0);
|
|
11330
11330
|
}
|
|
@@ -11383,18 +11383,18 @@ function slimForStorage(e) {
|
|
|
11383
11383
|
toolResponse: clip(p.toolResponse, TOOL_RESPONSE_MAX)
|
|
11384
11384
|
};
|
|
11385
11385
|
}
|
|
11386
|
-
let
|
|
11387
|
-
if (
|
|
11388
|
-
const r =
|
|
11386
|
+
let raw = e.raw;
|
|
11387
|
+
if (raw && typeof raw === "object") {
|
|
11388
|
+
const r = raw;
|
|
11389
11389
|
if (RAW_TOOL_KEYS.some((k) => (k in r))) {
|
|
11390
11390
|
const rest = {};
|
|
11391
11391
|
for (const k of Object.keys(r))
|
|
11392
11392
|
if (!RAW_TOOL_KEYS.includes(k))
|
|
11393
11393
|
rest[k] = r[k];
|
|
11394
|
-
|
|
11394
|
+
raw = rest;
|
|
11395
11395
|
}
|
|
11396
11396
|
}
|
|
11397
|
-
return { payload, raw
|
|
11397
|
+
return { payload, raw };
|
|
11398
11398
|
}
|
|
11399
11399
|
function toWire(e) {
|
|
11400
11400
|
const { raw: _raw, ...rest } = e;
|
|
@@ -11507,12 +11507,12 @@ class TeamForwarder {
|
|
|
11507
11507
|
return this.store.clusterKeyFor(projectId);
|
|
11508
11508
|
}
|
|
11509
11509
|
status() {
|
|
11510
|
-
const
|
|
11510
|
+
const team = this.store.policyFor(null).config.team;
|
|
11511
11511
|
const box = this.store.outboxStatus();
|
|
11512
11512
|
return {
|
|
11513
|
-
configured:
|
|
11514
|
-
url:
|
|
11515
|
-
forward:
|
|
11513
|
+
configured: team.url != null,
|
|
11514
|
+
url: team.url,
|
|
11515
|
+
forward: team.forward,
|
|
11516
11516
|
pending: box.pending,
|
|
11517
11517
|
oldest: box.oldest,
|
|
11518
11518
|
lastAckAt: this.store.metaValue("team_last_ack") ?? null,
|
|
@@ -11522,14 +11522,14 @@ class TeamForwarder {
|
|
|
11522
11522
|
};
|
|
11523
11523
|
}
|
|
11524
11524
|
async tick(now = Date.now()) {
|
|
11525
|
-
const
|
|
11526
|
-
if (!
|
|
11525
|
+
const team = this.store.policyFor(null).config.team;
|
|
11526
|
+
if (!team.url)
|
|
11527
11527
|
return 0;
|
|
11528
|
-
if (now - this.lastTry <
|
|
11528
|
+
if (now - this.lastTry < team.interval * 1000 + this.backoffMs)
|
|
11529
11529
|
return 0;
|
|
11530
11530
|
this.lastTry = now;
|
|
11531
11531
|
const records = [];
|
|
11532
|
-
const events =
|
|
11532
|
+
const events = team.forward.includes("ledger") ? this.store.outboxPending() : [];
|
|
11533
11533
|
for (const e of events) {
|
|
11534
11534
|
const body = JSON.parse(e.payload);
|
|
11535
11535
|
if (typeof body.projectId === "string")
|
|
@@ -11537,7 +11537,7 @@ class TeamForwarder {
|
|
|
11537
11537
|
records.push({ seq: e.seq, kind: e.kind, body });
|
|
11538
11538
|
}
|
|
11539
11539
|
let spendRows = 0;
|
|
11540
|
-
if (
|
|
11540
|
+
if (team.forward.includes("cost") && now - this.lastSpend > SPEND_EVERY_MS) {
|
|
11541
11541
|
const day = new Date(now).toISOString().slice(0, 10);
|
|
11542
11542
|
for (const r of this.store.spendRollup(day)) {
|
|
11543
11543
|
records.push({
|
|
@@ -11644,30 +11644,30 @@ class TeamForwarder {
|
|
|
11644
11644
|
});
|
|
11645
11645
|
if (!res.ok)
|
|
11646
11646
|
return;
|
|
11647
|
-
const { policy
|
|
11648
|
-
if (!
|
|
11647
|
+
const { policy } = await res.json();
|
|
11648
|
+
if (!policy)
|
|
11649
11649
|
return;
|
|
11650
11650
|
let pinned = this.store.metaValue("team_policy_pubkey");
|
|
11651
11651
|
if (!pinned) {
|
|
11652
|
-
pinned =
|
|
11652
|
+
pinned = policy.publicKey;
|
|
11653
11653
|
this.store.setMetaValue("team_policy_pubkey", pinned);
|
|
11654
11654
|
}
|
|
11655
|
-
if (!verifyPolicySignature(
|
|
11655
|
+
if (!verifyPolicySignature(policy.toml, policy.signature, pinned)) {
|
|
11656
11656
|
this.store.setMetaValue("team_last_error", "org policy signature invalid \u2014 not installed");
|
|
11657
11657
|
return;
|
|
11658
11658
|
}
|
|
11659
11659
|
const file = join9(this.store.home, "policy.toml");
|
|
11660
11660
|
const prev = this.store.metaValue("team_policy_sig");
|
|
11661
|
-
if (prev ===
|
|
11661
|
+
if (prev === policy.signature)
|
|
11662
11662
|
return;
|
|
11663
|
-
writeFileSync3(file,
|
|
11663
|
+
writeFileSync3(file, policy.toml, { mode: 384 });
|
|
11664
11664
|
writeFileSync3(join9(this.store.home, "policy.sig.json"), JSON.stringify({
|
|
11665
|
-
signature:
|
|
11665
|
+
signature: policy.signature,
|
|
11666
11666
|
publicKey: pinned,
|
|
11667
11667
|
fetchedAt: new Date(now).toISOString(),
|
|
11668
11668
|
url: base
|
|
11669
11669
|
}), { mode: 384 });
|
|
11670
|
-
this.store.setMetaValue("team_policy_sig",
|
|
11670
|
+
this.store.setMetaValue("team_policy_sig", policy.signature);
|
|
11671
11671
|
} catch {}
|
|
11672
11672
|
}
|
|
11673
11673
|
}
|
|
@@ -11678,12 +11678,12 @@ class WorkflowEngine {
|
|
|
11678
11678
|
runner;
|
|
11679
11679
|
forge;
|
|
11680
11680
|
active = new Map;
|
|
11681
|
-
constructor(store, runner,
|
|
11681
|
+
constructor(store, runner, forge) {
|
|
11682
11682
|
this.store = store;
|
|
11683
11683
|
this.runner = runner;
|
|
11684
|
-
this.forge =
|
|
11684
|
+
this.forge = forge;
|
|
11685
11685
|
store.wfSweepOrphans();
|
|
11686
|
-
runner.onEnd((
|
|
11686
|
+
runner.onEnd((run) => void this.onRunEnd(run));
|
|
11687
11687
|
}
|
|
11688
11688
|
start(projectId, task, workflow, opts = {}) {
|
|
11689
11689
|
const def = this.store.config(projectId).workflows[workflow];
|
|
@@ -11761,11 +11761,11 @@ class WorkflowEngine {
|
|
|
11761
11761
|
}
|
|
11762
11762
|
if (s.kind === "gate") {
|
|
11763
11763
|
const r = await this.store.runGates(w.projectId, w.task, [s.gate], { owner: w.owner });
|
|
11764
|
-
const
|
|
11765
|
-
if (!
|
|
11764
|
+
const run = r.runs.find((x) => x.gate === s.gate);
|
|
11765
|
+
if (!run)
|
|
11766
11766
|
return this.fail(w, `gate ${s.gate} did not run: ${r.skipped[0]?.reason ?? "unknown"}`);
|
|
11767
|
-
if (
|
|
11768
|
-
return this.fail(w, `gate ${s.gate} failed \u2014 ${
|
|
11767
|
+
if (run.verdict !== "pass")
|
|
11768
|
+
return this.fail(w, `gate ${s.gate} failed \u2014 ${run.rubric}`);
|
|
11769
11769
|
w.step++;
|
|
11770
11770
|
continue;
|
|
11771
11771
|
}
|
|
@@ -11785,15 +11785,15 @@ class WorkflowEngine {
|
|
|
11785
11785
|
}
|
|
11786
11786
|
this.finish(w, "done", null);
|
|
11787
11787
|
}
|
|
11788
|
-
async onRunEnd(
|
|
11789
|
-
const w = this.active.get(`${
|
|
11790
|
-
if (!w || w.runId !==
|
|
11788
|
+
async onRunEnd(run) {
|
|
11789
|
+
const w = this.active.get(`${run.projectId}:${run.task}`);
|
|
11790
|
+
if (!w || w.runId !== run.id)
|
|
11791
11791
|
return;
|
|
11792
11792
|
w.runId = null;
|
|
11793
|
-
if (
|
|
11793
|
+
if (run.stopped)
|
|
11794
11794
|
return this.finish(w, "stopped", `stopped during ${this.label(w)}`);
|
|
11795
|
-
if (
|
|
11796
|
-
return this.fail(w, `${this.label(w)} exited ${
|
|
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}`);
|
|
11797
11797
|
w.step++;
|
|
11798
11798
|
this.advance(w);
|
|
11799
11799
|
}
|
|
@@ -11863,7 +11863,7 @@ class WorkflowEngine {
|
|
|
11863
11863
|
}
|
|
11864
11864
|
|
|
11865
11865
|
// packages/daemon/src/app.ts
|
|
11866
|
-
var VERSION = "0.13.
|
|
11866
|
+
var VERSION = "0.13.2";
|
|
11867
11867
|
var WEB_DIR = (() => {
|
|
11868
11868
|
if (process.env.SWARM_WEB_DIR)
|
|
11869
11869
|
return process.env.SWARM_WEB_DIR;
|
|
@@ -11881,8 +11881,8 @@ function wireJson(e) {
|
|
|
11881
11881
|
}
|
|
11882
11882
|
return s;
|
|
11883
11883
|
}
|
|
11884
|
-
function hookRepoRoot(store,
|
|
11885
|
-
const cwd = typeof
|
|
11884
|
+
function hookRepoRoot(store, raw) {
|
|
11885
|
+
const cwd = typeof raw.cwd === "string" ? raw.cwd : "";
|
|
11886
11886
|
return cwd && existsSync7(cwd) ? store.resolveProject(cwd)?.root ?? null : null;
|
|
11887
11887
|
}
|
|
11888
11888
|
function claudeSettings() {
|
|
@@ -11917,17 +11917,17 @@ function expandHome(p, home = homedir4()) {
|
|
|
11917
11917
|
return join10(home, p.slice(2));
|
|
11918
11918
|
return p;
|
|
11919
11919
|
}
|
|
11920
|
-
function createApp(store = new Store,
|
|
11920
|
+
function createApp(store = new Store, hooks = {}) {
|
|
11921
11921
|
const app = new Hono2;
|
|
11922
|
-
const
|
|
11922
|
+
const forge = new ForgeService(store);
|
|
11923
11923
|
const runner = new Runner(store, store.home);
|
|
11924
|
-
const dispatcher = new Dispatcher(store, runner,
|
|
11925
|
-
const
|
|
11926
|
-
const
|
|
11924
|
+
const dispatcher = new Dispatcher(store, runner, forge);
|
|
11925
|
+
const workflows = new WorkflowEngine(store, runner, forge);
|
|
11926
|
+
const team = new TeamForwarder(store, VERSION);
|
|
11927
11927
|
store.onBudgetStop((projectId) => {
|
|
11928
11928
|
dispatcher.clear(projectId);
|
|
11929
|
-
for (const
|
|
11930
|
-
runner.stop(
|
|
11929
|
+
for (const run of runner.list(projectId))
|
|
11930
|
+
runner.stop(run.id);
|
|
11931
11931
|
});
|
|
11932
11932
|
app.use("/v1/*", async (c, next) => {
|
|
11933
11933
|
if (c.req.path === "/v1/health")
|
|
@@ -12041,8 +12041,8 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12041
12041
|
const reverted = new Set;
|
|
12042
12042
|
let stale = false;
|
|
12043
12043
|
const scope = store.liveProjects().filter((x) => !project || x.id === project);
|
|
12044
|
-
const
|
|
12045
|
-
for (const o of
|
|
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) {
|
|
12046
12046
|
if (!o.fresh)
|
|
12047
12047
|
stale = true;
|
|
12048
12048
|
for (const m of o.merged)
|
|
@@ -12050,7 +12050,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12050
12050
|
for (const sha of o.reverted)
|
|
12051
12051
|
reverted.add(sha);
|
|
12052
12052
|
}
|
|
12053
|
-
for (const pr of
|
|
12053
|
+
for (const pr of forge.prs())
|
|
12054
12054
|
if (!project || pr.projectId === project)
|
|
12055
12055
|
prs.push({
|
|
12056
12056
|
branch: pr.branch,
|
|
@@ -12125,10 +12125,10 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12125
12125
|
const offset = Math.max(0, Number(c.req.query("offset") ?? 0) || 0);
|
|
12126
12126
|
const { branches, stale } = await outcomesFor(project, { blocking: false });
|
|
12127
12127
|
const projects = store.projects().filter((p) => !project || p.id === project);
|
|
12128
|
-
const
|
|
12128
|
+
const tasks = [];
|
|
12129
12129
|
for (const p of projects)
|
|
12130
12130
|
for (const t of store.tasks(p.id)?.tasks ?? [])
|
|
12131
|
-
|
|
12131
|
+
tasks.push({ id: t.id, title: t.title, status: t.statusText, url: null });
|
|
12132
12132
|
const claims = store.claims(project).map((cl) => ({
|
|
12133
12133
|
task: cl.task,
|
|
12134
12134
|
sessionId: cl.sessionId,
|
|
@@ -12145,7 +12145,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12145
12145
|
branch: s.branch,
|
|
12146
12146
|
costUsd: s.costUsd
|
|
12147
12147
|
}));
|
|
12148
|
-
const full = provenance(
|
|
12148
|
+
const full = provenance(tasks, claims, sessions, branches);
|
|
12149
12149
|
return c.json({
|
|
12150
12150
|
...full,
|
|
12151
12151
|
chains: full.chains.slice(offset, offset + limit),
|
|
@@ -12176,7 +12176,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12176
12176
|
return c.json({ error: e.message }, 500);
|
|
12177
12177
|
}
|
|
12178
12178
|
});
|
|
12179
|
-
app.get("/v1/team", (c) => c.json(
|
|
12179
|
+
app.get("/v1/team", (c) => c.json(team.status()));
|
|
12180
12180
|
app.post("/v1/team/credentials", async (c) => {
|
|
12181
12181
|
const b = await c.req.json().catch(() => ({}));
|
|
12182
12182
|
if (typeof b.token !== "string" || !b.token)
|
|
@@ -12191,8 +12191,8 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12191
12191
|
const p = id ? store.project(id) : null;
|
|
12192
12192
|
if (id && !p)
|
|
12193
12193
|
return c.json({ error: "unknown project" }, 404);
|
|
12194
|
-
const { provenance
|
|
12195
|
-
return c.json({ ...
|
|
12194
|
+
const { provenance, overridden, policy } = store.policyFor(p?.root ?? null);
|
|
12195
|
+
return c.json({ ...policy, provenance, overridden });
|
|
12196
12196
|
});
|
|
12197
12197
|
app.get("/v1/audit", (c) => {
|
|
12198
12198
|
const since = sinceToIso(c.req.query("since"));
|
|
@@ -12212,9 +12212,9 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12212
12212
|
return c.body(formatAudit(rows, format), 200, { "content-type": ct });
|
|
12213
12213
|
});
|
|
12214
12214
|
app.post("/v1/daemon/restart", (c) => {
|
|
12215
|
-
if (!
|
|
12215
|
+
if (!hooks.restart)
|
|
12216
12216
|
return c.json({ error: "not restartable in this environment" }, 501);
|
|
12217
|
-
setTimeout(() =>
|
|
12217
|
+
setTimeout(() => hooks.restart?.(), 50);
|
|
12218
12218
|
return c.json({ ok: true, restarting: true });
|
|
12219
12219
|
});
|
|
12220
12220
|
app.get("/v1/rules/dryrun", (c) => {
|
|
@@ -12246,12 +12246,12 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12246
12246
|
const r = store.acquireResource({ ...b, name: b.name, owner: b.owner });
|
|
12247
12247
|
return r.ok ? c.json(r, 201) : c.json({ ok: false, error: r.reason }, 409);
|
|
12248
12248
|
});
|
|
12249
|
-
app.get("/v1/prs", (c) => c.json(
|
|
12249
|
+
app.get("/v1/prs", (c) => c.json(forge.prs()));
|
|
12250
12250
|
app.post("/v1/prs/merge", async (c) => {
|
|
12251
12251
|
const b = await c.req.json().catch(() => ({}));
|
|
12252
12252
|
if (!b.projectId || !b.number)
|
|
12253
12253
|
return c.json({ error: "projectId and number are required" }, 400);
|
|
12254
|
-
const r = await
|
|
12254
|
+
const r = await forge.merge(b.projectId, b.number);
|
|
12255
12255
|
return r.ok ? c.json(r) : c.json({ error: r.output || "merge failed" }, 409);
|
|
12256
12256
|
});
|
|
12257
12257
|
app.delete("/v1/resources/:name", (c) => {
|
|
@@ -12341,9 +12341,9 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12341
12341
|
const b = await c.req.json().catch(() => ({}));
|
|
12342
12342
|
const r = store.answer(Number(c.req.param("id")), b.text, b.by ?? null);
|
|
12343
12343
|
if (r.ok && r.question.sessionId) {
|
|
12344
|
-
const
|
|
12345
|
-
if (
|
|
12346
|
-
const sent = runner.send(
|
|
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}`);
|
|
12347
12347
|
if (sent.ok)
|
|
12348
12348
|
store.inbox(r.question.sessionId);
|
|
12349
12349
|
}
|
|
@@ -12355,13 +12355,13 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12355
12355
|
const project = c.req.query("project");
|
|
12356
12356
|
if (!project)
|
|
12357
12357
|
return c.json({ error: "project required" }, 400);
|
|
12358
|
-
return c.json({ defs: store.config(project).workflows, runs:
|
|
12358
|
+
return c.json({ defs: store.config(project).workflows, runs: workflows.status(project) });
|
|
12359
12359
|
});
|
|
12360
12360
|
app.post("/v1/workflows", async (c) => {
|
|
12361
12361
|
const b = await c.req.json().catch(() => ({}));
|
|
12362
12362
|
if (!b.projectId || !b.task || !b.workflow)
|
|
12363
12363
|
return c.json({ ok: false, error: "projectId, task, workflow required" }, 400);
|
|
12364
|
-
const r =
|
|
12364
|
+
const r = workflows.start(b.projectId, b.task, b.workflow, {
|
|
12365
12365
|
...b.owner ? { owner: b.owner } : {},
|
|
12366
12366
|
sessionId: b.sessionId ?? null
|
|
12367
12367
|
});
|
|
@@ -12371,7 +12371,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12371
12371
|
const b = await c.req.json().catch(() => ({}));
|
|
12372
12372
|
if (!b.projectId || !b.task)
|
|
12373
12373
|
return c.json({ ok: false, error: "projectId and task required" }, 400);
|
|
12374
|
-
const r =
|
|
12374
|
+
const r = workflows.stop(b.projectId, b.task);
|
|
12375
12375
|
return c.json(r, r.ok ? 200 : 404);
|
|
12376
12376
|
});
|
|
12377
12377
|
app.get("/v1/timeline", (c) => c.json(store.timelineDetail(Number(c.req.query("hours")) || 12, c.req.query("project") || null)));
|
|
@@ -12395,11 +12395,11 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12395
12395
|
if (!r.ok)
|
|
12396
12396
|
return c.json(r, 400);
|
|
12397
12397
|
const m = r.message;
|
|
12398
|
-
const
|
|
12399
|
-
if (
|
|
12400
|
-
const sent = runner.send(
|
|
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}`);
|
|
12401
12401
|
if (sent.ok)
|
|
12402
|
-
store.markMessageDelivered(m.id,
|
|
12402
|
+
store.markMessageDelivered(m.id, run.sessionId);
|
|
12403
12403
|
}
|
|
12404
12404
|
return c.json({ ok: true, message: store.message(m.id) }, 201);
|
|
12405
12405
|
});
|
|
@@ -12607,7 +12607,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12607
12607
|
const d = await store.prDraftFor(b.projectId, ref);
|
|
12608
12608
|
if (!d.ok)
|
|
12609
12609
|
return c.json(d, 404);
|
|
12610
|
-
const r = await
|
|
12610
|
+
const r = await forge.openPR(b.projectId, d.worktree, {
|
|
12611
12611
|
title: b.title?.trim() || d.title,
|
|
12612
12612
|
body: b.body ?? d.body,
|
|
12613
12613
|
isDraft: b.draft ?? false
|
|
@@ -12678,21 +12678,21 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12678
12678
|
app.post("/v1/sessions/:id/tail", (c) => c.json({ turns: store.tailSession(c.req.param("id")) }));
|
|
12679
12679
|
app.post("/v1/hook/:event", async (c) => {
|
|
12680
12680
|
const event = c.req.param("event");
|
|
12681
|
-
const
|
|
12682
|
-
store.ingestHook(event,
|
|
12683
|
-
if (event === "SessionStart" && typeof
|
|
12684
|
-
store.checkPolicy(
|
|
12685
|
-
const ctx = store.sessionContext(
|
|
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);
|
|
12686
12686
|
if (ctx)
|
|
12687
12687
|
return c.json({
|
|
12688
12688
|
additionalContext: ctx,
|
|
12689
12689
|
hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: ctx }
|
|
12690
12690
|
});
|
|
12691
12691
|
}
|
|
12692
|
-
const sid = typeof
|
|
12692
|
+
const sid = typeof raw.session_id === "string" ? raw.session_id : null;
|
|
12693
12693
|
const answers = event === "UserPromptSubmit" || event === "PreToolUse" || event === "PostToolUse" ? store.answerContext(sid) : null;
|
|
12694
|
-
if (event === "PreToolUse" && !store.guardDisabled(hookRepoRoot(store,
|
|
12695
|
-
const guard = store.guardHook(
|
|
12694
|
+
if (event === "PreToolUse" && !store.guardDisabled(hookRepoRoot(store, raw))) {
|
|
12695
|
+
const guard = store.guardHook(raw);
|
|
12696
12696
|
if (guard) {
|
|
12697
12697
|
return c.json({
|
|
12698
12698
|
hookSpecificOutput: {
|
|
@@ -12717,22 +12717,22 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12717
12717
|
});
|
|
12718
12718
|
app.get("/v1/events", (c) => {
|
|
12719
12719
|
const full = c.req.query("full") === "1";
|
|
12720
|
-
const
|
|
12721
|
-
const since =
|
|
12722
|
-
return streamSSE(c, async (
|
|
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) => {
|
|
12723
12723
|
for (const e of store.since(since, 5000, full)) {
|
|
12724
|
-
await
|
|
12724
|
+
await stream.writeSSE({ id: String(e.seq), event: e.type, data: JSON.stringify(e) });
|
|
12725
12725
|
}
|
|
12726
|
-
await
|
|
12727
|
-
await new Promise((
|
|
12726
|
+
await stream.writeSSE({ event: "ping", data: "" });
|
|
12727
|
+
await new Promise((resolve) => {
|
|
12728
12728
|
const off = store.subscribe((e) => {
|
|
12729
|
-
|
|
12729
|
+
stream.writeSSE({ id: String(e.seq), event: e.type, data: wireJson(e) });
|
|
12730
12730
|
});
|
|
12731
|
-
const beat = setInterval(() => void
|
|
12732
|
-
|
|
12731
|
+
const beat = setInterval(() => void stream.writeSSE({ event: "ping", data: "" }), 15000);
|
|
12732
|
+
stream.onAbort(() => {
|
|
12733
12733
|
clearInterval(beat);
|
|
12734
12734
|
off();
|
|
12735
|
-
|
|
12735
|
+
resolve();
|
|
12736
12736
|
});
|
|
12737
12737
|
});
|
|
12738
12738
|
});
|
|
@@ -12763,7 +12763,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12763
12763
|
etag
|
|
12764
12764
|
});
|
|
12765
12765
|
});
|
|
12766
|
-
return { app, store, forge
|
|
12766
|
+
return { app, store, forge, runner, dispatcher, workflows, team };
|
|
12767
12767
|
}
|
|
12768
12768
|
|
|
12769
12769
|
// packages/daemon/src/demo.ts
|
|
@@ -12800,8 +12800,8 @@ function seedDemo(store) {
|
|
|
12800
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));
|
|
12801
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)
|
|
12802
12802
|
VALUES ('p_demo1', 'webhooks', 'alice', '/work/acme-app-wt/webhooks', 'task/webhooks', ?, ?, NULL, 'orphaned', 'human', 'alice')`).run(iso(26 * H), iso(20 * H));
|
|
12803
|
-
const gate = (task,
|
|
12804
|
-
VALUES ('p_demo1', ?, ?, ?, ?, NULL, ?, ?, 'daemon', 'daemon')`).run(task,
|
|
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));
|
|
12805
12805
|
gate("checkout", "tests", "fail", "ran `bun test` \u2014 exit 1 in 41s", 3 * H, "demo-s1");
|
|
12806
12806
|
gate("checkout", "tests", "pass", "ran `bun test` \u2014 exit 0 in 39s", 1 * H, "demo-s1");
|
|
12807
12807
|
gate("checkout", "review", "pass", "review: no blocker/major findings", 40 * 60000, null);
|