@ra3orblade/swarm 0.12.1 → 0.13.1
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-hook.js +9 -5
- package/dist/swarmd.js +127 -59
- package/package.json +1 -1
- package/web/dashboard.css +1 -0
- package/web/dashboard.js +28 -0
- package/web/favicon.ico +0 -0
- package/web/favicon.svg +1 -0
- package/web/icons.js +1 -0
- package/web/index.html +7 -763
- package/web/release-notes.js +1 -1
- package/web/table.js +4 -1
- package/web/viz.js +4 -1
- package/web/app.js +0 -3508
package/dist/swarm-hook.js
CHANGED
|
@@ -196,22 +196,26 @@ function otherLiveInSameTree(current, sessions, now, withinMs = LIVE_WINDOW_MS)
|
|
|
196
196
|
}
|
|
197
197
|
return null;
|
|
198
198
|
}
|
|
199
|
+
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+)?))*";
|
|
200
|
+
function gitVerb(verb, tail) {
|
|
201
|
+
return new RegExp(`\\bgit${GIT_GLOBAL_OPTS}\\s+${verb}${tail}`);
|
|
202
|
+
}
|
|
199
203
|
function isBroadStage(cmd) {
|
|
200
204
|
const c = cmd.trim();
|
|
201
|
-
if (
|
|
205
|
+
if (gitVerb("add", "\\s+(-A\\b|--all\\b|\\.(\\s|$))").test(c))
|
|
202
206
|
return true;
|
|
203
|
-
if (
|
|
207
|
+
if (gitVerb("commit", "\\b[^|&;]*\\s-[a-zA-Z]*a").test(c))
|
|
204
208
|
return true;
|
|
205
|
-
if (
|
|
209
|
+
if (gitVerb("add", "\\s*$").test(c))
|
|
206
210
|
return true;
|
|
207
211
|
return false;
|
|
208
212
|
}
|
|
209
213
|
function isDestructiveGit(cmd) {
|
|
210
214
|
const c = cmd.trim();
|
|
211
|
-
return
|
|
215
|
+
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);
|
|
212
216
|
}
|
|
213
217
|
function isPatternKill(cmd) {
|
|
214
|
-
return /\bpkill\s
|
|
218
|
+
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);
|
|
215
219
|
}
|
|
216
220
|
function killedPorts(cmd) {
|
|
217
221
|
const ports = new Set;
|
package/dist/swarmd.js
CHANGED
|
@@ -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 (
|
|
1687
|
+
if (gitVerb("add", "\\s+(-A\\b|--all\\b|\\.(\\s|$))").test(c))
|
|
1684
1688
|
return true;
|
|
1685
|
-
if (
|
|
1689
|
+
if (gitVerb("commit", "\\b[^|&;]*\\s-[a-zA-Z]*a").test(c))
|
|
1686
1690
|
return true;
|
|
1687
|
-
if (
|
|
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
|
|
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
|
|
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;
|
|
@@ -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.
|
|
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
|
|
6250
|
-
|
|
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 run2 = this.fetchMerged(projectId, root).finally(() => this.outcomeInflight.delete(projectId));
|
|
6265
|
+
this.outcomeInflight.set(projectId, run2);
|
|
6266
|
+
return run2;
|
|
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 =
|
|
6297
|
-
|
|
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
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
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
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
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 = [];
|
|
@@ -6352,7 +6368,7 @@ class ForgeService {
|
|
|
6352
6368
|
ok: false,
|
|
6353
6369
|
error: `${worktree2.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";
|
|
@@ -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"];
|
|
@@ -7062,6 +7078,8 @@ function runBootstrap(plan, opts) {
|
|
|
7062
7078
|
}
|
|
7063
7079
|
|
|
7064
7080
|
// packages/daemon/src/task-sources.ts
|
|
7081
|
+
var PENDING = Object.freeze({ at: 0, tasks: [], error: null });
|
|
7082
|
+
|
|
7065
7083
|
class TaskSources {
|
|
7066
7084
|
env;
|
|
7067
7085
|
cache = new Map;
|
|
@@ -7073,11 +7091,11 @@ class TaskSources {
|
|
|
7073
7091
|
const hit = this.cache.get(projectId);
|
|
7074
7092
|
if (!hit || Date.now() - hit.at >= ttlMs)
|
|
7075
7093
|
this.refresh(projectId, kind, root, opts);
|
|
7076
|
-
return hit ??
|
|
7094
|
+
return hit ?? PENDING;
|
|
7077
7095
|
}
|
|
7078
7096
|
async refresh(projectId, kind, root, opts) {
|
|
7079
7097
|
if (this.inflight.has(projectId))
|
|
7080
|
-
return this.cache.get(projectId) ??
|
|
7098
|
+
return this.cache.get(projectId) ?? PENDING;
|
|
7081
7099
|
this.inflight.add(projectId);
|
|
7082
7100
|
const prev = this.cache.get(projectId);
|
|
7083
7101
|
let entry;
|
|
@@ -8311,6 +8329,7 @@ ${err}
|
|
|
8311
8329
|
return null;
|
|
8312
8330
|
let hit;
|
|
8313
8331
|
let error = null;
|
|
8332
|
+
let loading = false;
|
|
8314
8333
|
const kind = taskSourceKind(source);
|
|
8315
8334
|
if (kind === "github" || kind === "linear") {
|
|
8316
8335
|
const e = this.taskSources.get(projectId, kind, p.root, {
|
|
@@ -8319,10 +8338,17 @@ ${err}
|
|
|
8319
8338
|
});
|
|
8320
8339
|
hit = { tasks: e.tasks };
|
|
8321
8340
|
error = e.error;
|
|
8341
|
+
loading = e.at === 0 && e.error === null;
|
|
8322
8342
|
} else {
|
|
8323
8343
|
const path = join8(p.root, source);
|
|
8324
8344
|
if (!existsSync6(path))
|
|
8325
|
-
return {
|
|
8345
|
+
return {
|
|
8346
|
+
source,
|
|
8347
|
+
required: this.requiredGates(projectId),
|
|
8348
|
+
tasks: [],
|
|
8349
|
+
error: `${source} not found in this repository`,
|
|
8350
|
+
loading: false
|
|
8351
|
+
};
|
|
8326
8352
|
const mtime = statSync(path).mtimeMs;
|
|
8327
8353
|
let md = this.taskCache.get(projectId);
|
|
8328
8354
|
if (!md || md.path !== path || md.mtime !== mtime) {
|
|
@@ -8351,7 +8377,7 @@ ${err}
|
|
|
8351
8377
|
gated: gatesSatisfied(tr, required)
|
|
8352
8378
|
};
|
|
8353
8379
|
});
|
|
8354
|
-
return { source, required, tasks: board, error };
|
|
8380
|
+
return { source, required, tasks: board, error, loading };
|
|
8355
8381
|
}
|
|
8356
8382
|
rulesFor(repoRoot) {
|
|
8357
8383
|
return this.policyFor(repoRoot).config.rules;
|
|
@@ -8672,6 +8698,9 @@ ${err}
|
|
|
8672
8698
|
createdAt: r.created_at
|
|
8673
8699
|
}));
|
|
8674
8700
|
}
|
|
8701
|
+
liveProjects() {
|
|
8702
|
+
return this.projects().filter((p) => !(p.discovered && isScratchRoot(p.root)));
|
|
8703
|
+
}
|
|
8675
8704
|
project(id) {
|
|
8676
8705
|
const r = this.db.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
8677
8706
|
if (!r)
|
|
@@ -10196,6 +10225,7 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10196
10225
|
checkStalls() {
|
|
10197
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());
|
|
10198
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]));
|
|
10199
10229
|
for (const id of [...this.stalls.keys()])
|
|
10200
10230
|
if (!liveIds.has(id))
|
|
10201
10231
|
this.stalls.delete(id);
|
|
@@ -10237,6 +10267,9 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10237
10267
|
});
|
|
10238
10268
|
this.touch();
|
|
10239
10269
|
}
|
|
10270
|
+
const after = JSON.stringify([...this.stalls].map(([id, v]) => [id, v.kind, v.reason]));
|
|
10271
|
+
if (after !== before)
|
|
10272
|
+
this.touch();
|
|
10240
10273
|
return flagged;
|
|
10241
10274
|
}
|
|
10242
10275
|
sweepOrphans() {
|
|
@@ -10570,22 +10603,31 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
10570
10603
|
this.wtCache.clear();
|
|
10571
10604
|
}
|
|
10572
10605
|
async refreshAllWorktrees() {
|
|
10573
|
-
await Promise.all(this.
|
|
10606
|
+
await Promise.all(this.liveProjects().map((p) => this.refreshWorktrees(p.id)));
|
|
10574
10607
|
}
|
|
10575
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(",");
|
|
10576
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,
|
|
10577
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,
|
|
10578
10619
|
SUM(t.cost_usd) AS cost_usd, MAX(t.cost_usd IS NULL AND t.id IS NOT NULL) AS unpriced,
|
|
10579
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,
|
|
10580
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
|
|
10581
10622
|
FROM sessions s LEFT JOIN turns t ON t.session_id = s.id
|
|
10582
|
-
|
|
10623
|
+
WHERE s.id IN (${holes})
|
|
10624
|
+
GROUP BY s.id ORDER BY s.last_seen_at DESC`).all(...ids);
|
|
10583
10625
|
const idleBefore = Date.now() - IDLE_MS;
|
|
10584
10626
|
const sparkRows = this.db.query(`SELECT session_id, output, cost_usd FROM (
|
|
10585
10627
|
SELECT t.session_id, t.output, t.cost_usd, t.ts,
|
|
10586
10628
|
ROW_NUMBER() OVER (PARTITION BY t.session_id ORDER BY t.ts DESC) AS rn
|
|
10587
|
-
FROM turns t WHERE t.agent_id IS NULL AND t.sidechain = 0
|
|
10588
|
-
) 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);
|
|
10589
10631
|
const sparks = new Map;
|
|
10590
10632
|
for (const x of sparkRows) {
|
|
10591
10633
|
const a = sparks.get(x.session_id) ?? [];
|
|
@@ -11290,13 +11332,13 @@ ${p2.reason ?? ""}`.trim(),
|
|
|
11290
11332
|
}
|
|
11291
11333
|
snapshot() {
|
|
11292
11334
|
const worktrees = {};
|
|
11293
|
-
const projects = this.
|
|
11335
|
+
const projects = this.liveProjects();
|
|
11294
11336
|
for (const p of projects)
|
|
11295
11337
|
worktrees[p.id] = this.worktrees(p.id);
|
|
11296
11338
|
return {
|
|
11297
11339
|
projects,
|
|
11298
11340
|
worktrees,
|
|
11299
|
-
sessions: this.
|
|
11341
|
+
sessions: this.sessions(),
|
|
11300
11342
|
spend: this.memoised("spend", 30000, () => this.spend()),
|
|
11301
11343
|
spendSparks: this.memoised("spendSparks", 60000, () => this.spendSparks()),
|
|
11302
11344
|
claims: this.claims(),
|
|
@@ -11821,7 +11863,7 @@ class WorkflowEngine {
|
|
|
11821
11863
|
}
|
|
11822
11864
|
|
|
11823
11865
|
// packages/daemon/src/app.ts
|
|
11824
|
-
var VERSION = "0.
|
|
11866
|
+
var VERSION = "0.13.1";
|
|
11825
11867
|
var WEB_DIR = (() => {
|
|
11826
11868
|
if (process.env.SWARM_WEB_DIR)
|
|
11827
11869
|
return process.env.SWARM_WEB_DIR;
|
|
@@ -11868,6 +11910,13 @@ function diskVersion() {
|
|
|
11868
11910
|
return null;
|
|
11869
11911
|
}
|
|
11870
11912
|
}
|
|
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
|
+
}
|
|
11871
11920
|
function createApp(store = new Store, hooks2 = {}) {
|
|
11872
11921
|
const app = new Hono2;
|
|
11873
11922
|
const forge2 = new ForgeService(store);
|
|
@@ -11903,13 +11952,13 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
11903
11952
|
schema: store.schemaVersion(),
|
|
11904
11953
|
auth: store.policyFor(null).config.daemon.auth
|
|
11905
11954
|
}));
|
|
11906
|
-
app.get("/v1/projects", (c) => c.json(store.
|
|
11955
|
+
app.get("/v1/projects", (c) => c.json(store.liveProjects()));
|
|
11907
11956
|
app.post("/v1/projects", async (c) => {
|
|
11908
11957
|
const { path, name } = await c.req.json();
|
|
11909
11958
|
if (!path)
|
|
11910
11959
|
return c.json({ error: "path required" }, 400);
|
|
11911
11960
|
try {
|
|
11912
|
-
return c.json(store.resolveProject(path, true, name), 201);
|
|
11961
|
+
return c.json(store.resolveProject(expandHome(path), true, name), 201);
|
|
11913
11962
|
} catch (e) {
|
|
11914
11963
|
return c.json({ error: e.message }, 400);
|
|
11915
11964
|
}
|
|
@@ -11929,10 +11978,12 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
11929
11978
|
});
|
|
11930
11979
|
app.delete("/v1/projects/:id", (c) => store.removeProject(c.req.param("id")) ? c.body(null, 204) : c.json({ error: "not found" }, 404));
|
|
11931
11980
|
app.get("/v1/fs/ls", (c) => {
|
|
11932
|
-
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);
|
|
11933
11984
|
let dir;
|
|
11934
11985
|
try {
|
|
11935
|
-
dir = realpathSync3(q
|
|
11986
|
+
dir = realpathSync3(q || homedir4());
|
|
11936
11987
|
} catch {
|
|
11937
11988
|
dir = homedir4();
|
|
11938
11989
|
}
|
|
@@ -11944,7 +11995,14 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
11944
11995
|
return c.json({ error: e.message, path: dir }, 400);
|
|
11945
11996
|
}
|
|
11946
11997
|
});
|
|
11947
|
-
app.get("/v1/state", (c) =>
|
|
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
|
+
});
|
|
11948
12006
|
app.get("/v1/stats", (c) => c.json(store.stats(c.req.query("project") || undefined)));
|
|
11949
12007
|
app.get("/v1/graphs/collisions", (c) => c.json(store.collisions(c.req.query("project") || undefined)));
|
|
11950
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") ?? [])));
|
|
@@ -11971,7 +12029,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
11971
12029
|
})));
|
|
11972
12030
|
const outcomesFor = async (project, opts = {}) => {
|
|
11973
12031
|
const blocking = opts.blocking !== false;
|
|
11974
|
-
const sessions = store.
|
|
12032
|
+
const sessions = store.sessions().filter((s) => !project || s.projectId === project).map((s) => ({
|
|
11975
12033
|
id: s.id,
|
|
11976
12034
|
branch: s.branch,
|
|
11977
12035
|
model: s.model,
|
|
@@ -11982,8 +12040,9 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
11982
12040
|
const prs = [];
|
|
11983
12041
|
const reverted = new Set;
|
|
11984
12042
|
let stale = false;
|
|
11985
|
-
|
|
11986
|
-
|
|
12043
|
+
const scope = store.liveProjects().filter((x) => !project || x.id === project);
|
|
12044
|
+
const outcomes2 = await Promise.all(scope.map(async (p) => blocking ? { ...await forge2.merged(p.id, p.root), fresh: true } : forge2.mergedCached(p.id, p.root)));
|
|
12045
|
+
for (const o of outcomes2) {
|
|
11987
12046
|
if (!o.fresh)
|
|
11988
12047
|
stale = true;
|
|
11989
12048
|
for (const m of o.merged)
|
|
@@ -12079,7 +12138,7 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12079
12138
|
acquiredAt: cl.acquiredAt,
|
|
12080
12139
|
state: cl.state
|
|
12081
12140
|
}));
|
|
12082
|
-
const sessions = store.
|
|
12141
|
+
const sessions = store.sessions().filter((s) => !project || s.projectId === project).map((s) => ({
|
|
12083
12142
|
id: s.id,
|
|
12084
12143
|
title: s.title,
|
|
12085
12144
|
agent: s.agent,
|
|
@@ -12679,8 +12738,16 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12679
12738
|
});
|
|
12680
12739
|
});
|
|
12681
12740
|
app.get("/", (c) => c.html(readFileSync4(join10(WEB_DIR, "index.html"), "utf8")));
|
|
12682
|
-
|
|
12683
|
-
|
|
12741
|
+
app.get("/next", (c) => c.redirect("/", 308));
|
|
12742
|
+
const MIME = {
|
|
12743
|
+
js: "text/javascript",
|
|
12744
|
+
css: "text/css",
|
|
12745
|
+
svg: "image/svg+xml",
|
|
12746
|
+
ico: "image/x-icon",
|
|
12747
|
+
png: "image/png"
|
|
12748
|
+
};
|
|
12749
|
+
const BINARY = new Set(["ico", "png"]);
|
|
12750
|
+
app.get("/:file{[a-z0-9-]+\\.(js|css|svg|ico|png)}", (c) => {
|
|
12684
12751
|
const f = c.req.param("file");
|
|
12685
12752
|
const p = join10(WEB_DIR, f);
|
|
12686
12753
|
if (!existsSync7(p))
|
|
@@ -12689,8 +12756,9 @@ function createApp(store = new Store, hooks2 = {}) {
|
|
|
12689
12756
|
const etag = `W/"${st.size.toString(16)}-${st.mtimeMs.toString(16)}"`;
|
|
12690
12757
|
if (c.req.header("if-none-match") === etag)
|
|
12691
12758
|
return c.body(null, 304, { etag });
|
|
12692
|
-
|
|
12693
|
-
|
|
12759
|
+
const ext = f.split(".").pop() ?? "";
|
|
12760
|
+
return c.body(BINARY.has(ext) ? readFileSync4(p) : readFileSync4(p, "utf8"), 200, {
|
|
12761
|
+
"content-type": MIME[ext] ?? "text/plain",
|
|
12694
12762
|
"cache-control": "no-cache",
|
|
12695
12763
|
etag
|
|
12696
12764
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ra3orblade/swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Local-first control plane for AI-agent development: watch every Claude Code / Codex / Grok session on your machine, ledger tasks and worktrees, enforce rules as hook denials.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg:#f6f7f9;--panel:#fff;--panel-2:#f0f2f5;--line:#e4e7ec;--line-2:#eef0f3;--fg:#1a1d21;--fg-2:#3d4650;--dim:#8b929c;--faint:#aab1bb;--acc:#388114;--acc-fill:#559b1b;--acc-soft:#559b1b26;--ok:#1f9d55;--ok-soft:#1f9d551f;--warn:#c77800;--warn-soft:#c778001f;--bad:#d1352b;--bad-soft:#d1352b1f;--violet:#7857c9;--c0:#8b929c;--c1:#4d9e2a;--c2:#1b9e77;--c3:#9ab828;--c4:#2f7d4f;--c5:#7fb069;--c6:#0f5c3a;--c7:#c3d84a;--acc-1:#e2f6d8;--acc-2:#b8e4a1;--acc-3:#8bc866;--acc-4:#5aa12e;--acc-5:#35741f;--mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,monospace;--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;--fs-2xs:11px;--fs-xs:11.5px;--fs-sm:12px;--fs-mono:12.5px;--fs-md:13px;--fs-menu:13.5px;--fs-lg:14px;--fs-xl:15px;--fs-2xl:24px;--pill-h:calc(var(--fs-xs)*1.2 + 8px);--shadow:0 1px 2px #1018280a,0 1px 3px #1018280f;--shadow-pop:0 8px 24px #10182824,0 1px 3px #10182814;--overlay:#00000073;--r:9px;--r-sm:6px;--r-xs:4px;--r-pill:20px;--t-fast:.12s;--t-med:.2s;--t-in:.32s;--t-pulse:2s;--hdr-h:48px;--sb-w:240px;--inset-tl:96px;--acc-contrast:#fff;--pad-cell:9px 12px;--pad-page:20px 22px;--pad-ctl:7px 11px;--pad-row:7px 10px;--gap-sec:18px}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#0c0e12;--panel:#14171d;--panel-2:#1a1e26;--line:#242932;--line-2:#1e222a;--fg:#e6e9ee;--fg-2:#c2c8d0;--dim:#7c8592;--faint:#5a626e;--acc:#a3e635;--acc-fill:#a3e635;--acc-soft:#a3e63524;--ok:#3fb571;--ok-soft:#3fb57124;--warn:#d9a441;--warn-soft:#d9a44124;--bad:#f0655a;--bad-soft:#f0655a24;--violet:#a68bec;--c0:#7c8592;--c1:#6bbf3a;--c2:#2bb78f;--c3:#b0cf3a;--c4:#3f9d63;--c5:#93c47d;--c6:#2f8f5a;--c7:#d3e88a;--acc-1:#1c3311;--acc-2:#2f5a18;--acc-3:#4d8a24;--acc-4:#7cc02f;--acc-5:#bfef7a;--shadow:0 1px 2px #0000004d,0 2px 6px #00000040;--shadow-pop:0 8px 24px #00000080,0 1px 3px #0006;--acc-contrast:#0e1013}}:root[data-theme=dark]{--bg:#0c0e12;--panel:#14171d;--panel-2:#1a1e26;--line:#242932;--line-2:#1e222a;--fg:#e6e9ee;--fg-2:#c2c8d0;--dim:#7c8592;--faint:#5a626e;--acc:#a3e635;--acc-fill:#a3e635;--acc-soft:#a3e63524;--ok:#3fb571;--ok-soft:#3fb57124;--warn:#d9a441;--warn-soft:#d9a44124;--bad:#f0655a;--bad-soft:#f0655a24;--violet:#a68bec;--c0:#7c8592;--c1:#6bbf3a;--c2:#2bb78f;--c3:#b0cf3a;--c4:#3f9d63;--c5:#93c47d;--c6:#2f8f5a;--c7:#d3e88a;--acc-1:#1c3311;--acc-2:#2f5a18;--acc-3:#4d8a24;--acc-4:#7cc02f;--acc-5:#bfef7a;--shadow:0 1px 2px #0000004d,0 2px 6px #00000040;--shadow-pop:0 8px 24px #00000080,0 1px 3px #0006;--acc-contrast:#0e1013}*{box-sizing:border-box}html,body{height:100%}html{zoom:var(--ui-zoom,1)}body{background:var(--bg);color:var(--fg);font:var(--fs-lg)/1.5 var(--sans);-webkit-font-smoothing:antialiased;display:grid;grid-template-rows:var(--hdr-h)1fr;grid-template-columns:var(--sb-w)1fr;overflow:hidden;animation:appin var(--t-in)ease both;user-select:none;-webkit-user-select:none;margin:0}@keyframes appin{0%{opacity:0}to{opacity:1}}input,textarea,tbody td,.mono,code,kbd{user-select:text;-webkit-user-select:text}::selection{background:var(--acc-soft)}a{color:var(--acc);text-decoration:none}.dim{color:var(--dim)}.faint{color:var(--faint)}.mono{font-family:var(--mono);font-size:var(--fs-mono)}.num{text-align:right;font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}kbd{font:var(--fs-mono)var(--mono);background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-xs);color:var(--fg-2);padding:1px 5px}code{font:var(--fs-md)var(--mono);color:var(--fg-2)}body.nosb{grid-template-columns:46px 1fr}body.nosb aside{overflow:hidden;padding:12px 5px}body.nosb #projects{display:none}.sb-grip{position:fixed;top:var(--hdr-h);bottom:0;left:calc(var(--sb-w) - 4px);cursor:col-resize;z-index:40;background:0 0;border:0;border-radius:0;width:9px;padding:0}.sb-grip:hover,.sb-grip:active{outline:none;background:linear-gradient(to right,transparent 3px,var(--acc)3px,var(--acc)5px,transparent 5px)}.sb-grip:focus-visible{outline:none;background:linear-gradient(to right,transparent 3px,var(--acc)3px,var(--acc)5px,transparent 5px)}body.nosb .sb-grip{display:none}#root>aside{grid-column:1;grid-row:2}#root>main{grid-column:2;grid-row:2}#projects h4{display:flex;align-items: center;gap:5px}.h4-act{cursor:pointer;color:var(--faint);display:inline-flex;border-radius:var(--r-xs);margin-left:auto;padding:2px}.h4-act:hover{color:var(--acc)}.pk-h input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-mono)var(--mono);outline:none;flex:1;min-width:0;padding:5px 9px}.pk-h input:focus{border-color:var(--acc)}header{grid-column:1/3;display:flex;border-bottom:1px solid var(--line);background:var(--panel);z-index:5;align-items: center;gap:6px;padding:0 16px}html.chrome-inset header{padding-left:var(--inset-tl)}html.chrome-inset.fs header{padding-left:16px}html.chrome-inset header{cursor:default}header .logo{letter-spacing:.02em;font-weight:700;font-size:var(--fs-xl);display:flex;align-items: center;gap:8px;margin-right:14px}header .logo svg{image-rendering:pixelated;image-rendering:crisp-edges;width:22px;height:auto;transform:translateY(-2px)}header .nav{color:var(--dim);border-radius:var(--r-sm);transition:color var(--t-fast),background var(--t-fast);padding:6px 11px;font-weight:500}header .nav:hover{color:var(--fg-2)}header .nav.on{color:var(--fg);background:var(--panel-2)}header .sp{flex:1}header #today{color:var(--dim);font-variant-numeric:tabular-nums;margin-right:18px}header #today b{color:var(--fg-2);font-weight:600}#daemon{display:flex;color:var(--dim);font-size:var(--fs-md);align-items: center;gap:7px}#daemon .dot{background:var(--bad);transition:background var(--t-med);border-radius:50%;width:7px;height:7px}#daemon .dot.on{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}#root>aside{border-right:1px solid var(--line);background:var(--panel);overflow:auto;display:flex;flex-direction:column;padding:12px 10px}#viewnav{display:flex;align-items: center;gap:2px}#viewnav .navgrp{display:flex;color:var(--dim);border-radius:var(--r-sm);font:inherit;font-size:var(--fs-md);cursor:pointer;transition:color var(--t-fast),background var(--t-fast);background:0 0;border:0;align-items: center;gap:6px;padding:6px 10px;font-weight:500}#viewnav .navgrp:hover{color:var(--fg-2);background:var(--panel-2)}#viewnav .navgrp.on{color:var(--fg);background:var(--panel-2)}#viewnav .navview{color:var(--acc);font-weight:600}#viewnav .navview:before{content:"";display:inline-block;background:var(--line);width:1px;height:11px;margin:0 7px -1px}#viewnav .navgrp.open{color:var(--fg);background:var(--line)}#viewnav .navgrp.open .chev{opacity:.9;transform:rotate(180deg)}#viewnav .navgrp .chev{opacity:.5;flex:none}#viewnav .navcount{background:var(--bad-soft);color:var(--bad);font-size:var(--fs-2xs);border-radius:99px;padding:0 5px;font-weight:600}#root>aside h4{color:var(--faint);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.09em;margin:14px 8px 6px;font-weight:600}#root>aside h4:first-child{margin-top:2px}.proj{position:relative;padding:var(--pad-row);border-radius:var(--r-sm);cursor:pointer;display:flex;color:var(--fg-2);transition:background var(--t-fast);user-select:none;align-items: center;gap:9px}.proj:hover{background:var(--panel-2)}.proj.sel{background:var(--acc-soft);color:var(--fg)}.proj.sel .nm{font-weight:600}.proj .st{background:var(--faint);transition:background var(--t-med);border-radius:50%;flex:none;width:7px;height:7px}.proj .st.live{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft)}.proj .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.proj small{color:var(--dim);font-variant-numeric:tabular-nums;font-size:var(--fs-sm);text-align:right;flex:none;min-width:15px}.proj .act{position:absolute;right:var(--pad-row-x,7px);display:inline-flex;opacity:0;pointer-events:none;color:var(--faint);font-size:var(--fs-lg);border-radius:var(--r-xs);transition:color var(--t-fast),background var(--t-fast),opacity var(--t-fast);padding:2px;line-height:1;top:50%;transform:translateY(-50%)}.proj:hover .act,.proj.menu-open .act{opacity:1;pointer-events:auto}.proj .act:focus-visible{opacity:1;pointer-events:auto}.proj:hover small,.proj.menu-open small{visibility:hidden}.proj .act:hover{color:var(--fg);background:var(--line)}.proj .act.rm:hover{color:var(--bad)}.srch{display:flex;gap:8px;margin-bottom:14px}.srch input{background:var(--panel);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-md)var(--sans);flex:1;max-width:640px;padding:8px 12px}.srch input:focus{border-color:var(--acc);outline:none}.hit{border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel);margin-bottom:8px;padding:10px 12px}.hit .ht{display:flex;align-items: center;gap:8px;margin-bottom:4px}.hit .hs{font-size:var(--fs-sm);color:var(--fg-2);white-space:pre-wrap}.hit mark{background:var(--warn-soft);color:var(--fg);border-radius:2px;padding:0 1px}.dr-rules{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 16px;margin:10px 0}.dr-rule{display:flex;font-size:var(--fs-sm);align-items: center;gap:8px}.dr-rule select{background:var(--panel-2);color:var(--fg);border:1px solid var(--line);border-radius:var(--r-sm);padding:2px 4px}.dr-flaky{border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel-2);margin:6px 0;padding:6px 8px}table.plain{border-collapse:collapse;font-size:var(--fs-sm);width:100%}table.plain td{border-top:1px solid var(--line);vertical-align:top;padding:3px 6px}.pk.pal{align-self: start;margin-top:9vh}.pal .pk-list{min-height:120px;max-height:min(440px,62vh)}.pal .pk-row.on{background:var(--acc-soft);color:var(--fg)}.pal-empty{padding:16px}.pal .pk-row .grp{color:var(--faint);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.07em;margin-left:auto}#picker:empty{display:none}#picker{position:fixed;background:var(--overlay);display:grid;z-index:50;place-items: center;inset:0}.pk{display:flex;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-pop);overflow:hidden;flex-direction:column;width:min(560px,92vw);max-height:78vh}.pk-h{display:flex;border-bottom:1px solid var(--line);align-items: center;gap:8px;padding:12px 14px}.pk-list{overflow:auto;padding:6px}.pk-row{display:flex;padding:var(--pad-row);border-radius:var(--r-sm);cursor:pointer;color:var(--fg-2);align-items: center;gap:9px}button.pk-row{font:inherit;line-height:inherit;text-align:left;background:0 0;border:0;justify-content:flex-start;width:100%}.pk-row:hover{background:var(--panel-2)}.pk-row .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.pk-row.up{color:var(--dim)}.pk-row .badge{margin:0}.pk-b{display:grid;gap:10px;padding:12px 14px}.pk-b label{display:grid;font:500 var(--fs-sm)var(--sans);color:var(--dim);gap:4px}.pk-b input,.pk-b textarea,.pk-b select{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-md)var(--sans);outline:none;width:100%;padding:7px 9px}.pk-b textarea{resize:vertical;font-family:var(--mono);font-size:var(--fs-sm);min-height:120px;line-height:1.5}.pk-b input:focus,.pk-b textarea:focus,.pk-b select:focus{border-color:var(--acc)}.pk-b .row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}.pk.wide{width:min(960px,96vw)}.df-files{display:grid;overflow:auto;border:1px solid var(--line);border-radius:var(--r-sm);gap:1px;max-height:22vh}.df-files a{display:flex;color:var(--fg-2);text-decoration:none;font:var(--fs-sm)var(--mono);gap:10px;padding:4px 8px}.df-files a:hover,.df-files a.on{background:var(--panel-2);color:var(--fg)}.df-files .st{color:var(--dim);width:12px}.df-files .pl{color:var(--ok)}.df-files .mi{color:var(--warn)}.df-files .pa{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.df-patch{overflow:auto;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);font:var(--fs-sm)var(--mono);white-space:pre;max-height:44vh;margin:0;padding:8px 10px;line-height:1.45}.df-patch .a{color:var(--ok)}.df-patch .d{color:var(--warn)}.df-patch .h{color:var(--acc)}.df-patch .m{color:var(--dim)}.wn .pk-b{overflow:auto;gap:2px;max-height:64vh}.wn h3{font:600 var(--fs-lg)var(--sans);color:var(--fg);margin:0 0 2px}.wn .date{color:var(--dim);font-size:var(--fs-sm);margin-bottom:12px}.wn h4{font:600 var(--fs-md)var(--sans);color:var(--acc);text-transform:none;letter-spacing:0;margin:14px 0 4px}.wn ul{margin:0 0 6px;padding-left:18px}.wn li{color:var(--fg-2);margin:5px 0;line-height:1.55}.wn p{color:var(--fg-2);margin:0 0 10px;line-height:1.6}.wn code{font-family:var(--mono);font-size:var(--fs-sm);background:var(--panel-2);border-radius:var(--r-xs);padding:1px 5px}.wn a{color:var(--acc)}.wn b{color:var(--fg)}.wn .snip{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);font-family:var(--mono);font-size:var(--fs-sm);white-space:pre-wrap;word-break:break-word;color:var(--fg-2);margin:2px 0 12px;padding:9px 11px;line-height:1.5}.wn .cbtn{font-weight:500;font-size:var(--fs-sm);color:var(--acc);margin-left:8px}.rp{height:78vh}.rp .pk-b{overflow:auto;flex:1;gap:2px;min-height:0;max-height:none}.rp .snip{overflow:visible;max-height:none}.rp .pk-f input[type=range]{accent-color:var(--acc)}.perm{background:var(--warn-soft);border:1px solid var(--warn);border-radius:var(--r-sm);margin-top:10px;padding:10px 12px}.perm-t{font:500 var(--fs-md)var(--sans);color:var(--fg)}.perm-c{font-family:var(--mono);font-size:var(--fs-sm);color:var(--fg-2);word-break:break-all;background:var(--panel-2);border-radius:var(--r-xs);margin:6px 0 8px;padding:6px 8px}.perm-b{display:flex;gap:8px}.perm-b .ok{color:var(--acc);border-color:var(--acc-soft)}.perm-b .danger{color:var(--warn);border-color:var(--warn-soft)}.stdin{display:flex;align-items: center;gap:8px;margin-top:10px}.stdin input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-md)var(--sans);outline:none;flex:1;padding:8px 10px}.stdin input:focus{border-color:var(--acc)}.stdin button.danger{color:var(--warn);border-color:var(--warn-soft)}.stdin .hint{color:var(--dim);font-size:var(--fs-sm)}.pk-f{display:flex;border-top:1px solid var(--line);gap:8px;padding:12px 14px}.pk-f button{flex:none}.pk-err{color:var(--bad);font-size:var(--fs-sm)}.pk-f .pk-err{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;align-self: center}.grow{flex:1}.pk-f .primary{background:var(--acc);border-color:var(--acc);color:var(--acc-contrast)}.pk-f .primary:hover{filter:brightness(1.06);color:var(--acc-contrast)}.proj .nm .pdir{opacity:.5;font-weight:400}.proj[draggable]{cursor:grab}.proj.dragging{opacity:.35;background:var(--panel-2);outline:1px dashed var(--acc)}button{background:var(--panel);border:1px solid var(--line);color:var(--fg-2);padding:var(--pad-ctl);border-radius:var(--r-sm);font:600 var(--fs-md)var(--sans);cursor:pointer;transition:border-color var(--t-fast),color var(--t-fast);display:inline-flex;justify-content:center;align-items: center;gap:6px;line-height:1}.pk-h button{padding:6px;line-height:0}button:hover{border-color:var(--acc);color:var(--fg)}main{overflow:auto;padding:var(--pad-page)}h2.hrow{display:flex;flex-wrap:wrap;align-items: center;gap:7px}h2.hrow .s{margin:0}h2{font-size:var(--fs-md);color:var(--dim);text-transform:uppercase;letter-spacing:.07em;display:flex;align-items:baseline;gap:10px;margin:0 0 12px;font-weight:600}h2 span{text-transform:none;letter-spacing:0;color:var(--faint);font-weight:500;font-size:var(--fs-md)}h2 .nav{display:inline-flex;font:500 var(--fs-md)var(--sans);text-transform:none;letter-spacing:0;color:var(--acc);text-decoration:none;border-radius:var(--r-sm);transition:background var(--t-fast);align-items: center;gap:5px;padding:2px 8px}h2 .nav:hover,h2 .nav.on{background:var(--acc-soft)}h2 .nav .ph{display:block}h2 .back{color:var(--fg-2);text-transform:none;letter-spacing:0;font-weight:600;font-size:var(--fs-md);display:inline-flex;border:1px solid var(--line);border-radius:var(--r-pill);background:var(--panel);transition:border-color var(--t-fast),color var(--t-fast);align-items: center;gap:5px;padding:5px 11px 5px 8px}.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow-x:auto}table{border-collapse:collapse;table-layout:fixed;width:100%}.card table{background:var(--panel)}thead th{text-align:left;padding:var(--pad-cell);color:var(--dim);font-weight:500;font-size:var(--fs-sm);border-bottom:1px solid var(--line);background:var(--panel-2);white-space:nowrap}tbody td{padding:var(--pad-cell);border-bottom:1px solid var(--line-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}tbody tr:last-child td{border-bottom:0}tbody td.td-tools{text-overflow:clip;text-align:center;padding-left:0;padding-right:0;line-height:0}tbody td.td-badge{text-overflow:clip}tbody td.td-badge .badge{margin-right:0}tbody tr{transition:background var(--t-fast)}tbody tr[data-s]{cursor:pointer}tbody tr:hover{background:var(--panel-2)}tbody td.td-lead{overflow:visible}tbody td{--cap:calc(var(--fs-lg)*.352)}tbody td .pg{vertical-align:calc(var(--cap) - 8px)}tbody td .pg:before{content:"";display:block;width:0;height:16px}tbody td>*+.badge{overflow:hidden;vertical-align:calc(var(--cap) - var(--pill-h)/2)}tbody td:not(.td-tools)>svg.ph[width="12"]{vertical-align:calc(var(--cap) - 6px)}tbody td:not(.td-tools)>svg.ph[width="13"]{vertical-align:calc(var(--cap) - 6.5px)}tbody td:not(.td-tools)>svg.ph[width="14"]{vertical-align:calc(var(--cap) - 7px)}tbody td:not(.td-tools)>svg.ph[width="16"]{vertical-align:calc(var(--cap) - 8px)}.dt thead th{position:relative;user-select:none;-webkit-user-select:none}.dt thead tr:first-child th[data-col]:not(:last-child):after{content:"";position:absolute;border-right:1px solid var(--line);height:50%;top:25%;right:0}.dt thead th[data-sort]{cursor:pointer}.dt thead th[data-sort]:hover{color:var(--fg-2)}.dt thead th.dragging{opacity:.4}.dt .th-l{display:inline-flex;align-items: center;gap:5px}.dt th.num .th-l{justify-content:flex-end;width:100%}.dt .sort{display:inline-block;border-left:4px solid #0000;border-right:4px solid #0000;width:0;height:0}.dt .sort.asc{border-bottom:5px solid var(--acc)}.dt .sort.desc{border-top:5px solid var(--acc)}.dt .th-grip{position:absolute;cursor:col-resize;z-index:2;width:9px;height:100%;top:0;right:-4px}.dt .th-grip:hover{background:linear-gradient(to right,transparent 3px,var(--acc)3px,var(--acc)5px,transparent 5px)}.dt thead th:last-child .th-grip{right:0}.dt thead th:last-child .th-grip:hover{background:linear-gradient(to left,var(--acc)0,var(--acc)2px,transparent 2px)}body.col-resizing,body.col-resizing .dt thead th{cursor:col-resize}.dt .th-tools{text-align:right}.dt .th-tools,.dt .td-tools{position:sticky;z-index:1;right:0}.dt .th-tools{background:var(--panel-2)}.dt .td-tools{background:var(--panel)}tbody tr:hover .td-tools{background:var(--panel-2)}.dt .th-cols{cursor:pointer;color:var(--faint);display:inline-flex}.dt .th-cols:hover{color:var(--fg-2)}.dt tr.filters th{background:var(--panel);padding:4px 8px}.dt tr.filters input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:var(--fs-sm)var(--mono);outline:none;width:100%;padding:3px 7px}.dt tr.filters input:focus{border-color:var(--acc)}.dt .tbl-empty td{color:var(--dim);text-align:center;cursor:default;padding:18px}td b{color:var(--fg);font-weight:600}td.now,.br{font-family:var(--mono);font-size:var(--fs-mono);color:var(--fg-2)}.br+.badge{margin-left:6px}td.now{color:var(--dim)}td a{color:var(--acc)}.num,th.num{text-align:right}.s{display:inline-block;background:var(--faint);border-radius:50%;flex:none;width:8px;height:8px}.s.active{background:var(--ok);box-shadow:0 0 0 3px var(--ok-soft);animation:pulse var(--t-pulse)ease-in-out infinite}.s.waiting{background:var(--warn);box-shadow:0 0 0 3px var(--warn-soft)}.s.idle{background:var(--faint)}.s.ended{background:var(--line);border:1px solid var(--faint);box-shadow:none}@keyframes pulse{0%,to{box-shadow:0 0 0 3px var(--ok-soft)}50%{box-shadow:0 0 0 5px #0000}}.badge{display:inline-block;font:500 var(--fs-xs)/1.2 var(--sans);border-radius:var(--r-pill);background:var(--panel-2);color:var(--dim);vertical-align:middle;margin-right:4px;padding:4px 8px}.badge.warn{color:var(--warn);background:var(--warn-soft)}.badge.acc{color:var(--acc);background:var(--acc-soft)}.badge.ok{color:var(--ok);background:var(--ok-soft)}.badge.bad{color:var(--bad);background:var(--bad-soft)}.badge.agent{color:var(--violet);background:color-mix(in srgb,var(--violet)14%,transparent);font-weight:600}.err-h{color:var(--bad);align-items: center}.err-h .err-ic{color:var(--bad);flex:none}.err-card{border:1px solid var(--bad);max-width:760px;padding:18px}.err-detail{background:var(--panel-2);border-radius:var(--r-sm);font:var(--fs-mono)/1.5 var(--mono);color:var(--fg-2);white-space:pre-wrap;overflow-x:auto;border:0;max-height:220px;margin:0;padding:10px 12px}.clusters{list-style:none;margin:0;padding:0}.clusters li{display:grid;grid-template-columns:minmax(0,13em)3.2em minmax(0,1fr);border-bottom:1px solid var(--line-2);font-size:var(--fs-md);align-items: center;gap:10px;padding:6px 0}.clusters li:last-child{border-bottom:0}.clusters li>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.clusters li b{font-family:var(--mono);font-size:var(--fs-mono);color:var(--fg)}.clusters li .n{color:var(--fg-2);text-align:right;font-variant-numeric:tabular-nums}.clusters li>span:last-child{color:var(--dim);font-family:var(--mono);font-size:var(--fs-mono)}.plainlist{list-style:none;display:flex;flex-direction:column;gap:6px;margin:0;padding:0}.plainlist li{display:flex;font-size:var(--fs-md);color:var(--fg-2);border-bottom:1px solid var(--line-2);align-items: center;gap:8px;padding:5px 0}.plainlist li:last-child{border-bottom:0}.plainlist li b{font-family:var(--mono);font-size:var(--fs-mono);color:var(--fg);font-weight:600}td.path,span.path{font-family:var(--mono);font-size:var(--fs-mono)}td.path b,span.path b{color:var(--fg);font-weight:600}td.path .dim,span.path .dim{font-size:var(--fs-xs)}.heatlist{list-style:none;display:flex;flex-direction:column;gap:2px;margin:0;padding:0}.heatlist li{position:relative;display:flex;font-size:var(--fs-md);color:var(--fg-2);border-radius:var(--r-sm);overflow:hidden;align-items: center;gap:10px;padding:6px 10px}.heatlist li .bar{position:absolute;width:var(--w);background:var(--acc-soft);border-radius:var(--r-sm);inset:0 auto 0 0}.heatlist li>span.clip{position:relative;font-family:var(--mono);font-size:var(--fs-mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.heatlist li b{position:relative;font-variant-numeric:tabular-nums;color:var(--fg)}.heatlist li .dim{position:relative;font-size:var(--fs-sm);white-space:nowrap}.empty{color:var(--dim);text-align:center;border:1px dashed var(--line);border-radius:var(--r);background:var(--panel);padding:44px 24px;line-height:1.7}.sess{display:grid;grid-template-columns:1fr 1fr;align-items:stretch;gap:16px}.sess>*{height:calc(100vh - 140px);min-height:320px}.sess.has-stdin>*{height:calc(100vh - 196px)}@media (max-width:980px){.sess{grid-template-columns:1fr}.sess>*{height:auto;max-height:calc(100vh - 140px)}}#log{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:auto;padding:6px 0}.ev{display:grid;grid-template-columns:56px 54px 1fr;align-items:baseline;gap:10px;padding:5px 14px}.ev:hover{background:var(--panel-2)}.ev .t{color:var(--faint);font:var(--fs-sm)var(--mono)}.ev .k{color:var(--dim);font:500 var(--fs-sm)var(--mono);text-transform:lowercase;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ev .m{white-space:pre-wrap;word-break:break-word;color:var(--fg-2);font-size:var(--fs-menu)}.ev.assistant .k{color:var(--violet)}.ev.assistant .m{color:var(--fg)}.ev.tool\.requested .k{color:var(--acc)}.ev.tool\.requested .m{color:var(--dim);font-family:var(--mono);font-size:var(--fs-mono)}.ev.incident\.opened .k,.ev.incident\.opened .m{color:var(--warn)}.ev.session\.ended .k{color:var(--bad)}.ev.prompt\.submitted .k{color:var(--ok)}.ev.prompt\.submitted .m{color:var(--fg);font-weight:500}.side{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);overflow:auto;padding:14px 18px}.side .stats{display:grid;grid-template-columns:1fr 1fr;gap:0 28px}.stat{display:flex;border-bottom:1px solid var(--line-2);justify-content:space-between;align-items:baseline;padding:7px 0}.stat:last-of-type{border-bottom:0}.stat span{color:var(--dim)}.stat b{font-variant-numeric:tabular-nums;font-weight:600}.stat small{color:var(--faint);font-weight:400}.side h4{color:var(--faint);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:.08em;margin:16px 0 6px;font-weight:600}table.mini{table-layout:fixed;border:0;width:100%}table.mini td{font-size:var(--fs-md);vertical-align:middle;border:0;padding:6px 0}table.mini th{vertical-align:middle;padding-top:6px;padding-bottom:6px}table.mini th+th,table.mini td+td{padding-left:14px}table.mini td:first-child{color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}table.mini td.num{color:var(--dim);width:34px}table.mini td.clip{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bars{display:flex;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);margin-bottom:var(--gap-sec);align-items: flex-end;gap:5px;height:110px;padding:14px 16px 26px}.bar{position:relative;display:flex;flex:1;align-items: flex-end;min-width:0;height:100%}.bar>div{background:linear-gradient(180deg,var(--acc),color-mix(in srgb,var(--acc)70%,transparent));border-radius:var(--r-xs)var(--r-xs)0 0;transition:opacity var(--t-fast);width:100%;min-height:2px}.bar:hover>div{opacity:.75}.bar span{position:absolute;text-align:center;font:var(--fs-2xs)var(--mono);color:var(--faint);bottom:-20px;left:0;right:0}.cols{display:grid;grid-template-columns:1fr 1fr;gap:var(--gap-sec);align-items: start}.chart{display:block;overflow:visible;width:100%}.chart .grid{stroke:var(--line-2);stroke-width:1;vector-effect:non-scaling-stroke}.chart .base{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke}.chart .ax{fill:var(--faint);font:var(--fs-2xs)var(--mono);text-anchor:start}.chart .ax.mid{text-anchor:middle}.chart .col:hover path{filter:brightness(1.12)}.chart-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);margin-bottom:var(--gap-sec);padding:14px 16px 10px}.chart-card h3{font-size:var(--fs-md);color:var(--fg-2);display:flex;align-items:baseline;gap:10px;margin:0 0 10px;font-weight:600}.chart-card h3 span{color:var(--faint);font-weight:400}.legend{display:flex;font-size:var(--fs-mono);color:var(--fg-2);flex-wrap:wrap;gap:4px 14px;margin:8px 0 0}.legend span{display:inline-flex;align-items: center;gap:6px}.legend i{display:inline-block;border-radius:2px;width:9px;height:9px}.legend em{color:var(--dim);font-variant-numeric:tabular-nums;font-style:normal}.legend.small{font-size:var(--fs-sm);margin-top:6px}#tip{position:fixed;display:none;z-index:50;pointer-events:none;background:var(--fg);color:var(--bg);border-radius:var(--r-sm);font:var(--fs-mono)/1.5 var(--sans);box-shadow:var(--shadow-pop);font-variant-numeric:tabular-nums;max-width:280px;padding:7px 10px;top:0;left:0}#tip b{font-weight:600}#tip span{opacity:.7}#tip i{display:inline-block;border-radius:2px;width:8px;height:8px;margin-right:6px}.spark{display:block;overflow:visible;width:88px;height:20px}.spark path{fill:none;stroke-width:1.5;vector-effect:non-scaling-stroke;stroke-linejoin:round}.comp{display:flex;border-radius:var(--r-xs);overflow:hidden;gap:2px;height:12px;margin-top:4px}.comp i{display:block;transition:filter var(--t-fast);min-width:2px}.comp i:hover{filter:brightness(1.15)}.hbars{display:flex;flex-direction:column;gap:4px}.hb{display:grid;grid-template-columns:minmax(0,120px)1fr auto;font-size:var(--fs-md);align-items: center;gap:8px}.hb .k{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--fg-2)}.hb .t{background:var(--panel-2);overflow:hidden;border-radius:3px;height:8px}.hb .t i{display:block;border-radius:3px;min-width:2px;height:100%}.hb .n{text-align:right;white-space:nowrap;color:var(--dim);font-variant-numeric:tabular-nums;font-size:var(--fs-mono)}.cal{position:relative;padding-top:16px}.cal-months{position:absolute;height:14px;top:0;left:0;right:0}.cal-months span{position:absolute;font:var(--fs-2xs)var(--mono);color:var(--faint)}.cal-grid{display:grid;gap:2px}.cal-col{display:grid;grid-template-rows:repeat(7,1fr);aspect-ratio:1/7;gap:2px}.cal-col i{display:block;background:var(--acc);outline:1px solid var(--line-2);outline-offset:-1px;border-radius:2px}.cal-col i:hover{outline-color:var(--fg)}.chart.line .pt circle{opacity:0}.chart.line .pt:hover circle{opacity:1}.records{display:grid;grid-template-columns:1fr 1fr;gap:12px}.rec{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);display:flex;flex-direction:column;gap:2px;padding:10px 14px}.rec .l{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;font-weight:600}.rec .v{font-size:var(--fs-lg);color:var(--fg);font-variant-numeric:tabular-nums;font-weight:600}.rec .d{font-size:var(--fs-mono);color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hm{display:grid;grid-template-columns:34px 1fr;align-items: center;gap:3px 8px}.hm-lbl{font:var(--fs-xs)var(--mono);color:var(--faint)}.hm-row{display:grid;grid-template-columns:repeat(24,1fr);gap:2px;height:16px}.hm-row i{display:block;background:var(--acc);outline:1px solid var(--line-2);outline-offset:-1px;border-radius:2px}.hm-row i:hover{outline-color:var(--fg)}.hm-hours{position:relative;font:var(--fs-2xs)var(--mono);color:var(--faint);height:14px}.hm-hours span{position:absolute;top:0}.tl{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:8px 16px 12px}.tl-row{display:grid;grid-template-columns:200px 1fr;cursor:pointer;border-radius:var(--r-xs);align-items: center;gap:12px;height:24px}.tl-row.claimlane{cursor:default}.tl-row:hover{background:var(--panel-2)}.tl-row.head{cursor:default;position:sticky;background:var(--panel);z-index:2;height:22px;margin-bottom:4px;top:-20px}.tl-row.head:hover{background:var(--panel)}.tl-name{font-size:var(--fs-md);color:var(--fg-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-left:10px}.tl-track{position:relative;height:100%}.tl-track>i{position:absolute;opacity:.9;border-radius:3px;min-width:3px;height:12px;top:6px}.tl-track>i.live{box-shadow:0 0 0 2px var(--panel),0 0 0 3px currentColor;animation:none}.tl-track>i:hover{opacity:1;filter:brightness(1.1)}.tl-track>i:not([data-tip]){background:var(--line-2);opacity:1;pointer-events:none;border-radius:0;width:1px;min-width:0;height:100%;top:0}.tl-axis{position:absolute;font:var(--fs-2xs)var(--mono);color:var(--faint);inset:0}.tl-axis span{position:absolute;top:3px;transform:translate(-50%)}.tl-now{position:absolute;background:var(--bad);z-index:1;pointer-events:none;width:1px;top:0;bottom:-2000px}.tl-group{position:relative;border-top:1px solid var(--line-2);margin-top:6px;padding-top:4px}.tl-proj{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;padding:4px 10px 2px;font-weight:600}.tl-proj small{text-transform:none;letter-spacing:0;color:var(--faint);margin-left:8px;font-weight:400}.tl,.tl-group{overflow:hidden}.sw{display:inline-block;vertical-align:baseline;border-radius:2px;width:9px;height:9px;margin-right:7px}.kpis{display:grid;grid-template-columns:repeat(4,1fr);margin-bottom:var(--gap-sec);gap:12px}.kpi{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);padding:12px 14px}.kpi .l{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;font-weight:600}.kpi .v{font-size:var(--fs-2xl);font-variant-numeric:tabular-nums;color:var(--fg);margin-top:2px;font-weight:600}.kpi .d{font-size:var(--fs-mono);color:var(--faint)}.ph{display:inline-block;vertical-align:-.09em;flex:none}header .nav{display:inline-flex;align-items: center;gap:6px}.navcount{display:inline-block;font:500 var(--fs-xs)var(--sans);text-transform:none;letter-spacing:0;color:var(--warn);background:var(--warn-soft);border-radius:var(--r-pill);padding:4px 8px;line-height:1.2}header .nav .ph{opacity:.75}header .nav.on .ph{opacity:1;color:var(--acc)}.icon-btn{display:inline-flex;border-radius:var(--r-sm);color:var(--dim);background:0 0;border:1px solid #0000;justify-content:center;align-items: center;width:28px;height:28px;padding:0}.icon-btn:hover{background:var(--panel-2);color:var(--fg);border-color:#0000}.icon-btn.on{background:var(--panel-2);color:var(--fg)}header #settings{margin-left:10px}.proj .ph{color:var(--faint)}.proj.sel .ph{color:var(--acc)}.kind{color:var(--faint);margin-right:6px}td .more{opacity:0;color:var(--faint);border-radius:var(--r-xs);transition:opacity var(--t-fast),background var(--t-fast);display:inline-flex;vertical-align:middle;padding:2px;line-height:0}tr:hover td .more,.proj:hover .more,tr.menu-open td .more,.menu-open>.more,.more.menu-open{opacity:1}td .more:hover{background:var(--line);color:var(--fg)}.mini-act{display:inline-flex;color:var(--fg-2);border:1px solid var(--line);border-radius:var(--r-sm);font-size:var(--fs-sm);text-decoration:none;transition:color var(--t-fast),border-color var(--t-fast);align-items: center;padding:2px 9px;font-weight:500}.mini-act:hover{color:var(--acc);border-color:var(--acc)}.mini-act.bad:hover{color:var(--bad);border-color:var(--bad)}.track{display:inline-flex;align-items: center;gap:3px}.track i{background:var(--line);border:1px solid var(--line-2);border-radius:50%;width:7px;height:7px}.track i.on{background:var(--acc);border-color:var(--acc)}.chip.off{opacity:.4;pointer-events:none}.stat .ph{color:var(--faint);margin-right:7px}.empty .ph{display:block;color:var(--faint);margin:0 auto 10px}.empty .px{display:block;image-rendering:pixelated;margin:0 auto 14px}h2 .back:hover{border-color:var(--acc);color:var(--fg)}:root{--fm-surface-bg:var(--panel);--fm-surface-fg:var(--fg);--fm-surface-border:var(--line);--fm-surface-radius:var(--r);--fm-surface-shadow:var(--shadow-pop);--fm-accent:var(--acc);--fm-accent-fg:var(--acc-contrast);--fm-destructive:var(--bad);--fm-muted-fg:var(--dim);--fm-divider-color:var(--line-2);--fm-row-hover-bg:var(--panel-2);--fm-row-active-bg:var(--panel-2);--fm-row-active-fg:var(--fg);--fm-font-size-row:var(--fs-menu);--fm-font-size-caption:var(--fs-sm);--fm-font-size-section:var(--fs-xs);--fm-row-min-h:30px;--fm-row-radius:var(--r-sm)}.fm-row--danger,.fm-row--danger .fm-row__icon{color:var(--bad)}.fm-row--danger:hover{background:var(--bad-soft)}.fm-row[data-pressed=true]{background:var(--acc-soft);color:var(--acc);position:relative;font-weight:600}.fm-row[data-pressed=true]:before{content:"";position:absolute;border-radius:0 var(--r-xs)var(--r-xs)0;background:var(--acc);width:3px;height:58%;top:50%;left:0;transform:translateY(-50%)}.fm-row[data-pressed=true] .fm-ico{color:var(--acc)}.fm-row__icon,.fm-ico{display:inline-flex;justify-content:center;align-items: center}.fm-row__name{flex:auto;min-width:0}.fm-row__caption{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;max-width:45%;margin-left:auto}.fm-ico .ph{vertical-align:0}main>h2:not(:first-child),.mt-sec{margin-top:var(--gap-sec)}.pg{display:inline-flex;font-size:var(--fs-md);color:var(--dim);overflow:visible;flex:none;justify-content:center;align-items: center;width:16px;height:16px;line-height:1}.pg-c1{color:var(--c1)}.pg-c2{color:var(--c2)}.pg-c3{color:var(--c3)}.pg-c4{color:var(--c4)}.pg-c5{color:var(--c5)}.pg-c6{color:var(--c6)}.pg-c7{color:var(--c7)}.pg-img{object-fit:cover;border-radius:var(--r-xs);display:block;width:16px;height:16px}.pg-lg{font-size:var(--fs-2xl);border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel-2);overflow:hidden;width:34px;height:34px;line-height:1}.pg-lg .pg-img{border-radius:0;width:34px;height:34px}.icon-row{display:flex;align-items: center;gap:8px}.icon-row input{flex:1}.icon-row .btn{display:inline-flex;padding:var(--pad-ctl);border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel-2);color:var(--fg-2);cursor:pointer;font:500 var(--fs-sm)var(--sans);white-space:nowrap;align-items: center;gap:5px}.icon-row .btn:hover{border-color:var(--acc)}.emoji-row{display:flex;flex-wrap:wrap;gap:4px}.emoji-all{overflow:auto;border:1px solid var(--line);border-radius:var(--r-sm);display:grid;gap:6px;max-height:200px;padding:8px}.emoji-sec{font-size:var(--fs-2xs);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;margin-top:4px;font-weight:600}.more-emoji{color:var(--fg-2);font-weight:700}.emoji{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-sm);cursor:pointer;font-size:var(--fs-xl);background:var(--panel-2);color:var(--dim);overflow:hidden;justify-content:center;align-items: center;width:34px;height:34px;line-height:1}.emoji .ph{width:14px;height:14px}.emoji:hover{border-color:var(--acc)}.emoji.on{border-color:var(--acc);background:var(--acc-soft)}.swatches{display:flex;gap:6px}.swatch{cursor:pointer;box-shadow:inset 0 0 0 2px var(--panel);background:currentColor;border:2px solid #0000;border-radius:50%;width:22px;height:22px}.swatch.none{background:var(--panel-2);color:var(--line);box-shadow:inset 0 0 0 1px var(--line)}.swatch:hover{border-color:var(--fg-2)}.swatch.on{border-color:var(--fg)}.pk-b label.chk{display:flex;flex-direction:row;align-items: center;gap:8px}.pk-b label.chk input{width:auto}.kpis-5{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.kpi.hot .v{color:var(--warn)}.kpi.warm .v{color:var(--acc)}.grow{flex:1}.kanban{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin-bottom:var(--gap-sec);align-items: start;gap:12px}.lane{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);display:flex;flex-direction:column;gap:8px;min-height:60px;padding:8px}.lane-h{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;padding:2px 4px;font-weight:600}.lane-h span{color:var(--faint);font-weight:500}.lane-empty{color:var(--faint);text-align:center;padding:10px 0}.lane-more{font-size:var(--fs-xs);padding:2px 4px}.tcard{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow);cursor:pointer;transition:border-color var(--t-fast);padding:8px 10px}.tcard:hover{border-color:var(--acc);outline:none}.tcard:focus-visible{border-color:var(--acc);outline:none}.wt:focus-visible{border-color:var(--acc);outline:none}.more:focus-visible{border-color:var(--acc);outline:none}.more:focus-visible{opacity:1}.tcard.ready{border-color:var(--ok)}.tcard.held{border-color:var(--acc)}.tcard.blocked{border-color:var(--line)}.tcard.done{opacity:.7}.tc-h{display:flex;font-size:var(--fs-sm);align-items: center;gap:8px;margin-bottom:3px}.tc-h .dim{font-family:var(--mono);font-size:var(--fs-2xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tc-t{font-size:var(--fs-md);color:var(--fg);display:-webkit-box;-webkit-line-clamp:3;overflow:hidden;-webkit-box-orient:vertical;line-height:1.35}.tc-m{font-size:var(--fs-xs);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:4px}.tc-g{margin-top:6px}.wtmap{display:flex;margin-bottom:var(--gap-sec);flex-direction:column;gap:14px}.wt-proj{font-size:var(--fs-sm);color:var(--dim);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px;font-weight:600}.wt-proj span{color:var(--faint);font-weight:500}.wt-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.wt{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-sm);box-shadow:var(--shadow);cursor:pointer;transition:border-color var(--t-fast);min-width:0;padding:8px 10px}.wt:hover{border-color:var(--acc)}.wt.main{background:var(--panel-2)}.wt.live{border-color:var(--ok)}.wt.dirty{border-color:var(--warn)}.wt.merged{opacity:.6}.wt-b{display:flex;align-items: center;gap:6px;min-width:0}.wt-b .s{flex:none;margin:0}.wt-b .br{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wt-m{font-size:var(--fs-xs);color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:3px}.wt-m i{font-style:normal}.wt-m i.warn{color:var(--warn)}.wt-m i.acc{color:var(--acc)}.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}.chip{display:inline-flex;font:500 var(--fs-md)var(--sans);border-radius:var(--r-pill);background:var(--panel);border:1px solid var(--line);color:var(--dim);cursor:pointer;user-select:none;align-items: center;gap:5px;padding:4px 11px}.chip:hover{border-color:var(--acc);color:var(--fg-2)}.chip.on{background:var(--acc-soft);color:var(--acc);border-color:#0000}.seg{display:inline-flex;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);gap:2px;padding:2px}.seg a{border-radius:var(--r-sm);font:600 var(--fs-md)var(--sans);color:var(--dim);text-decoration:none;transition:color var(--t-fast),background var(--t-fast);letter-spacing:0;text-transform:none;padding:3px 10px}.seg a:hover{color:var(--fg-2)}.seg a.on{background:var(--acc-soft);color:var(--acc)}.chip b{font-weight:700}.wf-steps{display:inline-flex;flex-wrap:wrap;gap:6px}.wfs{font:500 var(--fs-sm)var(--sans);color:var(--dim);background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-pill);white-space:nowrap;padding:1px 8px}.wfs.ok{color:var(--ok);background:var(--ok-soft);border-color:#0000}.wfs.run{color:var(--acc);background:var(--acc-soft);border-color:#0000}.wfs.bad{color:var(--warn);background:var(--warn-soft);border-color:#0000}.tl-track u{position:absolute;opacity:.95;border-radius:1px;width:2px;top:4px;bottom:4px}.tl-track i.thin{opacity:.25}.tl-row.claimlane .tl-name{font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.06em}.tl-track i.claim{background:var(--violet);opacity:.6;border-radius:2px;height:4px;top:7px}.tl-track i.claim.orphaned{background:var(--warn);opacity:.9}.tl-track i.claim.expired{background:var(--faint)}.proj-spark{display:inline-flex;opacity:.7;flex:none;width:44px}.proj-spark .spark{width:44px;height:14px}.gh-strip{display:inline-flex;gap:12px;font-weight:400}.gh{font-size:var(--fs-sm);color:var(--dim);display:inline-flex;align-items: center;gap:3px}.gh i{display:inline-block;border-radius:2px;width:7px;height:7px}.gh i.ok{background:var(--ok)}.gh i.bad{background:var(--warn)}.msgs{display:flex;overflow:auto;flex-direction:column;gap:6px;max-height:220px;margin-bottom:8px}.msg{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);font-size:var(--fs-md);color:var(--fg-2);word-break:break-word;padding:6px 9px}.msg.out{background:var(--acc-soft);border-color:#0000}.msg-f{display:block;font-size:var(--fs-2xs);color:var(--dim);margin-bottom:2px}.msg-compose{display:flex;gap:6px}.msg-compose input{background:var(--panel-2);border:1px solid var(--line);color:var(--fg);border-radius:var(--r-sm);font:400 var(--fs-md)var(--sans);flex:1;min-width:0;padding:7px 10px}.msg-compose input:focus{border-color:var(--acc);outline:none}.msg-compose button{display:inline-flex;background:var(--panel-2);border:1px solid var(--line);color:var(--fg-2);border-radius:var(--r-sm);cursor:pointer;font:500 var(--fs-sm)var(--sans);flex:none;align-items: center;gap:5px;padding:7px 11px}.msg-compose button:hover{color:var(--acc);border-color:var(--acc)}.msg-compose input:disabled,.msg-compose button:disabled{opacity:.45;cursor:not-allowed}.msg-compose button:disabled:hover{color:var(--fg-2);border-color:var(--line)}.msg-hint{display:flex;color:var(--dim);font-size:var(--fs-sm);align-items: center;gap:5px;margin:7px 0 0;line-height:1.4}.msg-hint .ph{color:var(--faint);flex:none}.msg-hint b{color:var(--fg-2);font-weight:600}.pathrow{display:flex;text-align:left;cursor:pointer;background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);color:var(--fg-2);font:400 var(--fs-sm)var(--mono);align-items: center;gap:6px;width:100%;padding:6px 9px}.pathrow:hover{border-color:var(--acc);color:var(--fg)}.pathrow .dir{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dim);flex:0 9999 auto;min-width:0}.pathrow b{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;font-weight:400}.pathrow .cp{color:var(--faint);flex:none;margin-left:auto}.pathrow:hover .cp{color:var(--acc)}.pathrow.copied{border-color:var(--ok);color:var(--ok)}.pathrow.copied .dir,.pathrow.copied .cp{color:var(--ok)}.pathrow.copy-failed{border-color:var(--bad);color:var(--bad)}.pathrow.copy-failed .dir,.pathrow.copy-failed .cp{color:var(--bad)}.onboard{background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow);display:flex;flex-direction:column;align-items: flex-start;gap:16px;max-width:640px;margin:40px auto;padding:28px 30px}.onboard h3{font-size:var(--fs-xl);color:var(--fg)}.onboard .px{margin:0 auto}.ob-steps{display:flex;flex-direction:column;gap:12px}.ob-step{display:flex;color:var(--fg-2);align-items: flex-start;gap:12px;line-height:1.5}.ob-step.done{color:var(--dim)}.ob-n{border:1px solid var(--line);display:inline-flex;font:600 var(--fs-sm)var(--sans);color:var(--dim);border-radius:50%;flex:none;justify-content:center;align-items: center;width:22px;height:22px;margin-top:1px}.ob-step.done .ob-n{background:var(--ok-soft);color:var(--ok);border-color:#0000}.nudge{position:fixed;z-index:40;background:var(--panel);border:1px solid var(--line);border-radius:var(--r);box-shadow:var(--shadow-pop);display:flex;font:var(--fs-md)var(--sans);color:var(--fg-2);align-items: flex-start;gap:12px;max-width:400px;padding:14px 16px;line-height:1.5;bottom:18px;right:18px}.nudge .ic{color:var(--acc);flex:none;margin-top:1px}.nudge b{color:var(--fg);display:block;margin-bottom:2px}.nudge .row{display:flex;white-space:nowrap;align-items: center;gap:8px;margin-top:10px}.nudge button.pri{background:var(--acc-soft);color:var(--acc);border-color:#0000}.nudge a.dim{color:var(--dim);font-size:var(--fs-sm);margin-left:auto}body.nosb .nudge{right:18px}.dt .th-cols{font:inherit;background:0 0;border:0;padding:0}#root{display:contents}.note{font-size:var(--fs-sm);margin-top:10px}.proj,.chip,.h4-act,.nav-group,.mini-act{appearance:none;font:inherit;color:inherit;text-align:left;background:0 0;border:0}.proj{width:100%}.chart-card.flush{margin:0}.stack{display:flex;gap:var(--gap-sec);flex-direction:column;min-width:0}.rule-spark{display:flex;align-items: center;gap:12px;margin-bottom:10px}.rule-spark .dim{font-size:var(--fs-sm)}.rule-shape{font-size:var(--fs-md);margin:0 0 8px}.h4-note{text-transform:none;letter-spacing:0;font-weight:400}.chips.pager{margin-top:10px}.chips.pager .dim{font-size:var(--fs-sm);align-self: center}tr.clickable{cursor:pointer}.link{appearance:none;font:inherit;color:var(--acc);cursor:pointer;text-align:left;background:0 0;border:0;padding:0}.link:hover{text-decoration:underline}.h2-actions{margin-left:auto}.gpager{display:flex;border-top:1px solid var(--line);font-size:var(--fs-sm);align-items: center;gap:10px;padding:7px 12px}.gpager .gp-size{margin-left:auto}.gp-btn{appearance:none;border-radius:var(--r-xs);color:var(--dim);cursor:pointer;display:inline-flex;background:0 0;border:0;padding:2px}.gp-btn:hover:not(:disabled){color:var(--acc);background:var(--panel-2)}.gp-btn:disabled{color:var(--line-2);cursor:default}.mt-panel{margin-top:14px}.bipartite{max-width:1100px}.matrix-scroll{overflow:auto}.card-pad{padding:14px}.card-scroll{overflow:auto;max-height:72vh;padding:14px}.graph-key{display:flex;font-size:var(--fs-sm);flex-wrap:wrap;align-items: center;gap:18px;margin-top:10px}.graph-key svg{vertical-align:middle}.edge-key{display:inline-flex;align-items: center;gap:6px}.dag-node{cursor:pointer}.ring-card{margin-bottom:12px}.ring-list{margin:8px 0 0;padding-left:18px}.more{appearance:none;font:inherit;color:inherit;background:0 0;border:0}.pk-sub{flex:1;margin-left:8px}.rp-summary{font-family:var(--mono);font-size:var(--fs-sm);margin-bottom:8px}.rp-range{flex:1;max-width:280px}.df-commits{font-size:var(--fs-sm)}.df-files button{appearance:none;font:inherit;color:inherit;text-align:left;background:0 0;border:0;width:100%}.wn-full{color:var(--dim);font-size:var(--fs-sm);align-self: center}.linkish{font:inherit;cursor:pointer;text-decoration:underline;background:0 0;border:0;padding:0}.linkish:hover{color:var(--fg-2)}
|