lobstah 0.6.3 → 0.6.4
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/main.js +533 -208
- package/dist/runner.js +17 -4
- package/docs/configuration.md +21 -1
- package/docs/man.md +69 -19
- package/docs/vocabulary.md +15 -6
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1638,6 +1638,23 @@ var init_dist = __esm({
|
|
|
1638
1638
|
import * as fs7 from "node:fs";
|
|
1639
1639
|
import * as os2 from "node:os";
|
|
1640
1640
|
import * as path7 from "node:path";
|
|
1641
|
+
function parseBriefHooks(raw) {
|
|
1642
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
1643
|
+
return void 0;
|
|
1644
|
+
const out = {};
|
|
1645
|
+
for (const kind of [...BRIEF_KINDS, "all"]) {
|
|
1646
|
+
const text = raw[kind];
|
|
1647
|
+
if (typeof text === "string" && text.trim())
|
|
1648
|
+
out[kind] = text.trim();
|
|
1649
|
+
}
|
|
1650
|
+
return Object.keys(out).length ? out : void 0;
|
|
1651
|
+
}
|
|
1652
|
+
function withBriefHooks(brief, repo, kind) {
|
|
1653
|
+
const hooks = [repo?.briefHooks?.[kind], repo?.briefHooks?.all].filter((t) => !!t);
|
|
1654
|
+
return hooks.length ? `${brief}
|
|
1655
|
+
|
|
1656
|
+
${hooks.join("\n\n")}` : brief;
|
|
1657
|
+
}
|
|
1641
1658
|
function parseAttentionKinds(raw) {
|
|
1642
1659
|
if (raw === void 0)
|
|
1643
1660
|
return [...DEFAULT_ATTENTION_KINDS];
|
|
@@ -1675,7 +1692,8 @@ function loadConfig() {
|
|
|
1675
1692
|
pushEarly: r.pushEarly === void 0 ? void 0 : Boolean(r.pushEarly),
|
|
1676
1693
|
draftPr: r.draftPr === void 0 ? void 0 : Boolean(r.draftPr),
|
|
1677
1694
|
checkpointOnStop: r.checkpointOnStop === void 0 ? void 0 : Boolean(r.checkpointOnStop),
|
|
1678
|
-
humanGateChecks: Array.isArray(r.humanGateChecks) ? r.humanGateChecks.map(String).filter(Boolean) : void 0
|
|
1695
|
+
humanGateChecks: Array.isArray(r.humanGateChecks) ? r.humanGateChecks.map(String).filter(Boolean) : void 0,
|
|
1696
|
+
briefHooks: parseBriefHooks(r.briefHooks)
|
|
1679
1697
|
};
|
|
1680
1698
|
}
|
|
1681
1699
|
const groundsRaw = raw.grounds ?? {};
|
|
@@ -1716,12 +1734,13 @@ function resolveDispatch(d, cfg) {
|
|
|
1716
1734
|
flags: d.flags ?? []
|
|
1717
1735
|
};
|
|
1718
1736
|
}
|
|
1719
|
-
var ATTENTION_KINDS, DEFAULT_ATTENTION_KINDS, DEFAULT_SOAK, DEFAULT_HELM, DEFAULT_GLASS, DEFAULT_WATCH, DEFAULT_LIMITS;
|
|
1737
|
+
var BRIEF_KINDS, ATTENTION_KINDS, DEFAULT_ATTENTION_KINDS, DEFAULT_SOAK, DEFAULT_HELM, DEFAULT_GLASS, DEFAULT_WATCH, DEFAULT_LIMITS;
|
|
1720
1738
|
var init_config = __esm({
|
|
1721
1739
|
"packages/core/dist/config.js"() {
|
|
1722
1740
|
"use strict";
|
|
1723
1741
|
init_dist();
|
|
1724
1742
|
init_paths();
|
|
1743
|
+
BRIEF_KINDS = ["conflict", "checks", "review", "ciFix", "rebase"];
|
|
1725
1744
|
ATTENTION_KINDS = ["question", "decision", "landed", "pr:draft", "pr:review", "pr:checks", "pr:conflict", "pr:ready", "report"];
|
|
1726
1745
|
DEFAULT_ATTENTION_KINDS = ["question", "decision", "pr:ready", "pr:review", "pr:conflict", "pr:checks"];
|
|
1727
1746
|
DEFAULT_SOAK = {
|
|
@@ -1736,7 +1755,7 @@ var init_config = __esm({
|
|
|
1736
1755
|
armGraceSecs: 5
|
|
1737
1756
|
};
|
|
1738
1757
|
DEFAULT_GLASS = { port: 4949 };
|
|
1739
|
-
DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
|
|
1758
|
+
DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, maxRepairsWithoutProgress: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
|
|
1740
1759
|
DEFAULT_LIMITS = {
|
|
1741
1760
|
maxConcurrent: 2,
|
|
1742
1761
|
choreConcurrent: 1,
|
|
@@ -4716,6 +4735,20 @@ var init_compiled = __esm({
|
|
|
4716
4735
|
// packages/core/dist/pr.js
|
|
4717
4736
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
4718
4737
|
import { createHash as createHash5 } from "node:crypto";
|
|
4738
|
+
function dispatchPrUrls(ev) {
|
|
4739
|
+
const out = /* @__PURE__ */ new Map();
|
|
4740
|
+
for (const u of [ev.prUrl, ...ev.prUrls ?? []]) {
|
|
4741
|
+
const ref = u ? parsePrRef(u) : void 0;
|
|
4742
|
+
if (ref && !out.has(ref.key))
|
|
4743
|
+
out.set(ref.key, ref.url);
|
|
4744
|
+
}
|
|
4745
|
+
if (out.size === 0 && ev.pr?.url) {
|
|
4746
|
+
const ref = parsePrRef(ev.pr.url);
|
|
4747
|
+
if (ref)
|
|
4748
|
+
out.set(ref.key, ref.url);
|
|
4749
|
+
}
|
|
4750
|
+
return [...out.values()];
|
|
4751
|
+
}
|
|
4719
4752
|
function parsePrRef(s) {
|
|
4720
4753
|
const m = /^(?:pr:)?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)#(\d+)$/.exec(s.trim()) ?? /^https?:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/(\d+)(?:[/?#].*)?$/.exec(s.trim());
|
|
4721
4754
|
if (!m)
|
|
@@ -4769,11 +4802,14 @@ function prStandingKinds(pr) {
|
|
|
4769
4802
|
function prReview(view) {
|
|
4770
4803
|
const latest = /* @__PURE__ */ new Map();
|
|
4771
4804
|
let lastReviewAt;
|
|
4805
|
+
let lastApprovalAt;
|
|
4772
4806
|
const reviews = [...view.reviews ?? []].sort((a, b) => (a.submittedAt ?? "").localeCompare(b.submittedAt ?? ""));
|
|
4773
4807
|
for (const r of reviews) {
|
|
4774
4808
|
if (r.submittedAt && (!lastReviewAt || r.submittedAt > lastReviewAt))
|
|
4775
4809
|
lastReviewAt = r.submittedAt;
|
|
4776
4810
|
const state = (r.state ?? "").toUpperCase();
|
|
4811
|
+
if (state === "APPROVED" && r.submittedAt && (!lastApprovalAt || r.submittedAt > lastApprovalAt))
|
|
4812
|
+
lastApprovalAt = r.submittedAt;
|
|
4777
4813
|
if (state === "APPROVED" || state === "CHANGES_REQUESTED" || state === "DISMISSED")
|
|
4778
4814
|
latest.set(r.author?.login ?? "?", state);
|
|
4779
4815
|
}
|
|
@@ -4781,7 +4817,8 @@ function prReview(view) {
|
|
|
4781
4817
|
return {
|
|
4782
4818
|
...view.unresolvedThreads !== void 0 ? { unresolvedThreads: view.unresolvedThreads } : {},
|
|
4783
4819
|
changesRequested,
|
|
4784
|
-
...lastReviewAt ? { lastReviewAt } : {}
|
|
4820
|
+
...lastReviewAt ? { lastReviewAt } : {},
|
|
4821
|
+
...lastApprovalAt ? { lastApprovalAt } : {}
|
|
4785
4822
|
};
|
|
4786
4823
|
}
|
|
4787
4824
|
function prEvidence(ref, view, observedAt) {
|
|
@@ -5128,7 +5165,9 @@ function upsertPr(pr, dispatchId) {
|
|
|
5128
5165
|
headSince: sinceOf(before?.headSha === pr.headSha, before?.headSince, before?.observedAt, pr.observedAt),
|
|
5129
5166
|
baseSince: sinceOf(!!before && before.baseRefName === pr.baseRefName && before.baseSha === (pr.baseSha ?? before.baseSha), before?.baseSince, before?.observedAt, pr.observedAt),
|
|
5130
5167
|
...pr.failingChecks?.length ? { failingSince: sinceOf(!!before && failingKey(before) === failingKey(pr), before?.failingSince, before?.observedAt, pr.observedAt) } : { failingSince: void 0 },
|
|
5131
|
-
repair: before?.headSha === pr.headSha ? before?.repair : void 0
|
|
5168
|
+
repair: before?.headSha === pr.headSha ? before?.repair : void 0,
|
|
5169
|
+
// A merged or closed PR ends the run of repairs without progress.
|
|
5170
|
+
...pr.state === "OPEN" ? {} : { repairStreak: void 0 }
|
|
5132
5171
|
};
|
|
5133
5172
|
writePr(after);
|
|
5134
5173
|
return { before, after };
|
|
@@ -5157,6 +5196,23 @@ function removePr(key) {
|
|
|
5157
5196
|
fs27.rmSync(file, { force: true });
|
|
5158
5197
|
return existed;
|
|
5159
5198
|
}
|
|
5199
|
+
function resetRepairStreaks(key) {
|
|
5200
|
+
const reset = [];
|
|
5201
|
+
for (const pr of key === void 0 ? readPrs() : [readPr(key)].filter((p) => p !== void 0)) {
|
|
5202
|
+
if (!pr.repairStreak)
|
|
5203
|
+
continue;
|
|
5204
|
+
withPrLock(pr.key, () => {
|
|
5205
|
+
const current = readPr(pr.key);
|
|
5206
|
+
if (!current?.repairStreak)
|
|
5207
|
+
return;
|
|
5208
|
+
const stopped = current.repair?.status === "gave-up" && current.repairStreak.stoppedHead !== void 0;
|
|
5209
|
+
const { repairStreak: _streak, ...rest } = current;
|
|
5210
|
+
writePr({ ...rest, ...stopped ? { repair: void 0 } : {} });
|
|
5211
|
+
reset.push(pr.key);
|
|
5212
|
+
});
|
|
5213
|
+
}
|
|
5214
|
+
return reset;
|
|
5215
|
+
}
|
|
5160
5216
|
var init_prs = __esm({
|
|
5161
5217
|
"packages/core/dist/prs.js"() {
|
|
5162
5218
|
"use strict";
|
|
@@ -5275,24 +5331,43 @@ function repairKind(pr, gates = []) {
|
|
|
5275
5331
|
function repairLimit(n) {
|
|
5276
5332
|
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 2;
|
|
5277
5333
|
}
|
|
5278
|
-
function pushRule(branch, id = "<dispatch id>") {
|
|
5334
|
+
function pushRule(branch, id = "<dispatch id>", mode = "rebase") {
|
|
5279
5335
|
const b = branch ?? "the PR's head branch";
|
|
5280
5336
|
return [
|
|
5281
5337
|
`Push only to the existing branch ${b}.`,
|
|
5282
|
-
`If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, rebase your commits onto the moved head again, and push with \`--force-with-lease=${b}:<the head you just fetched>\`. Retry at most ${PUSH_RETRIES} times.`,
|
|
5338
|
+
mode === "merge" ? `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, merge the moved head into your branch, and push again with a normal push. Retry at most ${PUSH_RETRIES} times. Never force-push.` : `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, rebase your commits onto the moved head again, and push with \`--force-with-lease=${b}:<the head you just fetched>\`. Retry at most ${PUSH_RETRIES} times.`,
|
|
5283
5339
|
"If a push hook fails with a real test or type error, do not retry the push: fix the error, commit, and push again.",
|
|
5284
5340
|
`If you still cannot push, report \`lobstah report ${id} failed "${PUSH_REJECTED} <rejection text>; moved head <full sha of the head you fetched>"\` and leave the PR as it was.`,
|
|
5285
5341
|
"Never push to another branch. Never open a new PR."
|
|
5286
5342
|
].join(" ");
|
|
5287
5343
|
}
|
|
5344
|
+
function standalonePr(baseRefName, trunk) {
|
|
5345
|
+
return baseRefName !== void 0 && trunk !== void 0 && baseRefName === trunk;
|
|
5346
|
+
}
|
|
5347
|
+
function conflictUpdate(base, branch, standalone) {
|
|
5348
|
+
const b = base ?? "the PR's base branch";
|
|
5349
|
+
const head = branch ?? "the PR's head branch";
|
|
5350
|
+
return standalone ? `Fetch origin/${b} and merge it into ${head} with a merge commit (\`git merge origin/${b}\`), resolving the conflicts in that merge. Do not rebase, and do not force-push: push the merge commit with a normal push.` : `This PR is stacked on its base branch ${b}. Fetch origin/${b} and rebase ${head} onto it, resolving the conflicts. Push with \`--force-with-lease=${head}:<the head you started from>\`.`;
|
|
5351
|
+
}
|
|
5352
|
+
function prReviewed(pr) {
|
|
5353
|
+
return pr.reviewDecision === "APPROVED" || pr.reviewDecision === "CHANGES_REQUESTED" || pr.review?.lastReviewAt !== void 0 || pr.review?.changesRequested === true;
|
|
5354
|
+
}
|
|
5355
|
+
function rerequestReview(pr, id = "<dispatch id>", reviewers = []) {
|
|
5356
|
+
const n = pr.number !== void 0 ? String(pr.number) : pr.url;
|
|
5357
|
+
const repo = pr.repo ? ` --repo ${pr.repo}` : "";
|
|
5358
|
+
const who = reviewers.length ? `its reviewers: \`gh pr edit ${n}${repo} --add-reviewer ${reviewers.join(",")}\`` : `its current reviewers: list them with \`gh pr view ${n}${repo} --json reviews,reviewRequests\`, then \`gh pr edit ${n}${repo} --add-reviewer <login>,<login>\``;
|
|
5359
|
+
return `This PR had a review, and your push changes its head, which invalidates that review. After you push, re-request review from ${who}. Then report \`lobstah report ${id} paused "<note>" --waiting-on review --link ${pr.url}\` instead of done.`;
|
|
5360
|
+
}
|
|
5288
5361
|
function repairBrief(pr, kind, arg = {}) {
|
|
5289
5362
|
const opts = typeof arg === "string" ? { id: arg } : arg;
|
|
5290
5363
|
const intro = `Repair ${pr.url} on its existing branch ${pr.headRefName ?? "(see PR)"} at ${pr.headSha}. Do not open a new PR.`;
|
|
5291
5364
|
const resolution = "For code already on main, take main's version. Keep only this PR's own changes. Never change behavior. If resolving a conflict would change code behavior, stop and report needs-decision.";
|
|
5292
|
-
const
|
|
5365
|
+
const standalone = standalonePr(pr.baseRefName, opts.trunk);
|
|
5366
|
+
const report = prReviewed(pr) ? rerequestReview(pr, opts.id) : "Report done with the same PR URL.";
|
|
5367
|
+
const finish = (mode = "rebase") => `Run the relevant tests. ${pushRule(pr.headRefName, opts.id, mode)} ${report}`;
|
|
5293
5368
|
if (kind === "conflict")
|
|
5294
5369
|
return `${intro}
|
|
5295
|
-
${resolution}
|
|
5370
|
+
${resolution} ${conflictUpdate(pr.baseRefName, pr.headRefName, standalone)} ${finish(standalone ? "merge" : "rebase")}`;
|
|
5296
5371
|
if (kind === "checks") {
|
|
5297
5372
|
const failing = (pr.failingChecks ?? []).filter((c) => !opts.checks || opts.checks.includes(c.name));
|
|
5298
5373
|
const checks = failing.map((c) => `- ${c.name}${c.detailsUrl ? ` \u2014 ${c.detailsUrl}` : ""}`).join("\n");
|
|
@@ -5303,10 +5378,10 @@ These failing checks are human gates. They pass only when a person approves. Do
|
|
|
5303
5378
|
${resolution}
|
|
5304
5379
|
Latest failing checks:
|
|
5305
5380
|
${checks || "- Read the failing check from GitHub"}${gates}
|
|
5306
|
-
Read each check log. Fix a real failure. If it is a flake, rerun it at most once. If a check cannot pass until a person approves the change, it is a human gate: do not change code for it, and name it on your report with --human-gate "<check name>", once per check. ${finish}`;
|
|
5381
|
+
Read each check log. Fix a real failure. If it is a flake, rerun it at most once. If a check cannot pass until a person approves the change, it is a human gate: do not change code for it, and name it on your report with --human-gate "<check name>", once per check. ${finish()}`;
|
|
5307
5382
|
}
|
|
5308
5383
|
return `${intro}
|
|
5309
|
-
${resolution} Read the requested review changes and comments with gh pr view --comments. Address the feedback. If a comment needs a person's decision, report needs-decision instead of guessing. ${finish}`;
|
|
5384
|
+
${resolution} Read the requested review changes and comments with gh pr view --comments. Address the feedback. If a comment needs a person's decision, report needs-decision instead of guessing. ${finish()}`;
|
|
5310
5385
|
}
|
|
5311
5386
|
var PUSH_RETRIES, PUSH_REJECTED;
|
|
5312
5387
|
var init_pr_repair = __esm({
|
|
@@ -6134,7 +6209,7 @@ var init_types2 = __esm({
|
|
|
6134
6209
|
return Promise.resolve({ value: item, done: false });
|
|
6135
6210
|
if (this.closed)
|
|
6136
6211
|
return Promise.resolve({ value: void 0, done: true });
|
|
6137
|
-
return new Promise((
|
|
6212
|
+
return new Promise((resolve14) => this.waiters.push(resolve14));
|
|
6138
6213
|
}
|
|
6139
6214
|
};
|
|
6140
6215
|
}
|
|
@@ -6159,7 +6234,7 @@ var init_types2 = __esm({
|
|
|
6159
6234
|
return msg;
|
|
6160
6235
|
if (this.ended)
|
|
6161
6236
|
return void 0;
|
|
6162
|
-
await new Promise((
|
|
6237
|
+
await new Promise((resolve14) => this.waiter = resolve14);
|
|
6163
6238
|
this.waiter = null;
|
|
6164
6239
|
}
|
|
6165
6240
|
}
|
|
@@ -6298,20 +6373,20 @@ function startClaudeCli(invocation, opts) {
|
|
|
6298
6373
|
}
|
|
6299
6374
|
}
|
|
6300
6375
|
});
|
|
6301
|
-
const done = new Promise((
|
|
6376
|
+
const done = new Promise((resolve14) => {
|
|
6302
6377
|
child.on("error", (err) => {
|
|
6303
6378
|
events.push({ at: now(), type: "error", data: { message: err.message } });
|
|
6304
6379
|
events.close();
|
|
6305
|
-
|
|
6380
|
+
resolve14({ sessionId, error: err.message });
|
|
6306
6381
|
});
|
|
6307
6382
|
child.on("exit", (code) => {
|
|
6308
6383
|
events.close();
|
|
6309
6384
|
if (code === 0)
|
|
6310
|
-
|
|
6385
|
+
resolve14({ sessionId });
|
|
6311
6386
|
else {
|
|
6312
6387
|
const detail = stderrTail.trim().split("\n").at(-1);
|
|
6313
6388
|
const error = `claude exited ${code}${detail ? ` \u2014 ${detail}` : ""}`;
|
|
6314
|
-
|
|
6389
|
+
resolve14({ sessionId, error });
|
|
6315
6390
|
}
|
|
6316
6391
|
});
|
|
6317
6392
|
});
|
|
@@ -6404,25 +6479,25 @@ var init_claude = __esm({
|
|
|
6404
6479
|
});
|
|
6405
6480
|
|
|
6406
6481
|
// packages/adapters/dist/codex.js
|
|
6407
|
-
import * as
|
|
6482
|
+
import * as fs69 from "node:fs";
|
|
6408
6483
|
import * as os10 from "node:os";
|
|
6409
|
-
import * as
|
|
6484
|
+
import * as path56 from "node:path";
|
|
6410
6485
|
import { spawn as spawn6 } from "node:child_process";
|
|
6411
6486
|
function isolatedCodexHome() {
|
|
6412
|
-
const dir =
|
|
6413
|
-
|
|
6414
|
-
const auth =
|
|
6415
|
-
const realAuth =
|
|
6416
|
-
if (!
|
|
6487
|
+
const dir = path56.join(lobstahHome(), "codex-home");
|
|
6488
|
+
fs69.mkdirSync(dir, { recursive: true });
|
|
6489
|
+
const auth = path56.join(dir, "auth.json");
|
|
6490
|
+
const realAuth = path56.join(os10.homedir(), ".codex", "auth.json");
|
|
6491
|
+
if (!fs69.existsSync(auth) && fs69.existsSync(realAuth)) {
|
|
6417
6492
|
try {
|
|
6418
|
-
|
|
6493
|
+
fs69.symlinkSync(realAuth, auth);
|
|
6419
6494
|
} catch {
|
|
6420
|
-
|
|
6495
|
+
fs69.copyFileSync(realAuth, auth);
|
|
6421
6496
|
}
|
|
6422
6497
|
}
|
|
6423
|
-
const cfg =
|
|
6424
|
-
if (!
|
|
6425
|
-
|
|
6498
|
+
const cfg = path56.join(dir, "config.toml");
|
|
6499
|
+
if (!fs69.existsSync(cfg)) {
|
|
6500
|
+
fs69.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
|
|
6426
6501
|
}
|
|
6427
6502
|
return dir;
|
|
6428
6503
|
}
|
|
@@ -6474,7 +6549,7 @@ function startCodexCli(invoke, opts) {
|
|
|
6474
6549
|
let current;
|
|
6475
6550
|
let killed = false;
|
|
6476
6551
|
function runTurn(prompt, resumeThread) {
|
|
6477
|
-
return new Promise((
|
|
6552
|
+
return new Promise((resolve14) => {
|
|
6478
6553
|
const inv = invoke(codexExecArgs(opts, prompt, resumeThread));
|
|
6479
6554
|
const child = spawn6(inv.file, inv.argv, {
|
|
6480
6555
|
cwd: opts.cwd,
|
|
@@ -6505,14 +6580,14 @@ function startCodexCli(invoke, opts) {
|
|
|
6505
6580
|
}
|
|
6506
6581
|
}
|
|
6507
6582
|
});
|
|
6508
|
-
child.on("error", (err) =>
|
|
6583
|
+
child.on("error", (err) => resolve14(err.message));
|
|
6509
6584
|
child.on("exit", (code) => {
|
|
6510
6585
|
if (turnError)
|
|
6511
|
-
return
|
|
6586
|
+
return resolve14(turnError);
|
|
6512
6587
|
if (code === 0 || killed)
|
|
6513
|
-
return
|
|
6588
|
+
return resolve14(void 0);
|
|
6514
6589
|
const detail = stderrTail.trim().split("\n").at(-1);
|
|
6515
|
-
|
|
6590
|
+
resolve14(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
6516
6591
|
});
|
|
6517
6592
|
});
|
|
6518
6593
|
}
|
|
@@ -6724,7 +6799,7 @@ async function drive(run4, opts) {
|
|
|
6724
6799
|
let finalAt = 0;
|
|
6725
6800
|
let exit;
|
|
6726
6801
|
let abandon;
|
|
6727
|
-
const abandoned = new Promise((
|
|
6802
|
+
const abandoned = new Promise((resolve14) => abandon = resolve14);
|
|
6728
6803
|
const clearExit = () => {
|
|
6729
6804
|
if (!exit)
|
|
6730
6805
|
return;
|
|
@@ -6897,7 +6972,7 @@ var init_drive = __esm({
|
|
|
6897
6972
|
"use strict";
|
|
6898
6973
|
init_dist2();
|
|
6899
6974
|
WAITING_VERBS = ["needs-decision", "blocked", "paused"];
|
|
6900
|
-
sleep = (ms) => new Promise((
|
|
6975
|
+
sleep = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
6901
6976
|
}
|
|
6902
6977
|
});
|
|
6903
6978
|
|
|
@@ -7044,8 +7119,8 @@ var init_wallclock = __esm({
|
|
|
7044
7119
|
import { execFile as execFile4 } from "node:child_process";
|
|
7045
7120
|
import { promisify as promisify4 } from "node:util";
|
|
7046
7121
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7047
|
-
import * as
|
|
7048
|
-
import * as
|
|
7122
|
+
import * as fs70 from "node:fs";
|
|
7123
|
+
import * as path57 from "node:path";
|
|
7049
7124
|
function firstLine2(err) {
|
|
7050
7125
|
const stderr = err && typeof err === "object" && "stderr" in err ? String(err.stderr).trim() : "";
|
|
7051
7126
|
return (stderr || (err instanceof Error ? err.message : String(err))).replace(/\s+/g, " ").slice(0, 240);
|
|
@@ -7078,10 +7153,10 @@ function keepRemote(opts) {
|
|
|
7078
7153
|
if (existingPrUrl)
|
|
7079
7154
|
mergeEvidence(id, lane, { prUrl: existingPrUrl });
|
|
7080
7155
|
const registerWatch = async (url) => {
|
|
7081
|
-
const root =
|
|
7082
|
-
const localCli =
|
|
7156
|
+
const root = path57.resolve(path57.dirname(fileURLToPath4(import.meta.url)), "..", "..", "..");
|
|
7157
|
+
const localCli = path57.join(root, "apps", "cli", "dist", "main.js");
|
|
7083
7158
|
const args = ["watch", "add", url, "--for", id];
|
|
7084
|
-
if (
|
|
7159
|
+
if (fs70.existsSync(localCli))
|
|
7085
7160
|
await command(process.execPath, [localCli, ...args], 15e3);
|
|
7086
7161
|
else
|
|
7087
7162
|
await command("lobstah", args, 15e3);
|
|
@@ -7243,8 +7318,8 @@ var init_remote = __esm({
|
|
|
7243
7318
|
});
|
|
7244
7319
|
|
|
7245
7320
|
// packages/runner/dist/run.js
|
|
7246
|
-
import * as
|
|
7247
|
-
import * as
|
|
7321
|
+
import * as fs71 from "node:fs";
|
|
7322
|
+
import * as path58 from "node:path";
|
|
7248
7323
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
7249
7324
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7250
7325
|
function livePrState(url) {
|
|
@@ -7259,7 +7334,7 @@ function livePrState(url) {
|
|
|
7259
7334
|
}
|
|
7260
7335
|
function within(p, ms, fallback) {
|
|
7261
7336
|
let timer;
|
|
7262
|
-
const late = new Promise((
|
|
7337
|
+
const late = new Promise((resolve14) => timer = setTimeout(() => resolve14(fallback), ms));
|
|
7263
7338
|
return Promise.race([p, late]).finally(() => clearTimeout(timer));
|
|
7264
7339
|
}
|
|
7265
7340
|
function boundPr(descriptor, lane) {
|
|
@@ -7277,18 +7352,18 @@ function coldNote(cold, cwd, trunk) {
|
|
|
7277
7352
|
}
|
|
7278
7353
|
async function main(activeDir, lane, seams = {}) {
|
|
7279
7354
|
const deps = { ...defaultDeps, ...seams };
|
|
7280
|
-
const id =
|
|
7355
|
+
const id = path58.basename(activeDir);
|
|
7281
7356
|
if (isTrapCatch(id, lane))
|
|
7282
7357
|
throw new Error(`refusing headless runner for trap catch ${id}`);
|
|
7283
7358
|
const status = (verb, note) => appendStatus(id, lane, verb, note);
|
|
7284
7359
|
const cfg = loadConfig();
|
|
7285
|
-
const descriptor = JSON.parse(
|
|
7360
|
+
const descriptor = JSON.parse(fs71.readFileSync(path58.join(activeDir, "descriptor.json"), "utf8"));
|
|
7286
7361
|
const resolved = resolveDispatch(descriptor, cfg);
|
|
7287
7362
|
const repo = cfg.repos[descriptor.repo];
|
|
7288
|
-
const briefFile =
|
|
7289
|
-
if (!
|
|
7290
|
-
|
|
7291
|
-
const brief =
|
|
7363
|
+
const briefFile = path58.join(activeDir, "brief.md");
|
|
7364
|
+
if (!fs71.existsSync(briefFile))
|
|
7365
|
+
fs71.writeFileSync(briefFile, descriptor.brief);
|
|
7366
|
+
const brief = fs71.readFileSync(briefFile, "utf8");
|
|
7292
7367
|
const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
|
|
7293
7368
|
const wake = process.env.LOBSTAH_WAKE;
|
|
7294
7369
|
const runnerInfo = {
|
|
@@ -7296,7 +7371,7 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7296
7371
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7297
7372
|
attempts
|
|
7298
7373
|
};
|
|
7299
|
-
|
|
7374
|
+
fs71.writeFileSync(path58.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
|
|
7300
7375
|
if (descriptor.systemRepair && descriptor.pr?.url) {
|
|
7301
7376
|
const state = deps.prState(descriptor.pr.url) ?? readPr(parsePrRef(descriptor.pr.url)?.key ?? "")?.state;
|
|
7302
7377
|
if (state === "MERGED" || state === "CLOSED") {
|
|
@@ -7313,14 +7388,14 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7313
7388
|
resolvedHarness: resolved.harness,
|
|
7314
7389
|
envResume: process.env.LOBSTAH_RESUME
|
|
7315
7390
|
});
|
|
7316
|
-
const wtFile =
|
|
7391
|
+
const wtFile = path58.join(activeDir, "worktree.json");
|
|
7317
7392
|
let recorded;
|
|
7318
7393
|
let choice;
|
|
7319
|
-
if (
|
|
7320
|
-
recorded = JSON.parse(
|
|
7321
|
-
if (!
|
|
7394
|
+
if (fs71.existsSync(wtFile)) {
|
|
7395
|
+
recorded = JSON.parse(fs71.readFileSync(wtFile, "utf8")).path;
|
|
7396
|
+
if (!fs71.existsSync(recorded))
|
|
7322
7397
|
throw new Error(`recorded worktree ${recorded} is gone`);
|
|
7323
|
-
} else if (
|
|
7398
|
+
} else if (fs71.existsSync(worktreePath(id))) {
|
|
7324
7399
|
throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
|
|
7325
7400
|
} else if (descriptor.followUp && cfg.limits.reuseWorktree !== false) {
|
|
7326
7401
|
choice = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
|
|
@@ -7355,13 +7430,13 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7355
7430
|
type: "runner",
|
|
7356
7431
|
data: { worktree: cwd, worktreeOf: choice.owner, setupRan }
|
|
7357
7432
|
});
|
|
7358
|
-
|
|
7433
|
+
fs71.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
|
|
7359
7434
|
} else {
|
|
7360
7435
|
cwd = await deps.allocate(repo, id, descriptor.systemRepair ? descriptor.pr?.headRefName : void 0);
|
|
7361
7436
|
acquireWorktreeLock(cwd, id, lane);
|
|
7362
|
-
|
|
7437
|
+
fs71.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
|
|
7363
7438
|
}
|
|
7364
|
-
const worktreeOf = JSON.parse(
|
|
7439
|
+
const worktreeOf = JSON.parse(fs71.readFileSync(wtFile, "utf8")).of;
|
|
7365
7440
|
mergeEvidence(id, lane, { worktree: cwd, worktreeOf });
|
|
7366
7441
|
const existingPr = boundPr(descriptor, lane);
|
|
7367
7442
|
if (existingPr)
|
|
@@ -7395,14 +7470,14 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7395
7470
|
const envNudge = [process.env.LOBSTAH_NUDGE, wakeNote()].filter(Boolean).join("\n\n") || void 0;
|
|
7396
7471
|
const planNudge = plan.cold && !envNudge ? coldNote(plan.cold, cwd, repo.trunk) : void 0;
|
|
7397
7472
|
const promptWith = (nudge) => buildPrompt(brief, { id, nudge, attachments: descriptor.attachments, existingPr });
|
|
7398
|
-
const binDir =
|
|
7399
|
-
const workerPath =
|
|
7473
|
+
const binDir = path58.resolve(path58.dirname(fileURLToPath5(import.meta.url)), "..", "..", "..", "bin");
|
|
7474
|
+
const workerPath = fs71.existsSync(path58.join(binDir, "lobstah")) ? `${binDir}${path58.delimiter}${process.env.PATH ?? ""}` : void 0;
|
|
7400
7475
|
let wallClockHit = false;
|
|
7401
7476
|
let current;
|
|
7402
|
-
const wallFile =
|
|
7477
|
+
const wallFile = path58.join(activeDir, "wallclock.json");
|
|
7403
7478
|
const wallState = (() => {
|
|
7404
7479
|
try {
|
|
7405
|
-
return JSON.parse(
|
|
7480
|
+
return JSON.parse(fs71.readFileSync(wallFile, "utf8"));
|
|
7406
7481
|
} catch {
|
|
7407
7482
|
return { elapsedMs: 0, windowMs: resolved.limits.wallClockSecs * 1e3 };
|
|
7408
7483
|
}
|
|
@@ -7418,8 +7493,8 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7418
7493
|
const maxWallMs = Math.max(resolved.limits.wallClockSecs ?? 0, cfg.limits.maxWallClockSecs ?? (resolved.limits.wallClockSecs ?? 0) * 4) * 1e3;
|
|
7419
7494
|
const persistWall = (elapsedMs, windowMs) => {
|
|
7420
7495
|
const tmp = `${wallFile}.tmp-${process.pid}`;
|
|
7421
|
-
|
|
7422
|
-
|
|
7496
|
+
fs71.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
|
|
7497
|
+
fs71.renameSync(tmp, wallFile);
|
|
7423
7498
|
};
|
|
7424
7499
|
const madeProgress = () => {
|
|
7425
7500
|
const activity = readActivity(id, lane);
|
|
@@ -7706,17 +7781,17 @@ __export(dist_exports, {
|
|
|
7706
7781
|
startedBy: () => startedBy,
|
|
7707
7782
|
stopPids: () => stopPids
|
|
7708
7783
|
});
|
|
7709
|
-
import * as
|
|
7710
|
-
import * as
|
|
7784
|
+
import * as fs72 from "node:fs";
|
|
7785
|
+
import * as path59 from "node:path";
|
|
7711
7786
|
function runRunner(activeDirArg2, laneArg2) {
|
|
7712
7787
|
const lane = laneArg2 === "chore" ? "chore" : "work";
|
|
7713
|
-
const id =
|
|
7788
|
+
const id = path59.basename(activeDirArg2);
|
|
7714
7789
|
main(activeDirArg2, lane, { reap: () => reapStarted(process.pid) }).catch((err) => {
|
|
7715
7790
|
try {
|
|
7716
7791
|
if (!isFinished(id, lane)) {
|
|
7717
7792
|
appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
|
|
7718
7793
|
}
|
|
7719
|
-
|
|
7794
|
+
fs72.renameSync(activeDirArg2, path59.join(laneDirs(lane).done, id));
|
|
7720
7795
|
} catch {
|
|
7721
7796
|
}
|
|
7722
7797
|
process.exitCode = 1;
|
|
@@ -7744,11 +7819,11 @@ var init_dist5 = __esm({
|
|
|
7744
7819
|
// apps/cli/src/main.ts
|
|
7745
7820
|
init_dist2();
|
|
7746
7821
|
init_dist3();
|
|
7747
|
-
import * as
|
|
7822
|
+
import * as fs73 from "node:fs";
|
|
7748
7823
|
import * as os11 from "node:os";
|
|
7749
|
-
import * as
|
|
7824
|
+
import * as path60 from "node:path";
|
|
7750
7825
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
7751
|
-
import { spawnSync as
|
|
7826
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
7752
7827
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
7753
7828
|
|
|
7754
7829
|
// packages/supervisor/dist/liveness.js
|
|
@@ -8383,15 +8458,15 @@ async function daemon(intervalMs = 5e3, log = console.log, hooks = {}) {
|
|
|
8383
8458
|
} catch (err) {
|
|
8384
8459
|
log(`tick error: ${err instanceof Error ? err.message : String(err)}`);
|
|
8385
8460
|
}
|
|
8386
|
-
await new Promise((
|
|
8461
|
+
await new Promise((resolve14) => {
|
|
8387
8462
|
const timer = setTimeout(() => {
|
|
8388
8463
|
wake = void 0;
|
|
8389
|
-
|
|
8464
|
+
resolve14();
|
|
8390
8465
|
}, intervalMs);
|
|
8391
8466
|
wake = () => {
|
|
8392
8467
|
wake = void 0;
|
|
8393
8468
|
clearTimeout(timer);
|
|
8394
|
-
setTimeout(
|
|
8469
|
+
setTimeout(resolve14, 50);
|
|
8395
8470
|
};
|
|
8396
8471
|
});
|
|
8397
8472
|
}
|
|
@@ -8615,6 +8690,9 @@ var PickupState = class {
|
|
|
8615
8690
|
}
|
|
8616
8691
|
};
|
|
8617
8692
|
|
|
8693
|
+
// apps/pick/dist/sources/github.js
|
|
8694
|
+
init_dist2();
|
|
8695
|
+
|
|
8618
8696
|
// apps/pick/dist/pr-evidence.js
|
|
8619
8697
|
init_dist2();
|
|
8620
8698
|
import * as fs35 from "node:fs";
|
|
@@ -8717,7 +8795,7 @@ When the change is complete, commit it, push the branch, and open a PR against $
|
|
|
8717
8795
|
repoKey: this.cfg.key,
|
|
8718
8796
|
title: pr.title,
|
|
8719
8797
|
followUp: uuid,
|
|
8720
|
-
brief: `Address the review feedback on ${pr.html_url} (branch ${pr.head.ref}). Check out that branch in this worktree, read the full review discussion with \`gh pr view ${pr.number} --comments\`, implement what the feedback asks, and push. Reply to review threads where a reply is warranted. When the feedback is addressed, report status done
|
|
8798
|
+
brief: withBriefHooks(`Address the review feedback on ${pr.html_url} (branch ${pr.head.ref}). Check out that branch in this worktree, read the full review discussion with \`gh pr view ${pr.number} --comments\`, implement what the feedback asks, and push. Reply to review threads where a reply is warranted. When the feedback is addressed, report status done.`, loadConfig().repos[this.cfg.key], "review")
|
|
8721
8799
|
});
|
|
8722
8800
|
}
|
|
8723
8801
|
return items;
|
|
@@ -8842,6 +8920,7 @@ When the change is complete, commit it, push the branch, and open a PR against $
|
|
|
8842
8920
|
author: pr.user.login,
|
|
8843
8921
|
headSha: pr.head.sha,
|
|
8844
8922
|
headRef: pr.head.ref,
|
|
8923
|
+
baseRef: pr.base?.ref,
|
|
8845
8924
|
labels: pr.labels.map((l) => l.name),
|
|
8846
8925
|
assignees: pr.assignees.map((a) => a.login),
|
|
8847
8926
|
reviews: reviews.map((r) => ({ id: r.id, author: r.user.login, state: r.state, sha: r.commit_id })),
|
|
@@ -9399,12 +9478,16 @@ function staleApprovals(policy, pr) {
|
|
|
9399
9478
|
function approvalDedupKey(pr, review) {
|
|
9400
9479
|
return `${pr.number}#${review.id}@${pr.headSha}`;
|
|
9401
9480
|
}
|
|
9402
|
-
function rebaseBrief(pr, id) {
|
|
9481
|
+
function rebaseBrief(pr, id, trunk) {
|
|
9482
|
+
const standalone = standalonePr(pr.baseRef, trunk);
|
|
9403
9483
|
return [
|
|
9404
|
-
`
|
|
9484
|
+
`Bring the branch ${pr.headRef} of ${pr.url} up to date with its base branch${pr.baseRef ? ` ${pr.baseRef}` : ""}, resolving any conflicts`,
|
|
9405
9485
|
`in a way that preserves the intent of both sides.`,
|
|
9406
|
-
|
|
9407
|
-
|
|
9486
|
+
conflictUpdate(pr.baseRef, pr.headRef, standalone),
|
|
9487
|
+
pushRule(pr.headRef, id, standalone ? "merge" : "rebase"),
|
|
9488
|
+
`Do not merge the PR. Do not change anything beyond conflict resolution.`,
|
|
9489
|
+
// A reviewed PR's review goes stale at the new head: ask its reviewers again and park.
|
|
9490
|
+
pr.reviews.length > 0 ? rerequestReview({ url: pr.url, number: pr.number }, id, [...new Set(pr.reviews.map((r) => r.author))]) : `When pushed, report status done.`
|
|
9408
9491
|
].join(" ");
|
|
9409
9492
|
}
|
|
9410
9493
|
function choreVerdict(uuid) {
|
|
@@ -9509,7 +9592,7 @@ async function mergeLoop(ms, policy, state, log = () => {
|
|
|
9509
9592
|
enqueue({
|
|
9510
9593
|
id: uuid,
|
|
9511
9594
|
repo: ms.repoKey(),
|
|
9512
|
-
brief: rebaseBrief(pr, uuid),
|
|
9595
|
+
brief: withBriefHooks(rebaseBrief(pr, uuid, loadConfig().repos[ms.repoKey()]?.trunk), loadConfig().repos[ms.repoKey()], "rebase"),
|
|
9513
9596
|
// The chore works on this PR: the runner pushes no other branch and opens no PR.
|
|
9514
9597
|
pr: { url: pr.url, headRefName: pr.headRef, headSha: pr.headSha }
|
|
9515
9598
|
}, "chore");
|
|
@@ -9583,7 +9666,8 @@ function spawnContinuation(w, pending, log, fixedBrief, targetOverride) {
|
|
|
9583
9666
|
return void 0;
|
|
9584
9667
|
}
|
|
9585
9668
|
const id = randomUUID4();
|
|
9586
|
-
const
|
|
9669
|
+
const filled = (fixedBrief ?? w.brief ?? DEFAULT_BRIEF).replaceAll("{key}", w.key).replaceAll("{summaries}", pending.map((e) => `- ${e.summary ?? `event ${String(e.seq)}`}`).join("\n")).replaceAll("{events}", JSON.stringify(pending, null, 2));
|
|
9670
|
+
const brief = w.key.startsWith("pr:") ? withBriefHooks(filled, loadConfig().repos[descriptor.repo], "ciFix") : filled;
|
|
9587
9671
|
const claim = readSessionClaim(target, targetLane);
|
|
9588
9672
|
const deliveredTo = readEvidence(target, targetLane).deliveredTo;
|
|
9589
9673
|
const trapAddress = claim?.by.startsWith("wt:") ? claim.by : deliveredTo?.startsWith("wt:") ? deliveredTo : void 0;
|
|
@@ -10236,6 +10320,7 @@ function livenessView(id, lane) {
|
|
|
10236
10320
|
...lastCommit ? { lastCommit } : {},
|
|
10237
10321
|
...ahead !== void 0 && trunk ? { aheadTrunk: `${ahead} ahead of ${trunk}` } : {},
|
|
10238
10322
|
...ev.prUrl ? { draftPr: ev.prUrl } : {},
|
|
10323
|
+
...ev.prUrls && ev.prUrls.length > 1 ? { prs: ev.prUrls.join(", ") } : {},
|
|
10239
10324
|
...readStatusLog(id, lane).at(-1)?.at ? { updated: readStatusLog(id, lane).at(-1).at } : {}
|
|
10240
10325
|
};
|
|
10241
10326
|
}
|
|
@@ -10686,6 +10771,17 @@ function holdCancelledRepair(id, now2 = /* @__PURE__ */ new Date()) {
|
|
|
10686
10771
|
}
|
|
10687
10772
|
return held;
|
|
10688
10773
|
}
|
|
10774
|
+
function progressStreak(pr) {
|
|
10775
|
+
const s = pr.repairStreak ?? { count: 0 };
|
|
10776
|
+
if (s.stoppedHead !== void 0) return s.stoppedHead === pr.headSha ? s : { count: 0 };
|
|
10777
|
+
const id = s.lastRepairId;
|
|
10778
|
+
if (!id) return { count: s.count };
|
|
10779
|
+
const lane = laneOf(id) ?? "chore";
|
|
10780
|
+
if (readStatusLog(id, lane).at(-1)?.verb !== "done") return { count: s.count };
|
|
10781
|
+
const own = (readEvidence(id, lane).commits ?? []).some((c) => pr.headSha.startsWith(c.split(" ")[0]));
|
|
10782
|
+
if (!own && s.startHead !== void 0 && s.startHead !== pr.headSha) return { count: 0 };
|
|
10783
|
+
return { count: s.count + 1 };
|
|
10784
|
+
}
|
|
10689
10785
|
function trapAddressOf(id) {
|
|
10690
10786
|
const claim = readSessionClaim(id, "work");
|
|
10691
10787
|
const deliveredTo = readEvidence(id, "work").deliveredTo;
|
|
@@ -10805,10 +10901,15 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
|
10805
10901
|
Date.parse(pr.baseSince ?? pr.observedAt) || now2,
|
|
10806
10902
|
kind === "checks" ? Date.parse(pr.failingSince ?? pr.observedAt) || now2 : 0
|
|
10807
10903
|
);
|
|
10904
|
+
const approvedAt = kind === "conflict" ? Date.parse(pr.review?.lastApprovalAt ?? "") || 0 : 0;
|
|
10808
10905
|
if (now2 < changed + settleMs) {
|
|
10809
10906
|
const until2 = new Date(changed + settleMs).toISOString();
|
|
10810
10907
|
return wait({ heldBy: "settle", reason: `settling: the head, base, or failing checks changed at ${new Date(changed).toISOString()}` }, until2);
|
|
10811
10908
|
}
|
|
10909
|
+
if (now2 < approvedAt + settleMs) {
|
|
10910
|
+
const until2 = new Date(approvedAt + settleMs).toISOString();
|
|
10911
|
+
return wait({ heldBy: "settle", reason: `settling: the PR was approved at ${new Date(approvedAt).toISOString()}` }, until2);
|
|
10912
|
+
}
|
|
10812
10913
|
const chain = chainState(owner);
|
|
10813
10914
|
if (chain.busy) return;
|
|
10814
10915
|
if (kind === "checks") {
|
|
@@ -10829,9 +10930,29 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
|
10829
10930
|
const trap = address ? readTrap(address.slice(3)) : void 0;
|
|
10830
10931
|
const liveTrap2 = trap && now2 - trapLastSeen(trap) <= config.soak.ttlSecs * 1e3 ? trap : void 0;
|
|
10831
10932
|
const waitSecs = Math.max(0, Number.isFinite(cfg.repairTrapWaitSecs) ? cfg.repairTrapWaitSecs : 600);
|
|
10933
|
+
const streak = progressStreak(pr);
|
|
10934
|
+
const maxNoProgress = Number.isFinite(cfg.maxRepairsWithoutProgress) ? Math.max(1, Math.floor(cfg.maxRepairsWithoutProgress)) : 2;
|
|
10935
|
+
if (streak.count >= maxNoProgress) {
|
|
10936
|
+
const reason = `no merge progress after ${streak.count} repair(s) in a row ([watch].maxRepairsWithoutProgress = ${maxNoProgress}); \`lobstah watch release ${watch2.key}\` repairs again`;
|
|
10937
|
+
writePr({
|
|
10938
|
+
...pr,
|
|
10939
|
+
repairStreak: { count: streak.count, stoppedHead: pr.headSha },
|
|
10940
|
+
repair: { headSha: pr.headSha, kind, attempts, maxAttempts: limit, status: "gave-up", reason }
|
|
10941
|
+
});
|
|
10942
|
+
postNotice({
|
|
10943
|
+
kind: "repair-stopped",
|
|
10944
|
+
text: `${pr.key}: repairs stopped \u2014 ${reason} (${pr.url})`,
|
|
10945
|
+
refId: owner,
|
|
10946
|
+
repo: target.repo,
|
|
10947
|
+
dedupeKey: `repair-stopped-${pr.key}-${pr.headSha}`
|
|
10948
|
+
});
|
|
10949
|
+
log(`repair ${watch2.key}: stopped \u2014 ${reason}`);
|
|
10950
|
+
return;
|
|
10951
|
+
}
|
|
10832
10952
|
const id = randomUUID5();
|
|
10833
10953
|
writePr({
|
|
10834
10954
|
...pr,
|
|
10955
|
+
repairStreak: { count: streak.count, lastRepairId: id, startHead: pr.headSha },
|
|
10835
10956
|
repair: {
|
|
10836
10957
|
headSha: pr.headSha,
|
|
10837
10958
|
kind,
|
|
@@ -10846,7 +10967,16 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
|
10846
10967
|
}
|
|
10847
10968
|
});
|
|
10848
10969
|
try {
|
|
10849
|
-
const brief =
|
|
10970
|
+
const brief = withBriefHooks(
|
|
10971
|
+
repairBrief(pr, kind, {
|
|
10972
|
+
id,
|
|
10973
|
+
gates,
|
|
10974
|
+
trunk: config.repos[target.repo]?.trunk,
|
|
10975
|
+
...kind === "checks" && fresh.length > 0 ? { checks: fresh } : {}
|
|
10976
|
+
}),
|
|
10977
|
+
config.repos[target.repo],
|
|
10978
|
+
kind
|
|
10979
|
+
);
|
|
10850
10980
|
enqueue({
|
|
10851
10981
|
id,
|
|
10852
10982
|
repo: target.repo,
|
|
@@ -11216,6 +11346,7 @@ function describeDispatch(id, lane, bucket, staleSecs) {
|
|
|
11216
11346
|
at: last?.at ?? (bucket === "queued" ? queuedAt(id, lane) : claimedAt),
|
|
11217
11347
|
...answered ? { answeredAt: answered } : {},
|
|
11218
11348
|
prUrl: evidence.prUrl,
|
|
11349
|
+
...evidence.prUrls && evidence.prUrls.length > 1 ? { prUrls: evidence.prUrls } : {},
|
|
11219
11350
|
pr: evidence.pr,
|
|
11220
11351
|
...bucket === "queued" ? {} : worktreeView(id, lane),
|
|
11221
11352
|
...bucket === "queued" ? {} : livenessView(id, lane),
|
|
@@ -11228,6 +11359,15 @@ function awaitingView(id) {
|
|
|
11228
11359
|
const e = awaitingReply(id);
|
|
11229
11360
|
return e ? { awaitingReply: { sentAt: e.sentAt, from: e.from, line: e.line } } : {};
|
|
11230
11361
|
}
|
|
11362
|
+
function prsOf(chain) {
|
|
11363
|
+
const urls = [...new Set(chain.flatMap((d) => d.prUrls ?? []))];
|
|
11364
|
+
if (urls.length < 2) return void 0;
|
|
11365
|
+
return urls.map((u) => {
|
|
11366
|
+
const ref = parsePrRef(u);
|
|
11367
|
+
const record = ref ? readPr(ref.key) : void 0;
|
|
11368
|
+
return record ? `${prBadge(record).text} ${u}` : u;
|
|
11369
|
+
});
|
|
11370
|
+
}
|
|
11231
11371
|
function prStateOf(chain) {
|
|
11232
11372
|
const records = chain.map((d) => parsePrRef(d.prUrl ?? d.pr?.url ?? "")).filter((ref) => ref !== void 0).map((ref) => readPr(ref.key)).filter((pr) => pr !== void 0);
|
|
11233
11373
|
const record = records.sort(prNewestFirst(records))[0];
|
|
@@ -11387,12 +11527,14 @@ function buildTendReport(now2 = Date.now()) {
|
|
|
11387
11527
|
if (!fresh) continue;
|
|
11388
11528
|
const prUrl = chain.map((d) => d.prUrl).find((u) => u !== void 0);
|
|
11389
11529
|
const watch2 = chain.map((d) => watchByDispatch.get(d.id)).find((w) => w !== void 0);
|
|
11390
|
-
|
|
11530
|
+
const prs = prsOf(chain);
|
|
11531
|
+
stories.push({ key, dispatches: chain, prUrl, ...prs ? { prs } : {}, prState: prStateOf(chain), gate: gateFor(entry.uuid, prUrl), watch: watch2 });
|
|
11391
11532
|
}
|
|
11392
11533
|
const direct = (d) => ({
|
|
11393
11534
|
key: "(direct)",
|
|
11394
11535
|
dispatches: [d],
|
|
11395
11536
|
prUrl: d.prUrl,
|
|
11537
|
+
...prsOf([d]) ? { prs: prsOf([d]) } : {},
|
|
11396
11538
|
prState: prStateOf([d]),
|
|
11397
11539
|
gate: gateFor(d.id, d.prUrl),
|
|
11398
11540
|
watch: watchByDispatch.get(d.id)
|
|
@@ -11565,7 +11707,7 @@ function renderTend(r) {
|
|
|
11565
11707
|
dispatches: s.dispatches.map(
|
|
11566
11708
|
(d) => `${d.id.slice(0, 8)}:${d.outOfTimeWorkSaved ? "out of time, work saved" : d.state}` + (d.state === "held" && d.note ? ` (${named(d.note.replace(/^held: /, ""))})` : "") + (d.answeredAt ? ` (answered ${Math.max(0, Math.round((Date.now() - Date.parse(d.answeredAt)) / 6e4))}m ago)` : "") + (d.waiting ? ` (${waitingText(d.waiting)})` : "") + (d.awaitingReply ? ` (awaiting reply \xB7 ${ageLabel(Date.now() - (Date.parse(d.awaitingReply.sentAt) || Date.now()))})` : "")
|
|
11567
11709
|
).join(" \u2192 "),
|
|
11568
|
-
pr: s.prState ? `${s.prState} ${s.prUrl ?? ""}`.trim() : s.prUrl ?? "",
|
|
11710
|
+
pr: s.prs ? s.prs.join("; ") : s.prState ? `${s.prState} ${s.prUrl ?? ""}`.trim() : s.prUrl ?? "",
|
|
11569
11711
|
gate: s.gate ?? "",
|
|
11570
11712
|
watch: s.watch ?? "",
|
|
11571
11713
|
// Under the verb and note: what the live dispatch is doing now.
|
|
@@ -11913,7 +12055,7 @@ async function pruneGlassState() {
|
|
|
11913
12055
|
if (readStateText() === void 0) return void 0;
|
|
11914
12056
|
const first = readGlassState();
|
|
11915
12057
|
if (first) return first;
|
|
11916
|
-
await new Promise((
|
|
12058
|
+
await new Promise((resolve14) => setTimeout(resolve14, 50));
|
|
11917
12059
|
const second = readGlassState();
|
|
11918
12060
|
if (!second) removeGlassState();
|
|
11919
12061
|
return second;
|
|
@@ -11941,12 +12083,12 @@ function pidAlive3(pid) {
|
|
|
11941
12083
|
}
|
|
11942
12084
|
}
|
|
11943
12085
|
function probeVersionEndpoint(port, timeoutMs) {
|
|
11944
|
-
return new Promise((
|
|
12086
|
+
return new Promise((resolve14) => {
|
|
11945
12087
|
let settled = false;
|
|
11946
12088
|
const finish = (info) => {
|
|
11947
12089
|
if (settled) return;
|
|
11948
12090
|
settled = true;
|
|
11949
|
-
|
|
12091
|
+
resolve14(info);
|
|
11950
12092
|
};
|
|
11951
12093
|
const req = http.get(`${glassUrl(port)}/api/version`, { timeout: timeoutMs }, (res) => {
|
|
11952
12094
|
if (res.statusCode !== 200) {
|
|
@@ -11978,12 +12120,12 @@ function probeVersionEndpoint(port, timeoutMs) {
|
|
|
11978
12120
|
});
|
|
11979
12121
|
}
|
|
11980
12122
|
function probeLegacyGlass(port, timeoutMs) {
|
|
11981
|
-
return new Promise((
|
|
12123
|
+
return new Promise((resolve14) => {
|
|
11982
12124
|
let settled = false;
|
|
11983
12125
|
const finish = (info) => {
|
|
11984
12126
|
if (settled) return;
|
|
11985
12127
|
settled = true;
|
|
11986
|
-
|
|
12128
|
+
resolve14(info);
|
|
11987
12129
|
};
|
|
11988
12130
|
const req = http.get(`${glassUrl(port)}/data`, { timeout: timeoutMs }, (res) => {
|
|
11989
12131
|
if (res.statusCode !== 200) {
|
|
@@ -12026,7 +12168,7 @@ function glassLines(port, info, already = false) {
|
|
|
12026
12168
|
...info.version !== lobstahVersion() ? { stale: `${info.version} (run lobstah glass stop && lobstah glass --detach)` } : {}
|
|
12027
12169
|
};
|
|
12028
12170
|
}
|
|
12029
|
-
var delay = (ms) => new Promise((
|
|
12171
|
+
var delay = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
12030
12172
|
async function terminate(pid, timeoutMs) {
|
|
12031
12173
|
try {
|
|
12032
12174
|
process.kill(pid, "SIGTERM");
|
|
@@ -12609,8 +12751,14 @@ function planMergeRelease(now2 = Date.now(), batch = Infinity) {
|
|
|
12609
12751
|
return ownerOf.get(id);
|
|
12610
12752
|
};
|
|
12611
12753
|
const candidates = /* @__PURE__ */ new Map();
|
|
12754
|
+
const openSibling = (id) => {
|
|
12755
|
+
const k = all.get(id);
|
|
12756
|
+
const urls = k ? dispatchPrUrls(readEvidence(id, k.lane)) : [];
|
|
12757
|
+
return urls.length > 1 && urls.some((u) => readPr(parsePrRef(u).key)?.state === "OPEN");
|
|
12758
|
+
};
|
|
12612
12759
|
for (const pr of merged.values()) {
|
|
12613
12760
|
for (const id of pr.owners) {
|
|
12761
|
+
if (openSibling(id)) continue;
|
|
12614
12762
|
const chain = chains.get(id) ?? /* @__PURE__ */ new Set([id]);
|
|
12615
12763
|
const home = owner(id);
|
|
12616
12764
|
const add = (o) => {
|
|
@@ -13501,7 +13649,7 @@ import { randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual2 } from
|
|
|
13501
13649
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13502
13650
|
|
|
13503
13651
|
// apps/cli/src/glass-page.generated.ts
|
|
13504
|
-
var GLASS_PAGE = '<!doctype html>\n<html>\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width,initial-scale=1" />\n <title>spyglass</title>\n <link rel="icon" type="image/png" href="/icon.png" />\n <style>:root{--bg: #0e1116;--card: #161b22;--line: #2b3240;--line-hover: #3a455a;--row-hover: #1c2330;--inset: #10151c;--seg-on: #26436b;--scrim: rgba(4, 7, 11, 0.72);--fg: #dbe2ea;--dim: #8b96a5;--ok: #4fc17c;--warn: #e2b93d;--bad: #e26d5c;--link: #6cb2e2;--on-fill: #fff;--pr-merged: #8957e5;--pr-merged-fg: #ab7df8;--pr-open: #238636;--pr-draft: #6e7681;--pr-closed: #da3633;--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;--font-size: 13px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--page-pad-block: 14px;--radius: 6px;--card-radius: 8px;--card-pad: 12px 14px;--card-min: 300px;--deck-card-min: 240px;--badge-radius: 5px;--badge-pad: 1px 7px;--badge-font: 11px;--modal-radius: 10px;--modal-width: 780px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:var(--font-size)/1.45 var(--font-mono);padding-block:var(--page-pad-block);padding-inline:var(--space-4);display:flex;flex-direction:column;min-height:100vh}h1{font-size:15px;margin:0 0 10px}h1 .dim{color:var(--dim);font-weight:normal}h2{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;margin:20px 0 6px}.chips,.controls{display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center}.controls{margin:0 0 14px}.chips{margin-bottom:8px}.chip{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:3px 9px}.controls select,.newtrap select,.controls input[type=search]{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);color:var(--fg);font:inherit;padding:4px 8px}.controls input[type=search]{flex:1 1 140px;min-width:120px;max-width:340px}#chain-control{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}#f-chain{margin:0;width:14px;height:14px;accent-color:var(--link)}.tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--line);margin:8px 0 10px}.tabs a{color:var(--dim);padding:6px 11px;border-bottom:2px solid transparent}.tabs a.on{color:var(--fg);border-color:var(--link)}.tabpage{display:none}.tabpage.on{display:block;flex:1 0 auto}.headerline{display:flex;align-items:center;gap:var(--space-2)}.headerline h1{flex:1}#settings-slot{min-width:34px;text-align:right;color:var(--dim)}#settings-slot #gearbtn{margin-left:0}.deckgrid{display:flex;flex-direction:column;gap:var(--space-3)}.deckgrid h2{margin:5px 0}.deckgrid section{width:100%;min-width:0}.deckline{padding:2px 0;border-top:1px solid var(--line);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.deckline.acked,.card.acked,.rowhead.acked,.badge.acked{opacity:0.55}.deckmore{color:var(--dim);font-size:11px;display:inline-block;margin-top:4px}.deckline.click{cursor:pointer}.deckline.click:hover{background:var(--row-hover)}.deckgrid h2 a{color:inherit}.deckgrid h2 a:hover{color:var(--fg)}.deckgrid .cards{grid-template-columns:repeat(auto-fill,minmax(min(var(--deck-card-min),100%),1fr));gap:10px}.deckstack{margin-bottom:9px}.deckstack>.dim{font-size:11px;margin:3px 0 5px}.seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}.seg button{background:var(--card);border:none;color:var(--dim);font:inherit;padding:4px 11px;cursor:pointer}.seg button.on{background:var(--seg-on);color:var(--fg)}.wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}th,td{text-align:left;padding:4px 9px;border-top:1px solid var(--line);vertical-align:top;white-space:nowrap}td.grow{white-space:normal;word-break:break-word;min-width:140px}th{color:var(--dim);font-weight:normal;border-top:none;font-size:11px}tr.rowhead{cursor:pointer}tr.rowhead:hover{background:var(--row-hover)}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--card-min),100%),1fr));gap:var(--space-3)}.card{background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad);cursor:pointer;min-width:0;overflow:hidden;overflow-wrap:anywhere}.card:hover{border-color:var(--line-hover)}.card .top{display:flex;justify-content:space-between;gap:var(--space-3);align-items:center;min-height:22px}.card .top>b,.card .top>.prname{flex:1 1000000 auto;min-width:min(10ch,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .top .badge{flex:0 0 auto}.card .top .badge.long{flex:0 1 auto;min-width:min(12ch,100%);max-width:min(26ch,100%);overflow:hidden;text-overflow:ellipsis}.card .meta{color:var(--dim);font-size:12px;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.card .note{margin-top:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.activity{margin-top:4px;font-size:12px;color:var(--fg);opacity:0.85}.waiting{margin-top:4px;font-size:12px;color:var(--dim)}.activity.stale{color:var(--dim);opacity:1}.card .foot{margin-top:8px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;color:var(--dim);font-size:12px}.card .foot>*{min-width:0}.card .foot .footact{margin-left:auto}.btn{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:1px 8px;cursor:pointer;white-space:nowrap}.btn:hover{color:var(--fg);border-color:var(--line-hover)}.badge{border-radius:var(--badge-radius);padding:var(--badge-pad);font-size:var(--badge-font);line-height:1.5;border:1px solid var(--line);white-space:nowrap;display:inline-block}.badge.pr-merged{background:var(--pr-merged);border-color:var(--pr-merged-fg);color:var(--on-fill)}.badge.pr-open{background:var(--pr-open);border-color:var(--pr-open);color:var(--on-fill)}.badge.pr-draft{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.pr-closed{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-conflicts{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-behind{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.unreported{font-weight:normal;font-size:10px;padding:0 5px;vertical-align:1px}.v-done{color:var(--ok)}.v-working,.v-queued{color:var(--fg)}.v-needs-decision,.v-blocked{color:var(--bad);font-weight:bold}.v-failed{color:var(--bad)}.v-paused,.v-unknown{color:var(--dim)}.dim{color:var(--dim)}.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)}.detail{display:none}.detail.open{display:table-row}.detail td{background:var(--inset);white-space:pre-wrap;word-break:break-word;font-size:12px;color:var(--dim)}.detail b{color:var(--fg)}.empty{color:var(--dim);padding:6px 2px}#stale{display:none;color:var(--bad)}a{color:var(--link);text-decoration:none}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle}.dot.ok{background:var(--ok)}.dot.warn{background:var(--warn)}.dot.bad{background:var(--bad)}.dot.dim{background:var(--dim)}#overlay{position:fixed;inset:0;background:var(--scrim);display:none;z-index:10;padding:28px 16px;overflow:auto}#overlay.open{display:block}.modal{max-width:var(--modal-width);margin:0 auto;background:var(--card);border:1px solid var(--line);border-radius:var(--modal-radius);padding:16px 18px;position:relative}.modal .x{position:absolute;top:8px;right:12px;cursor:pointer;color:var(--dim);font-size:18px;line-height:1}.modal .x:hover{color:var(--fg)}.modal h3{margin:0 24px 2px 0;font-size:14px}.modal .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.modal .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.modal pre{white-space:pre-wrap;word-break:break-word;margin:0;font:inherit;color:var(--fg)}.msg{border-left:3px solid var(--line);padding:3px 10px;margin:8px 0;white-space:pre-wrap;word-break:break-word}.msg.from-helm{border-color:var(--link)}.msg .hdr{color:var(--dim);font-size:11px}.catch{border:1px solid var(--line);border-radius:var(--radius);padding:6px 10px;margin:8px 0}.catch .hdr{display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}.loglines{color:var(--dim);font-size:12px;white-space:pre-wrap;word-break:break-word;margin-top:4px}.cmd{display:flex;gap:var(--space-2);align-items:center;margin:5px 0;flex-wrap:wrap}.cmd code{background:var(--inset);border:1px solid var(--line);border-radius:5px;padding:3px 8px;word-break:break-all;cursor:pointer}.cmd code:hover{border-color:var(--line-hover)}.cmd code.copied{border-color:var(--ok)}.cmd button{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:2px 8px;cursor:pointer}.cmd button:hover{color:var(--fg)}.chip.click{cursor:pointer}.chip.click:hover{border-color:var(--line-hover)}footer{margin-top:26px;padding-top:10px;border-top:1px solid var(--line);color:var(--dim);font-size:12px;display:flex;gap:var(--space-2);flex-wrap:wrap}.lob{position:fixed;bottom:6px;left:0;z-index:5;cursor:pointer;font-size:34px;line-height:1;user-select:none;animation:crawl 18s linear infinite}a.lob{color:inherit}.lob .bub .badge{display:inline-block;margin:0 0 3px}.lob .sprite{width:72px;height:56px;background:url(/lob-sprite.png) 0 0 no-repeat;background-size:400% 100%;image-rendering:pixelated;animation:step 0.5s steps(4) infinite}.lob .fallback{display:inline-block;animation:waddle 0.45s ease-in-out infinite alternate}.lob .bub{display:none;position:absolute;bottom:58px;right:-8px;z-index:2;background:var(--card);border:1px solid var(--line);border-radius:9px;padding:6px 9px 5px;font-size:11px;line-height:1.35;color:var(--fg);width:max-content;max-width:150px;box-shadow:0 2px 8px rgba(0,0,0,0.4)}.lob:hover .bub{display:block}.lob .bub span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;white-space:normal;word-break:break-word}.lob .star{position:absolute;left:56px;top:-9px;height:14px;image-rendering:pixelated;z-index:3}.lob:hover{animation-play-state:paused}@keyframes crawl{0%{transform:translateX(-90px)}100%{transform:translateX(100vw)}}@keyframes step{to{background-position-x:-288px}}@keyframes waddle{from{transform:rotate(-8deg) translateY(0)}to{transform:rotate(8deg) translateY(-3px)}}#gearbtn{background:none;border:1px solid var(--line);border-radius:var(--radius);color:var(--dim);font:inherit;font-size:13px;padding:1px 7px;margin-left:8px;cursor:pointer;vertical-align:1px}#gearbtn:hover,#gearbtn.on{color:var(--fg);border-color:var(--line-hover)}.settings .row{display:flex;justify-content:space-between;align-items:center;gap:14px;margin:12px 0}.settings .lbl{color:var(--fg);font-size:12px}.settings .hint{display:block;color:var(--dim);font-size:11px;margin-top:2px}.dcards{display:flex;flex-direction:column;gap:10px}.dcard{width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad)}.dcard.focus{animation:dflash 1.6s ease-out 1}@keyframes dflash{0%,35%{border-color:var(--link);box-shadow:0 0 0 3px var(--link)}100%{border-color:var(--line);box-shadow:0 0 0 0 transparent}}.dcard.answered{opacity:0.7}.dcard .dtop{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2)}.dcard .dtop .badge{margin-top:0}.dcard .dtitle{white-space:pre-wrap;overflow-wrap:anywhere}.dcard .dmeta{font-size:11px;margin-left:auto}.dcard .dlink{color:var(--link)}.dfiles{display:flex;flex-wrap:wrap;gap:var(--space-2);margin:6px 0}.dfiles .dimg img{max-height:160px;max-width:320px;border:1px solid var(--line);border-radius:var(--radius);display:block}.dfiles .dfile{width:100%;font-size:11px}.doptions{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 6px}.btn.dopt{font-size:12px;padding:3px 12px;color:var(--fg)}.btn.dopt.on{background:var(--seg-on);border-color:var(--link)}.danswer{display:block;width:100%;min-height:3.2em;max-height:50vh;margin:6px 0;padding:6px 8px;resize:vertical;overflow-y:auto;field-sizing:content;background:var(--inset);color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);font:inherit}.danswer:focus{outline:none;border-color:var(--line-hover)}.dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.dfoot .dattach input{display:none}.dfoot .dchip{font-size:11px;border:1px solid var(--line);border-radius:var(--badge-radius);padding:0 0 0 6px}.dfoot .dx{background:none;border:none;color:var(--dim);cursor:pointer;font:inherit}.dfoot .derr{font-size:11px}.btn.dsend{margin-left:auto;color:var(--fg);padding:2px 14px}.btn:disabled{cursor:default;opacity:0.6}.danswered{margin-top:6px}.thumb,.dimg,.mdimg{background:none;border:none;padding:0;cursor:zoom-in;font:inherit}.thumb{display:block;margin:4px 0}.thumb img{max-height:120px;max-width:240px;border:1px solid var(--line);border-radius:var(--radius);display:block}.lightbox{position:fixed;inset:0;z-index:20;background:rgba(0,0,0,0.85);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2)}.lightbox .lbimg{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:var(--radius);box-shadow:0 8px 40px rgba(0,0,0,0.6)}.lightbox .lbclose{position:absolute;top:12px;right:16px;background:none;border:none;color:var(--fg);font-size:28px;line-height:1;cursor:pointer}.lightbox .lboriginal{color:var(--link);font-size:12px}.mdpage{background:var(--inset);border:1px solid var(--line);border-radius:6px;padding:8px 14px;margin:6px 0;line-height:1.5;overflow-wrap:anywhere}.mdpage h1,.mdpage h2,.mdpage h3,.mdpage h4{margin:12px 0 6px;line-height:1.25}.mdpage h1{font-size:18px}.mdpage h2{font-size:15px}.mdpage h3,.mdpage h4{font-size:13px}.mdpage p,.mdpage ul,.mdpage ol,.mdpage blockquote{margin:6px 0}.mdpage ul,.mdpage ol{padding-left:22px}.mdpage a{color:var(--link)}.mdpage code{background:var(--card);border-radius:3px;padding:0 3px}.mdpage pre.mdcode{background:var(--card);border:1px solid var(--line);border-radius:4px;padding:8px;overflow-x:auto;white-space:pre}.mdpage pre.mdcode code{padding:0;background:none}.mdpage blockquote{border-left:3px solid var(--line);padding-left:10px;color:var(--dim)}.mdpage img{max-width:100%;height:auto;border-radius:4px;display:block;margin:6px 0}.mdpage hr{border:0;border-top:1px solid var(--line)}.mdpage .mdtable{overflow-x:auto}.mdpage table{border-collapse:collapse;margin:6px 0}.mdpage th,.mdpage td{border:1px solid var(--line);padding:3px 8px;text-align:left}.tabhead{margin:0 0 8px}.newtrap{display:inline-flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12px;font-weight:normal;text-transform:none;letter-spacing:normal}.startcmds{margin:6px 0;font-size:11px;cursor:default}.reportview{max-width:1100px;margin:0 auto;padding:20px 16px 48px}.reportview h1{font-size:18px;margin:0 0 4px}.reportview .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.reportview .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.reportview .mdpage{padding:12px 20px}</style>\n </head>\n <body>\n <!-- The page is rendered by Preact (src/components/app.ts): header, tabs, controls, the five tab pages, footer, lobs, modal overlay. -->\n <script>"use strict";(()=>{var Ae,b,Bt,Bs,O,Nt,Ot,qt,Je,Ge,le,Ft,Ze,Xe,Qe,Os,Se={},Ce=[],qs=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Re=Array.isArray;function E(e,t){for(var n in t)e[n]=t[n];return e}function Ye(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Q(e,t,n){var s,r,i,o={};for(i in t)i=="key"?s=t[i]:i=="ref"?r=t[i]:o[i]=t[i];if(arguments.length>2&&(o.children=arguments.length>3?Ae.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)o[i]===void 0&&(o[i]=e.defaultProps[i]);return xe(e,o,s,r,null)}function xe(e,t,n,s,r){var i={type:e,props:t,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:r??++Bt,__i:-1,__u:0};return r==null&&b.vnode!=null&&b.vnode(i),i}function Z(e){return e.children}function Te(e,t){this.props=e,this.context=t}function V(e,t){if(t==null)return e.__?V(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?V(e):null}function Fs(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,s=[],r=[],i=E({},t);i.__v=t.__v+1,b.vnode&&b.vnode(i),et(e.__P,i,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,s,n??V(t),!!(32&t.__u),r),i.__v=t.__v,i.__.__k[i.__i]=i,Jt(s,i,r),t.__e=t.__=null,i.__e!=n&&Wt(i)}}function Wt(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),Wt(e)}function Ht(e){(!e.__d&&(e.__d=!0)&&O.push(e)&&!De.__r++||Nt!=b.debounceRendering)&&((Nt=b.debounceRendering)||Ot)(De)}function De(){try{for(var e,t=1;O.length;)O.length>t&&O.sort(qt),e=O.shift(),t=O.length,Fs(e)}finally{O.length=De.__r=0}}function Vt(e,t,n,s,r,i,o,c,u,l,f){var h,d,m,p,g,x,G=s&&s.__k||Ce,$=t.length;for(u=Ws(n,t,G,u,$),h=0;h<$;h++)(m=n.__k[h])!=null&&(d=m.__i!=-1&&G[m.__i]||Se,m.__i=h,x=et(e,m,d,r,i,o,c,u,l,f),p=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&tt(d.ref,null,m),f.push(m.ref,m.__c||p,m)),g==null&&p!=null&&(g=p),4&m.__u?(u=Kt(m,u,e),d.__e&&(d.__e=null)):typeof m.type=="function"&&x!==void 0?u=x:p&&(u=p.nextSibling),m.__u&=-7);return n.__e=g,u}function Ws(e,t,n,s,r){var i,o,c,u,l,f=n.length,h=f,d=0;for(e.__k=new Array(r),i=0;i<r;i++)(o=t[i])!=null&&typeof o!="boolean"&&typeof o!="function"?(typeof o=="string"||typeof o=="number"||typeof o=="bigint"||o.constructor==String?o=e.__k[i]=xe(null,o,null,null,null):Re(o)?o=e.__k[i]=xe(Z,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[i]=xe(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[i]=o,u=i+d,o.__=e,o.__b=e.__b+1,c=null,(l=o.__i=Vs(o,n,u,h))!=-1&&(h--,(c=n[l])&&(c.__u|=2)),c==null||c.__v==null?(l==-1&&(r>f?d--:r<f&&d++),typeof o.type!="function"&&(o.__u|=4)):l!=u&&(l==u-1?d--:l==u+1?d++:(l>u?d--:d++,o.__u|=4))):e.__k[i]=null;if(h)for(i=0;i<f;i++)(c=n[i])!=null&&(2&c.__u)==0&&(c.__e==s&&(s=V(c)),Qt(c,c));return s}function Kt(e,t,n){var s,r;if(typeof e.type=="function"){for(s=e.__k,r=0;s&&r<s.length;r++)s[r]&&(s[r].__=e,t=Kt(s[r],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=V(e)),t=n.insertBefore(e.__e,t||null));do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function Vs(e,t,n,s){var r,i,o,c=e.key,u=e.type,l=t[n],f=l!=null&&(2&l.__u)==0;if(l===null&&c==null||f&&c==l.key&&u==l.type)return n;if(s>(f?1:0)){for(r=n-1,i=n+1;r>=0||i<t.length;)if((l=t[o=r>=0?r--:i++])!=null&&(2&l.__u)==0&&c==l.key&&u==l.type)return o}return-1}function Ut(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||qs.test(t)?n:n+"px"}function we(e,t,n,s,r){var i,o;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof s=="string"&&(e.style.cssText=s=""),s)for(t in s)n&&t in n||Ut(e.style,t,"");if(n)for(t in n)s&&n[t]==s[t]||Ut(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(Ft,"$1")),o=t.toLowerCase(),t=o in e||t=="onFocusOut"||t=="onFocusIn"?o.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=n,n?s?n[le]=s[le]:(n[le]=Ze,e.addEventListener(t,i?Qe:Xe,i)):e.removeEventListener(t,i?Qe:Xe,i);else{if(r=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function jt(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t[Ge]==null)t[Ge]=Ze++;else if(t[Ge]<n[le])return;return n(b.event?b.event(t):t)}}}function et(e,t,n,s,r,i,o,c,u,l){var f,h,d,m,p,g,x,G,$,P,ie,W,ae,Lt,ye,ze,I=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(u=!!(32&n.__u),i=[c=t.__e=n.__e]),(f=b.__b)&&f(t);e:if(typeof I=="function"){h=o.length;try{if($=t.props,P=I.prototype&&I.prototype.render,ie=(f=I.contextType)&&s[f.__c],W=f?ie?ie.props.value:f.__:s,n.__c?G=(d=t.__c=n.__c).__=d.__E:(P?t.__c=d=new I($,W):(t.__c=d=new Te($,W),d.constructor=I,d.render=zs),ie&&ie.sub(d),d.state||(d.state={}),d.__n=s,m=d.__d=!0,d.__h=[],d._sb=[]),P&&d.__s==null&&(d.__s=d.state),P&&I.getDerivedStateFromProps!=null&&(d.__s==d.state&&(d.__s=E({},d.__s)),E(d.__s,I.getDerivedStateFromProps($,d.__s))),p=d.props,g=d.state,d.__v=t,m)P&&I.getDerivedStateFromProps==null&&d.componentWillMount!=null&&d.componentWillMount(),P&&d.componentDidMount!=null&&d.__h.push(d.componentDidMount);else{if(P&&I.getDerivedStateFromProps==null&&$!==p&&d.componentWillReceiveProps!=null&&d.componentWillReceiveProps($,W),t.__v==n.__v||!d.__e&&d.shouldComponentUpdate!=null&&d.shouldComponentUpdate($,d.__s,W)===!1){t.__v!=n.__v&&(d.props=$,d.state=d.__s,d.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(X){X&&(X.__=t)}),Ce.push.apply(d.__h,d._sb),d._sb=[],d.__h.length&&o.push(d),c=V(n);break e}d.componentWillUpdate!=null&&d.componentWillUpdate($,d.__s,W),P&&d.componentDidUpdate!=null&&d.__h.push(function(){d.componentDidUpdate(p,g,x)})}if(d.context=W,d.props=$,d.__P=e,d.__e=!1,ae=b.__r,Lt=0,P)d.state=d.__s,d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),Ce.push.apply(d.__h,d._sb),d._sb=[];else do d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),d.state=d.__s;while(d.__d&&++Lt<25);d.state=d.__s,d.getChildContext!=null&&(s=E(E({},s),d.getChildContext())),P&&!m&&d.getSnapshotBeforeUpdate!=null&&(x=d.getSnapshotBeforeUpdate(p,g)),ye=f!=null&&f.type===Z&&f.key==null?Xt(f.props.children):f,c=Vt(e,Re(ye)?ye:[ye],t,n,s,r,i,o,c,u,l),d.base=t.__e,t.__u&=-161,d.__h.length&&o.push(d),G&&(d.__E=d.__=null)}catch(X){if(o.length=h,t.__v=null,u||i!=null){if(X.then){for(t.__u|=u?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;i!=null&&(i[i.indexOf(c)]=null),t.__e=c}else if(i!=null)for(ze=i.length;ze--;)Ye(i[ze])}else t.__e=n.__e;t.__k==null&&(t.__k=n.__k||[]),X.then||zt(t),b.__e(X,t,n)}}else i==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):c=t.__e=Ks(n.__e,t,n,s,r,i,o,u,l);return(f=b.diffed)&&f(t),128&t.__u?void 0:c}function zt(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(zt))}function Jt(e,t,n){for(var s=0;s<n.length;s++)tt(n[s],n[++s],n[++s]);b.__c&&b.__c(t,e),e.some(function(r){try{e=r.__h,r.__h=[],e.some(function(i){i.call(r)})}catch(i){b.__e(i,r.__v)}})}function Xt(e){return typeof e!="object"||e==null||e.__b>0?e:Re(e)?e.map(Xt):e.constructor!==void 0?null:E({},e)}function Ks(e,t,n,s,r,i,o,c,u){var l,f,h,d,m,p,g,x=n.props||Se,G=t.props,$=t.type;if($=="svg"?r="http://www.w3.org/2000/svg":$=="math"?r="http://www.w3.org/1998/Math/MathML":r||(r="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l<i.length;l++)if((m=i[l])&&"setAttribute"in m==!!$&&($?m.localName==$:m.nodeType==3)){e=m,i[l]=null;break}}if(e==null){if($==null)return document.createTextNode(G);e=document.createElementNS(r,$,G.is&&G),c&&(b.__m&&b.__m(t,i),c=!1),i=null}if($==null)x===G||c&&e.data==G||(e.data=G);else{if(i=$=="textarea"&&G.defaultValue!=null?null:i&&Ae.call(e.childNodes),!c&&i!=null)for(x={},l=0;l<e.attributes.length;l++)x[(m=e.attributes[l]).name]=m.value;for(l in x)m=x[l],l=="dangerouslySetInnerHTML"?h=m:l=="children"||l in G||l=="value"&&"defaultValue"in G||l=="checked"&&"defaultChecked"in G||we(e,l,null,m,r);for(l in G)m=G[l],l=="children"?d=m:l=="dangerouslySetInnerHTML"?f=m:l=="value"?p=m:l=="checked"?g=m:c&&typeof m!="function"||x[l]===m||we(e,l,m,x[l],r);if(f)c||h&&(f.__html==h.__html||f.__html==e.innerHTML)||(e.innerHTML=f.__html),t.__k=[];else if(h&&(e.innerHTML=""),Vt(t.type=="template"?e.content:e,Re(d)?d:[d],t,n,s,$=="foreignObject"?"http://www.w3.org/1999/xhtml":r,i,o,i?i[0]:n.__k&&V(n,0),c,u),i!=null)for(l=i.length;l--;)Ye(i[l]);c&&$!="textarea"||(l="value",$=="progress"&&p==null?e.removeAttribute("value"):p!=null&&(p!==e[l]||$=="progress"&&!p||$=="option"&&p!=x[l])&&we(e,l,p,x[l],r),l="checked",g!=null&&g!=e[l]&&we(e,l,g,x[l],r))}return e}function tt(e,t,n){try{if(typeof e=="function"){var s=typeof e.__u=="function";s&&e.__u(),s&&t==null||(e.__u=e(t))}else e.current=t}catch(r){b.__e(r,n)}}function Qt(e,t,n){var s,r;if(b.unmount&&b.unmount(e),(s=e.ref)&&(s.current&&s.current!=e.__e||tt(s,null,t)),(s=e.__c)!=null){if(s.componentWillUnmount)try{s.componentWillUnmount()}catch(i){b.__e(i,t)}s.base=s.__P=s.__n=null}if(s=e.__k)for(r=0;r<s.length;r++)s[r]&&Qt(s[r],t,n||typeof e.type!="function");n||Ye(e.__e),e.__c=e.__=e.__e=void 0}function zs(e,t,n){return this.constructor(e,n)}function nt(e,t,n){var s,r,i,o;t==document&&(t=document.documentElement),b.__&&b.__(e,t),r=(s=typeof n=="function")?null:n&&n.__k||t.__k,i=[],o=[],et(t,e=(!s&&n||t).__k=Q(Z,null,[e]),r||Se,Se,t.namespaceURI,!s&&n?[n]:r?null:t.firstChild?Ae.call(t.childNodes):null,i,!s&&n?n:r?r.__e:t.firstChild,s,o),Jt(i,e,o),e.props.children=null}Ae=Ce.slice,b={__e:function(e,t,n,s){for(var r,i,o;t=t.__;)if((r=t.__c)&&!r.__)try{if((i=r.constructor)&&i.getDerivedStateFromError!=null&&(r.setState(i.getDerivedStateFromError(e)),o=r.__d),r.componentDidCatch!=null&&(r.componentDidCatch(e,s||{}),o=r.__d),o)return r.__E=r}catch(c){e=c}throw e}},Bt=0,Bs=function(e){return e!=null&&e.constructor===void 0},Te.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=E({},this.state),typeof e=="function"&&(e=e(E({},n),this.props)),e&&E(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),Ht(this))},Te.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Ht(this))},Te.prototype.render=Z,O=[],Ot=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,qt=function(e,t){return e.__v.__b-t.__v.__b},De.__r=0,Je=Math.random().toString(8),Ge="__d"+Je,le="__a"+Je,Ft=/(PointerCapture)$|Capture$/i,Ze=0,Xe=jt(!1),Qe=jt(!0),Os=0;function Js(e){return{id:e,lane:"work",bucket:"done",repo:"",brief:"",attachments:[],messageAttachments:[],verb:"unknown",log:[],inbox:[],sort:0}}var Zt=new WeakMap;function st(e,t){let n=e.dispatches||[],s=Zt.get(n);return s||Zt.set(n,s=new Map(n.map(r=>[r.id,r]))),{...t,catches:(t.catches||[]).map(r=>s.get(r)??Js(r))}}function L(e){if(!e)return"dim";let t=e.state||"open";return t==="open"&&e.merge?"pr-"+e.merge:t==="open"&&e.tone&&e.tone!=="ok"?e.tone:"pr-"+t}var ce=["deck","dispatches","traps","prs","reports","notices"];function nn(e){let t=String(e||"").replace(/^#/,"");return ce.includes(t)?t:"deck"}var rt=e=>`#decision/${encodeURIComponent(e)}`;function ot(e){let t=/^#?decision\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var it=(e,t,n)=>`/attachment/dispatch/${encodeURIComponent(e)}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,sn=(e,t)=>`/attachment/trap/${encodeURIComponent(e)}/${encodeURIComponent(t)}`,rn=e=>/\\.(png|jpe?g|gif|webp)$/i.test(e),at=(e,t)=>`/decision/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function lt(e,t){let n=new Map(t.map(r=>[r.key,r])),s=[];for(let r of e)if(r.kind==="decision"){let i=n.get(r.key);if(!i)continue;s.push({kind:"decision",key:i.key,title:i.title,detail:i.detail,options:i.options,attachments:i.attachments,...i.dispatch?{dispatch:i.dispatch,lane:i.lane??"work"}:{},...i.repo?{repo:i.repo}:{},at:i.askedAt})}else r.kind==="question"&&s.push({kind:"question",key:r.key,verb:r.verb,note:r.note||r.verb,dispatch:r.id,lane:r.lane,...r.repo?{repo:r.repo}:{},at:r.at??""});return s.sort((r,i)=>i.at.localeCompare(r.at)||r.key.localeCompare(i.key))}function on(e){let t=(e.text??"").split(`\n`).map(n=>n.trim()).find(Boolean)??"";return[e.option,t&&(t.length>80?t.slice(0,79)+"\\u2026":t),e.files?`${e.files} file${e.files===1?"":"s"}`:""].filter(Boolean).join(" \\xB7 ")}function an(e,t){return(e.reports||[]).find(n=>n.key===`report:${t.lane}:${t.id}`)}function de(e){return e.author==="helm"?"":e.trap?e.trap:e.author==="headless"?e.dispatch?e.dispatch.slice(0,8):"":e.author}var Yt=(e,t)=>+!!e.acked-+!!t.acked||t.filedAt.localeCompare(e.filedAt),C=e=>`/report/${encodeURIComponent(e)}`;function ln(e){let t=/^#?report\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}function cn(e){let t=/^\\/report\\/([^/?#]+)\\/?$/.exec(e);if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var dn=e=>`/report/${encodeURIComponent(e)}/md`,ct=(e,t)=>`/report/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function en(e,t,n){return!!e&&n-Date.parse(e)>t}function tn(e,t){if(t.lane&&e.lane!==t.lane||t.repo&&e.repo!==t.repo||t.verb&&e.verb!==t.verb)return!1;if(t.q){let n=t.q.toLowerCase();if(!(e.id+" "+(e.note||"")+" "+(e.brief||"")+" "+(e.repo||"")+" "+(e.for||"")).toLowerCase().includes(n))return!1}return!0}function Y(e,t){if(!t)return null;if(t.type==="helm")return e.helms.find(s=>s.grounds===t.key)||null;if(t.type==="dispatch")return e.dispatches.find(s=>s.lane+":"+s.id===t.key)||null;if(t.type==="pr")return(e.prs||[]).find(s=>s.key===t.key)||null;if(t.type==="settings")return{attentionKinds:e.attentionKinds||[],attentionError:e.attentionError};let n=e.traps.find(s=>s.trapId===t.key);return n?st(e,n):null}function pn(e,t,n){let s=t.st,r=String(s.q||"").toLowerCase(),i=(...p)=>!r||p.join(" ").toLowerCase().includes(r),o=p=>({x:p,stale:en(p.heartbeatAt,18e5,n)}),c=Y(e,t.modal),u=(p,g)=>!!p&&n-Date.parse(p)<=g,l=p=>Math.max(0,...(p.notices||[]).filter(g=>g.kind==="trap-stowed"||g.kind==="trap-ghosted").map(g=>Date.parse(g.at)||0)),f=p=>p.live||!!p.starting||!!p.requested,h=[...e.traps||[]].sort((p,g)=>Number(f(g))-Number(f(p))||Number(g.live)-Number(p.live)||(p.live?(Date.parse(p.signedOnAt||"")||0)-(Date.parse(g.signedOnAt||"")||0):l(g)-l(p))||(p.name??p.trapId).localeCompare(g.name??g.trapId)),d=h.filter(p=>(f(p)||(p.notices||[]).some(g=>(g.kind==="trap-stowed"||g.kind==="trap-ghosted")&&u(g.at,36e5)))&&i(p.name,p.trapId,p.repo,p.worktree)),m=({ageSecs:p,...g})=>g;return{chips:{daemon:e.daemon,daemonStale:!!e.daemon&&en(e.daemon.heartbeat,9e4,n),helms:e.helms.map(o)},deck:{view:s.view,decisions:lt(e.attention||[],e.decisions||[]).filter(p=>p.kind==="decision"?i(p.kind,p.repo,p.title,p.dispatch,p.detail):i(p.kind,p.repo,p.note,p.dispatch)),prAttention:(e.attention||[]).filter(p=>p.kind&&p.kind.startsWith("pr:")).map(m),landed:(e.landed||[]).filter(p=>u(p.at,864e5)&&i(p.repo,p.note,p.id)).sort((p,g)=>Date.parse(g.at)-Date.parse(p.at)).slice(0,8),reports:(e.reports||[]).filter(p=>i(p.title,p.author,p.dispatch,p.repo)).sort(Yt),inflight:e.dispatches.filter(p=>p.bucket!=="done"&&tn(p,{...s,lane:"",repo:"",verb:""})),traps:d.map(p=>o(st(e,p))),stacks:(e.stacks||[]).filter(p=>p.open&&i(p.repo,p.numbers.join(" "))),prs:(e.prs||[]).filter(p=>p.state==="OPEN"),error:e.attentionError},dispatches:{view:s.view,chain:s.chain,list:e.dispatches.filter(p=>tn(p,s))},traps:{view:s.view,list:h.filter(p=>(!s.repo||p.repo===s.repo)&&i(p.name,p.trapId,p.repo,p.worktree,p.harness)).map(p=>o(st(e,p)))},prs:{view:s.view,stacks:(e.stacks||[]).filter(p=>!s.repo||p.repo===s.repo),prs:(e.prs||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.number,p.title,p.url,p.state,p.baseRefName,p.headRefName)),watches:(e.watches||[]).filter(p=>!String(p.key).startsWith("pr:"))},reports:{view:s.view,list:(e.reports||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.title,p.author,p.dispatch,p.repo)).sort(Yt)},notices:{list:(e.notices||[]).filter(p=>(!s.repo||!p.repo||p.repo===s.repo)&&(!s.noticeKind||p.kind===s.noticeKind)&&i(p.kind,p.text,p.repo))},foot:{version:e.version,repoUrl:e.repoUrl},modal:{modal:t.modal,item:c&&o(c),prefs:t.modal&&t.modal.type==="settings"?{view:s.view,lobs:s.lobs}:void 0}}}function un(e,t){let n=(e.prs||[]).find(c=>c.key===t);if(!n)return null;let s=(e.stacks||[]).find(c=>c.id===n.stackId),r=new Map((e.dispatches||[]).map(c=>[c.id,c])),i=(n.dispatchIds||[]).map(c=>{let u=r.get(c);return u?{id:c,verb:u.verb,modalKey:u.lane+":"+u.id}:{id:c,culled:!0}}),o=n.watch;return{pr:n,stack:s?{numbers:s.numbers,titles:s.numbers.map(c=>(e.prs||[]).find(u=>u.stackId===s.id&&u.number===c)?.title||""),position:n.position+1,size:s.numbers.length,floor:s.floor,nextNumber:s.nextNumber,nextMergeable:!!n.nextMergeable,blockedBy:n.blockedBy}:null,chain:i,watch:o?{key:o.key,owner:o.owner||"",lastCheckedAt:o.lastCheckedAt||null,lastError:o.lastError||null,cursor:o.cursor}:null}}function fn(e){return e?{text:e.lastError?"failing":"watching",at:e.lastCheckedAt||null}:{text:"no watch",at:null}}function mn(){let e={view:"table",lane:"",repo:"",verb:"",q:"",lobs:!0,chain:!1,noticeKind:""};try{Object.assign(e,JSON.parse(localStorage.getItem("spyglass")||"{}"))}catch{}return e.view!=="table"&&e.view!=="cards"&&(e.view="table"),e.lobs=e.lobs!==!1,e}function hn(e){try{localStorage.setItem("spyglass",JSON.stringify(e))}catch{}}function gn(){try{return JSON.parse(localStorage.getItem("spyglass-lob-hidden")||"{}")||{}}catch{return{}}}function vn(e){try{localStorage.setItem("spyglass-lob-hidden",JSON.stringify(e))}catch{}}var pe,_n=new Set,_=()=>pe;function dt(e){pe=e}function T(e){pe={...pe,...e};for(let t of _n)t(pe)}var pt=e=>{_n.add(e)};function ut(e){let{modal:t,drafts:n}=_(),s=new Set(lt(e.attention||[],e.decisions||[]).map(i=>i.key)),r=Object.fromEntries(Object.entries(n).filter(([i])=>s.has(i)));T({snapshot:e,stale:!1,modal:t&&Y(e,t)?t:null,...Object.keys(r).length!==Object.keys(n).length?{drafts:r}:{}})}var $n=()=>({text:"",files:[]});function A(e,t){let n=_().drafts;T({drafts:{...n,[e]:{...n[e]??$n(),...t}}})}function bn(e,t){let n=_().drafts[e]?.option;A(e,{option:n===t?void 0:t,error:void 0})}var Xs=e=>new Promise((t,n)=>{let s=new FileReader;s.onload=()=>t(String(s.result).replace(/^data:[^,]*,/,"")),s.onerror=()=>n(s.error),s.readAsDataURL(e)});async function ft(e,t){let n=_().snapshot?.answerLimits,s=[...t??[]],r=_().drafts[e]?.files??[],i=[],o=[];for(let c of s){let u=(/\\.[^.]+$/.exec(c.name)?.[0]??"").toLowerCase();if(n&&!n.extensions.includes(u))i.push(`${c.name}: type not accepted`);else if(n&&c.size>n.maxBytes)i.push(`${c.name}: larger than ${n.maxBytes} bytes`);else if(n&&r.length+o.length>=n.maxFiles)i.push(`${c.name}: at most ${n.maxFiles} files`);else try{o.push({name:c.name,bytes:c.size,data:await Xs(c)})}catch{i.push(`${c.name}: could not be read`)}}A(e,{files:[..._().drafts[e]?.files??[],...o],error:i.length?i.join("; "):void 0})}var Qs={"image/png":".png","image/jpeg":".jpg","image/gif":".gif","image/webp":".webp"};async function kn(e,t){let n=[...t.clipboardData?.items??[]],s=new Date().toISOString().replace(/[:.]/g,"-").replace(/-\\d{3}Z$/,"Z"),r=n.filter(i=>i.kind==="file"&&i.type.startsWith("image/")).map(i=>i.getAsFile()).filter(i=>i!==null).map((i,o,c)=>{let u=`pasted-${s}${c.length>1?`-${o+1}`:""}${Qs[i.type]??".png"}`;return new File([i],u,{type:i.type})});r.length&&await ft(e,r)}function yn(e,t){let n=(_().drafts[e]?.files??[]).filter((s,r)=>r!==t);A(e,{files:n})}async function wn(e){let t=_().snapshot?.focusToken,n=_().drafts[e]??$n();if(!(n.sending||n.sent)){if(!n.option&&!n.text.trim()&&n.files.length===0){A(e,{error:"Choose an option, write an answer, or attach a file."});return}if(!t){A(e,{error:"This page has no answer token; reload it."});return}A(e,{sending:!0,error:void 0});try{let s=await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":t},body:JSON.stringify({kind:"decision-answer",payload:{key:e,...n.option?{option:n.option}:{},...n.text.trim()?{text:n.text}:{},files:n.files.map(i=>({name:i.name,data:i.data}))}})}),r=await s.json();if(!s.ok||!r.ok){A(e,{sending:!1,error:r.reason??`The answer was refused (${s.status}).`});return}A(e,{sending:!1,sent:on({option:n.option,text:n.text,files:n.files.length})})}catch{A(e,{sending:!1,error:"The answer could not be sent."})}}}var Gn=()=>T({stale:!0}),xn=(e,t)=>T({lightbox:{src:e,name:t}}),ee=()=>T({lightbox:null});function M(e,t){let{snapshot:n}=_(),s={type:e,key:t};T({modal:!n||Y(n,s)?s:null})}function te(){T({modal:null})}async function Tn(e){let t=_().snapshot?.focusToken;if(t)try{let s=await(await fetch(`/api/focus/${encodeURIComponent(e)}`,{method:"POST",headers:{"x-lobstah-focus-token":t}})).json();T({focusResults:{..._().focusResults,[e]:s.message??s.reason??"Window focus failed."}})}catch{T({focusResults:{..._().focusResults,[e]:"Window focus failed."}})}}async function Sn(e,t){let n=_().snapshot?.focusToken;if(!n)return"The glass has no page token yet.";try{let r=await(await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":n},body:JSON.stringify({kind:"trap-request",payload:{repo:e,harness:t}})})).json();if(!r.ok)return r.reason??"The request was refused."}catch{return"The request could not be sent."}try{ut(await(await fetch("/data")).json())}catch{}}function N(e){let t={..._().prefs,...e};hn(t),T({prefs:t})}function Cn(e){e!=="table"&&e!=="cards"||N({view:e})}var Dn=e=>N({lobs:e==="on"});function An(e,t){let n={..._().lobHidden,[e]:t};vn(n),setTimeout(()=>T({lobHidden:n}),0)}async function Rn(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}var In=function(e,t,n,s){var r;t[0]=0;for(var i=1;i<t.length;i++){var o=t[i++],c=t[i]?(t[0]|=o?1:2,n[t[i++]]):t[++i];o===3?s[0]=c:o===4?s[1]=Object.assign(s[1]||{},c):o===5?(s[1]=s[1]||{})[t[++i]]=c:o===6?s[1][t[++i]]+=c+"":o?(r=e.apply(c,In(e,c,n,["",null])),s.push(r),c[0]?t[0]|=2:(t[i-2]=0,t[i]=r)):s.push(c)}return s},Pn=new Map;function Mn(e){var t=Pn.get(this);return t||(t=new Map,Pn.set(this,t)),(t=In(this,t.get(e)||(t.set(e,t=(function(n){for(var s,r,i=1,o="",c="",u=[0],l=function(d){i===1&&(d||(o=o.replace(/^\\s*\\n\\s*|\\s*\\n\\s*$/g,"")))?u.push(0,d,o):i===3&&(d||o)?(u.push(3,d,o),i=2):i===2&&o==="..."&&d?u.push(4,d,0):i===2&&o&&!d?u.push(5,0,!0,o):i>=5&&((o||!d&&i===5)&&(u.push(i,0,o,r),i=6),d&&(u.push(i,d,0,r),i=6)),o=""},f=0;f<n.length;f++){f&&(i===1&&l(),l(f));for(var h=0;h<n[f].length;h++)s=n[f][h],i===1?s==="<"?(l(),u=[u],i=3):o+=s:i===4?o==="--"&&s===">"?(i=1,o=""):o=s+o[0]:c?s===c?c="":o+=s:s===\'"\'||s==="\'"?c=s:s===">"?(l(),i=1):i&&(s==="="?(i=5,r=o,o=""):s==="/"&&(i<5||n[f][h+1]===">")?(l(),i===3&&(u=u[0]),i=u,(u=u[0]).push(2,0,i),i=0):s===" "||s===" "||s===`\n`||s==="\\r"?(l(),i=2):o+=s),i===3&&o==="!--"&&(i=4,u=u[0])}return l(),u})(e)),t),arguments,[])).length>1?t:t[0]}var En=e=>Array.isArray(e)?e.map(En):e===""?null:e,Zs=(e,t,...n)=>Q(e,t,...n.map(En)),a=Mn.bind(Zs);var Ie,k,mt,Ln,ht=0,Fn=[],y=b,Nn=y.__b,Hn=y.__r,Un=y.diffed,jn=y.__c,Bn=y.unmount,On=y.__;function Wn(e,t){y.__h&&y.__h(k,e,ht||t),ht=0;var n=k.__H||(k.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function ne(e){return ht=1,Ys(Kn,e)}function Ys(e,t,n){var s=Wn(Ie++,2);if(s.t=e,!s.__c&&(s.__=[n?n(t):Kn(void 0,t),function(c){var u=s.__N?s.__N[0]:s.__[0],l=s.t(u,c);u!==l&&(s.__N=[l,s.__[1]],s.__c.setState({}))}],s.__c=k,!k.__f)){var r=function(c,u,l){if(!s.__c.__H)return!0;var f=!1,h=s.__c.props!==c;if(s.__c.__H.__.some(function(m){if(m.__N){f=!0;var p=m.__[0];m.__=m.__N,m.__N=void 0,p!==m.__[0]&&(h=!0)}}),i){var d=i.call(this,c,u,l);return f?d||h:d}return!f||h};k.__f=!0;var i=k.shouldComponentUpdate,o=k.componentWillUpdate;k.componentWillUpdate=function(c,u,l){if(this.__e){var f=i;i=void 0,r(c,u,l),i=f}o&&o.call(this,c,u,l)},k.shouldComponentUpdate=r}return s.__N||s.__}function Vn(e,t){var n=Wn(Ie++,7);return nr(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function er(){for(var e;e=Fn.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Pe),t.__h.some(gt),t.__h=[]}catch(n){t.__h=[],y.__e(n,e.__v)}}}y.__b=function(e){k=null,Nn&&Nn(e)},y.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),On&&On(e,t)},y.__r=function(e){Hn&&Hn(e),Ie=0;var t=(k=e.__c).__H;t&&(mt===k?(t.__h=[],k.__h=[],t.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.some(Pe),t.__h.some(gt),t.__h=[],Ie=0)),mt=k},y.diffed=function(e){Un&&Un(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(Fn.push(t)!==1&&Ln===y.requestAnimationFrame||((Ln=y.requestAnimationFrame)||tr)(er)),t.__H.__.some(function(n){n.u&&(n.__H=n.u,n.u=void 0)})),mt=k=null},y.__c=function(e,t){t.some(function(n){try{n.__h.some(Pe),n.__h=n.__h.filter(function(s){return!s.__||gt(s)})}catch(s){t.some(function(r){r.__h&&(r.__h=[])}),t=[],y.__e(s,n.__v)}}),jn&&jn(e,t)},y.unmount=function(e){Bn&&Bn(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(s){try{Pe(s)}catch(r){t=r}}),n.__H=void 0,t&&y.__e(t,n.__v))};var qn=typeof requestAnimationFrame=="function";function tr(e){var t,n=function(){clearTimeout(s),qn&&cancelAnimationFrame(t),setTimeout(e)},s=setTimeout(n,35);qn&&(t=requestAnimationFrame(n))}function Pe(e){var t=k,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),k=t}function gt(e){var t=k;e.__c=e.__(),k=t}function nr(e,t){return!e||e.length!==t.length||t.some(function(n,s){return n!==e[s]})}function Kn(e,t){return typeof t=="function"?t(e):t}var K=e=>{if(!e)return"";let t=Math.max(0,(Date.now()-Date.parse(e))/1e3);return t<90?Math.round(t)+"s":t<5400?Math.round(t/60)+"m":t<172800?(t/3600).toFixed(1)+"h":Math.round(t/86400)+"d"},v=e=>a`<span data-age=${e??""}>${K(e)}</span>`,Ee=e=>e.activity&&a`<div class=${"activity"+(e.activity.stale?" stale":"")} title=${e.activity.stale?"no activity past the wedge threshold":e.activity.kind}>${e.activity.stale?"stale \\xB7 ":""}${e.activity.summary} \xB7 ${v(e.activity.at)} ago</div>`,sr=e=>e&&/^https?:\\/\\//i.test(e)?e:void 0,Le=e=>{let t=e.waiting;if(!t)return;let n=sr(t.link),s=n;try{n&&(s=new URL(n).host+new URL(n).pathname.replace(/\\/$/,""))}catch{s=n}return a`<div class="waiting">${e.verb}: waiting on ${t.on} \xB7 ${v(t.since)}${n&&[" \\xB7 ",a`<a href=${n} target="_blank" rel="noopener noreferrer" onClick=${D}>${s}</a>`]}</div>`},rr=12,zn=e=>e!==void 0&&e.length>rr,H=e=>zn(e)?e:void 0,U=e=>zn(e)?" long":"",w=(e,t)=>()=>M(e,t),D=e=>e.stopPropagation(),or=e=>e.sessionId?(e.harness==="codex"?"codex resume ":"claude --resume ")+e.sessionId:void 0;function z(e){if(e.requested)return a`<span class="dim">Requested \u2014 no window yet</span>`;if(e.starting)return a`<span class="dim">${e.starting.failedAt?"Did not start":"Starting \\u2014 no window yet"}</span>`;if(!e.live){let s=or(e);return s?a`<span class="dim">Resume: <code onClick=${D}>${s}</code></span>`:a`<span class="dim">Resume command unavailable</span>`}let t=_();return!t.snapshot?.focusSupported&&!e.link?a`<span class="dim">Window focus is not supported here</span>`:a`<span class="winaction"><button class="btn open" title="open this trap\'s window" onClick=${s=>{D(s),Tn(e.trapId)}}>\u2197 open</button>${e.link&&[" ",a`<a href=${e.link} onClick=${D}>Session link</a>`]}${t.focusResults[e.trapId]&&[" ",a`<span class="dim">${t.focusResults[e.trapId]}</span>`]}</span>`}function R(e,t,n){return t.length?a`<div class="wrap"><table><tbody><tr>${e.map(s=>a`<th>${s}</th>`)}</tr>${t}</tbody></table></div>`:a`<div class="empty">${n}</div>`}function ir({text:e}){let[t,n]=ne(null),s=r=>async()=>{await Rn(e)&&(n(r),setTimeout(()=>n(null),1e3))};return a`<div class="cmd"><code class=${t==="code"?"copied":void 0} title="click to copy" onClick=${s("code")}>${e}</code><button title="copy" onClick=${s("button")}>${t==="button"?"\\u2713":"\\u29C9"}</button></div>`}var S=(e,t)=>a`<${ir} key=${t} text=${e} />`,ue=(e,t,n="thumb")=>a`<button\n type="button"\n class=${n}\n title=${"view "+t}\n onClick=${s=>{D(s),xn(e,t)}}\n >\n <img src=${e} alt=${t} loading="lazy" />\n </button>`,se=(e,t)=>e.map(n=>[a`<div class="sub">${n.name} \xB7 ${n.type} \xB7 ${n.bytes} bytes</div>`,t&&rn(n.name)&&ue(t(n.name),n.name),S(n.path)]),Jn=/\\bwt:([a-z0-9][a-z0-9-]*)/g,vt=e=>_().snapshot?.trapNames?.[e],j=e=>e.replace(Jn,(t,n,s)=>{let r=vt(n);return r&&!Xn(e,s,t.length,r)?r:t}),Xn=(e,t,n,s)=>e.slice(Math.max(0,t-s.length-2),t)===`${s} (`&&e[t+n]===")";function Ne(e){let t="wt:"+e,n=vt(e)??t;return _().snapshot?.traps.some(r=>r.trapId===e)?a`<a class="trapname" href="#traps" title=${t} onClick=${r=>{r.preventDefault(),D(r),M("trap",e)}}>${n}</a>`:a`<span class="trapname" title=${t}>${n}</span>`}var Me=e=>{let t=/^wt:([a-z0-9][a-z0-9-]*)$/.exec(e);return t?Ne(t[1]):e};function B(e,t=1/0){let n=[],s=0;for(let c of e.matchAll(Jn)){c.index>s&&n.push({text:e.slice(s,c.index)});let u=vt(c[1]);n.push(u&&Xn(e,c.index,c[0].length,u)?{text:c[0]}:{text:u??c[0],trapId:c[1]}),s=c.index+c[0].length}s<e.length&&n.push({text:e.slice(s)});let r=n.reduce((c,u)=>c+u.text.length,0),i=r>t?t-1:1/0,o=[];for(let c of n){if(i<=0)break;c.text.length<=i?o.push(c.trapId?Ne(c.trapId):c.text):o.push(c.text.slice(0,i)),i-=c.text.length}return r>t&&o.push("\\u2026"),o}var _t=e=>e.log.flatMap((t,n)=>[n?`\n`:"",t.at+" "+t.verb,...t.note?[" ",...B(t.note)]:[]]);function Qn(e){let t=[e.elapsed&&`elapsed: ${e.elapsed}`,e.attempt&&`attempt: ${e.attempt}`,e.branch&&`branch: ${e.branch}`,e.lastCommit&&`last commit: ${e.lastCommit}`,e.aheadTrunk&&`commits: ${e.aheadTrunk}`,e.draftPr&&`draft PR: ${e.draftPr}`,e.updated&&`updated: ${e.updated}`].filter(Boolean).join(`\n`);return a`${e.waiting&&[a`<div class="sec">waiting</div>`,Le(e)]}${e.activity&&[a`<div class="sec">activity</div>`,Ee(e)]}${t&&[a`<div class="sec">progress</div>`,a`<pre>${t}</pre>`]}<div class="sec">brief</div><pre>${e.brief}</pre>${e.attachments.length>0&&[a`<div class="sec">attachments (${e.attachments.length})</div>`,se(e.attachments,n=>it(e.lane,e.id,n))]}${e.messageAttachments.length>0&&[a`<div class="sec">message attachments (${e.messageAttachments.length})</div>`,se(e.messageAttachments,n=>it(e.lane,e.id,n))]}${e.followUp&&[a`<div class="sec">forks</div>`,a`<pre>${e.followUp}</pre>`]}<div class="sec">log</div><div class="loglines">${e.log.length?_t(e):"no entries yet"}</div>${e.inbox.length>0&&[a`<div class="sec">inbox</div>`,a`<div class="loglines">${e.inbox.join(`\n---\n`)}</div>`]}${e.awaitingReply&&[a`<div class="sec">awaiting reply</div>`,a`<div>${e.awaitingReply.line} \xB7 from ${e.awaitingReply.from} \xB7 ${v(e.awaitingReply.sentAt)} ago</div>`]}${e.evidence&&[a`<div class="sec">evidence</div>`,a`<div class="loglines">${JSON.stringify(e.evidence)}</div>`]}`}function fe(e){return e.for?[Me(e.for),e.evidence&&e.evidence.deliveredTo?[" ",a`<span class="ok">\u2713delivered</span>`]:[" ",a`<span class="warn">waiting</span>`]]:e.claimedBy?Me(e.claimedBy):""}function re(e){let t=e.evidence&&(e.evidence.prUrl||e.evidence.pr&&e.evidence.pr.url);if(!t)return"";let n=e.prBadge;return[a`<a href=${t} target="_blank" rel="noopener" onClick=${D}>PR</a>`,n&&[" ",a`<span class=${"badge "+L(n)} title=${"observed "+n.observedAt}>${n.text}</span>`],e.prGate&&[" ",a`<span class="badge dim" title="merge gate (pick)">${e.prGate}</span>`]]}var ar={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch"},$t={"pr:review":"bad","pr:checks":"bad","pr:conflict":"pr-conflicts","pr:ready":"ok","pr:draft":"dim",watch:"warn"},Zn=e=>ar[e]||"";var He=e=>a`<a href=${e.url} target="_blank" rel="noopener" onClick=${D}>#${e.number}</a>`,Ue=e=>a`<span class="prname" title=${e.title||void 0}><b>#${e.number}</b>${e.title&&[" ",e.title]}</span>`;function oe(e,t,n){return e.flatMap((s,r)=>{let i=t.get(s)||void 0,o=s===n?a`<b title=${i}>#${s}</b>`:a`<span title=${i}>#${s}</span>`;return r?[" \\u2192 ",o]:[o]})}function me(e){let t=e.checks;return t.passed+"/"+t.total+" passed"+(t.failed?" \\xB7 "+t.failed+" failed":"")+(t.pending?" \\xB7 "+t.pending+" pending":"")}function je(e){let t=e.review||{};return(e.reviewDecision||"")+(t.unresolvedThreads?" \\xB7 "+t.unresolvedThreads+" unresolved":"")+(t.changesRequested?" \\xB7 changes requested":"")}function he(e){return[e.mergeStateStatus,e.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:e.blockedBy?" \\xB7 blocked by #"+e.blockedBy:""]}function bt(e){let t=fn(e);return e?[t.text," \\xB7 ",t.at?[v(t.at)," ago"]:"never checked"]:a`<span class="dim">${t.text}</span>`}function ge(e){if(e.requested)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">requested</span>`],hb:a`<span class="dim">\u2014</span>`};if(e.starting)return{stale:!1,listen:e.starting.failedAt?[a`<span class="dot bad"></span>`,a`<span class="bad">start failed</span>`]:[a`<span class="dot warn"></span>`,"starting"],hb:a`<span class="dim">\u2014</span>`};if(!e.live)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">signed off</span>`],hb:a`<span class="dim">\u2014</span>`};let t=Date.now()-Date.parse(e.heartbeatAt)>18e5,n=e.listening??(!!e.firstParkedAt&&!t);return{stale:t,listen:n?[a`<span class="dot ok"></span>`,"listening"]:[a`<span class="dot warn"></span>`,"not listening"],hb:a`<span class=${t?"bad":"ok"}>${t&&"stale "}${v(e.heartbeatAt)} ago</span>`}}function kt(e){if(e.requested)return{kind:"requested",helmOn:!!_().snapshot?.helmOn};if(e.starting)return e.starting.failedAt?{kind:"start failed",reason:e.starting.reason??"no session signed on in time"}:{kind:"starting",deadline:e.starting.deadline};if(!e.live)return{kind:"signed off"};let t=e.claimed?e.catches.find(n=>n.id===e.claimed&&n.bucket==="active"):void 0;return t?t.waiting||t.verb==="paused"?{kind:"parked",current:t}:{kind:"working",current:t}:{kind:"idle",listening:e.listening??(!!e.firstParkedAt&&Date.now()-Date.parse(e.heartbeatAt??"")<=18e5)}}function lr(e){let t=kt(e);return t.kind==="working"||t.kind==="idle"&&t.listening?"ok":t.kind==="start failed"?"bad":t.kind==="parked"||t.kind==="starting"?"warn":"dim"}var Yn=e=>e.waiting?`waiting on ${e.waiting.on}`:e.note||"waiting",es=e=>e.brief.split(/\\r?\\n/,1)[0]?.trim().slice(0,40)||"(no title)";function ve(e){let t=kt(e),n=a`<span class=${"dot trapdot "+lr(e)}></span>`;if(t.kind==="requested")return[n,a`<span class="dim">${ts(e,t.helmOn)}</span>`];if(t.kind==="starting")return[n,"starting \\xB7 waiting for its session to sign on"];if(t.kind==="start failed")return[n,a`<span class="bad">start failed</span>`," \\xB7 ",t.reason];if(t.kind==="signed off")return[n,a`<span class="dim">signed off</span>`];if(t.kind==="idle")return[n,"idle \\xB7 ",t.listening?"listening":"not listening"];let s=t.current;return t.kind==="parked"?[n,"parked \\xB7 ",Yn(s)]:[n,"working \\xB7 ",a`<a href="#dispatches" onClick=${i=>{i.preventDefault(),D(i),M("dispatch",`${s.lane}:${s.id}`)}}>${s.id.slice(0,8)}</a>`," \\xB7 ",es(s),s.activity&&[" \\xB7 ",s.activity.summary," ",v(s.activity.at)," ago"]]}var ts=(e,t)=>`requested \\xB7 ${e.repo??""} \\xB7 ${e.harness??""} \\xB7 ${t?"waiting for the helm":"waiting for a helm"}`;function J(e){let t=e.starting?.commands;return t?.length?a`<div class="startcmds" onClick=${D}><div class="dim">start it: run one in a terminal</div>${t.map(n=>S(n.command,n.harness))}</div>`:null}function Be(){let t=_().snapshot?.repoKeys??[],[n,s]=ne(!1),[r,i]=ne(""),[o,c]=ne("claude"),[u,l]=ne(null),f=r||t[0]||"",h=async d=>{d.preventDefault();let m=await Sn(f,o);l(m??null),m||s(!1)};return n?a`<form class="newtrap" onClick=${D} onSubmit=${h}>\n <label>repo <select name="repo" value=${f} onChange=${d=>i(d.target.value)}>${t.map(d=>a`<option value=${d}>${d}</option>`)}</select></label>\n <label>harness <select name="harness" value=${o} onChange=${d=>c(d.target.value)}><option value="claude">claude</option><option value="codex">codex</option></select></label>\n <button class="btn" type="submit" disabled=${!f}>Request</button>\n <button class="btn" type="button" onClick=${()=>s(!1)}>Cancel</button>\n ${u&&a`<span class="bad">${u}</span>`}\n </form>`:a`<span class="newtrap"><button class="btn" onClick=${d=>(D(d),s(!0),l(null))}>+ New trap</button></span>`}function Oe(e){let t=kt(e);if(t.kind==="requested")return ts(e,t.helmOn);if(t.kind==="starting")return`starting \\xB7 sign-on due by ${t.deadline}`;if(t.kind==="start failed")return`start failed \\xB7 ${t.reason}`;if(t.kind==="signed off")return"signed off";if(t.kind==="idle")return`idle \\xB7 ${t.listening?"listening":"not listening"}`;if(t.kind==="parked")return`parked \\xB7 ${Yn(t.current)}`;let n=t.current;return[`working \\xB7 ${n.id.slice(0,8)} \\xB7 ${es(n)}`,n.activity&&`${n.activity.summary} ${K(n.activity.at)} ago`].filter(Boolean).join(" \\xB7 ")}function yt(e){let t=e.messages.filter(n=>n.state==="pending").length;return e.messages.length?["\\u2709 "+e.messages.length,t>0&&[" ",a`<span class="warn">(${t} pending)</span>`]]:null}var cr=e=>()=>{window.open(C(e.key),"_blank","noopener")};function wt(e){let t=de(e);return{nodes:[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked?" \\xB7 acked":""],text:[t,`${K(e.filedAt)} ago`,e.acked?"acked":""].filter(Boolean).join(" \\xB7 ")}}function dr(e){let t=wt(e);return a`<a key=${e.key} class=${"card"+(e.acked?" acked":"")} href=${C(e.key)} target="_blank" rel="noopener"><div class="top"><b title=${e.title}>${e.title}</b></div><div class="meta" title=${t.text}>${t.nodes}</div></a>`}function pr(e){return a`<tr key=${e.key} class=${"rowhead"+(e.acked?" acked":"")} onClick=${cr(e)}><td class="grow"><a href=${C(e.key)} target="_blank" rel="noopener" onClick=${t=>t.stopPropagation()}>${e.title}</a></td><td>${de(e)}</td><td>${v(e.filedAt)}</td><td>${e.acked?"acked":""}</td></tr>`}function ns({inp:e}){return e.view==="cards"?e.list.length?a`<div class="cards">${e.list.map(dr)}</div>`:a`<div class="empty">no reports match</div>`:R(["title","from","filed","acked"],e.list.map(pr),"no reports match")}function os(e){let t=e.trim();return/^(https?:\\/\\/|mailto:|#)/i.test(t)?t:void 0}function is(e){let t=e.trim();return/^[^/\\\\:?#]+$/.test(t)&&t!=="."&&t!==".."?t:void 0}function ss(e,t,n,s){let r=0;for(let i=t;i<e.length;i++){if(e[i]==="\\\\"){i++;continue}if(e[i]===n)r++;else if(e[i]===s){if(r===0)return i;r--}}return-1}function rs(e,t){let n=ss(e,t+1,"[","]");if(n<0||e[n+1]!=="(")return;let s=ss(e,n+2,"(",")");if(s<0)return;let r=e.slice(n+2,s).trim().replace(/\\s+"[^"]*"$/,"").replace(/^<(.*)>$/,"$1");return{text:e.slice(t+1,n),target:r,end:s+1}}function q(e){let t=[],n="",s=()=>{n&&t.push({t:"text",v:n}),n=""};for(let r=0;r<e.length;){let i=e[r];if(i==="\\\\"&&r+1<e.length&&/[\\\\`*_{}[\\]()#+\\-.!|<>~]/.test(e[r+1])){n+=e[r+1],r+=2;continue}if(i==="`"){let o=/^`+/.exec(e.slice(r))[0],c=e.indexOf(o,r+o.length);if(c>0){s(),t.push({t:"code",v:e.slice(r+o.length,c).replace(/^ (.*) $/,"$1")}),r=c+o.length;continue}}if(i==="!"&&e[r+1]==="["){let o=rs(e,r+1);if(o){s(),t.push({t:"img",alt:o.text,src:o.target}),r=o.end;continue}}if(i==="["){let o=rs(e,r);if(o){s(),t.push({t:"a",href:o.target,c:q(o.text)}),r=o.end;continue}}if((i==="*"||i==="_")&&e[r+1]===i){let o=e.indexOf(i+i,r+2);if(o>r+2){s(),t.push({t:"b",c:q(e.slice(r+2,o))}),r=o+2;continue}}if(i==="*"||i==="_"&&(r===0||!/\\w/.test(e[r-1]))){let o=r+1;for(;(o=e.indexOf(i,o))>0&&(e[o+1]===i||i==="_"&&/\\w/.test(e[o+1]??""));)o+=2;if(o>r+1&&e[r+1]!==" "){s(),t.push({t:"i",c:q(e.slice(r+1,o))}),r=o+1;continue}}if(i===`\n`){/ {2,}$/.test(n)||n.endsWith("\\\\")?(n=n.replace(/( {2,}|\\\\)$/,""),s(),t.push({t:"br"})):n+=" ",r++;continue}n+=i,r++}return s(),t}var as=/^ {0,3}(`{3,}|~{3,})\\s*([^`\\s]*)/,ls=/^ {0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/,cs=/^ {0,3}([-*_])(\\s*\\1){2,}\\s*$/,Tt=/^( {0,3})([-*+])\\s+(.*)$/,St=/^( {0,3})(\\d{1,9})[.)]\\s+(.*)$/,qe=/^ {0,3}>\\s?(.*)$/,ur=/^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/;function Gt(e){let t=e.trim();t.startsWith("|")&&(t=t.slice(1)),t.endsWith("|")&&!t.endsWith("\\\\|")&&(t=t.slice(0,-1));let n=[],s="";for(let r=0;r<t.length;r++)t[r]==="\\\\"&&t[r+1]==="|"?(s+="|",r++):t[r]==="|"?(n.push(s.trim()),s=""):s+=t[r];return n.push(s.trim()),n}var xt=e=>as.test(e)||ls.test(e)||cs.test(e)||Tt.test(e)||St.test(e)||qe.test(e);function Fe(e){let t=e.replace(/\\r\\n?/g,`\n`).replace(/\\t/g," ").split(`\n`),n=[],s=0;for(;s<t.length;){let r=t[s];if(!r.trim()){s++;continue}let i=as.exec(r);if(i){let f=i[1],h=[];for(s++;s<t.length&&!new RegExp(`^ {0,3}${f[0]}{${f.length},}\\\\s*$`).test(t[s]);)h.push(t[s++]);s++,n.push({t:"code",lang:i[2]??"",v:h.join(`\n`)});continue}let o=ls.exec(r);if(o){n.push({t:"h",level:o[1].length,c:q(o[2])}),s++;continue}if(cs.test(r)){n.push({t:"hr"}),s++;continue}if(qe.test(r)){let f=[];for(;s<t.length&&t[s].trim()&&(qe.test(t[s])||!xt(t[s]));)f.push(qe.exec(t[s])?.[1]??t[s]),s++;n.push({t:"quote",c:Fe(f.join(`\n`))});continue}let c=Tt.exec(r),u=St.exec(r);if(c||u){let f=!c,h=f?St:Tt,d=[];for(;s<t.length;){let m=t[s],p=h.exec(m);if(p&&p[1].length<=3){d.push([p[3]]),s++;continue}if(!m.trim()){if(s+1<t.length&&/^ {2,}\\S/.test(t[s+1])){d.at(-1).push(""),s++;continue}break}if(/^ {2,}\\S/.test(m)){d.at(-1).push(m.replace(/^ {2,4}/,"")),s++;continue}if(xt(m))break;d.at(-1).push(m.trim()),s++}n.push({t:"list",ordered:f,start:f?Number(u[2]):1,items:d.map(m=>Fe(m.join(`\n`)))});continue}if(r.includes("|")&&s+1<t.length&&ur.test(t[s+1])&&t[s+1].includes("-")){let f=Gt(r),h=Gt(t[s+1]).map(m=>m.startsWith(":")&&m.endsWith(":")?"center":m.endsWith(":")?"right":m.startsWith(":")?"left":"");s+=2;let d=[];for(;s<t.length&&t[s].trim()&&t[s].includes("|");){let m=Gt(t[s++]);d.push(f.map((p,g)=>q(m[g]??"")))}n.push({t:"table",align:f.map((m,p)=>h[p]??""),head:f.map(q),rows:d});continue}let l=[];for(;s<t.length&&t[s].trim()&&!xt(t[s])&&!(l.length&&/^ {0,3}(=+|-+)\\s*$/.test(t[s]));)l.push(t[s++].replace(/^ +/,""));if(l.length===0){n.push({t:"p",c:q(r.trim())}),s++;continue}n.push({t:"p",c:q(l.join(`\n`))})}return n}function F(e,t){return t.map(n=>{switch(n.t){case"text":return n.v;case"code":return a`<code>${n.v}</code>`;case"b":return a`<strong>${F(e,n.c)}</strong>`;case"i":return a`<em>${F(e,n.c)}</em>`;case"br":return a`<br />`;case"a":{let s=os(n.href);return s?a`<a href=${s} target="_blank" rel="noopener noreferrer">${F(e,n.c)}</a>`:F(e,n.c)}case"img":{let s=is(n.src);return s?ue(e(s),n.alt||s,"mdimg"):a`<span class="dim">[image not shown: ${n.alt||n.src}]</span>`}}})}function We(e,t){return t.map(n=>{switch(n.t){case"h":{let s=F(e,n.c);return n.level===1?a`<h1>${s}</h1>`:n.level===2?a`<h2>${s}</h2>`:n.level===3?a`<h3>${s}</h3>`:a`<h4>${s}</h4>`}case"p":return a`<p>${F(e,n.c)}</p>`;case"code":return a`<pre class="mdcode"><code>${n.v}</code></pre>`;case"hr":return a`<hr />`;case"quote":return a`<blockquote>${We(e,n.c)}</blockquote>`;case"list":{let s=n.items.map(r=>a`<li>${We(e,r)}</li>`);return n.ordered?a`<ol start=${n.start}>${s}</ol>`:a`<ul>${s}</ul>`}case"table":return a`<div class="mdtable"><table><thead><tr>${n.head.map((s,r)=>a`<th style=${n.align[r]?"text-align:"+n.align[r]:void 0}>${F(e,s)}</th>`)}</tr></thead><tbody>${n.rows.map(s=>a`<tr>${s.map((r,i)=>a`<td style=${n.align[i]?"text-align:"+n.align[i]:void 0}>${F(e,r)}</td>`)}</tr>`)}</tbody></table></div>`}})}var _e=new Map;function ds(e){let t=_e.get(e);return t||(t=Fe(e),_e.set(e,t),_e.size>8&&_e.delete(_e.keys().next().value)),t}function fr({r:e,text:t}){let n=t?.text!==void 0?We(s=>ct(e.key,s),ds(t.text)):t?.error?a`<div class="bad">${t.error}</div>`:a`<div class="dim">loading\u2026</div>`;return a`<div class="mdpage" data-report=${e.key}>${n}</div>`}function ps({text:e,fileUrl:t}){return a`<div class="mdpage">${We(t,ds(e))}</div>`}function Ct(e){let t=de(e);return[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked&&[" \\xB7 ",a`<span class="ok">acked</span>`]]}function us({r:e,text:t,heading:n}){return[n&&a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)}</div>`,a`<${fr} r=${e} text=${t} />`,e.attachments.length>0&&[a`<div class="sec">report attachments (${e.attachments.length})</div>`,se(e.attachments,s=>ct(e.key,s))],!e.acked&&[a`<div class="sec">ack</div>`,S("lobstah attention ack "+e.key)]]}var mr=/\\.(png|jpe?g|gif|webp)$/i,hr=e=>{e.style.height="auto",e.style.height=e.scrollHeight+"px"};function gr(e,t){return t.length?a`<div class="dfiles">${t.map(n=>mr.test(n.name)?a`<span key=${n.name}>${ue(at(e,n.name),n.name,"dimg")}</span>`:a`<div key=${n.name} class="dfile"><span>${n.name}</span> <span class="dim">\xB7 ${n.type} \xB7 ${n.bytes} bytes \xB7 </span><code>${n.path}</code></div>`)}</div>`:null}function vr(e,t,n){let s=e.key,r=e.kind==="decision"?e.options:[],i=t?.text??"",o=t?.files??[],c=!!t?.sending;return[r.length>0&&a`<div class="doptions">${r.map(u=>a`<button key=${u} type="button" class=${"btn dopt"+(t?.option===u?" on":"")} aria-pressed=${t?.option===u?"true":"false"} disabled=${c} onClick=${()=>bn(s,u)}>${u}</button>`)}</div>`,a`<textarea\n class="danswer"\n rows="2"\n placeholder=${r.length?"Add to your answer, or answer in your own words":"Your answer"}\n value=${i}\n disabled=${c}\n onInput=${u=>{let l=u.currentTarget;hr(l),A(s,{text:l.value,error:void 0})}}\n onPaste=${u=>{kn(s,u)}}\n ></textarea>`,a`<div class="dfoot">\n <label class="btn dattach" title="attach images or files">attach<input type="file" multiple accept=${n} disabled=${c} onChange=${u=>{let l=u.currentTarget;ft(s,l.files).then(()=>{l.value=""})}} /></label>\n ${o.map((u,l)=>a`<span key=${u.name+l} class="dchip">${u.name}<button type="button" class="dx" title="remove" disabled=${c} onClick=${()=>yn(s,l)}>\xD7</button></span>`)}\n ${t?.error&&a`<span class="bad derr">${t.error}</span>`}\n <button type="button" class="btn dsend" disabled=${c} onClick=${()=>{wn(s)}}>${c?"Sending\\u2026":"Send"}</button>\n </div>`]}function _r(e,t,n,s){let r=e.dispatch,i=e.lane??"work",c=[r&&a`<a class="dlink" href="#" onClick=${l=>{l.preventDefault(),w("dispatch",i+":"+r)()}}>${r.slice(0,8)}</a>`,e.repo,[v(e.at)," ago"]].filter(Boolean).flatMap((l,f)=>f?[" \\xB7 ",l]:[l]),u="dcard"+(e.kind==="question"?" plain":"")+(t?.sent?" answered":"")+(n?" focus":"");return a`<div key=${e.key} class=${u} data-decision=${e.key}>\n <div class="dtop">\n ${e.kind==="question"&&a`<span class=${"badge "+(e.verb==="blocked"?"bad":"warn")}>${e.verb}</span>`}\n <b class="dtitle">${e.kind==="decision"?e.title:B(e.note)}</b>\n <span class="dmeta dim">${c}</span>\n </div>\n ${e.kind==="decision"&&e.detail.trim()&&a`<${ps} text=${e.detail} fileUrl=${l=>at(e.key,l)} />`}\n ${e.kind==="decision"&&gr(e.key,e.attachments)}\n ${t?.sent?a`<div class="danswered ok">answered \xB7 ${t.sent}</div>`:vr(e,t,s)}\n </div>`}function fs({cards:e,drafts:t,focus:n,extensions:s}){let r=s.join(","),i=e.length?a`<div class="dcards">${e.map(o=>_r(o,t[o.key],o.key===n,r))}</div>`:a`<div class="empty">none</div>`;return a`<section class="decisions"><h2>decisions</h2>${i}</section>`}function yr(e,t){let n=e.badge&&a`<span class=${"badge "+(e.badge.tone||"dim")+U(e.badge.text)} title=${H(e.badge.text)}>${e.badge.text}</span>`;if(e.href){let s=(t==="cards"?"card":"deckline click")+(e.acked?" acked":"");return t==="cards"?a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener"><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}</a>`:a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener">${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}</a>`}return t==="cards"?a`<div key=${e.key} class=${"card"+(e.acked?" acked":"")} onClick=${e.open} style=${e.open?void 0:"cursor:default"}><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}${e.extra}${e.action&&a`<div class="foot"><span class="footact">${e.action}</span></div>`}</div>`:a`<div key=${e.key} class=${"deckline"+(e.open?" click":"")+(e.acked?" acked":"")} onClick=${e.open}>${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}${e.action&&[" ",a`<span class="dim">\xB7 </span>`,e.action]}${e.extra}</div>`}var ms=(e,t)=>e>0&&a`<a class="deckmore" href=${"#"+t}>+${e} more \u2192</a>`;function Ve(e,t,n,s,r,i){let o=t.slice(0,s),c=o.map(l=>yr(l,r)),u=o.length?r==="cards"?a`<div class="cards">${c}</div>`:c:a`<div class="empty">none</div>`;return a`<section><h2><a href=${"#"+n}>${e} \u2192</a></h2>${i&&a`<div class="tabhead">${i}</div>`}${u}${ms(t.length-o.length,n)}</section>`}function wr(e,t,n,s){let r=t.find(l=>l.number===e.nextNumber)||t[0],i=oe(e.numbers,new Map(t.map(l=>[l.number,l.title]))),o=r?"next #"+r.number:"nothing mergeable";if(s==="cards"){let l=f=>{let h=n.get(f.key)||[],d=h.length>0&&h.every(g=>g.acked),m=h.length?h.map(g=>a`<span class=${"badge "+($t[g.kind]||"dim")}>${Zn(g.kind)}</span>`):a`<span class=${"badge "+L(f.badge)+U(f.badge.text)} title=${H(f.badge.text)}>${f.badge.text}</span>`,p=`${f.repo} \\xB7 ${f.badge.text}${d?" \\xB7 acked":""}`;return a`<div key=${f.key} class=${"card"+(d?" acked":"")} onClick=${w("pr",f.key)}><div class="top">${Ue(f)}${m}</div><div class="meta" title=${p}>${p}</div></div>`};return a`<div key=${e.id} class="deckstack"><div class="dim">${i} \xB7 ${o}</div><div class="cards">${t.map(l)}</div></div>`}let c=t.flatMap(l=>(n.get(l.key)||[]).map(f=>a`<span class=${"badge "+($t[f.kind]||L(l.badge))+(f.acked?" acked":"")}>#${l.number} ${l.badge.text}</span>`)),u=c.flatMap((l,f)=>f?[" ",l]:[l]);return a`<div key=${e.id} class=${"deckline"+(r?" click":"")} onClick=${r&&w("pr",r.key)}><b>${i}</b> <span class="dim">\xB7 ${o}</span>${c.length>0&&[" \\xB7 ",u]}</div>`}function Gr(e,t){let n=e.stacks.slice(0,3),s=new Map;for(let i of e.prAttention){let o=s.get(i.key)||[];o.push(i),s.set(i.key,o)}let r=n.length?n.map(i=>wr(i,e.prs.filter(o=>o.stackId===i.id).sort((o,c)=>o.position-c.position),s,t)):a`<div class="empty">none</div>`;return a`<section><h2><a href="#prs">PRs \u2192</a></h2>${r}${ms(e.stacks.length-n.length,"prs")}</section>`}function hs({inp:e,drafts:t,focus:n,extensions:s}){let r=e.view,i=e.inflight.map(l=>({key:l.lane+":"+l.id,title:[l.id.slice(0,8)," ",l.repo||""],badge:{text:l.verb,tone:l.verb==="needs-decision"||l.verb==="blocked"?"bad":"dim"},meta:[B(l.note||"",90),l.verbAt&&[" \\xB7 ",v(l.verbAt)," ago"],(l.for||l.claimedBy)&&[" \\xB7 ",Me(l.for||l.claimedBy)]],metaText:[j(l.note||""),l.verbAt&&`${K(l.verbAt)} ago`,j(l.for||l.claimedBy||"")].filter(Boolean).join(" \\xB7 "),open:w("dispatch",l.lane+":"+l.id)})),o=e.landed.map(l=>({key:l.key,title:[l.id.slice(0,8)," ",l.repo||"",l.unreported&&[" ",a`<span class="badge warn unreported">unreported</span>`]],badge:{text:l.verb,tone:l.verb==="failed"?"bad":"ok"},meta:[B(l.note||"",90)," \\xB7 ",v(l.at)," ago"],metaText:`${j(l.note||"")} \\xB7 ${K(l.at)} ago`,open:w("dispatch",l.lane+":"+l.id)})),c=e.reports.map(l=>{let f=wt(l);return{key:l.key,title:l.title,meta:f.nodes,metaText:f.text,href:C(l.key),acked:!!l.acked}}),u=e.traps.map(({x:l})=>({key:l.trapId,title:"\\u{1FAA4} "+(l.label??`wt:${l.trapId}`),badge:l.requested?{text:"requested",tone:"dim"}:l.starting?{text:l.starting.failedAt?"start failed":"starting",tone:l.starting.failedAt?"bad":"warn"}:{text:l.live?l.harness||"live":"signed off",tone:l.live?"ok":"dim"},meta:[l.repo||""," \\xB7 ",ve(l)],metaText:`${l.repo||""} \\xB7 ${Oe(l)}`,action:z(l),extra:J(l),open:w("trap",l.trapId)}));return a`<div class="deckgrid"><${fs} cards=${e.decisions} drafts=${t} focus=${n} extensions=${s} />${Ve("in flight",i,"dispatches",4,r)}${Ve("Landed \\xB7 24h",o,"dispatches",8,r)}${Ve("reports",c,"reports",8,r)}${Ve("traps",u,"traps",8,r,a`<${Be} />`)}${Gr(e,r)}</div>`}function xr(e,t){if(!t)return e;let n=new Map(e.map(i=>[i.id,i])),s=i=>{let o=i,c=new Set;for(;o.followUp&&n.has(o.followUp)&&!c.has(o.id);)c.add(o.id),o=n.get(o.followUp);return o.id},r=new Map;for(let i of e){let o=s(i),c=r.get(o)||[];c.push(i),r.set(o,c)}return[...r.values()].sort((i,o)=>o[0].sort-i[0].sort).flatMap(i=>i.sort((o,c)=>o.id===s(o)?-1:c.id===s(c)?1:o.sort-c.sort))}function Tr(e,t){let n=e.lane+":"+e.id;return a`<tr key=${n} class="rowhead" onClick=${w("dispatch",n)}><td>${t&&e.followUp&&a`<span class="dim">\u21B3 </span>`}${e.id.slice(0,8)}</td><td>${e.lane} / ${e.bucket}</td><td>${e.repo}</td><td class=${"v-"+e.verb}>${e.verb}</td><td class="grow">${B(e.note??"",90)}${Le(e)}${Ee(e)}</td><td>${v(e.verbAt)}</td><td>${fe(e)}</td><td>${re(e)}</td></tr>`}function Sr(e){let t=e.lane+":"+e.id;return a`<div key=${t} class="card" onClick=${w("dispatch",t)}><div class="top"><b>${e.id.slice(0,8)}</b><span class=${"badge v-"+e.verb+U(e.verb)} title=${H(e.verb)}>${e.verb}</span></div><div class="meta" title=${`${e.repo} \\xB7 ${e.lane} ${e.bucket}`}>${e.repo} \xB7 ${e.lane} ${e.bucket}${e.verbAt&&[" \\xB7 ",v(e.verbAt)]}</div>${e.note&&a`<div class="note" title=${j(e.note)}>${B(e.note)}</div>`}${Le(e)}${Ee(e)}<div class="foot">${fe(e)} ${re(e)}</div></div>`}function gs({inp:e}){let t=e.list;return e.view==="cards"?t.length?a`<div class="cards">${t.map(Sr)}</div>`:a`<div class="empty">no dispatches match</div>`:R(["id","lane / bucket","repo","verb","note","age","addressed","pr"],xr(t,e.chain).map(n=>Tr(n,e.chain)),"no dispatches match")}var Cr={deck:"On deck",dispatches:"Dispatches",traps:"Traps",prs:"PRs",reports:"Reports",notices:"Notices"},Dr=["working","needs-decision","blocked","paused","done","failed","unknown"];function Ar(e,t){let n=t.daemonStale,s=e.daemon?[a`<span class=${n?"bad":"ok"}>${n&&"stale "}${v(e.daemon.heartbeat)} ago</span>`," ",a`<span class="dim">v${e.daemon.version}</span>`]:a`<span class="bad">down</span>`;return[a`<span class="chip">daemon ${s}</span>`,e.slots&&a`<span class="chip">active headless: ${e.slots.headless} of ${e.slots.limit}; traps: ${e.slots.traps}${e.slots.parked?`; parked: ${e.slots.parked} (no slot)`:""}</span>`,t.helms.map(({x:r,stale:i})=>a`<span key=${r.grounds} class="chip click" onClick=${w("helm",r.grounds)}>\u26F5 <b>${r.man}</b> <span class="dim">helm ${r.grounds}</span> <span class=${i?"warn":"ok"}>${i&&"stale "}${v(r.heartbeatAt)} ago</span></span>`)]}var Dt=[];function Rr(e){let t=[...new Set([...e.dispatches.map(n=>n.repo),...e.notices.map(n=>n.repo),...e.prs.map(n=>n.repo)].filter(Boolean))].sort();return t.length!==Dt.length&&(Dt=t),Dt}var At=[];function Pr(e){let t=[...new Set(e.notices.map(n=>n.kind))].sort();return t.length!==At.length&&(At=t),At}var $e=e=>e.currentTarget.value;function Ir(e,t,n){let s=o=>t===o?"":"display:none",r=e?Rr(e):[],i=e?Pr(e):[];return a`<div class="controls">\n <select id="f-lane" style=${e&&s("dispatches")} value=${n.lane} onChange=${o=>N({lane:$e(o)})}>\n <option value="">all lanes</option>\n <option value="work">work</option>\n <option value="chore">chore</option>\n </select>\n <select id="f-repo" value=${n.repo} onChange=${o=>N({repo:$e(o)})}>\n <option value="">all repos</option>\n ${r.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <select id="f-verb" style=${e&&s("dispatches")} value=${n.verb} onChange=${o=>N({verb:$e(o)})}>\n <option value="">all verbs</option>\n ${Dr.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <label id="chain-control" style=${e&&s("dispatches")}><input id="f-chain" type="checkbox" checked=${!!n.chain} onChange=${o=>N({chain:o.currentTarget.checked})} /> group by chain</label>\n <select id="f-kind" style=${e&&s("notices")} value=${n.noticeKind} onChange=${o=>N({noticeKind:$e(o)})}>\n <option value="">all notice kinds</option>\n ${i.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <input id="f-q" type="search" placeholder="search id \xB7 note \xB7 brief" value=${n.q} onInput=${o=>N({q:$e(o)})} />\n </div>`}function vs({d:e,inp:t,tab:n,st:s,stale:r}){return[a`<div class="headerline"><h1>\u{1F99E}\u2728 spyglass<span id="stale" style=${e||r?r?"display:inline":"display:none":void 0}> \xB7 STALE FEED</span></h1><span id="settings-slot"><button id="gearbtn" title="settings" aria-label="settings" onClick=${()=>M("settings","browser")}>\u2699</button></span></div>`,a`<div class="chips"><span id="chips" style="display:contents">${e&&t&&Ar(e,t.chips)}</span><span class="chip dim" id="clock">${e&&new Date(e.now).toLocaleTimeString("en-GB")}</span></div>`,a`<nav class="tabs" id="tabs" aria-label="Spyglass views">${ce.map(i=>a`<a href=${"#"+i} data-tab=${i} class=${e?i===n?"on":"":void 0}>${Cr[i]}</a>`)}</nav>`,Ir(e,n,s)]}var _s=({d:e})=>e&&["\\u{1F99E}\\u2728 lobstah v"+e.version+" \\xB7 ",a`<a href=${e.repoUrl} target="_blank">${e.repoUrl.replace("https://github.com/","")}</a>`];function $s(e,t){if(!t.lobs)return[];let n={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch",report:"report"},s=t.hidden??{},r=e.filter(o=>!o.acked&&!(o.key!==void 0&&s[o.key]===o.stateHash)).map(o=>{let c=n[o.kind??""]??"",u=o.key!==void 0?{hideKey:o.key,hideHash:o.stateHash??""}:{};return o.kind==="decision"&&o.key?{key:"decision:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:(o.kind??"question")==="question"&&o.key?{key:"question:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:o.kind==="report"&&o.key?{key:"report:"+o.key,text:o.note||o.verb,label:c,href:C(o.key),...u}:typeof o.kind=="string"&&o.kind.startsWith("pr:")&&o.prUrl?{key:o.kind+":"+(o.key??o.prUrl),text:o.note||o.kind,href:o.prUrl,label:c,...u}:{key:(o.kind??"question")+":"+(o.key??o.lane+":"+o.id),text:o.note||o.verb,label:c,...o.kind==="watch"?{}:{open:{type:"dispatch",key:o.lane+":"+o.id}},...u}});!r.length&&t.preview&&(r=[{key:"preview",text:"attention questions crawl in here",...t.previewHelm?{open:{type:"helm",key:t.previewHelm}}:{}}]);let i=r.length>4?r.length-4:0;return r=r.slice(0,4),i&&(r[3]={...r[3],text:"\\u2026and "+i+" more \\u2014 see attention",label:""}),r}var bs=!0;function Mr(e,t,n){let s="animation-duration:"+((innerWidth+180)/(100+t*12)).toFixed(1)+"s;animation-delay:-"+t*9%14+"s",r=()=>{e.hideKey&&An(e.hideKey,e.hideHash??"")},i=[a`<div class="bub">${e.label&&[a`<span class="badge dim">${e.label}</span>`," "]}<span>${e.text.length>48?e.text.slice(0,47)+"\\u2026":e.text}</span></div>`,n===!1?a`<span class="fallback">\u{1F99E}</span>`:a`<div class="sprite"></div>`,bs&&a`<img\n class="star"\n src="/star.png"\n alt=""\n onError=${c=>{bs=!1,c.currentTarget.remove()}}\n />`],o=e.open;return e.hash?a`<a key=${e.key} class="lob" style=${s} title="open the decision" href=${e.hash} onClick=${r}>${i}</a>`:e.href?a`<a key=${e.key} class="lob" style=${s} title=${e.label==="report"?"open the report":"open the PR"} href=${e.href} target="_blank" rel="noopener" onClick=${r}>${i}</a>`:a`<div\n key=${e.key}\n class="lob"\n style=${s}\n title="click to open"\n onClick=${()=>{r(),o&&M(o.type,o.key)}}\n >\n ${i}\n </div>`}function ks({state:e}){let t=e.snapshot,n=$s(t?.attention||[],{lobs:e.prefs.lobs,hidden:e.lobHidden,preview:e.preview,previewHelm:t&&t.helms.length?t.helms[0].grounds:void 0}),s=n.map(r=>r.key).join("|")+(e.spriteOk===null?"?":e.spriteOk?"s":"e");return Vn(()=>n.map((r,i)=>Mr(r,i,e.spriteOk)),[s])}function Ke({box:e}){if(!e)return null;let t=n=>{n.target===n.currentTarget&&ee()};return a`<div id="lightbox" class="lightbox" role="dialog" aria-label=${e.name} onClick=${t}>\n <button type="button" class="lbclose" title="close" onClick=${ee}>\xD7</button>\n <img class="lbimg" src=${e.src} alt=${e.name} />\n <a class="lboriginal" href=${e.src} target="_blank" rel="noopener">open original \xB7 ${e.name}</a>\n </div>`}var be=a`<span class="x" onClick=${te}>\xD7</span>`,ys=(e,t,n)=>a`<span class="seg">${t.map(([s,r])=>a`<button class=${s===n?"on":""} onClick=${()=>e(s)}>${r}</button>`)}</span>`;function Er(e,t){let n=ys(Cn,[["table","table"],["cards","cards"]],t.view),s=ys(Dn,[["on","on"],["off","off"]],t.lobs?"on":"off"),r=e.attentionError?e.attentionError:(e.attentionKinds||[]).join(" \\xB7 ");return[be,a`<h3>\u2699 settings <span class="dim" style="font-weight:normal">\xB7 this browser only</span></h3>`,a`<div class="settings"><div class="row"><span class="lbl">view<span class="hint">cards or table (notices stay a table)</span></span>${n}</div><div class="row"><span class="lbl">lobs<span class="hint">crawling lobsters in this page</span></span>${s}</div><div class="row"><span class="lbl">attention<span class="hint">kinds shown \u2014 attentionKinds in config.toml (read-only here)</span></span><span class="dim" style="font-size:11px;text-align:right;max-width:260px">${r}</span></div></div>`]}function Lr(e,t){let n=oe(t.numbers,new Map(t.numbers.map((r,i)=>[r,t.titles[i]])),e.number),s=t.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:t.blockedBy?" \\xB7 blocked by #"+t.blockedBy:t.nextNumber?" \\xB7 next is #"+t.nextNumber:"";return[n," \\xB7 ",t.position," of ",t.size," \\xB7 floor ",t.floor,s]}var Nr=e=>t=>{t.preventDefault(),M("dispatch",e)};function Hr(e,t){let n=un(e,t),s=n.pr,r=n.stack,i=n.watch,o=je(s),c=n.chain.length?n.chain.flatMap((l,f)=>[f?" \\u2192 ":"",l.culled?a`<span class="dim">${l.id.slice(0,8)} (culled)</span>`:[a`<a href="#" onClick=${Nr(l.modalKey)}>${l.id.slice(0,8)}</a>`," ",a`<span class="dim">${l.verb}</span>`]]):a`<span class="dim">none</span>`,u=i?[a`<div>${i.key} \xB7 owner ${i.owner||"?"} \xB7 ${i.lastCheckedAt?["checked ",v(i.lastCheckedAt)," ago"]:"never checked"}</div>`,i.lastError&&a`<div class="bad">${i.lastError}</div>`,a`<div class="dim" style="font-size:11px;margin-top:4px">cursor</div>`,S(i.cursor)]:a`<div class="dim">no watch \u2014 <code>lobstah watch add ${s.url}</code> registers one</div>`;return[be,a`<h3>${He(s)} ${s.title||""} <span class=${"badge "+L(s.badge)}>${s.badge.text}</span>${s.draft&&s.badge.text!=="draft"&&[" ",a`<span class="badge pr-draft">draft</span>`]}</h3>`,a`<div class="sub">${s.repo} \xB7 ${s.state} \xB7 observed ${v(s.observedAt)} ago${s.gate&&" \\xB7 gate "+s.gate}</div>`,a`<div class="sec">checks</div><div>${me(s)}</div><div class="sec">review</div><div>${o||a`<span class="dim">no review yet</span>`}</div><div class="sec">merge</div><div>${he(s)}</div>${s.repairWait&&a`<div class="sec">repair waits</div><div>${s.repairWait.heldBy}: ${s.repairWait.reason}${s.repairWait.until?" \\xB7 until "+s.repairWait.until:""}</div>`}<div class="sec">refs</div><div>${s.headRefName||"?"} \u2192 ${s.baseRefName||"?"}</div><div class="sec">stack</div><div>${r?Lr(s,r):a`<span class="dim">not stacked</span>`}</div><div class="sec">dispatch chain</div><div>${c}</div><div class="sec">watch</div>`,u]}var ws=(e,t)=>(e==="codex"?"codex resume ":"claude --resume ")+t;function Ur(e){let t=Date.now()-Date.parse(e.heartbeatAt)>18e5;return[be,a`<h3>\u26F5 ${e.man}</h3><div class="sub">helm of <b>${e.grounds}</b> (${(e.repos||[]).join(", ")})</div><div class="sub">${e.harness??"?"} \xB7 ${e.cwd??"?"}${e.host&&" \\xB7 "+e.host}</div><div class="sub">session ${e.sessionId??""} \xB7 signed on ${v(e.signedOnAt)} ago \xB7 heartbeat <span class=${t?"warn":"ok"}>${v(e.heartbeatAt)} ago</span></div>`,e.sessionId&&[a`<div class="sec">open this session</div>`,S(ws(e.harness,e.sessionId))],e.transcript&&[a`<div class="sec">transcript</div>`,S(e.transcript)]]}function jr(e){return[a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)} \xB7 <a href=${C(e.key)} target="_blank" rel="noopener">open the report \u2197</a></div>`]}function Br(e,t){let n=e.claimedBy&&e.claimedBy.startsWith("wt:")?[a`<div class="sec">worked by trap</div>`,a`<div class="claimedby">claimed by ${Ne(e.claimedBy.slice(3))}</div>`,S(j(e.claimedBy)),a`<div class="dim" style="font-size:11px">an opted-in interactive session mans this seat \u2014 attach would resume someone\'s live thread. Message it instead: lobstah send ${j(e.claimedBy)} "\u2026"</div>`]:[a`<div class="sec">open this session</div>`,S("lobstah attach "+e.id)];return[be,a`<h3>${e.id.slice(0,8)} <span class=${"badge v-"+e.verb}>${e.verb}</span></h3>`,a`<div class="sub">${e.repo} \xB7 ${e.lane} ${e.bucket} \xB7 ${v(e.verbAt)}${e.for&&[" \\xB7 ",fe(e)]} ${re(e)}</div>`,n,e.worktree&&[a`<div class="sec">worktree${e.worktreeOf?" (reused from "+e.worktreeOf.slice(0,8)+")":""}</div>`,S(e.worktree),e.worktreeKept&&a`<div class="dim" style="font-size:11px">kept: ${e.worktreeKept}</div>`],e.transcript&&[a`<div class="sec">transcript</div>`,S(e.transcript)],t&&jr(t),Qn(e)]}function Or(e){let t=ge(e),n=e.requested?[e.repo??""," \\xB7 ",e.harness??""," \\xB7 requested ",v(e.requested.at)," ago from the glass; the helm reserves it and starts its session."]:e.starting?e.starting.failedAt?[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 start failed: ",e.starting.reason??"",". Its addressed work stays queued; a session can still redeem the ticket, or `lobstah stow --wt ",e.name??e.trapId,"` withdraws it."]:[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 starting: waiting for a session to redeem the ticket (due by ",e.starting.deadline,")"]:e.live?[e.repo??"addressed bait only"," \\xB7 session ",e.sessionId??""," \\xB7 signed on ",v(e.signedOnAt)," ago \\xB7 ",t.listen," \\xB7 heartbeat ",t.hb]:"signed off \\u2014 registration gone; the lifecycle, messages, and catches are the surviving record. Re-soaking the same worktree restores this address.",s=e.notices.length?e.notices.map(o=>a`<div key=${o.seq} class="loglines">${v(o.at)} ago \xB7 <b>${o.kind}</b> \u2014 ${o.text}</div>`):a`<div class="empty">none recorded</div>`,r=e.messages.length?e.messages.map(o=>a`<div key=${o.file} class=${"msg"+(o.from==="helm"?" from-helm":"")}><div class="hdr">from ${o.from} \xB7 ${o.at&&[v(o.at)," ago"]} \xB7 ${o.state==="pending"?a`<span class="warn">pending</span>`:a`<span class="ok">delivered</span>`}</div>${o.text}${o.attachments?.length?se(o.attachments,c=>sn(e.trapId,c)):""}</div>`):a`<div class="empty">none</div>`,i=e.catches.length?e.catches.map(o=>a`<div key=${o.lane+":"+o.id} class="catch"><div class="hdr"><b>${o.id.slice(0,8)}</b><span class=${"badge v-"+o.verb}>${o.verb}</span><span class="dim">${v(o.verbAt)}</span>${re(o)}</div><div class="loglines">${_t(o)}</div></div>`):a`<div class="empty">none yet</div>`;return[be,a`<h3>\u{1FAA4} ${e.label??`wt:${e.trapId}`} <span class="badge">${e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??"signed off"}</span></h3>`,e.worktree&&a`<div class="sub">${e.worktree}</div>`,a`<div class="sub">${n}</div>`,a`<div class="sec">window</div>`,z(e),J(e),e.live&&e.sessionId&&[a`<div class="sec">open this session</div>`,S(ws(e.harness,e.sessionId)),a`<div class="dim" style="font-size:11px">as registered at sign-on \u2014 a hookless enlistment may hold a made-up id</div>`],a`<div class="sec">lifecycle (${e.notices.length})</div>`,s,a`<div class="sec">messages (${e.messages.length})</div>`,r,a`<div class="sec">catches (${e.catches.length})</div>`,i]}function Gs({snapshot:e,modal:t,prefs:n}){if(!e||!t)return null;let s=Y(e,t);if(!s)return null;if(t.type==="settings")return Er(s,n);if(t.type==="pr")return Hr(e,t.key);if(t.type==="helm")return Ur(s);if(t.type==="dispatch"){let r=an(e,s);return Br(s,r)}return Or(s)}var qr=e=>a`<tr key=${e.seq}><td class="dim">${v(e.at)}</td><td>${e.kind}</td><td class="grow">${e.text}</td><td class="dim">${e.repo??""}</td></tr>`,xs=({inp:e})=>R(["at","kind","text","repo"],e.list.map(qr),"no notices");function Ts(e){let t=new Map;for(let n of e.prs){let s=t.get(n.stackId)||[];s.push(n),t.set(n.stackId,s)}return e.stacks.map(n=>({s:n,prs:t.get(n.id)||[]})).filter(n=>n.prs.length)}var Ss=(e,t)=>oe(e.numbers,new Map(t.map(n=>[n.number,n.title]))),Rt=e=>e.state+(e.draft?" \\xB7 draft":"")+(e.state==="MERGED"&&e.mergedAt?" \\xB7 "+e.mergedAt:"");function Cs(e){let t=s=>{let r=String(s??"");return r.length>24?r.slice(0,23)+"\\u2026":r},n=e.watches.map(s=>a`<tr key=${s.key}><td>${s.key}</td><td>${s.owner}</td><td title=${s.cursor}>${t(s.cursor)}</td><td>${s.lastCheckedAt&&v(s.lastCheckedAt)}</td><td>${s.lastError||""}</td></tr>`);return[a`<h2>other watches</h2>`,R(["key","owner","cursor","last check","error"],n,"no other watches")]}function Fr(e){let t=Ts(e).flatMap(({s:n,prs:s})=>[a`<tr key=${"stack:"+n.id}><th colspan="8">${Ss(n,s)} \xB7 floor ${n.floor}${n.open?" \\xB7 open":" \\xB7 history"}</th></tr>`,...s.map(r=>a`<tr key=${r.key} class="rowhead" onClick=${w("pr",r.key)}><td>${He(r)}</td><td class="grow">${r.title||""}</td><td>${Rt(r)}</td><td>${me(r)}</td><td>${je(r)}</td><td>${he(r)}</td><td>${bt(r.watch)}</td><td>${r.gate||""}</td></tr>`)]);return[R(["PR","title","state","checks","review","merge","watch","gate"],t,"no PR evidence"),Cs(e)]}function Wr(e){let t=Ts(e),n=r=>a`<div key=${r.key} class="card" onClick=${w("pr",r.key)}><div class="top">${Ue(r)}<span class=${"badge "+L(r.badge)+U(r.badge.text)} title=${H(r.badge.text)}>${r.badge.text}</span></div><div class="meta" title=${`${r.repo} \\xB7 ${Rt(r)}`}>${r.repo} \xB7 ${Rt(r)} \xB7 ${he(r)}</div><div class="foot"><span>${me(r)}</span><span>${bt(r.watch)}</span>${r.gate&&a`<span>gate ${r.gate}</span>`}</div></div>`;return[t.length?t.map(({s:r,prs:i})=>a`<${Z} key=${"stack:"+r.id}><h2>${Ss(r,i)} \xB7 floor ${r.floor}${r.open?"":" \\xB7 history"}</h2><div class="cards">${i.map(n)}</div><//>`):a`<div class="empty">no PR evidence</div>`,Cs(e)]}var Ds=({inp:e})=>e.view==="cards"?Wr(e):Fr(e);var Pt=e=>e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??(e.live?"":"signed off"),As=e=>e.live||e.starting?"":" dim";function Vr(e){let t=ge(e);return a`<tr key=${e.trapId} class=${"rowhead"+As(e)} onClick=${w("trap",e.trapId)}><td><b>${e.label??`wt:${e.trapId}`}</b></td><td>${e.repo??"\\u2014"}</td><td class="grow">${e.worktree??""}</td><td>${e.harness??""}</td><td>${(e.sessionId??"").slice(0,8)}</td><td>${ve(e)}${J(e)}</td><td>${t.listen}</td><td>${t.hb}</td><td>${yt(e)}</td><td>${e.catches.length}</td><td>${z(e)}</td></tr>`}function Kr(e){let t=ge(e),n=yt(e),s=`${e.repo??(e.live?"addressed bait only":"history")}${e.sessionId?" \\xB7 session "+e.sessionId.slice(0,8):""}`;return a`<div key=${e.trapId} class=${"card"+As(e)} onClick=${w("trap",e.trapId)}><div class="top"><b>\u{1FAA4} ${e.label??`wt:${e.trapId}`}</b><span class=${"badge"+U(Pt(e))} title=${H(Pt(e))}>${Pt(e)}</span></div><div class="meta" title=${s}>${s}</div>${e.worktree&&a`<div class="note" title=${e.worktree}>${e.worktree}</div>`}<div class="note" title=${Oe(e)}>${ve(e)}</div>${J(e)}<div class="foot"><span>${t.listen}${e.live&&[" \\xB7 heartbeat ",t.hb]} \xB7 ${e.catches.length} catch${e.catches.length===1?"":"es"}${n&&[" \\xB7 ",n]}</span><span class="footact">${z(e)}</span></div></div>`}function Rs({inp:e}){let t=e.list.map(s=>s.x),n=a`<div class="tabhead"><${Be} /></div>`;return e.view==="cards"?[n,t.length?a`<div class="cards">${t.map(Kr)}</div>`:a`<div class="empty">no traps soaking</div>`]:[n,R(["address","repo","worktree","harness","session","now","listening","heartbeat","mail","catches","window"],t.map(Vr),"no traps soaking")]}function Ps({state:e}){let t=e.snapshot,n=e.route,s=t&&pn(t,{st:e.prefs,modal:e.modal},Date.now()),r=o=>!s||o!==n?null:o==="deck"?a`<${hs} inp=${s.deck} drafts=${e.drafts} focus=${e.focusDecision} extensions=${t?.answerLimits?.extensions??[]} />`:o==="dispatches"?a`<${gs} inp=${s.dispatches} />`:o==="traps"?a`<${Rs} inp=${s.traps} />`:o==="prs"?a`<${Ds} inp=${s.prs} />`:o==="reports"?a`<${ns} inp=${s.reports} />`:a`<${xs} inp=${s.notices} />`,i=o=>{o.target===o.currentTarget&&te()};return[a`<${vs} d=${t} inp=${s} tab=${n} st=${e.prefs} stale=${e.stale} />`,ce.map(o=>a`<main key=${o} id=${"page-"+o} class=${t&&o===n?"tabpage on":"tabpage"}><div id=${o}>${r(o)}</div></main>`),a`<footer id="foot"><${_s} d=${t} /></footer>`,a`<div id="lobs"><${ks} state=${e} /></div>`,a`<${Ke} box=${e.lightbox} />`,a`<div id="overlay" class=${e.modal&&t?"open":t?"":void 0} onClick=${i}><div class="modal" id="modalbox"><${Gs} snapshot=${t} modal=${e.modal} prefs=${e.prefs} /></div></div>`]}function zr({view:e}){if(e.state==="loading")return a`<main class="reportview"><div class="dim">loading\u2026</div></main>`;if(e.state==="error")return a`<main class="reportview"><div class="bad">${e.error}</div></main>`;let t=e.report;return a`<main class="reportview">\n <h1>${t.title}</h1>\n <${us} r=${t} text=${e.text!==void 0?{text:e.text}:{error:e.error}} />\n </main>`}function Is({view:e,box:t}){return[a`<${zr} view=${e} />`,a`<${Ke} box=${t} />`]}async function Ms(e){let t=encodeURIComponent(e),n;try{let s=await fetch(`/report/${t}/meta`);if(!s.ok)return{state:"error",error:`report not found (${s.status})`};n=JSON.parse(await s.text())}catch{return{state:"error",error:"report could not be read"}}try{let s=await fetch(dn(e));return s.ok?{state:"ready",report:n,text:await s.text()}:{state:"ready",report:n,error:`report not found (${s.status})`}}catch{return{state:"ready",report:n,error:"report could not be read"}}}var It=!1,ke=null;async function Mt(){if(!It){It=!0;try{let e=await fetch("/data");if(!e.ok)throw new Error(`/data answered ${e.status}`);ut(await e.json())}catch{Gn()}finally{It=!1}}}var Es=()=>{ke||(ke=setInterval(Mt,2e3))},Jr=()=>{ke&&clearInterval(ke),ke=null};function Ls(){document.addEventListener("visibilitychange",()=>{document.hidden?Jr():(Mt(),Es())}),Mt(),document.hidden||Es()}var Et=()=>nn(location.hash),Ns=e=>window.addEventListener("hashchange",()=>e(Et()));function Us(){let e=ln(location.hash);return e===null?!1:(location.replace(C(e)),!0)}var js=()=>({snapshot:void 0,route:Et(),prefs:mn(),modal:null,stale:!1,lobHidden:gn(),spriteOk:null,focusResults:{},preview:new URLSearchParams(location.search).has("lob"),drafts:{},focusDecision:ot(location.hash),lightbox:null}),Hs=cn(location.pathname);Hs!==null?Xr(Hs):Us()||Qr();function Xr(e){dt(js());let t={state:"loading"},n=()=>nt(Q(Is,{view:t,box:_().lightbox}),document.body);document.title="report \\xB7 lobstah glass",pt(n),n(),Ms(e).then(s=>{t=s,s.state==="ready"&&(document.title=`${s.report.title} \\xB7 lobstah glass`),n()}),document.addEventListener("keydown",s=>{s.key==="Escape"&&_().lightbox&&ee()})}function Qr(){dt(js());let e,t=!1,n=()=>{let o=_(),c=o.route!==e||!t&&!!o.snapshot;nt(Q(Ps,{state:o}),document.body),e=o.route,t||(t=!!o.snapshot),c&&window.scrollTo(0,0),o.focusDecision||(s=null),r(o.focusDecision)},s=null;function r(o){if(!o||o===s)return;let c=[...document.querySelectorAll("[data-decision]")].find(u=>u.getAttribute("data-decision")===o);c&&(s=o,c.scrollIntoView?.({block:"center"}))}pt(n),n();let i=new Image;i.onload=()=>T({spriteOk:!0}),i.onerror=()=>T({spriteOk:!1}),i.src="/lob-sprite.png",Ns(o=>{if(Us())return;let c=ot(location.hash);c!==_().focusDecision&&(s=null),T({route:o,focusDecision:c})}),document.addEventListener("keydown",o=>{o.key==="Escape"&&(_().lightbox?ee():te())}),Ls()}})();</script>\n </body>\n</html>\n';
|
|
13652
|
+
var GLASS_PAGE = '<!doctype html>\n<html>\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width,initial-scale=1" />\n <title>spyglass</title>\n <link rel="icon" type="image/png" href="/icon.png" />\n <style>:root{--bg: #0e1116;--card: #161b22;--line: #2b3240;--line-hover: #3a455a;--row-hover: #1c2330;--inset: #10151c;--seg-on: #26436b;--scrim: rgba(4, 7, 11, 0.72);--fg: #dbe2ea;--dim: #8b96a5;--ok: #4fc17c;--warn: #e2b93d;--bad: #e26d5c;--link: #6cb2e2;--on-fill: #fff;--pr-merged: #8957e5;--pr-merged-fg: #ab7df8;--pr-open: #238636;--pr-draft: #6e7681;--pr-closed: #da3633;--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;--font-size: 13px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--page-pad-block: 14px;--radius: 6px;--card-radius: 8px;--card-pad: 12px 14px;--card-min: 300px;--deck-card-min: 240px;--badge-radius: 5px;--badge-pad: 1px 7px;--badge-font: 11px;--modal-radius: 10px;--modal-width: 780px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:var(--font-size)/1.45 var(--font-mono);padding-block:var(--page-pad-block);padding-inline:var(--space-4);display:flex;flex-direction:column;min-height:100vh}h1{font-size:15px;margin:0 0 10px}h1 .dim{color:var(--dim);font-weight:normal}h2{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;margin:20px 0 6px}.chips,.controls{display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center}.controls{margin:0 0 14px}.chips{margin-bottom:8px}.chip{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:3px 9px}.controls select,.newtrap select,.controls input[type=search]{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);color:var(--fg);font:inherit;padding:4px 8px}.controls input[type=search]{flex:1 1 140px;min-width:120px;max-width:340px}#chain-control{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}#f-chain{margin:0;width:14px;height:14px;accent-color:var(--link)}.tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--line);margin:8px 0 10px}.tabs a{color:var(--dim);padding:6px 11px;border-bottom:2px solid transparent}.tabs a.on{color:var(--fg);border-color:var(--link)}.tabpage{display:none}.tabpage.on{display:block;flex:1 0 auto}.headerline{display:flex;align-items:center;gap:var(--space-2)}.headerline h1{flex:1}#settings-slot{min-width:34px;text-align:right;color:var(--dim)}#settings-slot #gearbtn{margin-left:0}.deckgrid{display:flex;flex-direction:column;gap:var(--space-3)}.deckgrid h2{margin:5px 0}.deckgrid section{width:100%;min-width:0}.deckline{padding:2px 0;border-top:1px solid var(--line);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.deckline.acked,.card.acked,.rowhead.acked,.badge.acked{opacity:0.55}.deckmore{color:var(--dim);font-size:11px;display:inline-block;margin-top:4px}.deckline.click{cursor:pointer}.deckline.click:hover{background:var(--row-hover)}.deckgrid h2 a{color:inherit}.deckgrid h2 a:hover{color:var(--fg)}.deckgrid .cards{grid-template-columns:repeat(auto-fill,minmax(min(var(--deck-card-min),100%),1fr));gap:10px}.deckstack{margin-bottom:9px}.deckstack>.dim{font-size:11px;margin:3px 0 5px}.seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}.seg button{background:var(--card);border:none;color:var(--dim);font:inherit;padding:4px 11px;cursor:pointer}.seg button.on{background:var(--seg-on);color:var(--fg)}.wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}th,td{text-align:left;padding:4px 9px;border-top:1px solid var(--line);vertical-align:top;white-space:nowrap}td.grow{white-space:normal;word-break:break-word;min-width:140px}th{color:var(--dim);font-weight:normal;border-top:none;font-size:11px}tr.rowhead{cursor:pointer}tr.rowhead:hover{background:var(--row-hover)}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--card-min),100%),1fr));gap:var(--space-3)}.card{background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad);cursor:pointer;min-width:0;overflow:hidden;overflow-wrap:anywhere}.card:hover{border-color:var(--line-hover)}.card .top{display:flex;justify-content:space-between;gap:var(--space-3);align-items:center;min-height:22px}.card .top>b,.card .top>.prname{flex:1 1000000 auto;min-width:min(10ch,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .top .badge{flex:0 0 auto}.card .top .badge.long{flex:0 1 auto;min-width:min(12ch,100%);max-width:min(26ch,100%);overflow:hidden;text-overflow:ellipsis}.card .meta{color:var(--dim);font-size:12px;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.card .note{margin-top:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.activity{margin-top:4px;font-size:12px;color:var(--fg);opacity:0.85}.waiting{margin-top:4px;font-size:12px;color:var(--dim)}.activity.stale{color:var(--dim);opacity:1}.card .foot{margin-top:8px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;color:var(--dim);font-size:12px}.card .foot>*{min-width:0}.card .foot .footact{margin-left:auto}.btn{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:1px 8px;cursor:pointer;white-space:nowrap}.btn:hover{color:var(--fg);border-color:var(--line-hover)}.badge{border-radius:var(--badge-radius);padding:var(--badge-pad);font-size:var(--badge-font);line-height:1.5;border:1px solid var(--line);white-space:nowrap;display:inline-block}.badge.pr-merged{background:var(--pr-merged);border-color:var(--pr-merged-fg);color:var(--on-fill)}.badge.pr-open{background:var(--pr-open);border-color:var(--pr-open);color:var(--on-fill)}.badge.pr-draft{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.pr-closed{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-conflicts{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-behind{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.unreported{font-weight:normal;font-size:10px;padding:0 5px;vertical-align:1px}.v-done{color:var(--ok)}.v-working,.v-queued{color:var(--fg)}.v-needs-decision,.v-blocked{color:var(--bad);font-weight:bold}.v-failed{color:var(--bad)}.v-paused,.v-unknown{color:var(--dim)}.dim{color:var(--dim)}.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)}.detail{display:none}.detail.open{display:table-row}.detail td{background:var(--inset);white-space:pre-wrap;word-break:break-word;font-size:12px;color:var(--dim)}.detail b{color:var(--fg)}.empty{color:var(--dim);padding:6px 2px}#stale{display:none;color:var(--bad)}a{color:var(--link);text-decoration:none}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle}.dot.ok{background:var(--ok)}.dot.warn{background:var(--warn)}.dot.bad{background:var(--bad)}.dot.dim{background:var(--dim)}#overlay{position:fixed;inset:0;background:var(--scrim);display:none;z-index:10;padding:28px 16px;overflow:auto}#overlay.open{display:block}.modal{max-width:var(--modal-width);margin:0 auto;background:var(--card);border:1px solid var(--line);border-radius:var(--modal-radius);padding:16px 18px;position:relative}.modal .x{position:absolute;top:8px;right:12px;cursor:pointer;color:var(--dim);font-size:18px;line-height:1}.modal .x:hover{color:var(--fg)}.modal h3{margin:0 24px 2px 0;font-size:14px}.modal .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.modal .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.modal pre{white-space:pre-wrap;word-break:break-word;margin:0;font:inherit;color:var(--fg)}.msg{border-left:3px solid var(--line);padding:3px 10px;margin:8px 0;white-space:pre-wrap;word-break:break-word}.msg.from-helm{border-color:var(--link)}.msg .hdr{color:var(--dim);font-size:11px}.catch{border:1px solid var(--line);border-radius:var(--radius);padding:6px 10px;margin:8px 0}.catch .hdr{display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}.loglines{color:var(--dim);font-size:12px;white-space:pre-wrap;word-break:break-word;margin-top:4px}.cmd{display:flex;gap:var(--space-2);align-items:center;margin:5px 0;flex-wrap:wrap}.cmd code{background:var(--inset);border:1px solid var(--line);border-radius:5px;padding:3px 8px;word-break:break-all;cursor:pointer}.cmd code:hover{border-color:var(--line-hover)}.cmd code.copied{border-color:var(--ok)}.cmd button{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:2px 8px;cursor:pointer}.cmd button:hover{color:var(--fg)}.chip.click{cursor:pointer}.chip.click:hover{border-color:var(--line-hover)}footer{margin-top:26px;padding-top:10px;border-top:1px solid var(--line);color:var(--dim);font-size:12px;display:flex;gap:var(--space-2);flex-wrap:wrap}.lob{position:fixed;bottom:6px;left:0;z-index:5;cursor:pointer;font-size:34px;line-height:1;user-select:none;animation:crawl 18s linear infinite}a.lob{color:inherit}.lob .bub .badge{display:inline-block;margin:0 0 3px}.lob .sprite{width:72px;height:56px;background:url(/lob-sprite.png) 0 0 no-repeat;background-size:400% 100%;image-rendering:pixelated;animation:step 0.5s steps(4) infinite}.lob .fallback{display:inline-block;animation:waddle 0.45s ease-in-out infinite alternate}.lob .bub{display:none;position:absolute;bottom:58px;right:-8px;z-index:2;background:var(--card);border:1px solid var(--line);border-radius:9px;padding:6px 9px 5px;font-size:11px;line-height:1.35;color:var(--fg);width:max-content;max-width:150px;box-shadow:0 2px 8px rgba(0,0,0,0.4)}.lob:hover .bub{display:block}.lob .bub span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;white-space:normal;word-break:break-word}.lob .star{position:absolute;left:56px;top:-9px;height:14px;image-rendering:pixelated;z-index:3}.lob:hover{animation-play-state:paused}@keyframes crawl{0%{transform:translateX(-90px)}100%{transform:translateX(100vw)}}@keyframes step{to{background-position-x:-288px}}@keyframes waddle{from{transform:rotate(-8deg) translateY(0)}to{transform:rotate(8deg) translateY(-3px)}}#gearbtn{background:none;border:1px solid var(--line);border-radius:var(--radius);color:var(--dim);font:inherit;font-size:13px;padding:1px 7px;margin-left:8px;cursor:pointer;vertical-align:1px}#gearbtn:hover,#gearbtn.on{color:var(--fg);border-color:var(--line-hover)}.settings .row{display:flex;justify-content:space-between;align-items:center;gap:14px;margin:12px 0}.settings .lbl{color:var(--fg);font-size:12px}.settings .hint{display:block;color:var(--dim);font-size:11px;margin-top:2px}.dcards{display:flex;flex-direction:column;gap:10px}.dcard{width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad)}.dcard.focus{animation:dflash 1.6s ease-out 1}@keyframes dflash{0%,35%{border-color:var(--link);box-shadow:0 0 0 3px var(--link)}100%{border-color:var(--line);box-shadow:0 0 0 0 transparent}}.dcard.answered{opacity:0.7}.dcard .dtop{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2)}.dcard .dtop .badge{margin-top:0}.dcard .dtitle{white-space:pre-wrap;overflow-wrap:anywhere}.dcard .dmeta{font-size:11px;margin-left:auto}.dcard .dlink{color:var(--link)}.dfiles{display:flex;flex-wrap:wrap;gap:var(--space-2);margin:6px 0}.dfiles .dimg img{max-height:160px;max-width:320px;border:1px solid var(--line);border-radius:var(--radius);display:block}.dfiles .dfile{width:100%;font-size:11px}.doptions{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 6px}.btn.dopt{font-size:12px;padding:3px 12px;color:var(--fg)}.btn.dopt.on{background:var(--seg-on);border-color:var(--link)}.danswer{display:block;width:100%;min-height:3.2em;max-height:50vh;margin:6px 0;padding:6px 8px;resize:vertical;overflow-y:auto;field-sizing:content;background:var(--inset);color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);font:inherit}.danswer:focus{outline:none;border-color:var(--line-hover)}.dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.dfoot .dattach input{display:none}.dfoot .dchip{font-size:11px;border:1px solid var(--line);border-radius:var(--badge-radius);padding:0 0 0 6px}.dfoot .dx{background:none;border:none;color:var(--dim);cursor:pointer;font:inherit}.dfoot .derr{font-size:11px}.btn.dsend{margin-left:auto;color:var(--fg);padding:2px 14px}.btn:disabled{cursor:default;opacity:0.6}.danswered{margin-top:6px}.thumb,.dimg,.mdimg{background:none;border:none;padding:0;cursor:zoom-in;font:inherit}.thumb{display:block;margin:4px 0}.thumb img{max-height:120px;max-width:240px;border:1px solid var(--line);border-radius:var(--radius);display:block}.lightbox{position:fixed;inset:0;z-index:20;background:rgba(0,0,0,0.85);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2)}.lightbox .lbimg{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:var(--radius);box-shadow:0 8px 40px rgba(0,0,0,0.6)}.lightbox .lbclose{position:absolute;top:12px;right:16px;background:none;border:none;color:var(--fg);font-size:28px;line-height:1;cursor:pointer}.lightbox .lboriginal{color:var(--link);font-size:12px}.mdpage{background:var(--inset);border:1px solid var(--line);border-radius:6px;padding:8px 14px;margin:6px 0;line-height:1.5;overflow-wrap:anywhere}.mdpage h1,.mdpage h2,.mdpage h3,.mdpage h4{margin:12px 0 6px;line-height:1.25}.mdpage h1{font-size:18px}.mdpage h2{font-size:15px}.mdpage h3,.mdpage h4{font-size:13px}.mdpage p,.mdpage ul,.mdpage ol,.mdpage blockquote{margin:6px 0}.mdpage ul,.mdpage ol{padding-left:22px}.mdpage a{color:var(--link)}.mdpage code{background:var(--card);border-radius:3px;padding:0 3px}.mdpage pre.mdcode{background:var(--card);border:1px solid var(--line);border-radius:4px;padding:8px;overflow-x:auto;white-space:pre}.mdpage pre.mdcode code{padding:0;background:none}.mdpage blockquote{border-left:3px solid var(--line);padding-left:10px;color:var(--dim)}.mdpage img{max-width:100%;height:auto;border-radius:4px;display:block;margin:6px 0}.mdpage hr{border:0;border-top:1px solid var(--line)}.mdpage .mdtable{overflow-x:auto}.mdpage table{border-collapse:collapse;margin:6px 0}.mdpage th,.mdpage td{border:1px solid var(--line);padding:3px 8px;text-align:left}.tabhead{margin:0 0 8px}.newtrap{display:inline-flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12px;font-weight:normal;text-transform:none;letter-spacing:normal}.startcmds{margin:6px 0;font-size:11px;cursor:default}.reportview{max-width:1100px;margin:0 auto;padding:20px 16px 48px}.reportview h1{font-size:18px;margin:0 0 4px}.reportview .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.reportview .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.reportview .mdpage{padding:12px 20px}</style>\n </head>\n <body>\n <!-- The page is rendered by Preact (src/components/app.ts): header, tabs, controls, the five tab pages, footer, lobs, modal overlay. -->\n <script>"use strict";(()=>{var Ae,b,Bt,Bs,O,Nt,Ot,qt,Je,Ge,le,Ft,Ze,Xe,Qe,Os,xe={},Ce=[],qs=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Re=Array.isArray;function E(e,t){for(var n in t)e[n]=t[n];return e}function Ye(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Q(e,t,n){var s,r,i,o={};for(i in t)i=="key"?s=t[i]:i=="ref"?r=t[i]:o[i]=t[i];if(arguments.length>2&&(o.children=arguments.length>3?Ae.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)o[i]===void 0&&(o[i]=e.defaultProps[i]);return Te(e,o,s,r,null)}function Te(e,t,n,s,r){var i={type:e,props:t,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:r??++Bt,__i:-1,__u:0};return r==null&&b.vnode!=null&&b.vnode(i),i}function Z(e){return e.children}function Se(e,t){this.props=e,this.context=t}function V(e,t){if(t==null)return e.__?V(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?V(e):null}function Fs(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,s=[],r=[],i=E({},t);i.__v=t.__v+1,b.vnode&&b.vnode(i),et(e.__P,i,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,s,n??V(t),!!(32&t.__u),r),i.__v=t.__v,i.__.__k[i.__i]=i,Jt(s,i,r),t.__e=t.__=null,i.__e!=n&&Wt(i)}}function Wt(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),Wt(e)}function Ht(e){(!e.__d&&(e.__d=!0)&&O.push(e)&&!De.__r++||Nt!=b.debounceRendering)&&((Nt=b.debounceRendering)||Ot)(De)}function De(){try{for(var e,t=1;O.length;)O.length>t&&O.sort(qt),e=O.shift(),t=O.length,Fs(e)}finally{O.length=De.__r=0}}function Vt(e,t,n,s,r,i,o,c,u,l,f){var h,d,m,p,g,T,G=s&&s.__k||Ce,$=t.length;for(u=Ws(n,t,G,u,$),h=0;h<$;h++)(m=n.__k[h])!=null&&(d=m.__i!=-1&&G[m.__i]||xe,m.__i=h,T=et(e,m,d,r,i,o,c,u,l,f),p=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&tt(d.ref,null,m),f.push(m.ref,m.__c||p,m)),g==null&&p!=null&&(g=p),4&m.__u?(u=Kt(m,u,e),d.__e&&(d.__e=null)):typeof m.type=="function"&&T!==void 0?u=T:p&&(u=p.nextSibling),m.__u&=-7);return n.__e=g,u}function Ws(e,t,n,s,r){var i,o,c,u,l,f=n.length,h=f,d=0;for(e.__k=new Array(r),i=0;i<r;i++)(o=t[i])!=null&&typeof o!="boolean"&&typeof o!="function"?(typeof o=="string"||typeof o=="number"||typeof o=="bigint"||o.constructor==String?o=e.__k[i]=Te(null,o,null,null,null):Re(o)?o=e.__k[i]=Te(Z,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[i]=Te(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[i]=o,u=i+d,o.__=e,o.__b=e.__b+1,c=null,(l=o.__i=Vs(o,n,u,h))!=-1&&(h--,(c=n[l])&&(c.__u|=2)),c==null||c.__v==null?(l==-1&&(r>f?d--:r<f&&d++),typeof o.type!="function"&&(o.__u|=4)):l!=u&&(l==u-1?d--:l==u+1?d++:(l>u?d--:d++,o.__u|=4))):e.__k[i]=null;if(h)for(i=0;i<f;i++)(c=n[i])!=null&&(2&c.__u)==0&&(c.__e==s&&(s=V(c)),Qt(c,c));return s}function Kt(e,t,n){var s,r;if(typeof e.type=="function"){for(s=e.__k,r=0;s&&r<s.length;r++)s[r]&&(s[r].__=e,t=Kt(s[r],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=V(e)),t=n.insertBefore(e.__e,t||null));do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function Vs(e,t,n,s){var r,i,o,c=e.key,u=e.type,l=t[n],f=l!=null&&(2&l.__u)==0;if(l===null&&c==null||f&&c==l.key&&u==l.type)return n;if(s>(f?1:0)){for(r=n-1,i=n+1;r>=0||i<t.length;)if((l=t[o=r>=0?r--:i++])!=null&&(2&l.__u)==0&&c==l.key&&u==l.type)return o}return-1}function Ut(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||qs.test(t)?n:n+"px"}function we(e,t,n,s,r){var i,o;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof s=="string"&&(e.style.cssText=s=""),s)for(t in s)n&&t in n||Ut(e.style,t,"");if(n)for(t in n)s&&n[t]==s[t]||Ut(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(Ft,"$1")),o=t.toLowerCase(),t=o in e||t=="onFocusOut"||t=="onFocusIn"?o.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=n,n?s?n[le]=s[le]:(n[le]=Ze,e.addEventListener(t,i?Qe:Xe,i)):e.removeEventListener(t,i?Qe:Xe,i);else{if(r=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function jt(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t[Ge]==null)t[Ge]=Ze++;else if(t[Ge]<n[le])return;return n(b.event?b.event(t):t)}}}function et(e,t,n,s,r,i,o,c,u,l){var f,h,d,m,p,g,T,G,$,P,ie,W,ae,Lt,ye,ze,I=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(u=!!(32&n.__u),i=[c=t.__e=n.__e]),(f=b.__b)&&f(t);e:if(typeof I=="function"){h=o.length;try{if($=t.props,P=I.prototype&&I.prototype.render,ie=(f=I.contextType)&&s[f.__c],W=f?ie?ie.props.value:f.__:s,n.__c?G=(d=t.__c=n.__c).__=d.__E:(P?t.__c=d=new I($,W):(t.__c=d=new Se($,W),d.constructor=I,d.render=zs),ie&&ie.sub(d),d.state||(d.state={}),d.__n=s,m=d.__d=!0,d.__h=[],d._sb=[]),P&&d.__s==null&&(d.__s=d.state),P&&I.getDerivedStateFromProps!=null&&(d.__s==d.state&&(d.__s=E({},d.__s)),E(d.__s,I.getDerivedStateFromProps($,d.__s))),p=d.props,g=d.state,d.__v=t,m)P&&I.getDerivedStateFromProps==null&&d.componentWillMount!=null&&d.componentWillMount(),P&&d.componentDidMount!=null&&d.__h.push(d.componentDidMount);else{if(P&&I.getDerivedStateFromProps==null&&$!==p&&d.componentWillReceiveProps!=null&&d.componentWillReceiveProps($,W),t.__v==n.__v||!d.__e&&d.shouldComponentUpdate!=null&&d.shouldComponentUpdate($,d.__s,W)===!1){t.__v!=n.__v&&(d.props=$,d.state=d.__s,d.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(X){X&&(X.__=t)}),Ce.push.apply(d.__h,d._sb),d._sb=[],d.__h.length&&o.push(d),c=V(n);break e}d.componentWillUpdate!=null&&d.componentWillUpdate($,d.__s,W),P&&d.componentDidUpdate!=null&&d.__h.push(function(){d.componentDidUpdate(p,g,T)})}if(d.context=W,d.props=$,d.__P=e,d.__e=!1,ae=b.__r,Lt=0,P)d.state=d.__s,d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),Ce.push.apply(d.__h,d._sb),d._sb=[];else do d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),d.state=d.__s;while(d.__d&&++Lt<25);d.state=d.__s,d.getChildContext!=null&&(s=E(E({},s),d.getChildContext())),P&&!m&&d.getSnapshotBeforeUpdate!=null&&(T=d.getSnapshotBeforeUpdate(p,g)),ye=f!=null&&f.type===Z&&f.key==null?Xt(f.props.children):f,c=Vt(e,Re(ye)?ye:[ye],t,n,s,r,i,o,c,u,l),d.base=t.__e,t.__u&=-161,d.__h.length&&o.push(d),G&&(d.__E=d.__=null)}catch(X){if(o.length=h,t.__v=null,u||i!=null){if(X.then){for(t.__u|=u?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;i!=null&&(i[i.indexOf(c)]=null),t.__e=c}else if(i!=null)for(ze=i.length;ze--;)Ye(i[ze])}else t.__e=n.__e;t.__k==null&&(t.__k=n.__k||[]),X.then||zt(t),b.__e(X,t,n)}}else i==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):c=t.__e=Ks(n.__e,t,n,s,r,i,o,u,l);return(f=b.diffed)&&f(t),128&t.__u?void 0:c}function zt(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(zt))}function Jt(e,t,n){for(var s=0;s<n.length;s++)tt(n[s],n[++s],n[++s]);b.__c&&b.__c(t,e),e.some(function(r){try{e=r.__h,r.__h=[],e.some(function(i){i.call(r)})}catch(i){b.__e(i,r.__v)}})}function Xt(e){return typeof e!="object"||e==null||e.__b>0?e:Re(e)?e.map(Xt):e.constructor!==void 0?null:E({},e)}function Ks(e,t,n,s,r,i,o,c,u){var l,f,h,d,m,p,g,T=n.props||xe,G=t.props,$=t.type;if($=="svg"?r="http://www.w3.org/2000/svg":$=="math"?r="http://www.w3.org/1998/Math/MathML":r||(r="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l<i.length;l++)if((m=i[l])&&"setAttribute"in m==!!$&&($?m.localName==$:m.nodeType==3)){e=m,i[l]=null;break}}if(e==null){if($==null)return document.createTextNode(G);e=document.createElementNS(r,$,G.is&&G),c&&(b.__m&&b.__m(t,i),c=!1),i=null}if($==null)T===G||c&&e.data==G||(e.data=G);else{if(i=$=="textarea"&&G.defaultValue!=null?null:i&&Ae.call(e.childNodes),!c&&i!=null)for(T={},l=0;l<e.attributes.length;l++)T[(m=e.attributes[l]).name]=m.value;for(l in T)m=T[l],l=="dangerouslySetInnerHTML"?h=m:l=="children"||l in G||l=="value"&&"defaultValue"in G||l=="checked"&&"defaultChecked"in G||we(e,l,null,m,r);for(l in G)m=G[l],l=="children"?d=m:l=="dangerouslySetInnerHTML"?f=m:l=="value"?p=m:l=="checked"?g=m:c&&typeof m!="function"||T[l]===m||we(e,l,m,T[l],r);if(f)c||h&&(f.__html==h.__html||f.__html==e.innerHTML)||(e.innerHTML=f.__html),t.__k=[];else if(h&&(e.innerHTML=""),Vt(t.type=="template"?e.content:e,Re(d)?d:[d],t,n,s,$=="foreignObject"?"http://www.w3.org/1999/xhtml":r,i,o,i?i[0]:n.__k&&V(n,0),c,u),i!=null)for(l=i.length;l--;)Ye(i[l]);c&&$!="textarea"||(l="value",$=="progress"&&p==null?e.removeAttribute("value"):p!=null&&(p!==e[l]||$=="progress"&&!p||$=="option"&&p!=T[l])&&we(e,l,p,T[l],r),l="checked",g!=null&&g!=e[l]&&we(e,l,g,T[l],r))}return e}function tt(e,t,n){try{if(typeof e=="function"){var s=typeof e.__u=="function";s&&e.__u(),s&&t==null||(e.__u=e(t))}else e.current=t}catch(r){b.__e(r,n)}}function Qt(e,t,n){var s,r;if(b.unmount&&b.unmount(e),(s=e.ref)&&(s.current&&s.current!=e.__e||tt(s,null,t)),(s=e.__c)!=null){if(s.componentWillUnmount)try{s.componentWillUnmount()}catch(i){b.__e(i,t)}s.base=s.__P=s.__n=null}if(s=e.__k)for(r=0;r<s.length;r++)s[r]&&Qt(s[r],t,n||typeof e.type!="function");n||Ye(e.__e),e.__c=e.__=e.__e=void 0}function zs(e,t,n){return this.constructor(e,n)}function nt(e,t,n){var s,r,i,o;t==document&&(t=document.documentElement),b.__&&b.__(e,t),r=(s=typeof n=="function")?null:n&&n.__k||t.__k,i=[],o=[],et(t,e=(!s&&n||t).__k=Q(Z,null,[e]),r||xe,xe,t.namespaceURI,!s&&n?[n]:r?null:t.firstChild?Ae.call(t.childNodes):null,i,!s&&n?n:r?r.__e:t.firstChild,s,o),Jt(i,e,o),e.props.children=null}Ae=Ce.slice,b={__e:function(e,t,n,s){for(var r,i,o;t=t.__;)if((r=t.__c)&&!r.__)try{if((i=r.constructor)&&i.getDerivedStateFromError!=null&&(r.setState(i.getDerivedStateFromError(e)),o=r.__d),r.componentDidCatch!=null&&(r.componentDidCatch(e,s||{}),o=r.__d),o)return r.__E=r}catch(c){e=c}throw e}},Bt=0,Bs=function(e){return e!=null&&e.constructor===void 0},Se.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=E({},this.state),typeof e=="function"&&(e=e(E({},n),this.props)),e&&E(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),Ht(this))},Se.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Ht(this))},Se.prototype.render=Z,O=[],Ot=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,qt=function(e,t){return e.__v.__b-t.__v.__b},De.__r=0,Je=Math.random().toString(8),Ge="__d"+Je,le="__a"+Je,Ft=/(PointerCapture)$|Capture$/i,Ze=0,Xe=jt(!1),Qe=jt(!0),Os=0;function Js(e){return{id:e,lane:"work",bucket:"done",repo:"",brief:"",attachments:[],messageAttachments:[],verb:"unknown",log:[],inbox:[],sort:0}}var Zt=new WeakMap;function st(e,t){let n=e.dispatches||[],s=Zt.get(n);return s||Zt.set(n,s=new Map(n.map(r=>[r.id,r]))),{...t,catches:(t.catches||[]).map(r=>s.get(r)??Js(r))}}function L(e){if(!e)return"dim";let t=e.state||"open";return t==="open"&&e.merge?"pr-"+e.merge:t==="open"&&e.tone&&e.tone!=="ok"?e.tone:"pr-"+t}var ce=["deck","dispatches","traps","prs","reports","notices"];function nn(e){let t=String(e||"").replace(/^#/,"");return ce.includes(t)?t:"deck"}var rt=e=>`#decision/${encodeURIComponent(e)}`;function ot(e){let t=/^#?decision\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var it=(e,t,n)=>`/attachment/dispatch/${encodeURIComponent(e)}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,sn=(e,t)=>`/attachment/trap/${encodeURIComponent(e)}/${encodeURIComponent(t)}`,rn=e=>/\\.(png|jpe?g|gif|webp)$/i.test(e),at=(e,t)=>`/decision/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function lt(e,t){let n=new Map(t.map(r=>[r.key,r])),s=[];for(let r of e)if(r.kind==="decision"){let i=n.get(r.key);if(!i)continue;s.push({kind:"decision",key:i.key,title:i.title,detail:i.detail,options:i.options,attachments:i.attachments,...i.dispatch?{dispatch:i.dispatch,lane:i.lane??"work"}:{},...i.repo?{repo:i.repo}:{},at:i.askedAt})}else r.kind==="question"&&s.push({kind:"question",key:r.key,verb:r.verb,note:r.note||r.verb,dispatch:r.id,lane:r.lane,...r.repo?{repo:r.repo}:{},at:r.at??""});return s.sort((r,i)=>i.at.localeCompare(r.at)||r.key.localeCompare(i.key))}function on(e){let t=(e.text??"").split(`\n`).map(n=>n.trim()).find(Boolean)??"";return[e.option,t&&(t.length>80?t.slice(0,79)+"\\u2026":t),e.files?`${e.files} file${e.files===1?"":"s"}`:""].filter(Boolean).join(" \\xB7 ")}function an(e,t){return(e.reports||[]).find(n=>n.key===`report:${t.lane}:${t.id}`)}function de(e){return e.author==="helm"?"":e.trap?e.trap:e.author==="headless"?e.dispatch?e.dispatch.slice(0,8):"":e.author}var Yt=(e,t)=>+!!e.acked-+!!t.acked||t.filedAt.localeCompare(e.filedAt),D=e=>`/report/${encodeURIComponent(e)}`;function ln(e){let t=/^#?report\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}function cn(e){let t=/^\\/report\\/([^/?#]+)\\/?$/.exec(e);if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var dn=e=>`/report/${encodeURIComponent(e)}/md`,ct=(e,t)=>`/report/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function en(e,t,n){return!!e&&n-Date.parse(e)>t}function tn(e,t){if(t.lane&&e.lane!==t.lane||t.repo&&e.repo!==t.repo||t.verb&&e.verb!==t.verb)return!1;if(t.q){let n=t.q.toLowerCase();if(!(e.id+" "+(e.note||"")+" "+(e.brief||"")+" "+(e.repo||"")+" "+(e.for||"")).toLowerCase().includes(n))return!1}return!0}function Y(e,t){if(!t)return null;if(t.type==="helm")return e.helms.find(s=>s.grounds===t.key)||null;if(t.type==="dispatch")return e.dispatches.find(s=>s.lane+":"+s.id===t.key)||null;if(t.type==="pr")return(e.prs||[]).find(s=>s.key===t.key)||null;if(t.type==="settings")return{attentionKinds:e.attentionKinds||[],attentionError:e.attentionError};let n=e.traps.find(s=>s.trapId===t.key);return n?st(e,n):null}function pn(e,t,n){let s=t.st,r=String(s.q||"").toLowerCase(),i=(...p)=>!r||p.join(" ").toLowerCase().includes(r),o=p=>({x:p,stale:en(p.heartbeatAt,18e5,n)}),c=Y(e,t.modal),u=(p,g)=>!!p&&n-Date.parse(p)<=g,l=p=>Math.max(0,...(p.notices||[]).filter(g=>g.kind==="trap-stowed"||g.kind==="trap-ghosted").map(g=>Date.parse(g.at)||0)),f=p=>p.live||!!p.starting||!!p.requested,h=[...e.traps||[]].sort((p,g)=>Number(f(g))-Number(f(p))||Number(g.live)-Number(p.live)||(p.live?(Date.parse(p.signedOnAt||"")||0)-(Date.parse(g.signedOnAt||"")||0):l(g)-l(p))||(p.name??p.trapId).localeCompare(g.name??g.trapId)),d=h.filter(p=>(f(p)||(p.notices||[]).some(g=>(g.kind==="trap-stowed"||g.kind==="trap-ghosted")&&u(g.at,36e5)))&&i(p.name,p.trapId,p.repo,p.worktree)),m=({ageSecs:p,...g})=>g;return{chips:{daemon:e.daemon,daemonStale:!!e.daemon&&en(e.daemon.heartbeat,9e4,n),helms:e.helms.map(o)},deck:{view:s.view,decisions:lt(e.attention||[],e.decisions||[]).filter(p=>p.kind==="decision"?i(p.kind,p.repo,p.title,p.dispatch,p.detail):i(p.kind,p.repo,p.note,p.dispatch)),prAttention:(e.attention||[]).filter(p=>p.kind&&p.kind.startsWith("pr:")).map(m),landed:(e.landed||[]).filter(p=>u(p.at,864e5)&&i(p.repo,p.note,p.id)).sort((p,g)=>Date.parse(g.at)-Date.parse(p.at)).slice(0,8),reports:(e.reports||[]).filter(p=>i(p.title,p.author,p.dispatch,p.repo)).sort(Yt),inflight:e.dispatches.filter(p=>p.bucket!=="done"&&tn(p,{...s,lane:"",repo:"",verb:""})),traps:d.map(p=>o(st(e,p))),stacks:(e.stacks||[]).filter(p=>p.open&&i(p.repo,p.numbers.join(" "))),prs:(e.prs||[]).filter(p=>p.state==="OPEN"),error:e.attentionError},dispatches:{view:s.view,chain:s.chain,list:e.dispatches.filter(p=>tn(p,s))},traps:{view:s.view,list:h.filter(p=>(!s.repo||p.repo===s.repo)&&i(p.name,p.trapId,p.repo,p.worktree,p.harness)).map(p=>o(st(e,p)))},prs:{view:s.view,stacks:(e.stacks||[]).filter(p=>!s.repo||p.repo===s.repo),prs:(e.prs||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.number,p.title,p.url,p.state,p.baseRefName,p.headRefName)),watches:(e.watches||[]).filter(p=>!String(p.key).startsWith("pr:"))},reports:{view:s.view,list:(e.reports||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.title,p.author,p.dispatch,p.repo)).sort(Yt)},notices:{list:(e.notices||[]).filter(p=>(!s.repo||!p.repo||p.repo===s.repo)&&(!s.noticeKind||p.kind===s.noticeKind)&&i(p.kind,p.text,p.repo))},foot:{version:e.version,repoUrl:e.repoUrl},modal:{modal:t.modal,item:c&&o(c),prefs:t.modal&&t.modal.type==="settings"?{view:s.view,lobs:s.lobs}:void 0}}}function un(e,t){let n=(e.prs||[]).find(c=>c.key===t);if(!n)return null;let s=(e.stacks||[]).find(c=>c.id===n.stackId),r=new Map((e.dispatches||[]).map(c=>[c.id,c])),i=(n.dispatchIds||[]).map(c=>{let u=r.get(c);return u?{id:c,verb:u.verb,modalKey:u.lane+":"+u.id}:{id:c,culled:!0}}),o=n.watch;return{pr:n,stack:s?{numbers:s.numbers,titles:s.numbers.map(c=>(e.prs||[]).find(u=>u.stackId===s.id&&u.number===c)?.title||""),position:n.position+1,size:s.numbers.length,floor:s.floor,nextNumber:s.nextNumber,nextMergeable:!!n.nextMergeable,blockedBy:n.blockedBy}:null,chain:i,watch:o?{key:o.key,owner:o.owner||"",lastCheckedAt:o.lastCheckedAt||null,lastError:o.lastError||null,cursor:o.cursor}:null}}function fn(e){return e?{text:e.lastError?"failing":"watching",at:e.lastCheckedAt||null}:{text:"no watch",at:null}}function mn(){let e={view:"table",lane:"",repo:"",verb:"",q:"",lobs:!0,chain:!1,noticeKind:""};try{Object.assign(e,JSON.parse(localStorage.getItem("spyglass")||"{}"))}catch{}return e.view!=="table"&&e.view!=="cards"&&(e.view="table"),e.lobs=e.lobs!==!1,e}function hn(e){try{localStorage.setItem("spyglass",JSON.stringify(e))}catch{}}function gn(){try{return JSON.parse(localStorage.getItem("spyglass-lob-hidden")||"{}")||{}}catch{return{}}}function vn(e){try{localStorage.setItem("spyglass-lob-hidden",JSON.stringify(e))}catch{}}var pe,_n=new Set,_=()=>pe;function dt(e){pe=e}function S(e){pe={...pe,...e};for(let t of _n)t(pe)}var pt=e=>{_n.add(e)};function ut(e){let{modal:t,drafts:n}=_(),s=new Set(lt(e.attention||[],e.decisions||[]).map(i=>i.key)),r=Object.fromEntries(Object.entries(n).filter(([i])=>s.has(i)));S({snapshot:e,stale:!1,modal:t&&Y(e,t)?t:null,...Object.keys(r).length!==Object.keys(n).length?{drafts:r}:{}})}var $n=()=>({text:"",files:[]});function A(e,t){let n=_().drafts;S({drafts:{...n,[e]:{...n[e]??$n(),...t}}})}function bn(e,t){let n=_().drafts[e]?.option;A(e,{option:n===t?void 0:t,error:void 0})}var Xs=e=>new Promise((t,n)=>{let s=new FileReader;s.onload=()=>t(String(s.result).replace(/^data:[^,]*,/,"")),s.onerror=()=>n(s.error),s.readAsDataURL(e)});async function ft(e,t){let n=_().snapshot?.answerLimits,s=[...t??[]],r=_().drafts[e]?.files??[],i=[],o=[];for(let c of s){let u=(/\\.[^.]+$/.exec(c.name)?.[0]??"").toLowerCase();if(n&&!n.extensions.includes(u))i.push(`${c.name}: type not accepted`);else if(n&&c.size>n.maxBytes)i.push(`${c.name}: larger than ${n.maxBytes} bytes`);else if(n&&r.length+o.length>=n.maxFiles)i.push(`${c.name}: at most ${n.maxFiles} files`);else try{o.push({name:c.name,bytes:c.size,data:await Xs(c)})}catch{i.push(`${c.name}: could not be read`)}}A(e,{files:[..._().drafts[e]?.files??[],...o],error:i.length?i.join("; "):void 0})}var Qs={"image/png":".png","image/jpeg":".jpg","image/gif":".gif","image/webp":".webp"};async function kn(e,t){let n=[...t.clipboardData?.items??[]],s=new Date().toISOString().replace(/[:.]/g,"-").replace(/-\\d{3}Z$/,"Z"),r=n.filter(i=>i.kind==="file"&&i.type.startsWith("image/")).map(i=>i.getAsFile()).filter(i=>i!==null).map((i,o,c)=>{let u=`pasted-${s}${c.length>1?`-${o+1}`:""}${Qs[i.type]??".png"}`;return new File([i],u,{type:i.type})});r.length&&await ft(e,r)}function yn(e,t){let n=(_().drafts[e]?.files??[]).filter((s,r)=>r!==t);A(e,{files:n})}async function wn(e){let t=_().snapshot?.focusToken,n=_().drafts[e]??$n();if(!(n.sending||n.sent)){if(!n.option&&!n.text.trim()&&n.files.length===0){A(e,{error:"Choose an option, write an answer, or attach a file."});return}if(!t){A(e,{error:"This page has no answer token; reload it."});return}A(e,{sending:!0,error:void 0});try{let s=await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":t},body:JSON.stringify({kind:"decision-answer",payload:{key:e,...n.option?{option:n.option}:{},...n.text.trim()?{text:n.text}:{},files:n.files.map(i=>({name:i.name,data:i.data}))}})}),r=await s.json();if(!s.ok||!r.ok){A(e,{sending:!1,error:r.reason??`The answer was refused (${s.status}).`});return}A(e,{sending:!1,sent:on({option:n.option,text:n.text,files:n.files.length})})}catch{A(e,{sending:!1,error:"The answer could not be sent."})}}}var Gn=()=>S({stale:!0}),Tn=(e,t)=>S({lightbox:{src:e,name:t}}),ee=()=>S({lightbox:null});function M(e,t){let{snapshot:n}=_(),s={type:e,key:t};S({modal:!n||Y(n,s)?s:null})}function te(){S({modal:null})}async function Sn(e){let t=_().snapshot?.focusToken;if(t)try{let s=await(await fetch(`/api/focus/${encodeURIComponent(e)}`,{method:"POST",headers:{"x-lobstah-focus-token":t}})).json();S({focusResults:{..._().focusResults,[e]:s.message??s.reason??"Window focus failed."}})}catch{S({focusResults:{..._().focusResults,[e]:"Window focus failed."}})}}async function xn(e,t){let n=_().snapshot?.focusToken;if(!n)return"The glass has no page token yet.";try{let r=await(await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":n},body:JSON.stringify({kind:"trap-request",payload:{repo:e,harness:t}})})).json();if(!r.ok)return r.reason??"The request was refused."}catch{return"The request could not be sent."}try{ut(await(await fetch("/data")).json())}catch{}}function N(e){let t={..._().prefs,...e};hn(t),S({prefs:t})}function Cn(e){e!=="table"&&e!=="cards"||N({view:e})}var Dn=e=>N({lobs:e==="on"});function An(e,t){let n={..._().lobHidden,[e]:t};vn(n),setTimeout(()=>S({lobHidden:n}),0)}async function Rn(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}var In=function(e,t,n,s){var r;t[0]=0;for(var i=1;i<t.length;i++){var o=t[i++],c=t[i]?(t[0]|=o?1:2,n[t[i++]]):t[++i];o===3?s[0]=c:o===4?s[1]=Object.assign(s[1]||{},c):o===5?(s[1]=s[1]||{})[t[++i]]=c:o===6?s[1][t[++i]]+=c+"":o?(r=e.apply(c,In(e,c,n,["",null])),s.push(r),c[0]?t[0]|=2:(t[i-2]=0,t[i]=r)):s.push(c)}return s},Pn=new Map;function Mn(e){var t=Pn.get(this);return t||(t=new Map,Pn.set(this,t)),(t=In(this,t.get(e)||(t.set(e,t=(function(n){for(var s,r,i=1,o="",c="",u=[0],l=function(d){i===1&&(d||(o=o.replace(/^\\s*\\n\\s*|\\s*\\n\\s*$/g,"")))?u.push(0,d,o):i===3&&(d||o)?(u.push(3,d,o),i=2):i===2&&o==="..."&&d?u.push(4,d,0):i===2&&o&&!d?u.push(5,0,!0,o):i>=5&&((o||!d&&i===5)&&(u.push(i,0,o,r),i=6),d&&(u.push(i,d,0,r),i=6)),o=""},f=0;f<n.length;f++){f&&(i===1&&l(),l(f));for(var h=0;h<n[f].length;h++)s=n[f][h],i===1?s==="<"?(l(),u=[u],i=3):o+=s:i===4?o==="--"&&s===">"?(i=1,o=""):o=s+o[0]:c?s===c?c="":o+=s:s===\'"\'||s==="\'"?c=s:s===">"?(l(),i=1):i&&(s==="="?(i=5,r=o,o=""):s==="/"&&(i<5||n[f][h+1]===">")?(l(),i===3&&(u=u[0]),i=u,(u=u[0]).push(2,0,i),i=0):s===" "||s===" "||s===`\n`||s==="\\r"?(l(),i=2):o+=s),i===3&&o==="!--"&&(i=4,u=u[0])}return l(),u})(e)),t),arguments,[])).length>1?t:t[0]}var En=e=>Array.isArray(e)?e.map(En):e===""?null:e,Zs=(e,t,...n)=>Q(e,t,...n.map(En)),a=Mn.bind(Zs);var Ie,k,mt,Ln,ht=0,Fn=[],y=b,Nn=y.__b,Hn=y.__r,Un=y.diffed,jn=y.__c,Bn=y.unmount,On=y.__;function Wn(e,t){y.__h&&y.__h(k,e,ht||t),ht=0;var n=k.__H||(k.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function ne(e){return ht=1,Ys(Kn,e)}function Ys(e,t,n){var s=Wn(Ie++,2);if(s.t=e,!s.__c&&(s.__=[n?n(t):Kn(void 0,t),function(c){var u=s.__N?s.__N[0]:s.__[0],l=s.t(u,c);u!==l&&(s.__N=[l,s.__[1]],s.__c.setState({}))}],s.__c=k,!k.__f)){var r=function(c,u,l){if(!s.__c.__H)return!0;var f=!1,h=s.__c.props!==c;if(s.__c.__H.__.some(function(m){if(m.__N){f=!0;var p=m.__[0];m.__=m.__N,m.__N=void 0,p!==m.__[0]&&(h=!0)}}),i){var d=i.call(this,c,u,l);return f?d||h:d}return!f||h};k.__f=!0;var i=k.shouldComponentUpdate,o=k.componentWillUpdate;k.componentWillUpdate=function(c,u,l){if(this.__e){var f=i;i=void 0,r(c,u,l),i=f}o&&o.call(this,c,u,l)},k.shouldComponentUpdate=r}return s.__N||s.__}function Vn(e,t){var n=Wn(Ie++,7);return nr(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function er(){for(var e;e=Fn.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Pe),t.__h.some(gt),t.__h=[]}catch(n){t.__h=[],y.__e(n,e.__v)}}}y.__b=function(e){k=null,Nn&&Nn(e)},y.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),On&&On(e,t)},y.__r=function(e){Hn&&Hn(e),Ie=0;var t=(k=e.__c).__H;t&&(mt===k?(t.__h=[],k.__h=[],t.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.some(Pe),t.__h.some(gt),t.__h=[],Ie=0)),mt=k},y.diffed=function(e){Un&&Un(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(Fn.push(t)!==1&&Ln===y.requestAnimationFrame||((Ln=y.requestAnimationFrame)||tr)(er)),t.__H.__.some(function(n){n.u&&(n.__H=n.u,n.u=void 0)})),mt=k=null},y.__c=function(e,t){t.some(function(n){try{n.__h.some(Pe),n.__h=n.__h.filter(function(s){return!s.__||gt(s)})}catch(s){t.some(function(r){r.__h&&(r.__h=[])}),t=[],y.__e(s,n.__v)}}),jn&&jn(e,t)},y.unmount=function(e){Bn&&Bn(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(s){try{Pe(s)}catch(r){t=r}}),n.__H=void 0,t&&y.__e(t,n.__v))};var qn=typeof requestAnimationFrame=="function";function tr(e){var t,n=function(){clearTimeout(s),qn&&cancelAnimationFrame(t),setTimeout(e)},s=setTimeout(n,35);qn&&(t=requestAnimationFrame(n))}function Pe(e){var t=k,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),k=t}function gt(e){var t=k;e.__c=e.__(),k=t}function nr(e,t){return!e||e.length!==t.length||t.some(function(n,s){return n!==e[s]})}function Kn(e,t){return typeof t=="function"?t(e):t}var K=e=>{if(!e)return"";let t=Math.max(0,(Date.now()-Date.parse(e))/1e3);return t<90?Math.round(t)+"s":t<5400?Math.round(t/60)+"m":t<172800?(t/3600).toFixed(1)+"h":Math.round(t/86400)+"d"},v=e=>a`<span data-age=${e??""}>${K(e)}</span>`,Ee=e=>e.activity&&a`<div class=${"activity"+(e.activity.stale?" stale":"")} title=${e.activity.stale?"no activity past the wedge threshold":e.activity.kind}>${e.activity.stale?"stale \\xB7 ":""}${e.activity.summary} \xB7 ${v(e.activity.at)} ago</div>`,sr=e=>e&&/^https?:\\/\\//i.test(e)?e:void 0,Le=e=>{let t=e.waiting;if(!t)return;let n=sr(t.link),s=n;try{n&&(s=new URL(n).host+new URL(n).pathname.replace(/\\/$/,""))}catch{s=n}return a`<div class="waiting">${e.verb}: waiting on ${t.on} \xB7 ${v(t.since)}${n&&[" \\xB7 ",a`<a href=${n} target="_blank" rel="noopener noreferrer" onClick=${x}>${s}</a>`]}</div>`},rr=12,zn=e=>e!==void 0&&e.length>rr,H=e=>zn(e)?e:void 0,U=e=>zn(e)?" long":"",w=(e,t)=>()=>M(e,t),x=e=>e.stopPropagation(),or=e=>e.sessionId?(e.harness==="codex"?"codex resume ":"claude --resume ")+e.sessionId:void 0;function z(e){if(e.requested)return a`<span class="dim">Requested \u2014 no window yet</span>`;if(e.starting)return a`<span class="dim">${e.starting.failedAt?"Did not start":"Starting \\u2014 no window yet"}</span>`;if(!e.live){let s=or(e);return s?a`<span class="dim">Resume: <code onClick=${x}>${s}</code></span>`:a`<span class="dim">Resume command unavailable</span>`}let t=_();return!t.snapshot?.focusSupported&&!e.link?a`<span class="dim">Window focus is not supported here</span>`:a`<span class="winaction"><button class="btn open" title="open this trap\'s window" onClick=${s=>{x(s),Sn(e.trapId)}}>\u2197 open</button>${e.link&&[" ",a`<a href=${e.link} onClick=${x}>Session link</a>`]}${t.focusResults[e.trapId]&&[" ",a`<span class="dim">${t.focusResults[e.trapId]}</span>`]}</span>`}function R(e,t,n){return t.length?a`<div class="wrap"><table><tbody><tr>${e.map(s=>a`<th>${s}</th>`)}</tr>${t}</tbody></table></div>`:a`<div class="empty">${n}</div>`}function ir({text:e}){let[t,n]=ne(null),s=r=>async()=>{await Rn(e)&&(n(r),setTimeout(()=>n(null),1e3))};return a`<div class="cmd"><code class=${t==="code"?"copied":void 0} title="click to copy" onClick=${s("code")}>${e}</code><button title="copy" onClick=${s("button")}>${t==="button"?"\\u2713":"\\u29C9"}</button></div>`}var C=(e,t)=>a`<${ir} key=${t} text=${e} />`,ue=(e,t,n="thumb")=>a`<button\n type="button"\n class=${n}\n title=${"view "+t}\n onClick=${s=>{x(s),Tn(e,t)}}\n >\n <img src=${e} alt=${t} loading="lazy" />\n </button>`,se=(e,t)=>e.map(n=>[a`<div class="sub">${n.name} \xB7 ${n.type} \xB7 ${n.bytes} bytes</div>`,t&&rn(n.name)&&ue(t(n.name),n.name),C(n.path)]),Jn=/\\bwt:([a-z0-9][a-z0-9-]*)/g,vt=e=>_().snapshot?.trapNames?.[e],j=e=>e.replace(Jn,(t,n,s)=>{let r=vt(n);return r&&!Xn(e,s,t.length,r)?r:t}),Xn=(e,t,n,s)=>e.slice(Math.max(0,t-s.length-2),t)===`${s} (`&&e[t+n]===")";function Ne(e){let t="wt:"+e,n=vt(e)??t;return _().snapshot?.traps.some(r=>r.trapId===e)?a`<a class="trapname" href="#traps" title=${t} onClick=${r=>{r.preventDefault(),x(r),M("trap",e)}}>${n}</a>`:a`<span class="trapname" title=${t}>${n}</span>`}var Me=e=>{let t=/^wt:([a-z0-9][a-z0-9-]*)$/.exec(e);return t?Ne(t[1]):e};function B(e,t=1/0){let n=[],s=0;for(let c of e.matchAll(Jn)){c.index>s&&n.push({text:e.slice(s,c.index)});let u=vt(c[1]);n.push(u&&Xn(e,c.index,c[0].length,u)?{text:c[0]}:{text:u??c[0],trapId:c[1]}),s=c.index+c[0].length}s<e.length&&n.push({text:e.slice(s)});let r=n.reduce((c,u)=>c+u.text.length,0),i=r>t?t-1:1/0,o=[];for(let c of n){if(i<=0)break;c.text.length<=i?o.push(c.trapId?Ne(c.trapId):c.text):o.push(c.text.slice(0,i)),i-=c.text.length}return r>t&&o.push("\\u2026"),o}var _t=e=>e.log.flatMap((t,n)=>[n?`\n`:"",t.at+" "+t.verb,...t.note?[" ",...B(t.note)]:[]]);function Qn(e){let t=[e.elapsed&&`elapsed: ${e.elapsed}`,e.attempt&&`attempt: ${e.attempt}`,e.branch&&`branch: ${e.branch}`,e.lastCommit&&`last commit: ${e.lastCommit}`,e.aheadTrunk&&`commits: ${e.aheadTrunk}`,e.draftPr&&`draft PR: ${e.draftPr}`,e.updated&&`updated: ${e.updated}`].filter(Boolean).join(`\n`);return a`${e.waiting&&[a`<div class="sec">waiting</div>`,Le(e)]}${e.activity&&[a`<div class="sec">activity</div>`,Ee(e)]}${t&&[a`<div class="sec">progress</div>`,a`<pre>${t}</pre>`]}<div class="sec">brief</div><pre>${e.brief}</pre>${e.attachments.length>0&&[a`<div class="sec">attachments (${e.attachments.length})</div>`,se(e.attachments,n=>it(e.lane,e.id,n))]}${e.messageAttachments.length>0&&[a`<div class="sec">message attachments (${e.messageAttachments.length})</div>`,se(e.messageAttachments,n=>it(e.lane,e.id,n))]}${e.followUp&&[a`<div class="sec">forks</div>`,a`<pre>${e.followUp}</pre>`]}<div class="sec">log</div><div class="loglines">${e.log.length?_t(e):"no entries yet"}</div>${e.inbox.length>0&&[a`<div class="sec">inbox</div>`,a`<div class="loglines">${e.inbox.join(`\n---\n`)}</div>`]}${e.awaitingReply&&[a`<div class="sec">awaiting reply</div>`,a`<div>${e.awaitingReply.line} \xB7 from ${e.awaitingReply.from} \xB7 ${v(e.awaitingReply.sentAt)} ago</div>`]}${e.evidence&&[a`<div class="sec">evidence</div>`,a`<div class="loglines">${JSON.stringify(e.evidence)}</div>`]}`}function fe(e){return e.for?[Me(e.for),e.evidence&&e.evidence.deliveredTo?[" ",a`<span class="ok">\u2713delivered</span>`]:[" ",a`<span class="warn">waiting</span>`]]:e.claimedBy?Me(e.claimedBy):""}function re(e){let t=e.evidence&&(e.evidence.prUrl||e.evidence.pr&&e.evidence.pr.url);if(!t)return"";let n=e.prBadge,s=e.evidence&&e.evidence.prUrls&&e.evidence.prUrls.length>1?e.evidence.prUrls:void 0;return[s?s.map((r,i)=>[i?" ":"",a`<a href=${r} target="_blank" rel="noopener" onClick=${x}>#${r.split("/").pop()}</a>`]):a`<a href=${t} target="_blank" rel="noopener" onClick=${x}>PR</a>`,n&&[" ",a`<span class=${"badge "+L(n)} title=${"observed "+n.observedAt}>${n.text}</span>`],e.prGate&&[" ",a`<span class="badge dim" title="merge gate (pick)">${e.prGate}</span>`]]}var ar={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch"},$t={"pr:review":"bad","pr:checks":"bad","pr:conflict":"pr-conflicts","pr:ready":"ok","pr:draft":"dim",watch:"warn"},Zn=e=>ar[e]||"";var He=e=>a`<a href=${e.url} target="_blank" rel="noopener" onClick=${x}>#${e.number}</a>`,Ue=e=>a`<span class="prname" title=${e.title||void 0}><b>#${e.number}</b>${e.title&&[" ",e.title]}</span>`;function oe(e,t,n){return e.flatMap((s,r)=>{let i=t.get(s)||void 0,o=s===n?a`<b title=${i}>#${s}</b>`:a`<span title=${i}>#${s}</span>`;return r?[" \\u2192 ",o]:[o]})}function me(e){let t=e.checks;return t.passed+"/"+t.total+" passed"+(t.failed?" \\xB7 "+t.failed+" failed":"")+(t.pending?" \\xB7 "+t.pending+" pending":"")}function je(e){let t=e.review||{};return(e.reviewDecision||"")+(t.unresolvedThreads?" \\xB7 "+t.unresolvedThreads+" unresolved":"")+(t.changesRequested?" \\xB7 changes requested":"")}function he(e){return[e.mergeStateStatus,e.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:e.blockedBy?" \\xB7 blocked by #"+e.blockedBy:""]}function bt(e){let t=fn(e);return e?[t.text," \\xB7 ",t.at?[v(t.at)," ago"]:"never checked"]:a`<span class="dim">${t.text}</span>`}function ge(e){if(e.requested)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">requested</span>`],hb:a`<span class="dim">\u2014</span>`};if(e.starting)return{stale:!1,listen:e.starting.failedAt?[a`<span class="dot bad"></span>`,a`<span class="bad">start failed</span>`]:[a`<span class="dot warn"></span>`,"starting"],hb:a`<span class="dim">\u2014</span>`};if(!e.live)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">signed off</span>`],hb:a`<span class="dim">\u2014</span>`};let t=Date.now()-Date.parse(e.heartbeatAt)>18e5,n=e.listening??(!!e.firstParkedAt&&!t);return{stale:t,listen:n?[a`<span class="dot ok"></span>`,"listening"]:[a`<span class="dot warn"></span>`,"not listening"],hb:a`<span class=${t?"bad":"ok"}>${t&&"stale "}${v(e.heartbeatAt)} ago</span>`}}function kt(e){if(e.requested)return{kind:"requested",helmOn:!!_().snapshot?.helmOn};if(e.starting)return e.starting.failedAt?{kind:"start failed",reason:e.starting.reason??"no session signed on in time"}:{kind:"starting",deadline:e.starting.deadline};if(!e.live)return{kind:"signed off"};let t=e.claimed?e.catches.find(n=>n.id===e.claimed&&n.bucket==="active"):void 0;return t?t.waiting||t.verb==="paused"?{kind:"parked",current:t}:{kind:"working",current:t}:{kind:"idle",listening:e.listening??(!!e.firstParkedAt&&Date.now()-Date.parse(e.heartbeatAt??"")<=18e5)}}function lr(e){let t=kt(e);return t.kind==="working"||t.kind==="idle"&&t.listening?"ok":t.kind==="start failed"?"bad":t.kind==="parked"||t.kind==="starting"?"warn":"dim"}var Yn=e=>e.waiting?`waiting on ${e.waiting.on}`:e.note||"waiting",es=e=>e.brief.split(/\\r?\\n/,1)[0]?.trim().slice(0,40)||"(no title)";function ve(e){let t=kt(e),n=a`<span class=${"dot trapdot "+lr(e)}></span>`;if(t.kind==="requested")return[n,a`<span class="dim">${ts(e,t.helmOn)}</span>`];if(t.kind==="starting")return[n,"starting \\xB7 waiting for its session to sign on"];if(t.kind==="start failed")return[n,a`<span class="bad">start failed</span>`," \\xB7 ",t.reason];if(t.kind==="signed off")return[n,a`<span class="dim">signed off</span>`];if(t.kind==="idle")return[n,"idle \\xB7 ",t.listening?"listening":"not listening"];let s=t.current;return t.kind==="parked"?[n,"parked \\xB7 ",Yn(s)]:[n,"working \\xB7 ",a`<a href="#dispatches" onClick=${i=>{i.preventDefault(),x(i),M("dispatch",`${s.lane}:${s.id}`)}}>${s.id.slice(0,8)}</a>`," \\xB7 ",es(s),s.activity&&[" \\xB7 ",s.activity.summary," ",v(s.activity.at)," ago"]]}var ts=(e,t)=>`requested \\xB7 ${e.repo??""} \\xB7 ${e.harness??""} \\xB7 ${t?"waiting for the helm":"waiting for a helm"}`;function J(e){let t=e.starting?.commands;return t?.length?a`<div class="startcmds" onClick=${x}><div class="dim">start it: run one in a terminal</div>${t.map(n=>C(n.command,n.harness))}</div>`:null}function Be(){let t=_().snapshot?.repoKeys??[],[n,s]=ne(!1),[r,i]=ne(""),[o,c]=ne("claude"),[u,l]=ne(null),f=r||t[0]||"",h=async d=>{d.preventDefault();let m=await xn(f,o);l(m??null),m||s(!1)};return n?a`<form class="newtrap" onClick=${x} onSubmit=${h}>\n <label>repo <select name="repo" value=${f} onChange=${d=>i(d.target.value)}>${t.map(d=>a`<option value=${d}>${d}</option>`)}</select></label>\n <label>harness <select name="harness" value=${o} onChange=${d=>c(d.target.value)}><option value="claude">claude</option><option value="codex">codex</option></select></label>\n <button class="btn" type="submit" disabled=${!f}>Request</button>\n <button class="btn" type="button" onClick=${()=>s(!1)}>Cancel</button>\n ${u&&a`<span class="bad">${u}</span>`}\n </form>`:a`<span class="newtrap"><button class="btn" onClick=${d=>(x(d),s(!0),l(null))}>+ New trap</button></span>`}function Oe(e){let t=kt(e);if(t.kind==="requested")return ts(e,t.helmOn);if(t.kind==="starting")return`starting \\xB7 sign-on due by ${t.deadline}`;if(t.kind==="start failed")return`start failed \\xB7 ${t.reason}`;if(t.kind==="signed off")return"signed off";if(t.kind==="idle")return`idle \\xB7 ${t.listening?"listening":"not listening"}`;if(t.kind==="parked")return`parked \\xB7 ${Yn(t.current)}`;let n=t.current;return[`working \\xB7 ${n.id.slice(0,8)} \\xB7 ${es(n)}`,n.activity&&`${n.activity.summary} ${K(n.activity.at)} ago`].filter(Boolean).join(" \\xB7 ")}function yt(e){let t=e.messages.filter(n=>n.state==="pending").length;return e.messages.length?["\\u2709 "+e.messages.length,t>0&&[" ",a`<span class="warn">(${t} pending)</span>`]]:null}var cr=e=>()=>{window.open(D(e.key),"_blank","noopener")};function wt(e){let t=de(e);return{nodes:[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked?" \\xB7 acked":""],text:[t,`${K(e.filedAt)} ago`,e.acked?"acked":""].filter(Boolean).join(" \\xB7 ")}}function dr(e){let t=wt(e);return a`<a key=${e.key} class=${"card"+(e.acked?" acked":"")} href=${D(e.key)} target="_blank" rel="noopener"><div class="top"><b title=${e.title}>${e.title}</b></div><div class="meta" title=${t.text}>${t.nodes}</div></a>`}function pr(e){return a`<tr key=${e.key} class=${"rowhead"+(e.acked?" acked":"")} onClick=${cr(e)}><td class="grow"><a href=${D(e.key)} target="_blank" rel="noopener" onClick=${t=>t.stopPropagation()}>${e.title}</a></td><td>${de(e)}</td><td>${v(e.filedAt)}</td><td>${e.acked?"acked":""}</td></tr>`}function ns({inp:e}){return e.view==="cards"?e.list.length?a`<div class="cards">${e.list.map(dr)}</div>`:a`<div class="empty">no reports match</div>`:R(["title","from","filed","acked"],e.list.map(pr),"no reports match")}function os(e){let t=e.trim();return/^(https?:\\/\\/|mailto:|#)/i.test(t)?t:void 0}function is(e){let t=e.trim();return/^[^/\\\\:?#]+$/.test(t)&&t!=="."&&t!==".."?t:void 0}function ss(e,t,n,s){let r=0;for(let i=t;i<e.length;i++){if(e[i]==="\\\\"){i++;continue}if(e[i]===n)r++;else if(e[i]===s){if(r===0)return i;r--}}return-1}function rs(e,t){let n=ss(e,t+1,"[","]");if(n<0||e[n+1]!=="(")return;let s=ss(e,n+2,"(",")");if(s<0)return;let r=e.slice(n+2,s).trim().replace(/\\s+"[^"]*"$/,"").replace(/^<(.*)>$/,"$1");return{text:e.slice(t+1,n),target:r,end:s+1}}function q(e){let t=[],n="",s=()=>{n&&t.push({t:"text",v:n}),n=""};for(let r=0;r<e.length;){let i=e[r];if(i==="\\\\"&&r+1<e.length&&/[\\\\`*_{}[\\]()#+\\-.!|<>~]/.test(e[r+1])){n+=e[r+1],r+=2;continue}if(i==="`"){let o=/^`+/.exec(e.slice(r))[0],c=e.indexOf(o,r+o.length);if(c>0){s(),t.push({t:"code",v:e.slice(r+o.length,c).replace(/^ (.*) $/,"$1")}),r=c+o.length;continue}}if(i==="!"&&e[r+1]==="["){let o=rs(e,r+1);if(o){s(),t.push({t:"img",alt:o.text,src:o.target}),r=o.end;continue}}if(i==="["){let o=rs(e,r);if(o){s(),t.push({t:"a",href:o.target,c:q(o.text)}),r=o.end;continue}}if((i==="*"||i==="_")&&e[r+1]===i){let o=e.indexOf(i+i,r+2);if(o>r+2){s(),t.push({t:"b",c:q(e.slice(r+2,o))}),r=o+2;continue}}if(i==="*"||i==="_"&&(r===0||!/\\w/.test(e[r-1]))){let o=r+1;for(;(o=e.indexOf(i,o))>0&&(e[o+1]===i||i==="_"&&/\\w/.test(e[o+1]??""));)o+=2;if(o>r+1&&e[r+1]!==" "){s(),t.push({t:"i",c:q(e.slice(r+1,o))}),r=o+1;continue}}if(i===`\n`){/ {2,}$/.test(n)||n.endsWith("\\\\")?(n=n.replace(/( {2,}|\\\\)$/,""),s(),t.push({t:"br"})):n+=" ",r++;continue}n+=i,r++}return s(),t}var as=/^ {0,3}(`{3,}|~{3,})\\s*([^`\\s]*)/,ls=/^ {0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/,cs=/^ {0,3}([-*_])(\\s*\\1){2,}\\s*$/,St=/^( {0,3})([-*+])\\s+(.*)$/,xt=/^( {0,3})(\\d{1,9})[.)]\\s+(.*)$/,qe=/^ {0,3}>\\s?(.*)$/,ur=/^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/;function Gt(e){let t=e.trim();t.startsWith("|")&&(t=t.slice(1)),t.endsWith("|")&&!t.endsWith("\\\\|")&&(t=t.slice(0,-1));let n=[],s="";for(let r=0;r<t.length;r++)t[r]==="\\\\"&&t[r+1]==="|"?(s+="|",r++):t[r]==="|"?(n.push(s.trim()),s=""):s+=t[r];return n.push(s.trim()),n}var Tt=e=>as.test(e)||ls.test(e)||cs.test(e)||St.test(e)||xt.test(e)||qe.test(e);function Fe(e){let t=e.replace(/\\r\\n?/g,`\n`).replace(/\\t/g," ").split(`\n`),n=[],s=0;for(;s<t.length;){let r=t[s];if(!r.trim()){s++;continue}let i=as.exec(r);if(i){let f=i[1],h=[];for(s++;s<t.length&&!new RegExp(`^ {0,3}${f[0]}{${f.length},}\\\\s*$`).test(t[s]);)h.push(t[s++]);s++,n.push({t:"code",lang:i[2]??"",v:h.join(`\n`)});continue}let o=ls.exec(r);if(o){n.push({t:"h",level:o[1].length,c:q(o[2])}),s++;continue}if(cs.test(r)){n.push({t:"hr"}),s++;continue}if(qe.test(r)){let f=[];for(;s<t.length&&t[s].trim()&&(qe.test(t[s])||!Tt(t[s]));)f.push(qe.exec(t[s])?.[1]??t[s]),s++;n.push({t:"quote",c:Fe(f.join(`\n`))});continue}let c=St.exec(r),u=xt.exec(r);if(c||u){let f=!c,h=f?xt:St,d=[];for(;s<t.length;){let m=t[s],p=h.exec(m);if(p&&p[1].length<=3){d.push([p[3]]),s++;continue}if(!m.trim()){if(s+1<t.length&&/^ {2,}\\S/.test(t[s+1])){d.at(-1).push(""),s++;continue}break}if(/^ {2,}\\S/.test(m)){d.at(-1).push(m.replace(/^ {2,4}/,"")),s++;continue}if(Tt(m))break;d.at(-1).push(m.trim()),s++}n.push({t:"list",ordered:f,start:f?Number(u[2]):1,items:d.map(m=>Fe(m.join(`\n`)))});continue}if(r.includes("|")&&s+1<t.length&&ur.test(t[s+1])&&t[s+1].includes("-")){let f=Gt(r),h=Gt(t[s+1]).map(m=>m.startsWith(":")&&m.endsWith(":")?"center":m.endsWith(":")?"right":m.startsWith(":")?"left":"");s+=2;let d=[];for(;s<t.length&&t[s].trim()&&t[s].includes("|");){let m=Gt(t[s++]);d.push(f.map((p,g)=>q(m[g]??"")))}n.push({t:"table",align:f.map((m,p)=>h[p]??""),head:f.map(q),rows:d});continue}let l=[];for(;s<t.length&&t[s].trim()&&!Tt(t[s])&&!(l.length&&/^ {0,3}(=+|-+)\\s*$/.test(t[s]));)l.push(t[s++].replace(/^ +/,""));if(l.length===0){n.push({t:"p",c:q(r.trim())}),s++;continue}n.push({t:"p",c:q(l.join(`\n`))})}return n}function F(e,t){return t.map(n=>{switch(n.t){case"text":return n.v;case"code":return a`<code>${n.v}</code>`;case"b":return a`<strong>${F(e,n.c)}</strong>`;case"i":return a`<em>${F(e,n.c)}</em>`;case"br":return a`<br />`;case"a":{let s=os(n.href);return s?a`<a href=${s} target="_blank" rel="noopener noreferrer">${F(e,n.c)}</a>`:F(e,n.c)}case"img":{let s=is(n.src);return s?ue(e(s),n.alt||s,"mdimg"):a`<span class="dim">[image not shown: ${n.alt||n.src}]</span>`}}})}function We(e,t){return t.map(n=>{switch(n.t){case"h":{let s=F(e,n.c);return n.level===1?a`<h1>${s}</h1>`:n.level===2?a`<h2>${s}</h2>`:n.level===3?a`<h3>${s}</h3>`:a`<h4>${s}</h4>`}case"p":return a`<p>${F(e,n.c)}</p>`;case"code":return a`<pre class="mdcode"><code>${n.v}</code></pre>`;case"hr":return a`<hr />`;case"quote":return a`<blockquote>${We(e,n.c)}</blockquote>`;case"list":{let s=n.items.map(r=>a`<li>${We(e,r)}</li>`);return n.ordered?a`<ol start=${n.start}>${s}</ol>`:a`<ul>${s}</ul>`}case"table":return a`<div class="mdtable"><table><thead><tr>${n.head.map((s,r)=>a`<th style=${n.align[r]?"text-align:"+n.align[r]:void 0}>${F(e,s)}</th>`)}</tr></thead><tbody>${n.rows.map(s=>a`<tr>${s.map((r,i)=>a`<td style=${n.align[i]?"text-align:"+n.align[i]:void 0}>${F(e,r)}</td>`)}</tr>`)}</tbody></table></div>`}})}var _e=new Map;function ds(e){let t=_e.get(e);return t||(t=Fe(e),_e.set(e,t),_e.size>8&&_e.delete(_e.keys().next().value)),t}function fr({r:e,text:t}){let n=t?.text!==void 0?We(s=>ct(e.key,s),ds(t.text)):t?.error?a`<div class="bad">${t.error}</div>`:a`<div class="dim">loading\u2026</div>`;return a`<div class="mdpage" data-report=${e.key}>${n}</div>`}function ps({text:e,fileUrl:t}){return a`<div class="mdpage">${We(t,ds(e))}</div>`}function Ct(e){let t=de(e);return[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked&&[" \\xB7 ",a`<span class="ok">acked</span>`]]}function us({r:e,text:t,heading:n}){return[n&&a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)}</div>`,a`<${fr} r=${e} text=${t} />`,e.attachments.length>0&&[a`<div class="sec">report attachments (${e.attachments.length})</div>`,se(e.attachments,s=>ct(e.key,s))],!e.acked&&[a`<div class="sec">ack</div>`,C("lobstah attention ack "+e.key)]]}var mr=/\\.(png|jpe?g|gif|webp)$/i,hr=e=>{e.style.height="auto",e.style.height=e.scrollHeight+"px"};function gr(e,t){return t.length?a`<div class="dfiles">${t.map(n=>mr.test(n.name)?a`<span key=${n.name}>${ue(at(e,n.name),n.name,"dimg")}</span>`:a`<div key=${n.name} class="dfile"><span>${n.name}</span> <span class="dim">\xB7 ${n.type} \xB7 ${n.bytes} bytes \xB7 </span><code>${n.path}</code></div>`)}</div>`:null}function vr(e,t,n){let s=e.key,r=e.kind==="decision"?e.options:[],i=t?.text??"",o=t?.files??[],c=!!t?.sending;return[r.length>0&&a`<div class="doptions">${r.map(u=>a`<button key=${u} type="button" class=${"btn dopt"+(t?.option===u?" on":"")} aria-pressed=${t?.option===u?"true":"false"} disabled=${c} onClick=${()=>bn(s,u)}>${u}</button>`)}</div>`,a`<textarea\n class="danswer"\n rows="2"\n placeholder=${r.length?"Add to your answer, or answer in your own words":"Your answer"}\n value=${i}\n disabled=${c}\n onInput=${u=>{let l=u.currentTarget;hr(l),A(s,{text:l.value,error:void 0})}}\n onPaste=${u=>{kn(s,u)}}\n ></textarea>`,a`<div class="dfoot">\n <label class="btn dattach" title="attach images or files">attach<input type="file" multiple accept=${n} disabled=${c} onChange=${u=>{let l=u.currentTarget;ft(s,l.files).then(()=>{l.value=""})}} /></label>\n ${o.map((u,l)=>a`<span key=${u.name+l} class="dchip">${u.name}<button type="button" class="dx" title="remove" disabled=${c} onClick=${()=>yn(s,l)}>\xD7</button></span>`)}\n ${t?.error&&a`<span class="bad derr">${t.error}</span>`}\n <button type="button" class="btn dsend" disabled=${c} onClick=${()=>{wn(s)}}>${c?"Sending\\u2026":"Send"}</button>\n </div>`]}function _r(e,t,n,s){let r=e.dispatch,i=e.lane??"work",c=[r&&a`<a class="dlink" href="#" onClick=${l=>{l.preventDefault(),w("dispatch",i+":"+r)()}}>${r.slice(0,8)}</a>`,e.repo,[v(e.at)," ago"]].filter(Boolean).flatMap((l,f)=>f?[" \\xB7 ",l]:[l]),u="dcard"+(e.kind==="question"?" plain":"")+(t?.sent?" answered":"")+(n?" focus":"");return a`<div key=${e.key} class=${u} data-decision=${e.key}>\n <div class="dtop">\n ${e.kind==="question"&&a`<span class=${"badge "+(e.verb==="blocked"?"bad":"warn")}>${e.verb}</span>`}\n <b class="dtitle">${e.kind==="decision"?e.title:B(e.note)}</b>\n <span class="dmeta dim">${c}</span>\n </div>\n ${e.kind==="decision"&&e.detail.trim()&&a`<${ps} text=${e.detail} fileUrl=${l=>at(e.key,l)} />`}\n ${e.kind==="decision"&&gr(e.key,e.attachments)}\n ${t?.sent?a`<div class="danswered ok">answered \xB7 ${t.sent}</div>`:vr(e,t,s)}\n </div>`}function fs({cards:e,drafts:t,focus:n,extensions:s}){let r=s.join(","),i=e.length?a`<div class="dcards">${e.map(o=>_r(o,t[o.key],o.key===n,r))}</div>`:a`<div class="empty">none</div>`;return a`<section class="decisions"><h2>decisions</h2>${i}</section>`}function yr(e,t){let n=e.badge&&a`<span class=${"badge "+(e.badge.tone||"dim")+U(e.badge.text)} title=${H(e.badge.text)}>${e.badge.text}</span>`;if(e.href){let s=(t==="cards"?"card":"deckline click")+(e.acked?" acked":"");return t==="cards"?a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener"><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}</a>`:a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener">${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}</a>`}return t==="cards"?a`<div key=${e.key} class=${"card"+(e.acked?" acked":"")} onClick=${e.open} style=${e.open?void 0:"cursor:default"}><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}${e.extra}${e.action&&a`<div class="foot"><span class="footact">${e.action}</span></div>`}</div>`:a`<div key=${e.key} class=${"deckline"+(e.open?" click":"")+(e.acked?" acked":"")} onClick=${e.open}>${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}${e.action&&[" ",a`<span class="dim">\xB7 </span>`,e.action]}${e.extra}</div>`}var ms=(e,t)=>e>0&&a`<a class="deckmore" href=${"#"+t}>+${e} more \u2192</a>`;function Ve(e,t,n,s,r,i){let o=t.slice(0,s),c=o.map(l=>yr(l,r)),u=o.length?r==="cards"?a`<div class="cards">${c}</div>`:c:a`<div class="empty">none</div>`;return a`<section><h2><a href=${"#"+n}>${e} \u2192</a></h2>${i&&a`<div class="tabhead">${i}</div>`}${u}${ms(t.length-o.length,n)}</section>`}function wr(e,t,n,s){let r=t.find(l=>l.number===e.nextNumber)||t[0],i=oe(e.numbers,new Map(t.map(l=>[l.number,l.title]))),o=r?"next #"+r.number:"nothing mergeable";if(s==="cards"){let l=f=>{let h=n.get(f.key)||[],d=h.length>0&&h.every(g=>g.acked),m=h.length?h.map(g=>a`<span class=${"badge "+($t[g.kind]||"dim")}>${Zn(g.kind)}</span>`):a`<span class=${"badge "+L(f.badge)+U(f.badge.text)} title=${H(f.badge.text)}>${f.badge.text}</span>`,p=`${f.repo} \\xB7 ${f.badge.text}${d?" \\xB7 acked":""}`;return a`<div key=${f.key} class=${"card"+(d?" acked":"")} onClick=${w("pr",f.key)}><div class="top">${Ue(f)}${m}</div><div class="meta" title=${p}>${p}</div></div>`};return a`<div key=${e.id} class="deckstack"><div class="dim">${i} \xB7 ${o}</div><div class="cards">${t.map(l)}</div></div>`}let c=t.flatMap(l=>(n.get(l.key)||[]).map(f=>a`<span class=${"badge "+($t[f.kind]||L(l.badge))+(f.acked?" acked":"")}>#${l.number} ${l.badge.text}</span>`)),u=c.flatMap((l,f)=>f?[" ",l]:[l]);return a`<div key=${e.id} class=${"deckline"+(r?" click":"")} onClick=${r&&w("pr",r.key)}><b>${i}</b> <span class="dim">\xB7 ${o}</span>${c.length>0&&[" \\xB7 ",u]}</div>`}function Gr(e,t){let n=e.stacks.slice(0,3),s=new Map;for(let i of e.prAttention){let o=s.get(i.key)||[];o.push(i),s.set(i.key,o)}let r=n.length?n.map(i=>wr(i,e.prs.filter(o=>o.stackId===i.id).sort((o,c)=>o.position-c.position),s,t)):a`<div class="empty">none</div>`;return a`<section><h2><a href="#prs">PRs \u2192</a></h2>${r}${ms(e.stacks.length-n.length,"prs")}</section>`}function hs({inp:e,drafts:t,focus:n,extensions:s}){let r=e.view,i=e.inflight.map(l=>({key:l.lane+":"+l.id,title:[l.id.slice(0,8)," ",l.repo||""],badge:{text:l.verb,tone:l.verb==="needs-decision"||l.verb==="blocked"?"bad":"dim"},meta:[B(l.note||"",90),l.verbAt&&[" \\xB7 ",v(l.verbAt)," ago"],(l.for||l.claimedBy)&&[" \\xB7 ",Me(l.for||l.claimedBy)]],metaText:[j(l.note||""),l.verbAt&&`${K(l.verbAt)} ago`,j(l.for||l.claimedBy||"")].filter(Boolean).join(" \\xB7 "),open:w("dispatch",l.lane+":"+l.id)})),o=e.landed.map(l=>({key:l.key,title:[l.id.slice(0,8)," ",l.repo||"",l.unreported&&[" ",a`<span class="badge warn unreported">unreported</span>`]],badge:{text:l.verb,tone:l.verb==="failed"?"bad":"ok"},meta:[B(l.note||"",90)," \\xB7 ",v(l.at)," ago"],metaText:`${j(l.note||"")} \\xB7 ${K(l.at)} ago`,open:w("dispatch",l.lane+":"+l.id)})),c=e.reports.map(l=>{let f=wt(l);return{key:l.key,title:l.title,meta:f.nodes,metaText:f.text,href:D(l.key),acked:!!l.acked}}),u=e.traps.map(({x:l})=>({key:l.trapId,title:"\\u{1FAA4} "+(l.label??`wt:${l.trapId}`),badge:l.requested?{text:"requested",tone:"dim"}:l.starting?{text:l.starting.failedAt?"start failed":"starting",tone:l.starting.failedAt?"bad":"warn"}:{text:l.live?l.harness||"live":"signed off",tone:l.live?"ok":"dim"},meta:[l.repo||""," \\xB7 ",ve(l)],metaText:`${l.repo||""} \\xB7 ${Oe(l)}`,action:z(l),extra:J(l),open:w("trap",l.trapId)}));return a`<div class="deckgrid"><${fs} cards=${e.decisions} drafts=${t} focus=${n} extensions=${s} />${Ve("in flight",i,"dispatches",4,r)}${Ve("Landed \\xB7 24h",o,"dispatches",8,r)}${Ve("reports",c,"reports",8,r)}${Ve("traps",u,"traps",8,r,a`<${Be} />`)}${Gr(e,r)}</div>`}function Tr(e,t){if(!t)return e;let n=new Map(e.map(i=>[i.id,i])),s=i=>{let o=i,c=new Set;for(;o.followUp&&n.has(o.followUp)&&!c.has(o.id);)c.add(o.id),o=n.get(o.followUp);return o.id},r=new Map;for(let i of e){let o=s(i),c=r.get(o)||[];c.push(i),r.set(o,c)}return[...r.values()].sort((i,o)=>o[0].sort-i[0].sort).flatMap(i=>i.sort((o,c)=>o.id===s(o)?-1:c.id===s(c)?1:o.sort-c.sort))}function Sr(e,t){let n=e.lane+":"+e.id;return a`<tr key=${n} class="rowhead" onClick=${w("dispatch",n)}><td>${t&&e.followUp&&a`<span class="dim">\u21B3 </span>`}${e.id.slice(0,8)}</td><td>${e.lane} / ${e.bucket}</td><td>${e.repo}</td><td class=${"v-"+e.verb}>${e.verb}</td><td class="grow">${B(e.note??"",90)}${Le(e)}${Ee(e)}</td><td>${v(e.verbAt)}</td><td>${fe(e)}</td><td>${re(e)}</td></tr>`}function xr(e){let t=e.lane+":"+e.id;return a`<div key=${t} class="card" onClick=${w("dispatch",t)}><div class="top"><b>${e.id.slice(0,8)}</b><span class=${"badge v-"+e.verb+U(e.verb)} title=${H(e.verb)}>${e.verb}</span></div><div class="meta" title=${`${e.repo} \\xB7 ${e.lane} ${e.bucket}`}>${e.repo} \xB7 ${e.lane} ${e.bucket}${e.verbAt&&[" \\xB7 ",v(e.verbAt)]}</div>${e.note&&a`<div class="note" title=${j(e.note)}>${B(e.note)}</div>`}${Le(e)}${Ee(e)}<div class="foot">${fe(e)} ${re(e)}</div></div>`}function gs({inp:e}){let t=e.list;return e.view==="cards"?t.length?a`<div class="cards">${t.map(xr)}</div>`:a`<div class="empty">no dispatches match</div>`:R(["id","lane / bucket","repo","verb","note","age","addressed","pr"],Tr(t,e.chain).map(n=>Sr(n,e.chain)),"no dispatches match")}var Cr={deck:"On deck",dispatches:"Dispatches",traps:"Traps",prs:"PRs",reports:"Reports",notices:"Notices"},Dr=["working","needs-decision","blocked","paused","done","failed","unknown"];function Ar(e,t){let n=t.daemonStale,s=e.daemon?[a`<span class=${n?"bad":"ok"}>${n&&"stale "}${v(e.daemon.heartbeat)} ago</span>`," ",a`<span class="dim">v${e.daemon.version}</span>`]:a`<span class="bad">down</span>`;return[a`<span class="chip">daemon ${s}</span>`,e.slots&&a`<span class="chip">active headless: ${e.slots.headless} of ${e.slots.limit}; traps: ${e.slots.traps}${e.slots.parked?`; parked: ${e.slots.parked} (no slot)`:""}</span>`,t.helms.map(({x:r,stale:i})=>a`<span key=${r.grounds} class="chip click" onClick=${w("helm",r.grounds)}>\u26F5 <b>${r.man}</b> <span class="dim">helm ${r.grounds}</span> <span class=${i?"warn":"ok"}>${i&&"stale "}${v(r.heartbeatAt)} ago</span></span>`)]}var Dt=[];function Rr(e){let t=[...new Set([...e.dispatches.map(n=>n.repo),...e.notices.map(n=>n.repo),...e.prs.map(n=>n.repo)].filter(Boolean))].sort();return t.length!==Dt.length&&(Dt=t),Dt}var At=[];function Pr(e){let t=[...new Set(e.notices.map(n=>n.kind))].sort();return t.length!==At.length&&(At=t),At}var $e=e=>e.currentTarget.value;function Ir(e,t,n){let s=o=>t===o?"":"display:none",r=e?Rr(e):[],i=e?Pr(e):[];return a`<div class="controls">\n <select id="f-lane" style=${e&&s("dispatches")} value=${n.lane} onChange=${o=>N({lane:$e(o)})}>\n <option value="">all lanes</option>\n <option value="work">work</option>\n <option value="chore">chore</option>\n </select>\n <select id="f-repo" value=${n.repo} onChange=${o=>N({repo:$e(o)})}>\n <option value="">all repos</option>\n ${r.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <select id="f-verb" style=${e&&s("dispatches")} value=${n.verb} onChange=${o=>N({verb:$e(o)})}>\n <option value="">all verbs</option>\n ${Dr.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <label id="chain-control" style=${e&&s("dispatches")}><input id="f-chain" type="checkbox" checked=${!!n.chain} onChange=${o=>N({chain:o.currentTarget.checked})} /> group by chain</label>\n <select id="f-kind" style=${e&&s("notices")} value=${n.noticeKind} onChange=${o=>N({noticeKind:$e(o)})}>\n <option value="">all notice kinds</option>\n ${i.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <input id="f-q" type="search" placeholder="search id \xB7 note \xB7 brief" value=${n.q} onInput=${o=>N({q:$e(o)})} />\n </div>`}function vs({d:e,inp:t,tab:n,st:s,stale:r}){return[a`<div class="headerline"><h1>\u{1F99E}\u2728 spyglass<span id="stale" style=${e||r?r?"display:inline":"display:none":void 0}> \xB7 STALE FEED</span></h1><span id="settings-slot"><button id="gearbtn" title="settings" aria-label="settings" onClick=${()=>M("settings","browser")}>\u2699</button></span></div>`,a`<div class="chips"><span id="chips" style="display:contents">${e&&t&&Ar(e,t.chips)}</span><span class="chip dim" id="clock">${e&&new Date(e.now).toLocaleTimeString("en-GB")}</span></div>`,a`<nav class="tabs" id="tabs" aria-label="Spyglass views">${ce.map(i=>a`<a href=${"#"+i} data-tab=${i} class=${e?i===n?"on":"":void 0}>${Cr[i]}</a>`)}</nav>`,Ir(e,n,s)]}var _s=({d:e})=>e&&["\\u{1F99E}\\u2728 lobstah v"+e.version+" \\xB7 ",a`<a href=${e.repoUrl} target="_blank">${e.repoUrl.replace("https://github.com/","")}</a>`];function $s(e,t){if(!t.lobs)return[];let n={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch",report:"report"},s=t.hidden??{},r=e.filter(o=>!o.acked&&!(o.key!==void 0&&s[o.key]===o.stateHash)).map(o=>{let c=n[o.kind??""]??"",u=o.key!==void 0?{hideKey:o.key,hideHash:o.stateHash??""}:{};return o.kind==="decision"&&o.key?{key:"decision:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:(o.kind??"question")==="question"&&o.key?{key:"question:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:o.kind==="report"&&o.key?{key:"report:"+o.key,text:o.note||o.verb,label:c,href:D(o.key),...u}:typeof o.kind=="string"&&o.kind.startsWith("pr:")&&o.prUrl?{key:o.kind+":"+(o.key??o.prUrl),text:o.note||o.kind,href:o.prUrl,label:c,...u}:{key:(o.kind??"question")+":"+(o.key??o.lane+":"+o.id),text:o.note||o.verb,label:c,...o.kind==="watch"?{}:{open:{type:"dispatch",key:o.lane+":"+o.id}},...u}});!r.length&&t.preview&&(r=[{key:"preview",text:"attention questions crawl in here",...t.previewHelm?{open:{type:"helm",key:t.previewHelm}}:{}}]);let i=r.length>4?r.length-4:0;return r=r.slice(0,4),i&&(r[3]={...r[3],text:"\\u2026and "+i+" more \\u2014 see attention",label:""}),r}var bs=!0;function Mr(e,t,n){let s="animation-duration:"+((innerWidth+180)/(100+t*12)).toFixed(1)+"s;animation-delay:-"+t*9%14+"s",r=()=>{e.hideKey&&An(e.hideKey,e.hideHash??"")},i=[a`<div class="bub">${e.label&&[a`<span class="badge dim">${e.label}</span>`," "]}<span>${e.text.length>48?e.text.slice(0,47)+"\\u2026":e.text}</span></div>`,n===!1?a`<span class="fallback">\u{1F99E}</span>`:a`<div class="sprite"></div>`,bs&&a`<img\n class="star"\n src="/star.png"\n alt=""\n onError=${c=>{bs=!1,c.currentTarget.remove()}}\n />`],o=e.open;return e.hash?a`<a key=${e.key} class="lob" style=${s} title="open the decision" href=${e.hash} onClick=${r}>${i}</a>`:e.href?a`<a key=${e.key} class="lob" style=${s} title=${e.label==="report"?"open the report":"open the PR"} href=${e.href} target="_blank" rel="noopener" onClick=${r}>${i}</a>`:a`<div\n key=${e.key}\n class="lob"\n style=${s}\n title="click to open"\n onClick=${()=>{r(),o&&M(o.type,o.key)}}\n >\n ${i}\n </div>`}function ks({state:e}){let t=e.snapshot,n=$s(t?.attention||[],{lobs:e.prefs.lobs,hidden:e.lobHidden,preview:e.preview,previewHelm:t&&t.helms.length?t.helms[0].grounds:void 0}),s=n.map(r=>r.key).join("|")+(e.spriteOk===null?"?":e.spriteOk?"s":"e");return Vn(()=>n.map((r,i)=>Mr(r,i,e.spriteOk)),[s])}function Ke({box:e}){if(!e)return null;let t=n=>{n.target===n.currentTarget&&ee()};return a`<div id="lightbox" class="lightbox" role="dialog" aria-label=${e.name} onClick=${t}>\n <button type="button" class="lbclose" title="close" onClick=${ee}>\xD7</button>\n <img class="lbimg" src=${e.src} alt=${e.name} />\n <a class="lboriginal" href=${e.src} target="_blank" rel="noopener">open original \xB7 ${e.name}</a>\n </div>`}var be=a`<span class="x" onClick=${te}>\xD7</span>`,ys=(e,t,n)=>a`<span class="seg">${t.map(([s,r])=>a`<button class=${s===n?"on":""} onClick=${()=>e(s)}>${r}</button>`)}</span>`;function Er(e,t){let n=ys(Cn,[["table","table"],["cards","cards"]],t.view),s=ys(Dn,[["on","on"],["off","off"]],t.lobs?"on":"off"),r=e.attentionError?e.attentionError:(e.attentionKinds||[]).join(" \\xB7 ");return[be,a`<h3>\u2699 settings <span class="dim" style="font-weight:normal">\xB7 this browser only</span></h3>`,a`<div class="settings"><div class="row"><span class="lbl">view<span class="hint">cards or table (notices stay a table)</span></span>${n}</div><div class="row"><span class="lbl">lobs<span class="hint">crawling lobsters in this page</span></span>${s}</div><div class="row"><span class="lbl">attention<span class="hint">kinds shown \u2014 attentionKinds in config.toml (read-only here)</span></span><span class="dim" style="font-size:11px;text-align:right;max-width:260px">${r}</span></div></div>`]}function Lr(e,t){let n=oe(t.numbers,new Map(t.numbers.map((r,i)=>[r,t.titles[i]])),e.number),s=t.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:t.blockedBy?" \\xB7 blocked by #"+t.blockedBy:t.nextNumber?" \\xB7 next is #"+t.nextNumber:"";return[n," \\xB7 ",t.position," of ",t.size," \\xB7 floor ",t.floor,s]}var Nr=e=>t=>{t.preventDefault(),M("dispatch",e)};function Hr(e,t){let n=un(e,t),s=n.pr,r=n.stack,i=n.watch,o=je(s),c=n.chain.length?n.chain.flatMap((l,f)=>[f?" \\u2192 ":"",l.culled?a`<span class="dim">${l.id.slice(0,8)} (culled)</span>`:[a`<a href="#" onClick=${Nr(l.modalKey)}>${l.id.slice(0,8)}</a>`," ",a`<span class="dim">${l.verb}</span>`]]):a`<span class="dim">none</span>`,u=i?[a`<div>${i.key} \xB7 owner ${i.owner||"?"} \xB7 ${i.lastCheckedAt?["checked ",v(i.lastCheckedAt)," ago"]:"never checked"}</div>`,i.lastError&&a`<div class="bad">${i.lastError}</div>`,a`<div class="dim" style="font-size:11px;margin-top:4px">cursor</div>`,C(i.cursor)]:a`<div class="dim">no watch \u2014 <code>lobstah watch add ${s.url}</code> registers one</div>`;return[be,a`<h3>${He(s)} ${s.title||""} <span class=${"badge "+L(s.badge)}>${s.badge.text}</span>${s.draft&&s.badge.text!=="draft"&&[" ",a`<span class="badge pr-draft">draft</span>`]}</h3>`,a`<div class="sub">${s.repo} \xB7 ${s.state} \xB7 observed ${v(s.observedAt)} ago${s.gate&&" \\xB7 gate "+s.gate}</div>`,a`<div class="sec">checks</div><div>${me(s)}</div><div class="sec">review</div><div>${o||a`<span class="dim">no review yet</span>`}</div><div class="sec">merge</div><div>${he(s)}</div>${s.repairWait&&a`<div class="sec">repair waits</div><div>${s.repairWait.heldBy}: ${s.repairWait.reason}${s.repairWait.until?" \\xB7 until "+s.repairWait.until:""}</div>`}<div class="sec">refs</div><div>${s.headRefName||"?"} \u2192 ${s.baseRefName||"?"}</div><div class="sec">stack</div><div>${r?Lr(s,r):a`<span class="dim">not stacked</span>`}</div><div class="sec">dispatch chain</div><div>${c}</div><div class="sec">watch</div>`,u]}var ws=(e,t)=>(e==="codex"?"codex resume ":"claude --resume ")+t;function Ur(e){let t=Date.now()-Date.parse(e.heartbeatAt)>18e5;return[be,a`<h3>\u26F5 ${e.man}</h3><div class="sub">helm of <b>${e.grounds}</b> (${(e.repos||[]).join(", ")})</div><div class="sub">${e.harness??"?"} \xB7 ${e.cwd??"?"}${e.host&&" \\xB7 "+e.host}</div><div class="sub">session ${e.sessionId??""} \xB7 signed on ${v(e.signedOnAt)} ago \xB7 heartbeat <span class=${t?"warn":"ok"}>${v(e.heartbeatAt)} ago</span></div>`,e.sessionId&&[a`<div class="sec">open this session</div>`,C(ws(e.harness,e.sessionId))],e.transcript&&[a`<div class="sec">transcript</div>`,C(e.transcript)]]}function jr(e){return[a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)} \xB7 <a href=${D(e.key)} target="_blank" rel="noopener">open the report \u2197</a></div>`]}function Br(e,t){let n=e.claimedBy&&e.claimedBy.startsWith("wt:")?[a`<div class="sec">worked by trap</div>`,a`<div class="claimedby">claimed by ${Ne(e.claimedBy.slice(3))}</div>`,C(j(e.claimedBy)),a`<div class="dim" style="font-size:11px">an opted-in interactive session mans this seat \u2014 attach would resume someone\'s live thread. Message it instead: lobstah send ${j(e.claimedBy)} "\u2026"</div>`]:[a`<div class="sec">open this session</div>`,C("lobstah attach "+e.id)];return[be,a`<h3>${e.id.slice(0,8)} <span class=${"badge v-"+e.verb}>${e.verb}</span></h3>`,a`<div class="sub">${e.repo} \xB7 ${e.lane} ${e.bucket} \xB7 ${v(e.verbAt)}${e.for&&[" \\xB7 ",fe(e)]} ${re(e)}</div>`,n,e.worktree&&[a`<div class="sec">worktree${e.worktreeOf?" (reused from "+e.worktreeOf.slice(0,8)+")":""}</div>`,C(e.worktree),e.worktreeKept&&a`<div class="dim" style="font-size:11px">kept: ${e.worktreeKept}</div>`],e.transcript&&[a`<div class="sec">transcript</div>`,C(e.transcript)],t&&jr(t),Qn(e)]}function Or(e){let t=ge(e),n=e.requested?[e.repo??""," \\xB7 ",e.harness??""," \\xB7 requested ",v(e.requested.at)," ago from the glass; the helm reserves it and starts its session."]:e.starting?e.starting.failedAt?[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 start failed: ",e.starting.reason??"",". Its addressed work stays queued; a session can still redeem the ticket, or `lobstah stow --wt ",e.name??e.trapId,"` withdraws it."]:[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 starting: waiting for a session to redeem the ticket (due by ",e.starting.deadline,")"]:e.live?[e.repo??"addressed bait only"," \\xB7 session ",e.sessionId??""," \\xB7 signed on ",v(e.signedOnAt)," ago \\xB7 ",t.listen," \\xB7 heartbeat ",t.hb]:"signed off \\u2014 registration gone; the lifecycle, messages, and catches are the surviving record. Re-soaking the same worktree restores this address.",s=e.notices.length?e.notices.map(o=>a`<div key=${o.seq} class="loglines">${v(o.at)} ago \xB7 <b>${o.kind}</b> \u2014 ${o.text}</div>`):a`<div class="empty">none recorded</div>`,r=e.messages.length?e.messages.map(o=>a`<div key=${o.file} class=${"msg"+(o.from==="helm"?" from-helm":"")}><div class="hdr">from ${o.from} \xB7 ${o.at&&[v(o.at)," ago"]} \xB7 ${o.state==="pending"?a`<span class="warn">pending</span>`:a`<span class="ok">delivered</span>`}</div>${o.text}${o.attachments?.length?se(o.attachments,c=>sn(e.trapId,c)):""}</div>`):a`<div class="empty">none</div>`,i=e.catches.length?e.catches.map(o=>a`<div key=${o.lane+":"+o.id} class="catch"><div class="hdr"><b>${o.id.slice(0,8)}</b><span class=${"badge v-"+o.verb}>${o.verb}</span><span class="dim">${v(o.verbAt)}</span>${re(o)}</div><div class="loglines">${_t(o)}</div></div>`):a`<div class="empty">none yet</div>`;return[be,a`<h3>\u{1FAA4} ${e.label??`wt:${e.trapId}`} <span class="badge">${e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??"signed off"}</span></h3>`,e.worktree&&a`<div class="sub">${e.worktree}</div>`,a`<div class="sub">${n}</div>`,a`<div class="sec">window</div>`,z(e),J(e),e.live&&e.sessionId&&[a`<div class="sec">open this session</div>`,C(ws(e.harness,e.sessionId)),a`<div class="dim" style="font-size:11px">as registered at sign-on \u2014 a hookless enlistment may hold a made-up id</div>`],a`<div class="sec">lifecycle (${e.notices.length})</div>`,s,a`<div class="sec">messages (${e.messages.length})</div>`,r,a`<div class="sec">catches (${e.catches.length})</div>`,i]}function Gs({snapshot:e,modal:t,prefs:n}){if(!e||!t)return null;let s=Y(e,t);if(!s)return null;if(t.type==="settings")return Er(s,n);if(t.type==="pr")return Hr(e,t.key);if(t.type==="helm")return Ur(s);if(t.type==="dispatch"){let r=an(e,s);return Br(s,r)}return Or(s)}var qr=e=>a`<tr key=${e.seq}><td class="dim">${v(e.at)}</td><td>${e.kind}</td><td class="grow">${e.text}</td><td class="dim">${e.repo??""}</td></tr>`,Ts=({inp:e})=>R(["at","kind","text","repo"],e.list.map(qr),"no notices");function Ss(e){let t=new Map;for(let n of e.prs){let s=t.get(n.stackId)||[];s.push(n),t.set(n.stackId,s)}return e.stacks.map(n=>({s:n,prs:t.get(n.id)||[]})).filter(n=>n.prs.length)}var xs=(e,t)=>oe(e.numbers,new Map(t.map(n=>[n.number,n.title]))),Rt=e=>e.state+(e.draft?" \\xB7 draft":"")+(e.state==="MERGED"&&e.mergedAt?" \\xB7 "+e.mergedAt:"");function Cs(e){let t=s=>{let r=String(s??"");return r.length>24?r.slice(0,23)+"\\u2026":r},n=e.watches.map(s=>a`<tr key=${s.key}><td>${s.key}</td><td>${s.owner}</td><td title=${s.cursor}>${t(s.cursor)}</td><td>${s.lastCheckedAt&&v(s.lastCheckedAt)}</td><td>${s.lastError||""}</td></tr>`);return[a`<h2>other watches</h2>`,R(["key","owner","cursor","last check","error"],n,"no other watches")]}function Fr(e){let t=Ss(e).flatMap(({s:n,prs:s})=>[a`<tr key=${"stack:"+n.id}><th colspan="8">${xs(n,s)} \xB7 floor ${n.floor}${n.open?" \\xB7 open":" \\xB7 history"}</th></tr>`,...s.map(r=>a`<tr key=${r.key} class="rowhead" onClick=${w("pr",r.key)}><td>${He(r)}</td><td class="grow">${r.title||""}</td><td>${Rt(r)}</td><td>${me(r)}</td><td>${je(r)}</td><td>${he(r)}</td><td>${bt(r.watch)}</td><td>${r.gate||""}</td></tr>`)]);return[R(["PR","title","state","checks","review","merge","watch","gate"],t,"no PR evidence"),Cs(e)]}function Wr(e){let t=Ss(e),n=r=>a`<div key=${r.key} class="card" onClick=${w("pr",r.key)}><div class="top">${Ue(r)}<span class=${"badge "+L(r.badge)+U(r.badge.text)} title=${H(r.badge.text)}>${r.badge.text}</span></div><div class="meta" title=${`${r.repo} \\xB7 ${Rt(r)}`}>${r.repo} \xB7 ${Rt(r)} \xB7 ${he(r)}</div><div class="foot"><span>${me(r)}</span><span>${bt(r.watch)}</span>${r.gate&&a`<span>gate ${r.gate}</span>`}</div></div>`;return[t.length?t.map(({s:r,prs:i})=>a`<${Z} key=${"stack:"+r.id}><h2>${xs(r,i)} \xB7 floor ${r.floor}${r.open?"":" \\xB7 history"}</h2><div class="cards">${i.map(n)}</div><//>`):a`<div class="empty">no PR evidence</div>`,Cs(e)]}var Ds=({inp:e})=>e.view==="cards"?Wr(e):Fr(e);var Pt=e=>e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??(e.live?"":"signed off"),As=e=>e.live||e.starting?"":" dim";function Vr(e){let t=ge(e);return a`<tr key=${e.trapId} class=${"rowhead"+As(e)} onClick=${w("trap",e.trapId)}><td><b>${e.label??`wt:${e.trapId}`}</b></td><td>${e.repo??"\\u2014"}</td><td class="grow">${e.worktree??""}</td><td>${e.harness??""}</td><td>${(e.sessionId??"").slice(0,8)}</td><td>${ve(e)}${J(e)}</td><td>${t.listen}</td><td>${t.hb}</td><td>${yt(e)}</td><td>${e.catches.length}</td><td>${z(e)}</td></tr>`}function Kr(e){let t=ge(e),n=yt(e),s=`${e.repo??(e.live?"addressed bait only":"history")}${e.sessionId?" \\xB7 session "+e.sessionId.slice(0,8):""}`;return a`<div key=${e.trapId} class=${"card"+As(e)} onClick=${w("trap",e.trapId)}><div class="top"><b>\u{1FAA4} ${e.label??`wt:${e.trapId}`}</b><span class=${"badge"+U(Pt(e))} title=${H(Pt(e))}>${Pt(e)}</span></div><div class="meta" title=${s}>${s}</div>${e.worktree&&a`<div class="note" title=${e.worktree}>${e.worktree}</div>`}<div class="note" title=${Oe(e)}>${ve(e)}</div>${J(e)}<div class="foot"><span>${t.listen}${e.live&&[" \\xB7 heartbeat ",t.hb]} \xB7 ${e.catches.length} catch${e.catches.length===1?"":"es"}${n&&[" \\xB7 ",n]}</span><span class="footact">${z(e)}</span></div></div>`}function Rs({inp:e}){let t=e.list.map(s=>s.x),n=a`<div class="tabhead"><${Be} /></div>`;return e.view==="cards"?[n,t.length?a`<div class="cards">${t.map(Kr)}</div>`:a`<div class="empty">no traps soaking</div>`]:[n,R(["address","repo","worktree","harness","session","now","listening","heartbeat","mail","catches","window"],t.map(Vr),"no traps soaking")]}function Ps({state:e}){let t=e.snapshot,n=e.route,s=t&&pn(t,{st:e.prefs,modal:e.modal},Date.now()),r=o=>!s||o!==n?null:o==="deck"?a`<${hs} inp=${s.deck} drafts=${e.drafts} focus=${e.focusDecision} extensions=${t?.answerLimits?.extensions??[]} />`:o==="dispatches"?a`<${gs} inp=${s.dispatches} />`:o==="traps"?a`<${Rs} inp=${s.traps} />`:o==="prs"?a`<${Ds} inp=${s.prs} />`:o==="reports"?a`<${ns} inp=${s.reports} />`:a`<${Ts} inp=${s.notices} />`,i=o=>{o.target===o.currentTarget&&te()};return[a`<${vs} d=${t} inp=${s} tab=${n} st=${e.prefs} stale=${e.stale} />`,ce.map(o=>a`<main key=${o} id=${"page-"+o} class=${t&&o===n?"tabpage on":"tabpage"}><div id=${o}>${r(o)}</div></main>`),a`<footer id="foot"><${_s} d=${t} /></footer>`,a`<div id="lobs"><${ks} state=${e} /></div>`,a`<${Ke} box=${e.lightbox} />`,a`<div id="overlay" class=${e.modal&&t?"open":t?"":void 0} onClick=${i}><div class="modal" id="modalbox"><${Gs} snapshot=${t} modal=${e.modal} prefs=${e.prefs} /></div></div>`]}function zr({view:e}){if(e.state==="loading")return a`<main class="reportview"><div class="dim">loading\u2026</div></main>`;if(e.state==="error")return a`<main class="reportview"><div class="bad">${e.error}</div></main>`;let t=e.report;return a`<main class="reportview">\n <h1>${t.title}</h1>\n <${us} r=${t} text=${e.text!==void 0?{text:e.text}:{error:e.error}} />\n </main>`}function Is({view:e,box:t}){return[a`<${zr} view=${e} />`,a`<${Ke} box=${t} />`]}async function Ms(e){let t=encodeURIComponent(e),n;try{let s=await fetch(`/report/${t}/meta`);if(!s.ok)return{state:"error",error:`report not found (${s.status})`};n=JSON.parse(await s.text())}catch{return{state:"error",error:"report could not be read"}}try{let s=await fetch(dn(e));return s.ok?{state:"ready",report:n,text:await s.text()}:{state:"ready",report:n,error:`report not found (${s.status})`}}catch{return{state:"ready",report:n,error:"report could not be read"}}}var It=!1,ke=null;async function Mt(){if(!It){It=!0;try{let e=await fetch("/data");if(!e.ok)throw new Error(`/data answered ${e.status}`);ut(await e.json())}catch{Gn()}finally{It=!1}}}var Es=()=>{ke||(ke=setInterval(Mt,2e3))},Jr=()=>{ke&&clearInterval(ke),ke=null};function Ls(){document.addEventListener("visibilitychange",()=>{document.hidden?Jr():(Mt(),Es())}),Mt(),document.hidden||Es()}var Et=()=>nn(location.hash),Ns=e=>window.addEventListener("hashchange",()=>e(Et()));function Us(){let e=ln(location.hash);return e===null?!1:(location.replace(D(e)),!0)}var js=()=>({snapshot:void 0,route:Et(),prefs:mn(),modal:null,stale:!1,lobHidden:gn(),spriteOk:null,focusResults:{},preview:new URLSearchParams(location.search).has("lob"),drafts:{},focusDecision:ot(location.hash),lightbox:null}),Hs=cn(location.pathname);Hs!==null?Xr(Hs):Us()||Qr();function Xr(e){dt(js());let t={state:"loading"},n=()=>nt(Q(Is,{view:t,box:_().lightbox}),document.body);document.title="report \\xB7 lobstah glass",pt(n),n(),Ms(e).then(s=>{t=s,s.state==="ready"&&(document.title=`${s.report.title} \\xB7 lobstah glass`),n()}),document.addEventListener("keydown",s=>{s.key==="Escape"&&_().lightbox&&ee()})}function Qr(){dt(js());let e,t=!1,n=()=>{let o=_(),c=o.route!==e||!t&&!!o.snapshot;nt(Q(Ps,{state:o}),document.body),e=o.route,t||(t=!!o.snapshot),c&&window.scrollTo(0,0),o.focusDecision||(s=null),r(o.focusDecision)},s=null;function r(o){if(!o||o===s)return;let c=[...document.querySelectorAll("[data-decision]")].find(u=>u.getAttribute("data-decision")===o);c&&(s=o,c.scrollIntoView?.({block:"center"}))}pt(n),n();let i=new Image;i.onload=()=>S({spriteOk:!0}),i.onerror=()=>S({spriteOk:!1}),i.src="/lob-sprite.png",Ns(o=>{if(Us())return;let c=ot(location.hash);c!==_().focusDecision&&(s=null),S({route:o,focusDecision:c})}),document.addEventListener("keydown",o=>{o.key==="Escape"&&(_().lightbox?ee():te())}),Ls()}})();</script>\n </body>\n</html>\n';
|
|
13505
13653
|
|
|
13506
13654
|
// apps/cli/src/glass-snapshot-thread.ts
|
|
13507
13655
|
init_dist2();
|
|
@@ -13548,8 +13696,8 @@ function startSnapshotThread() {
|
|
|
13548
13696
|
const pending = inFlight.get(local);
|
|
13549
13697
|
if (pending) return pending;
|
|
13550
13698
|
const id = ++next;
|
|
13551
|
-
const promise = new Promise((
|
|
13552
|
-
waiting.set(id, { resolve:
|
|
13699
|
+
const promise = new Promise((resolve14, reject) => {
|
|
13700
|
+
waiting.set(id, { resolve: resolve14, reject });
|
|
13553
13701
|
worker.postMessage({ id, local });
|
|
13554
13702
|
}).finally(() => inFlight.delete(local));
|
|
13555
13703
|
inFlight.set(local, promise);
|
|
@@ -14588,9 +14736,11 @@ function observePr(ref, view, opts = {}) {
|
|
|
14588
14736
|
const pr = prEvidence(ref, view, now2.toISOString());
|
|
14589
14737
|
const id = opts.dispatchId;
|
|
14590
14738
|
const lane = id ? laneOf4(id) : void 0;
|
|
14591
|
-
const
|
|
14739
|
+
const ev = id && lane ? readEvidence(id, lane) : void 0;
|
|
14740
|
+
const legacyBefore = ev?.pr && parsePrRef(ev.pr.url)?.key === ref.key ? ev.pr : void 0;
|
|
14592
14741
|
const { before, after } = upsertPr(pr, lane ? id : void 0);
|
|
14593
|
-
|
|
14742
|
+
const other = ev?.prUrls?.some((u) => parsePrRef(u)?.key === ref.key) && parsePrRef(ev.prUrl ?? "")?.key !== ref.key;
|
|
14743
|
+
if (id && lane && !other) mergeEvidence(id, lane, { pr });
|
|
14594
14744
|
const was = before?.state ?? legacyBefore?.state;
|
|
14595
14745
|
const terminal3 = pr.state === "MERGED" || pr.state === "CLOSED";
|
|
14596
14746
|
if (terminal3) cancelQueuedRepairs(ref.key);
|
|
@@ -14682,26 +14832,81 @@ function cutTitle(title, max = 60) {
|
|
|
14682
14832
|
// apps/cli/src/pr-waits.ts
|
|
14683
14833
|
init_dist2();
|
|
14684
14834
|
var PR_WAITS = /* @__PURE__ */ new Set(["pr", "review"]);
|
|
14685
|
-
function
|
|
14835
|
+
function waitedPrs(id, lane, entry) {
|
|
14686
14836
|
const link = entry.link ? parsePrRef(entry.link) : void 0;
|
|
14687
|
-
|
|
14837
|
+
const own = ownPrs(id, lane);
|
|
14838
|
+
if (link && !own.some((r) => r.key === link.key)) return [link];
|
|
14839
|
+
return own;
|
|
14688
14840
|
}
|
|
14689
|
-
function
|
|
14841
|
+
function ownPrs(id, lane) {
|
|
14690
14842
|
const evidence = readEvidence(id, lane);
|
|
14691
|
-
|
|
14692
|
-
|
|
14843
|
+
let urls = dispatchPrUrls(evidence);
|
|
14844
|
+
if (urls.length === 0) {
|
|
14845
|
+
const chain = chainPr(id, lane)?.url;
|
|
14846
|
+
urls = chain ? [chain] : listWatches().filter((w) => w.key.startsWith("pr:") && w.owner === `dispatch:${id}`).map((w) => w.key);
|
|
14847
|
+
}
|
|
14848
|
+
return urls.map((u) => parsePrRef(u)).filter((r) => !!r);
|
|
14849
|
+
}
|
|
14850
|
+
function waitWarning(id, lane, entry) {
|
|
14851
|
+
if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return void 0;
|
|
14852
|
+
if (waitedPrs(id, lane, entry).length > 0) return void 0;
|
|
14853
|
+
return `no PR known for this wait: --link names no GitHub PR and the dispatch has no PR. A merge or close will not finish it. Report again with --link <PR url>, or report --pr <url> first.`;
|
|
14693
14854
|
}
|
|
14694
|
-
function
|
|
14855
|
+
function registerWaitWatches(id, lane, entry) {
|
|
14695
14856
|
try {
|
|
14696
|
-
if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return
|
|
14697
|
-
const own =
|
|
14857
|
+
if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return [];
|
|
14858
|
+
const own = ownPrs(id, lane);
|
|
14698
14859
|
const link = entry.link ? parsePrRef(entry.link) : void 0;
|
|
14699
|
-
if (
|
|
14700
|
-
return autoRegisterPrWatch(id,
|
|
14860
|
+
if (own.length === 0 || link && !own.some((r) => r.key === link.key)) return [];
|
|
14861
|
+
return own.map((r) => autoRegisterPrWatch(id, r.url)).filter((w) => !!w);
|
|
14701
14862
|
} catch {
|
|
14702
|
-
return
|
|
14863
|
+
return [];
|
|
14703
14864
|
}
|
|
14704
14865
|
}
|
|
14866
|
+
function observeWaitedPrs(opts = {}) {
|
|
14867
|
+
const now2 = opts.now ?? Date.now();
|
|
14868
|
+
const everyMs = (opts.everySecs ?? 45) * 1e3;
|
|
14869
|
+
const view = opts.view ?? ghPrView;
|
|
14870
|
+
const observed = [];
|
|
14871
|
+
let daemonWatched;
|
|
14872
|
+
try {
|
|
14873
|
+
daemonWatched = new Set(
|
|
14874
|
+
listWatches().filter((w) => w.key.startsWith("pr:") && w.owner.startsWith("dispatch:") && !w.done && laneOf(w.owner.slice("dispatch:".length))).map((w) => w.key)
|
|
14875
|
+
);
|
|
14876
|
+
} catch {
|
|
14877
|
+
return observed;
|
|
14878
|
+
}
|
|
14879
|
+
const done = /* @__PURE__ */ new Set();
|
|
14880
|
+
for (const lane of ["work", "chore"]) {
|
|
14881
|
+
let ids;
|
|
14882
|
+
try {
|
|
14883
|
+
ids = activeIds(lane);
|
|
14884
|
+
} catch {
|
|
14885
|
+
continue;
|
|
14886
|
+
}
|
|
14887
|
+
for (const id of ids) {
|
|
14888
|
+
try {
|
|
14889
|
+
const last = readStatusLog(id, lane).at(-1);
|
|
14890
|
+
if (last?.verb !== "paused" || !last.waitingOn || !PR_WAITS.has(last.waitingOn)) continue;
|
|
14891
|
+
const own = new Set(ownPrs(id, lane).map((r) => r.key));
|
|
14892
|
+
for (const ref of waitedPrs(id, lane, last)) {
|
|
14893
|
+
if (done.has(ref.key) || daemonWatched.has(ref.key)) continue;
|
|
14894
|
+
done.add(ref.key);
|
|
14895
|
+
const record = readPr(ref.key);
|
|
14896
|
+
if (record && (record.state === "MERGED" || record.state === "CLOSED")) continue;
|
|
14897
|
+
if (record && now2 - Date.parse(record.observedAt) < everyMs) continue;
|
|
14898
|
+
try {
|
|
14899
|
+
observePr(ref, view(ref), { ...own.has(ref.key) ? { dispatchId: id } : {}, now: new Date(now2) });
|
|
14900
|
+
observed.push(ref.key);
|
|
14901
|
+
} catch {
|
|
14902
|
+
}
|
|
14903
|
+
}
|
|
14904
|
+
} catch {
|
|
14905
|
+
}
|
|
14906
|
+
}
|
|
14907
|
+
}
|
|
14908
|
+
return observed;
|
|
14909
|
+
}
|
|
14705
14910
|
function finishResolvedWaits(log = () => {
|
|
14706
14911
|
}) {
|
|
14707
14912
|
const out = [];
|
|
@@ -14715,27 +14920,114 @@ function finishResolvedWaits(log = () => {
|
|
|
14715
14920
|
for (const id of ids) {
|
|
14716
14921
|
const last = readStatusLog(id, lane).at(-1);
|
|
14717
14922
|
if (last?.verb !== "paused" || !last.waitingOn || !PR_WAITS.has(last.waitingOn)) continue;
|
|
14718
|
-
const
|
|
14719
|
-
const
|
|
14720
|
-
if (
|
|
14721
|
-
const merged =
|
|
14722
|
-
const
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14923
|
+
const refs = waitedPrs(id, lane, last);
|
|
14924
|
+
const states = refs.map((r) => readPr(r.key)?.state);
|
|
14925
|
+
if (refs.length === 0 || !states.every((st) => st === "MERGED" || st === "CLOSED")) continue;
|
|
14926
|
+
const merged = refs.filter((_, i) => states[i] === "MERGED");
|
|
14927
|
+
const closed = refs.filter((_, i) => states[i] === "CLOSED");
|
|
14928
|
+
const verb = merged.length > 0 ? "done" : "failed";
|
|
14929
|
+
const urls = (rs) => rs.map((r) => r.url).join(", ");
|
|
14930
|
+
const note = refs.length === 1 ? merged.length ? `the PR merged: ${refs[0].url}` : `the PR closed without merge: ${refs[0].url}` : closed.length === 0 ? `the PRs merged: ${urls(merged)}` : merged.length === 0 ? `the PRs closed without merge: ${urls(closed)}` : `the PRs ended \u2014 merged: ${urls(merged)}; closed without merge: ${urls(closed)}`;
|
|
14931
|
+
appendStatus(id, lane, verb, note);
|
|
14932
|
+
if (!readEvidence(id, lane).prUrl) mergeEvidence(id, lane, { prUrl: refs[0].url });
|
|
14933
|
+
const keys = refs.map((r) => r.key).join(", ");
|
|
14934
|
+
out.push({ id, lane, pr: keys, verb });
|
|
14935
|
+
log(`${id}: waited on ${keys}, which ${verb === "done" ? "merged" : "closed without merge"} \u2014 ${verb}`);
|
|
14727
14936
|
}
|
|
14728
14937
|
}
|
|
14729
14938
|
return out;
|
|
14730
14939
|
}
|
|
14731
14940
|
|
|
14941
|
+
// apps/cli/src/pr-stack.ts
|
|
14942
|
+
init_dist2();
|
|
14943
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
14944
|
+
import * as fs65 from "node:fs";
|
|
14945
|
+
import * as path53 from "node:path";
|
|
14946
|
+
var defaultRun = (cmd, args, cwd) => {
|
|
14947
|
+
const res = spawnSync11(cmd, args, { cwd, encoding: "utf8", timeout: cmd === "gh" ? 1e4 : 3e3, windowsHide: true });
|
|
14948
|
+
return { status: res.error ? null : res.status, stdout: res.stdout ?? "" };
|
|
14949
|
+
};
|
|
14950
|
+
var MAX_CHAIN_BRANCHES = 12;
|
|
14951
|
+
function ghStackPrs(cwd, keys, run4) {
|
|
14952
|
+
for (const which of ["--git-dir", "--git-common-dir"]) {
|
|
14953
|
+
const res = run4("git", ["rev-parse", which], cwd);
|
|
14954
|
+
if (res.status !== 0 || !res.stdout.trim()) continue;
|
|
14955
|
+
let state;
|
|
14956
|
+
try {
|
|
14957
|
+
state = JSON.parse(fs65.readFileSync(path53.join(path53.resolve(cwd, res.stdout.trim()), "gh-stack"), "utf8"));
|
|
14958
|
+
} catch {
|
|
14959
|
+
continue;
|
|
14960
|
+
}
|
|
14961
|
+
for (const stack of state.stacks ?? []) {
|
|
14962
|
+
const refs = (stack.branches ?? []).map((b) => b.pullRequest?.url ? parsePrRef(b.pullRequest.url) : void 0).filter((r) => !!r);
|
|
14963
|
+
if (refs.some((r) => keys.has(r.key))) return refs;
|
|
14964
|
+
}
|
|
14965
|
+
}
|
|
14966
|
+
return [];
|
|
14967
|
+
}
|
|
14968
|
+
function baseChainPrs(reported, branches, cwd, run4) {
|
|
14969
|
+
if (branches.length < 2 || reported.length === 0) return [];
|
|
14970
|
+
const repo = `${reported[0].owner}/${reported[0].repo}`;
|
|
14971
|
+
const byHead = /* @__PURE__ */ new Map();
|
|
14972
|
+
for (const branch of branches.slice(0, MAX_CHAIN_BRANCHES)) {
|
|
14973
|
+
const res = run4("gh", ["pr", "view", branch, "--repo", repo, "--json", "url,headRefName,baseRefName"], cwd);
|
|
14974
|
+
if (res.status !== 0) continue;
|
|
14975
|
+
try {
|
|
14976
|
+
const view = JSON.parse(res.stdout);
|
|
14977
|
+
const ref = view.url ? parsePrRef(view.url) : void 0;
|
|
14978
|
+
if (ref && view.headRefName === branch && view.baseRefName) byHead.set(branch, { ref, head: branch, base: view.baseRefName });
|
|
14979
|
+
} catch {
|
|
14980
|
+
}
|
|
14981
|
+
}
|
|
14982
|
+
const keys = new Set(reported.map((r) => r.key));
|
|
14983
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14984
|
+
const todo = [...byHead.values()].filter((n) => keys.has(n.ref.key));
|
|
14985
|
+
const out = [];
|
|
14986
|
+
while (todo.length > 0) {
|
|
14987
|
+
const n = todo.pop();
|
|
14988
|
+
if (seen.has(n.head)) continue;
|
|
14989
|
+
seen.add(n.head);
|
|
14990
|
+
out.push(n);
|
|
14991
|
+
const below = byHead.get(n.base);
|
|
14992
|
+
if (below) todo.push(below);
|
|
14993
|
+
for (const above of byHead.values()) if (above.base === n.head) todo.push(above);
|
|
14994
|
+
}
|
|
14995
|
+
const heads = new Set(out.map((n) => n.head));
|
|
14996
|
+
const ordered = [];
|
|
14997
|
+
let next = out.find((n) => !heads.has(n.base));
|
|
14998
|
+
while (next && !ordered.includes(next)) {
|
|
14999
|
+
ordered.push(next);
|
|
15000
|
+
const head = next.head;
|
|
15001
|
+
next = out.find((n) => n.base === head);
|
|
15002
|
+
}
|
|
15003
|
+
for (const n of out) if (!ordered.includes(n)) ordered.push(n);
|
|
15004
|
+
return ordered.map((n) => n.ref);
|
|
15005
|
+
}
|
|
15006
|
+
function stackPrUrls(reported, opts) {
|
|
15007
|
+
const run4 = opts.run ?? defaultRun;
|
|
15008
|
+
const refs = reported.map((u) => parsePrRef(u)).filter((r) => !!r);
|
|
15009
|
+
const out = /* @__PURE__ */ new Map();
|
|
15010
|
+
const add = (r) => {
|
|
15011
|
+
if (!out.has(r.key)) out.set(r.key, r.url);
|
|
15012
|
+
};
|
|
15013
|
+
refs.forEach(add);
|
|
15014
|
+
if (refs.length === 0) return [];
|
|
15015
|
+
try {
|
|
15016
|
+
const keys = new Set(refs.map((r) => r.key));
|
|
15017
|
+
ghStackPrs(opts.cwd, keys, run4).forEach(add);
|
|
15018
|
+
baseChainPrs(refs, opts.branches ?? [], opts.cwd, run4).forEach(add);
|
|
15019
|
+
} catch {
|
|
15020
|
+
}
|
|
15021
|
+
return [...out.values()];
|
|
15022
|
+
}
|
|
15023
|
+
|
|
14732
15024
|
// apps/cli/src/soak-worktree.ts
|
|
14733
15025
|
init_dist2();
|
|
14734
15026
|
init_dist3();
|
|
14735
|
-
import * as
|
|
14736
|
-
import { spawnSync as
|
|
15027
|
+
import * as fs66 from "node:fs";
|
|
15028
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
14737
15029
|
function branchExists(repo, branch) {
|
|
14738
|
-
return
|
|
15030
|
+
return spawnSync12("git", ["-C", repo.path, "rev-parse", "--verify", "-q", `refs/heads/${branch}`], { stdio: "ignore" }).status === 0;
|
|
14739
15031
|
}
|
|
14740
15032
|
async function createSoakWorktree(opts) {
|
|
14741
15033
|
const need = opts.minFreeGB * GB;
|
|
@@ -14752,7 +15044,7 @@ async function createSoakWorktree(opts) {
|
|
|
14752
15044
|
);
|
|
14753
15045
|
}
|
|
14754
15046
|
}
|
|
14755
|
-
const taken = (id2) => branchExists(opts.repo, `lobstah/soak-${id2}`) ||
|
|
15047
|
+
const taken = (id2) => branchExists(opts.repo, `lobstah/soak-${id2}`) || fs66.existsSync(worktreePath(`soak-${id2}`));
|
|
14756
15048
|
let trapId = opts.trapId ?? newTrapId();
|
|
14757
15049
|
if (opts.trapId !== void 0 && taken(trapId)) {
|
|
14758
15050
|
throw new Error(`could not create a worktree for repo ${opts.repoKey}: soak-${trapId} or its branch already exists`);
|
|
@@ -14839,14 +15131,14 @@ async function setTerminalTitle(win, title, options = {}) {
|
|
|
14839
15131
|
|
|
14840
15132
|
// apps/cli/src/beat.ts
|
|
14841
15133
|
init_dist2();
|
|
14842
|
-
import * as
|
|
14843
|
-
import * as
|
|
15134
|
+
import * as fs67 from "node:fs";
|
|
15135
|
+
import * as path54 from "node:path";
|
|
14844
15136
|
|
|
14845
15137
|
// apps/cli/src/beat-pr.ts
|
|
14846
15138
|
init_dist2();
|
|
14847
|
-
import { spawnSync as
|
|
14848
|
-
var
|
|
14849
|
-
const res =
|
|
15139
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
15140
|
+
var defaultRun2 = (cmd, args, cwd) => {
|
|
15141
|
+
const res = spawnSync13(cmd, args, { cwd, encoding: "utf8", timeout: cmd === "gh" ? 1e4 : 3e3, windowsHide: true });
|
|
14850
15142
|
return { status: res.error ? null : res.status, stdout: res.stdout ?? "" };
|
|
14851
15143
|
};
|
|
14852
15144
|
var PR_PROBE_INTERVAL_MS = 6e4;
|
|
@@ -14854,7 +15146,7 @@ function probeTrapPr(reg, opts = {}) {
|
|
|
14854
15146
|
if (!hasOpenCatch(reg)) return { recorded: false, reason: "no-catch" };
|
|
14855
15147
|
const id = reg.claimed;
|
|
14856
15148
|
const now2 = opts.now ?? Date.now();
|
|
14857
|
-
const run4 = opts.run ??
|
|
15149
|
+
const run4 = opts.run ?? defaultRun2;
|
|
14858
15150
|
const last = readTrapPrProbe(reg.trapId);
|
|
14859
15151
|
const sameDispatch = last?.dispatch === id;
|
|
14860
15152
|
if (sameDispatch && now2 - (Date.parse(last.checkedAt) || 0) < PR_PROBE_INTERVAL_MS) return { recorded: false, reason: "throttled" };
|
|
@@ -14871,6 +15163,12 @@ function probeTrapPr(reg, opts = {}) {
|
|
|
14871
15163
|
const upstream = git10(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
|
14872
15164
|
const tracked = upstream.status === 0 ? upstream.stdout.trim() : "";
|
|
14873
15165
|
if (!tracked || tracked.endsWith(`/${trunk}`)) return { recorded: false, reason: "no-upstream" };
|
|
15166
|
+
const claimedAt = Date.parse(readSessionClaim(id, "work")?.at ?? "");
|
|
15167
|
+
if (Number.isFinite(claimedAt)) {
|
|
15168
|
+
const committed = git10(["log", "-1", "--format=%ct", "HEAD"]);
|
|
15169
|
+
const at = committed.status === 0 ? Number(committed.stdout.trim()) * 1e3 : NaN;
|
|
15170
|
+
if (!(at >= claimedAt)) return { recorded: false, reason: "before-claim" };
|
|
15171
|
+
}
|
|
14874
15172
|
const view = run4("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], reg.worktree);
|
|
14875
15173
|
const prUrl = view.status === 0 ? view.stdout.trim() : "";
|
|
14876
15174
|
if (!parsePrRef(prUrl)) return { recorded: false, reason: "no-pr" };
|
|
@@ -14884,10 +15182,10 @@ function probeTrapPr(reg, opts = {}) {
|
|
|
14884
15182
|
// apps/cli/src/beat.ts
|
|
14885
15183
|
function logError(err) {
|
|
14886
15184
|
try {
|
|
14887
|
-
const dir =
|
|
14888
|
-
|
|
15185
|
+
const dir = path54.join(lobstahHome(), "logs");
|
|
15186
|
+
fs67.mkdirSync(dir, { recursive: true });
|
|
14889
15187
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14890
|
-
|
|
15188
|
+
fs67.appendFileSync(path54.join(dir, "beat.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg.replace(/\s+/g, " ").slice(0, 300)}
|
|
14891
15189
|
`);
|
|
14892
15190
|
} catch {
|
|
14893
15191
|
}
|
|
@@ -14984,7 +15282,7 @@ var COMMANDS = {
|
|
|
14984
15282
|
cancel: { flags: { "--session": { value: "<id>" } }, positionals: "<uuid>" },
|
|
14985
15283
|
report: {
|
|
14986
15284
|
flags: {
|
|
14987
|
-
"--pr": { value: "<url>" },
|
|
15285
|
+
"--pr": { value: "<url>", repeatable: true },
|
|
14988
15286
|
"--no-watch": {},
|
|
14989
15287
|
"--waiting-on": { value: "review|pr|deploy|person|external" },
|
|
14990
15288
|
"--link": { value: "<url>" },
|
|
@@ -15335,15 +15633,15 @@ function detectHarness(opts) {
|
|
|
15335
15633
|
|
|
15336
15634
|
// apps/cli/src/watchers.ts
|
|
15337
15635
|
init_dist2();
|
|
15338
|
-
import * as
|
|
15339
|
-
import * as
|
|
15636
|
+
import * as fs68 from "node:fs";
|
|
15637
|
+
import * as path55 from "node:path";
|
|
15340
15638
|
var STALE_MS = 5e3;
|
|
15341
15639
|
var HEARTBEAT_MS = 1500;
|
|
15342
|
-
var watcherDir = () =>
|
|
15343
|
-
var watcherFile = (sessionId) =>
|
|
15640
|
+
var watcherDir = () => path55.join(lobstahHome(), "watchers");
|
|
15641
|
+
var watcherFile = (sessionId) => path55.join(watcherDir(), `${encodeURIComponent(sessionId)}.json`);
|
|
15344
15642
|
function liveRegistration(sessionId, now2 = Date.now()) {
|
|
15345
15643
|
try {
|
|
15346
|
-
const w = JSON.parse(
|
|
15644
|
+
const w = JSON.parse(fs68.readFileSync(watcherFile(sessionId), "utf8"));
|
|
15347
15645
|
const beat = Date.parse(w.heartbeatAt);
|
|
15348
15646
|
if (w.sessionId !== sessionId || !Number.isInteger(w.pid) || w.pid <= 0 || !Number.isFinite(beat) || now2 - beat > STALE_MS) return void 0;
|
|
15349
15647
|
return w;
|
|
@@ -15360,17 +15658,17 @@ async function awaitWatcher(sessionId, kind, graceMs, trapId, pollMs = 100) {
|
|
|
15360
15658
|
for (; ; ) {
|
|
15361
15659
|
const w = liveWatcher(sessionId, kind, trapId);
|
|
15362
15660
|
if (w || Date.now() >= deadline) return w;
|
|
15363
|
-
await new Promise((
|
|
15661
|
+
await new Promise((resolve14) => setTimeout(resolve14, Math.min(pollMs, Math.max(1, deadline - Date.now()))));
|
|
15364
15662
|
}
|
|
15365
15663
|
}
|
|
15366
15664
|
function armWatcher(sessionId, kind, trapId) {
|
|
15367
|
-
|
|
15665
|
+
fs68.mkdirSync(watcherDir(), { recursive: true });
|
|
15368
15666
|
const file = watcherFile(sessionId);
|
|
15369
15667
|
const own = () => ({ sessionId, kind, pid: process.pid, heartbeatAt: (/* @__PURE__ */ new Date()).toISOString(), ...trapId ? { trapId } : {} });
|
|
15370
15668
|
let claimed = false;
|
|
15371
15669
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
15372
15670
|
try {
|
|
15373
|
-
|
|
15671
|
+
fs68.writeFileSync(file, `${JSON.stringify(own())}
|
|
15374
15672
|
`, { flag: "wx" });
|
|
15375
15673
|
claimed = true;
|
|
15376
15674
|
break;
|
|
@@ -15379,7 +15677,7 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15379
15677
|
const live = liveRegistration(sessionId);
|
|
15380
15678
|
if (live) throw new Error(`watcher already armed for session ${sessionId} (pid ${live.pid})`);
|
|
15381
15679
|
try {
|
|
15382
|
-
|
|
15680
|
+
fs68.rmSync(file);
|
|
15383
15681
|
} catch {
|
|
15384
15682
|
}
|
|
15385
15683
|
}
|
|
@@ -15387,7 +15685,7 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15387
15685
|
if (!claimed) throw new Error(`could not arm watcher for session ${sessionId}`);
|
|
15388
15686
|
const ownsFile = () => {
|
|
15389
15687
|
try {
|
|
15390
|
-
return JSON.parse(
|
|
15688
|
+
return JSON.parse(fs68.readFileSync(file, "utf8")).pid === process.pid;
|
|
15391
15689
|
} catch {
|
|
15392
15690
|
return false;
|
|
15393
15691
|
}
|
|
@@ -15396,12 +15694,12 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15396
15694
|
if (!ownsFile()) return;
|
|
15397
15695
|
const tmp = `${file}.tmp-${process.pid}`;
|
|
15398
15696
|
try {
|
|
15399
|
-
|
|
15697
|
+
fs68.writeFileSync(tmp, `${JSON.stringify(own())}
|
|
15400
15698
|
`);
|
|
15401
|
-
|
|
15699
|
+
fs68.renameSync(tmp, file);
|
|
15402
15700
|
} catch {
|
|
15403
15701
|
try {
|
|
15404
|
-
|
|
15702
|
+
fs68.rmSync(tmp, { force: true });
|
|
15405
15703
|
} catch {
|
|
15406
15704
|
}
|
|
15407
15705
|
}
|
|
@@ -15413,7 +15711,7 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15413
15711
|
if (stopped) return;
|
|
15414
15712
|
stopped = true;
|
|
15415
15713
|
clearInterval(timer);
|
|
15416
|
-
if (ownsFile())
|
|
15714
|
+
if (ownsFile()) fs68.rmSync(file, { force: true });
|
|
15417
15715
|
process.off("exit", stop);
|
|
15418
15716
|
process.off("SIGINT", interrupt);
|
|
15419
15717
|
process.off("SIGTERM", terminate2);
|
|
@@ -15508,7 +15806,9 @@ work (humans and agents):
|
|
|
15508
15806
|
hold ends when that dispatch ends
|
|
15509
15807
|
watch release <key>|--all end a hold: a watch held by the per-cycle
|
|
15510
15808
|
fork cap ([watch].maxForksPerCycle), by
|
|
15511
|
-
watch hold, or by a cancelled repair
|
|
15809
|
+
watch hold, or by a cancelled repair;
|
|
15810
|
+
resume repairs stopped by
|
|
15811
|
+
[watch].maxRepairsWithoutProgress
|
|
15512
15812
|
|
|
15513
15813
|
host processes:
|
|
15514
15814
|
daemon [--interval <ms>] the supervisor: claims, worktrees, liveness,
|
|
@@ -15591,12 +15891,15 @@ lobstah man (orchestrator sessions \u2014 bare \`lobstah man\` prints the manual
|
|
|
15591
15891
|
man wait (lobstah man).
|
|
15592
15892
|
|
|
15593
15893
|
workers (dispatched agents; injected into every brief):
|
|
15594
|
-
report <uuid> <verb> [--pr <url>] [--no-watch] [--report <file.md> [--attach <file> ...]] [--session <id>] [--] [note]
|
|
15894
|
+
report <uuid> <verb> [--pr <url> ...] [--no-watch] [--report <file.md> [--attach <file> ...]] [--session <id>] [--] [note]
|
|
15595
15895
|
the validated status write path
|
|
15596
15896
|
(${VERBS.join(" | ")}). --pr on any
|
|
15597
15897
|
verb but failed records the PR and
|
|
15598
15898
|
registers its pr: watch for this chain;
|
|
15599
|
-
|
|
15899
|
+
repeat it for several PRs. A PR in a gh
|
|
15900
|
+
stack, or in a base chain of this
|
|
15901
|
+
dispatch's branches, adds the stack's
|
|
15902
|
+
other PRs. --no-watch opts out. done|failed
|
|
15600
15903
|
--report files a markdown page as the
|
|
15601
15904
|
dispatch's report, images --attach'ed.
|
|
15602
15905
|
|
|
@@ -15792,7 +16095,7 @@ function emitWatchAttention(attns, sessionId) {
|
|
|
15792
16095
|
function findLane(id) {
|
|
15793
16096
|
for (const lane of ["work", "chore"]) {
|
|
15794
16097
|
const d = laneDirs(lane);
|
|
15795
|
-
if (
|
|
16098
|
+
if (fs73.existsSync(path60.join(d.active, id)) || fs73.existsSync(path60.join(d.queue, `${id}.json`)) || fs73.existsSync(path60.join(d.done, id)) || fs73.existsSync(path60.join(d.state, `${id}.status`))) {
|
|
15796
16099
|
return lane;
|
|
15797
16100
|
}
|
|
15798
16101
|
}
|
|
@@ -15801,7 +16104,7 @@ function findLane(id) {
|
|
|
15801
16104
|
function anythingInFlight() {
|
|
15802
16105
|
const dispatches = ["work", "chore"].some((lane) => {
|
|
15803
16106
|
const dirs = laneDirs(lane);
|
|
15804
|
-
return [dirs.queue, dirs.active].some((dir) =>
|
|
16107
|
+
return [dirs.queue, dirs.active].some((dir) => fs73.readdirSync(dir).some((file) => !file.startsWith(".")));
|
|
15805
16108
|
});
|
|
15806
16109
|
return dispatches || listWatches().some((watch2) => watch2.owner === "man");
|
|
15807
16110
|
}
|
|
@@ -15818,7 +16121,7 @@ function dispatchChain(target) {
|
|
|
15818
16121
|
for (const lane of ["work", "chore"]) {
|
|
15819
16122
|
const dirs = laneDirs(lane);
|
|
15820
16123
|
for (const bucket of ["queue", "active", "done"]) {
|
|
15821
|
-
for (const entry of
|
|
16124
|
+
for (const entry of fs73.readdirSync(dirs[bucket])) {
|
|
15822
16125
|
if (entry.startsWith(".")) continue;
|
|
15823
16126
|
const id = bucket === "queue" ? entry.replace(/\.json$/, "") : entry;
|
|
15824
16127
|
const descriptor = storedDescriptor(id, lane);
|
|
@@ -15840,7 +16143,7 @@ function dispatchChain(target) {
|
|
|
15840
16143
|
}
|
|
15841
16144
|
function liveChainMember(member) {
|
|
15842
16145
|
if (member.bucket !== "active") return false;
|
|
15843
|
-
const dir =
|
|
16146
|
+
const dir = path60.join(laneDirs(member.lane).active, member.id);
|
|
15844
16147
|
const claim = readSessionClaim(member.id, member.lane);
|
|
15845
16148
|
if (claim?.by.startsWith("wt:")) {
|
|
15846
16149
|
const reg = readTrap(claim.by.slice("wt:".length));
|
|
@@ -15848,7 +16151,7 @@ function liveChainMember(member) {
|
|
|
15848
16151
|
}
|
|
15849
16152
|
if (isParked(member.id, member.lane)) return true;
|
|
15850
16153
|
try {
|
|
15851
|
-
const runner = JSON.parse(
|
|
16154
|
+
const runner = JSON.parse(fs73.readFileSync(path60.join(dir, "runner.json"), "utf8"));
|
|
15852
16155
|
return pidAlive2(runner.pid, runner.processStartTime);
|
|
15853
16156
|
} catch {
|
|
15854
16157
|
return false;
|
|
@@ -15857,7 +16160,7 @@ function liveChainMember(member) {
|
|
|
15857
16160
|
function rowsFor(lane, bucket) {
|
|
15858
16161
|
const dir = laneDirs(lane)[bucket];
|
|
15859
16162
|
const staleSecs = wedgeSecs2();
|
|
15860
|
-
const entries =
|
|
16163
|
+
const entries = fs73.readdirSync(dir).filter((f) => !f.startsWith(".")).map((f) => ({ f, m: fs73.statSync(path60.join(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
|
|
15861
16164
|
const sliced = bucket === "done" ? entries.slice(0, 10) : entries;
|
|
15862
16165
|
return sliced.map(({ f, m }) => {
|
|
15863
16166
|
const id = f.replace(/\.json$/, "");
|
|
@@ -15882,7 +16185,7 @@ function rowsFor(lane, bucket) {
|
|
|
15882
16185
|
function dispatchTrap(id, lane, names) {
|
|
15883
16186
|
const claimedBy = (bucket) => {
|
|
15884
16187
|
try {
|
|
15885
|
-
return JSON.parse(
|
|
16188
|
+
return JSON.parse(fs73.readFileSync(path60.join(laneDirs(lane)[bucket], id, "claim.json"), "utf8")).by;
|
|
15886
16189
|
} catch {
|
|
15887
16190
|
return void 0;
|
|
15888
16191
|
}
|
|
@@ -16075,7 +16378,7 @@ ${usageFor("trap")}`);
|
|
|
16075
16378
|
const d = {
|
|
16076
16379
|
id: opt("--id") ?? randomUUID6(),
|
|
16077
16380
|
repo,
|
|
16078
|
-
brief: briefText ??
|
|
16381
|
+
brief: briefText ?? fs73.readFileSync(briefFile, "utf8"),
|
|
16079
16382
|
harness: opt("--harness"),
|
|
16080
16383
|
model: opt("--model"),
|
|
16081
16384
|
effort: opt("--effort"),
|
|
@@ -16132,7 +16435,7 @@ ${usageFor("trap")}`);
|
|
|
16132
16435
|
const since = queuedAt(id, lane);
|
|
16133
16436
|
const claimedAt = claim?.at;
|
|
16134
16437
|
const state = displayState({ log, lastEventAt: lastEventAt(id, lane), queued: since !== void 0, claimedAt });
|
|
16135
|
-
const live =
|
|
16438
|
+
const live = fs73.existsSync(path60.join(laneDirs(lane).active, id));
|
|
16136
16439
|
const activity = live ? activityView(readActivity(id, lane), wedgeSecs2()) : void 0;
|
|
16137
16440
|
const waitingNow = live ? waitingView(log.at(-1)) : void 0;
|
|
16138
16441
|
console.log(
|
|
@@ -16172,8 +16475,8 @@ ${usageFor("trap")}`);
|
|
|
16172
16475
|
if (!id) throw new Error("logs requires a dispatch id");
|
|
16173
16476
|
const lane = findLane(id);
|
|
16174
16477
|
const file = eventsPath(id, lane);
|
|
16175
|
-
if (
|
|
16176
|
-
const raw =
|
|
16478
|
+
if (fs73.existsSync(file)) {
|
|
16479
|
+
const raw = fs73.readFileSync(file, "utf8");
|
|
16177
16480
|
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
16178
16481
|
const LIMIT = 50;
|
|
16179
16482
|
if (!has("--full") && lines.length > LIMIT) {
|
|
@@ -16184,15 +16487,15 @@ ${usageFor("trap")}`);
|
|
|
16184
16487
|
}
|
|
16185
16488
|
}
|
|
16186
16489
|
if (has("--follow")) {
|
|
16187
|
-
let size =
|
|
16490
|
+
let size = fs73.existsSync(file) ? fs73.statSync(file).size : 0;
|
|
16188
16491
|
setInterval(() => {
|
|
16189
|
-
if (!
|
|
16190
|
-
const now2 =
|
|
16492
|
+
if (!fs73.existsSync(file)) return;
|
|
16493
|
+
const now2 = fs73.statSync(file).size;
|
|
16191
16494
|
if (now2 > size) {
|
|
16192
|
-
const fd =
|
|
16495
|
+
const fd = fs73.openSync(file, "r");
|
|
16193
16496
|
const buf = Buffer.alloc(now2 - size);
|
|
16194
|
-
|
|
16195
|
-
|
|
16497
|
+
fs73.readSync(fd, buf, 0, buf.length, size);
|
|
16498
|
+
fs73.closeSync(fd);
|
|
16196
16499
|
process.stdout.write(buf.toString("utf8"));
|
|
16197
16500
|
size = now2;
|
|
16198
16501
|
}
|
|
@@ -16268,10 +16571,10 @@ ${[text, block].filter(Boolean).join("\n\n")}`,
|
|
|
16268
16571
|
}
|
|
16269
16572
|
let address;
|
|
16270
16573
|
let headless = false;
|
|
16271
|
-
const claimFile =
|
|
16574
|
+
const claimFile = path60.join(laneDirs(origin.lane)[origin.bucket], origin.id, "claim.json");
|
|
16272
16575
|
let claimedBy;
|
|
16273
16576
|
try {
|
|
16274
|
-
claimedBy = JSON.parse(
|
|
16577
|
+
claimedBy = JSON.parse(fs73.readFileSync(claimFile, "utf8")).by;
|
|
16275
16578
|
} catch {
|
|
16276
16579
|
claimedBy = readEvidence(origin.id, origin.lane).deliveredTo;
|
|
16277
16580
|
}
|
|
@@ -16302,7 +16605,8 @@ ${text}`, from);
|
|
|
16302
16605
|
if (!id || !verb) throw new Error(`report requires an id and a verb (${VERBS.join("|")})`);
|
|
16303
16606
|
const lane = findLane(id);
|
|
16304
16607
|
const note = rest.join(" ") || void 0;
|
|
16305
|
-
const
|
|
16608
|
+
const reportedPrs = values("--pr");
|
|
16609
|
+
const prUrl = reportedPrs[0];
|
|
16306
16610
|
const noWatch = has("--no-watch");
|
|
16307
16611
|
const waiting = { waitingOn: opt("--waiting-on"), link: opt("--link"), until: opt("--until") };
|
|
16308
16612
|
const saysWaiting = waiting.waitingOn !== void 0 || waiting.link !== void 0 || waiting.until !== void 0;
|
|
@@ -16354,24 +16658,35 @@ ${usageFor("report")}`);
|
|
|
16354
16658
|
if (verb === "done") {
|
|
16355
16659
|
const claim = readSessionClaim(id, lane);
|
|
16356
16660
|
let ownCheckout = false;
|
|
16357
|
-
if (claim?.by.startsWith("wt:") && claim.worktree &&
|
|
16358
|
-
const rel =
|
|
16359
|
-
const inside = rel === "" || !rel.startsWith("..") && !
|
|
16661
|
+
if (claim?.by.startsWith("wt:") && claim.worktree && fs73.existsSync(claim.worktree)) {
|
|
16662
|
+
const rel = path60.relative(canon(claim.worktree), canon(process.cwd()));
|
|
16663
|
+
const inside = rel === "" || !rel.startsWith("..") && !path60.isAbsolute(rel);
|
|
16360
16664
|
const who = callerSession(opt("--session"))?.id;
|
|
16361
16665
|
const manning = readTrap(claim.by.slice("wt:".length))?.sessionId;
|
|
16362
16666
|
ownCheckout = inside || who !== void 0 && (who === claim.sessionId || who === manning);
|
|
16363
16667
|
}
|
|
16364
16668
|
if (ownCheckout) {
|
|
16365
|
-
const git10 = (args2) =>
|
|
16669
|
+
const git10 = (args2) => spawnSync14("git", args2, { cwd: claim.worktree, encoding: "utf8", timeout: 1e4 });
|
|
16366
16670
|
const head = git10(["rev-parse", "HEAD"]);
|
|
16367
16671
|
const branch = git10(["branch", "--show-current"]);
|
|
16368
16672
|
if (head.status === 0 && branch.status === 0)
|
|
16369
16673
|
mergeEvidence(id, lane, { commits: [head.stdout.trim()], branch: branch.stdout.trim() });
|
|
16370
16674
|
}
|
|
16371
16675
|
}
|
|
16372
|
-
|
|
16676
|
+
let reportPrs = [];
|
|
16677
|
+
if (prUrl) {
|
|
16678
|
+
const ev = readEvidence(id, lane);
|
|
16679
|
+
const worktree = readSessionClaim(id, lane)?.worktree ?? ev.worktree;
|
|
16680
|
+
const branches = [.../* @__PURE__ */ new Set([ev.branch, ...(ev.pushes ?? []).map((p) => p.branch)])].filter(
|
|
16681
|
+
(b) => !!b && b !== "HEAD"
|
|
16682
|
+
);
|
|
16683
|
+
reportPrs = stackPrUrls(reportedPrs, { cwd: worktree && fs73.existsSync(worktree) ? worktree : process.cwd(), branches });
|
|
16684
|
+
const all = dispatchPrUrls({ prUrl, prUrls: [...reportPrs, ...dispatchPrUrls(ev)] });
|
|
16685
|
+
mergeEvidence(id, lane, { prUrl, ...all.length > 1 ? { prUrls: all } : {} });
|
|
16686
|
+
}
|
|
16373
16687
|
const gatesNamed = recordReportedGates(id, lane, values("--human-gate"), prUrl);
|
|
16374
|
-
const
|
|
16688
|
+
const prWatches = verb !== "failed" && prUrl && !noWatch && !storedDescriptor(id, lane)?.systemRepair ? reportPrs.map((u) => autoRegisterPrWatch(id, u)).filter((w) => !!w) : verb === "paused" && !noWatch ? registerWaitWatches(id, lane, entry) : [];
|
|
16689
|
+
const warning = waitWarning(id, lane, entry);
|
|
16375
16690
|
console.log(
|
|
16376
16691
|
toonKV({
|
|
16377
16692
|
id,
|
|
@@ -16382,7 +16697,9 @@ ${usageFor("report")}`);
|
|
|
16382
16697
|
...entry.link ? { link: entry.link } : {},
|
|
16383
16698
|
...entry.until ? { until: entry.until } : {},
|
|
16384
16699
|
...prUrl ? { prUrl } : {},
|
|
16385
|
-
...
|
|
16700
|
+
...reportPrs.length > 1 ? { prs: reportPrs.join(", ") } : {},
|
|
16701
|
+
...prWatches.length ? { watch: prWatches.map((w) => w.key).join(", ") } : {},
|
|
16702
|
+
...warning ? { warning } : {},
|
|
16386
16703
|
...gatesNamed.length ? { humanGates: gatesNamed.join(", ") } : {},
|
|
16387
16704
|
...filed ? { report: reportMarkdownPath(filed.key), reportKey: filed.key, reportTitle: filed.title } : {}
|
|
16388
16705
|
})
|
|
@@ -16429,14 +16746,14 @@ ${usageFor("report")}`);
|
|
|
16429
16746
|
);
|
|
16430
16747
|
}
|
|
16431
16748
|
const worktree = dispatchWorktree(id, lane).path;
|
|
16432
|
-
const cwd =
|
|
16749
|
+
const cwd = fs73.existsSync(worktree) ? worktree : process.cwd();
|
|
16433
16750
|
const invocation = harness === "codex" ? codexInvocation(["resume", sessionId]) : { file: "claude", argv: ["--resume", sessionId] };
|
|
16434
16751
|
if (!invocation) throw new Error("no codex CLI found \u2014 neither on PATH nor vendored by @openai/codex-sdk");
|
|
16435
16752
|
if (has("--print")) {
|
|
16436
16753
|
console.log(toonKV({ id, harness, sessionId, cwd, command: `${invocation.file} ${invocation.argv.join(" ")}` }));
|
|
16437
16754
|
break;
|
|
16438
16755
|
}
|
|
16439
|
-
const res =
|
|
16756
|
+
const res = spawnSync14(invocation.file, invocation.argv, { cwd, stdio: "inherit" });
|
|
16440
16757
|
if (res.error) throw new Error(`could not launch ${invocation.file}: ${res.error.message}`);
|
|
16441
16758
|
break;
|
|
16442
16759
|
}
|
|
@@ -16447,10 +16764,10 @@ ${usageFor("report")}`);
|
|
|
16447
16764
|
gateHelm(callerSession(opt("--session")));
|
|
16448
16765
|
}
|
|
16449
16766
|
const lane = findLane(id);
|
|
16450
|
-
const activeDir =
|
|
16451
|
-
if (!
|
|
16452
|
-
const descFile =
|
|
16453
|
-
const descriptor = JSON.parse(
|
|
16767
|
+
const activeDir = path60.join(laneDirs(lane).active, id);
|
|
16768
|
+
if (!fs73.existsSync(activeDir)) throw new Error(`${id} is not active \u2014 swap only applies to in-flight dispatches`);
|
|
16769
|
+
const descFile = path60.join(activeDir, "descriptor.json");
|
|
16770
|
+
const descriptor = JSON.parse(fs73.readFileSync(descFile, "utf8"));
|
|
16454
16771
|
const fromHarness = resolveSessionHarness(id, loadConfig(), lane).harness ?? descriptor.harness ?? "default";
|
|
16455
16772
|
for (const key of ["harness", "model", "effort"]) {
|
|
16456
16773
|
const v = opt(`--${key}`);
|
|
@@ -16458,14 +16775,14 @@ ${usageFor("report")}`);
|
|
|
16458
16775
|
}
|
|
16459
16776
|
if (opt("--harness")) descriptor.harnessExplicit = true;
|
|
16460
16777
|
if (opt("--model")) descriptor.modelExplicit = true;
|
|
16461
|
-
|
|
16778
|
+
fs73.writeFileSync(descFile, JSON.stringify(descriptor, null, 2));
|
|
16462
16779
|
const worktree = dispatchWorktree(id, lane).path;
|
|
16463
|
-
|
|
16464
|
-
const runnerFile =
|
|
16465
|
-
if (
|
|
16466
|
-
const runner = JSON.parse(
|
|
16780
|
+
fs73.writeFileSync(path60.join(activeDir, "handoff"), handoffNote(fromHarness, worktreeProgress(worktree)));
|
|
16781
|
+
const runnerFile = path60.join(activeDir, "runner.json");
|
|
16782
|
+
if (fs73.existsSync(runnerFile)) {
|
|
16783
|
+
const runner = JSON.parse(fs73.readFileSync(runnerFile, "utf8"));
|
|
16467
16784
|
if (pidAlive2(runner.pid, runner.processStartTime)) killGroup(runner.pid, "SIGKILL");
|
|
16468
|
-
|
|
16785
|
+
fs73.unlinkSync(runnerFile);
|
|
16469
16786
|
}
|
|
16470
16787
|
appendStatus(id, lane, "working", `swapped to ${descriptor.harness ?? "default"} \u2014 awaiting respawn`);
|
|
16471
16788
|
console.log(toonKV({ id, harness: descriptor.harness ?? "default", model: descriptor.model, swapped: true }));
|
|
@@ -16489,6 +16806,12 @@ ${usageFor("report")}`);
|
|
|
16489
16806
|
}),
|
|
16490
16807
|
branch: ev.branch,
|
|
16491
16808
|
prUrl: ev.prUrl,
|
|
16809
|
+
...dispatchPrUrls(ev).length > 1 ? {
|
|
16810
|
+
prs: dispatchPrUrls(ev).map((u) => {
|
|
16811
|
+
const record = readPr(parsePrRef(u).key);
|
|
16812
|
+
return record ? `${prBadge(record).text} ${u}` : u;
|
|
16813
|
+
}).join("; ")
|
|
16814
|
+
} : {},
|
|
16492
16815
|
sessionId: ev.sessionId,
|
|
16493
16816
|
...worktreeView(id, lane),
|
|
16494
16817
|
...dispatchTrap(id, lane, names),
|
|
@@ -16520,7 +16843,7 @@ ${usageFor("report")}`);
|
|
|
16520
16843
|
console.log(`commits[${ev.commits.length}]:`);
|
|
16521
16844
|
for (const c of ev.commits) console.log(` ${c}`);
|
|
16522
16845
|
}
|
|
16523
|
-
if (
|
|
16846
|
+
if (fs73.existsSync(path60.join(laneDirs(lane).done, id))) {
|
|
16524
16847
|
console.log(toonHelp([`lobstah send ${id} "<instruction>" (wakes it as a follow-up in its worktree)`]));
|
|
16525
16848
|
}
|
|
16526
16849
|
const attachments = storedDescriptor(id, lane)?.attachments ?? [];
|
|
@@ -16808,7 +17131,7 @@ ${errs.map((e) => `- ${e}`).join("\n")}`);
|
|
|
16808
17131
|
const keys = holdCancelledRepair(id);
|
|
16809
17132
|
return keys.length > 0 ? { repairHeld: `${keys.join(", ")} (lobstah watch release <key> frees it)` } : {};
|
|
16810
17133
|
};
|
|
16811
|
-
if (
|
|
17134
|
+
if (fs73.existsSync(path60.join(laneDirs(lane).active, id))) {
|
|
16812
17135
|
requestCancel(id, lane);
|
|
16813
17136
|
console.log(toonKV({ id, cancel: "requested", note: "the claimant (daemon or trap) winds it down at its next check", ...repairHeld() }));
|
|
16814
17137
|
break;
|
|
@@ -16817,7 +17140,7 @@ ${errs.map((e) => `- ${e}`).join("\n")}`);
|
|
|
16817
17140
|
console.log(toonKV({ id, cancel: "finalized", note: "cancelled before claim \u2014 recorded as failed in done/", ...repairHeld() }));
|
|
16818
17141
|
break;
|
|
16819
17142
|
}
|
|
16820
|
-
if (
|
|
17143
|
+
if (fs73.existsSync(path60.join(laneDirs(lane).active, id))) {
|
|
16821
17144
|
requestCancel(id, lane);
|
|
16822
17145
|
console.log(toonKV({ id, cancel: "requested", note: "claimed while cancelling \u2014 the claimant winds it down", ...repairHeld() }));
|
|
16823
17146
|
break;
|
|
@@ -16941,20 +17264,20 @@ ${usageFor("man:wait")}`);
|
|
|
16941
17264
|
}
|
|
16942
17265
|
}
|
|
16943
17266
|
case "man:init": {
|
|
16944
|
-
const file = has("--global") ?
|
|
17267
|
+
const file = has("--global") ? path60.join(os11.homedir(), ".claude", "settings.json") : path60.join(".claude", has("--shared") ? "settings.json" : "settings.local.json");
|
|
16945
17268
|
let existing;
|
|
16946
17269
|
try {
|
|
16947
|
-
existing = JSON.parse(
|
|
17270
|
+
existing = JSON.parse(fs73.readFileSync(file, "utf8"));
|
|
16948
17271
|
} catch {
|
|
16949
17272
|
existing = void 0;
|
|
16950
17273
|
}
|
|
16951
17274
|
const { settings, changed } = mergeHaulHook(existing);
|
|
16952
17275
|
if (changed) {
|
|
16953
|
-
|
|
16954
|
-
|
|
17276
|
+
fs73.mkdirSync(path60.dirname(file), { recursive: true });
|
|
17277
|
+
fs73.writeFileSync(file, `${JSON.stringify(settings, null, 2)}
|
|
16955
17278
|
`);
|
|
16956
17279
|
}
|
|
16957
|
-
if (has("--marker"))
|
|
17280
|
+
if (has("--marker")) fs73.writeFileSync(".lobstah-man", "");
|
|
16958
17281
|
console.log(
|
|
16959
17282
|
toonKV({
|
|
16960
17283
|
hook: "lobstah man haul",
|
|
@@ -17000,7 +17323,7 @@ ${usageFor("man:wait")}`);
|
|
|
17000
17323
|
if (helm) releaseHeldQuestions(helm);
|
|
17001
17324
|
const cfgHaul = loadConfig();
|
|
17002
17325
|
if (!helm && liveHelms(cfgHaul.helm.ttlSecs * 1e3).length > 0) break;
|
|
17003
|
-
if (!helm && process.env.LOBSTAH_MAN !== "1" && !
|
|
17326
|
+
if (!helm && process.env.LOBSTAH_MAN !== "1" && !fs73.existsSync(".lobstah-man")) break;
|
|
17004
17327
|
const dueDigest = () => helm ? dueHelmDigest(helm, cfgHaul.helm.reportSecs) : void 0;
|
|
17005
17328
|
const blockDigest = (d) => {
|
|
17006
17329
|
advanceCursor(helm.grounds, d.now);
|
|
@@ -17217,7 +17540,7 @@ ${behind}` : "");
|
|
|
17217
17540
|
`this session already mans trap wt:${mine.trapId} (repo ${mine.repo ?? "none"}) at ${mine.worktree}. Sign it off with \`lobstah stow\` before you soak for repo ${repoFlag}.`
|
|
17218
17541
|
);
|
|
17219
17542
|
}
|
|
17220
|
-
if (!
|
|
17543
|
+
if (!fs73.existsSync(mine.worktree)) {
|
|
17221
17544
|
throw new Error(
|
|
17222
17545
|
`this session's trap wt:${mine.trapId} is anchored at ${mine.worktree}, which no longer exists. Run \`lobstah stow\`, then soak again.`
|
|
17223
17546
|
);
|
|
@@ -17293,8 +17616,8 @@ ${usageFor("soak")}`
|
|
|
17293
17616
|
if (reservation) dropReservation(reservation.trapId);
|
|
17294
17617
|
const titled = reg.name ? await setTerminalTitle(reg.window, reg.name) : void 0;
|
|
17295
17618
|
const inside = (() => {
|
|
17296
|
-
const rel =
|
|
17297
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
17619
|
+
const rel = path60.relative(reg.worktree, canon(process.cwd()));
|
|
17620
|
+
return rel === "" || !rel.startsWith("..") && !path60.isAbsolute(rel);
|
|
17298
17621
|
})();
|
|
17299
17622
|
const sessionFlag = inside ? "" : ` --session ${sessionId}`;
|
|
17300
17623
|
console.log(
|
|
@@ -17383,7 +17706,7 @@ ${usageFor("soak")}`
|
|
|
17383
17706
|
const anchor = wtDir !== void 0 ? readTrapAnchor(wtDir) : void 0;
|
|
17384
17707
|
const created = reg?.createdWorktree === true || anchor?.createdBy === "soak";
|
|
17385
17708
|
let worktreeOut = {};
|
|
17386
|
-
if (wtDir !== void 0 &&
|
|
17709
|
+
if (wtDir !== void 0 && fs73.existsSync(wtDir)) {
|
|
17387
17710
|
const keepReason = has("--keep") ? "--keep" : sessionEnd ? "the session ended; the SessionEnd hook keeps the worktree" : !created ? "soak did not create this worktree; stow leaves it in place" : void 0;
|
|
17388
17711
|
if (keepReason) {
|
|
17389
17712
|
worktreeOut = { worktree: "kept", path: wtDir, reason: keepReason };
|
|
@@ -17459,7 +17782,7 @@ ${usageFor("pick")}`);
|
|
|
17459
17782
|
restarted: res.command,
|
|
17460
17783
|
...kind === "daemon" ? { headless: usage.headless, trapCatches: `${usage.traps} unaffected` } : {},
|
|
17461
17784
|
...active > 0 ? { interrupted: `${active} headless dispatch(es) (--force)` } : {},
|
|
17462
|
-
...hb ? { running: `v${hb.version ?? "?"}`, ...hb.pid !== void 0 ? { pid: hb.pid } : {} } : { heartbeat: `none within ${RESTART_WAIT_MS / 1e3}s \u2014 see ${
|
|
17785
|
+
...hb ? { running: `v${hb.version ?? "?"}`, ...hb.pid !== void 0 ? { pid: hb.pid } : {} } : { heartbeat: `none within ${RESTART_WAIT_MS / 1e3}s \u2014 see ${path60.join(lobstahHome(), "logs", "daemon.err")}` }
|
|
17463
17786
|
})
|
|
17464
17787
|
);
|
|
17465
17788
|
if (!hb) process.exitCode = 1;
|
|
@@ -17470,6 +17793,7 @@ ${usageFor("pick")}`);
|
|
|
17470
17793
|
culler: cliCuller,
|
|
17471
17794
|
prWatches: (now2, log) => {
|
|
17472
17795
|
observeDispatchPrWatches(pollSecs(), now2);
|
|
17796
|
+
observeWaitedPrs({ everySecs: pollSecs(), now: now2 });
|
|
17473
17797
|
finishResolvedWaits(log);
|
|
17474
17798
|
deliverPrRepairs(log, loadConfig().watch.maxForksPerCycle);
|
|
17475
17799
|
stampRepairerBeat(now2);
|
|
@@ -17570,8 +17894,8 @@ ${usageFor("pet")}`);
|
|
|
17570
17894
|
break;
|
|
17571
17895
|
}
|
|
17572
17896
|
const server = serveGlass(port);
|
|
17573
|
-
await new Promise((
|
|
17574
|
-
server.once("listening", () =>
|
|
17897
|
+
await new Promise((resolve14, reject) => {
|
|
17898
|
+
server.once("listening", () => resolve14());
|
|
17575
17899
|
server.once("error", reject);
|
|
17576
17900
|
});
|
|
17577
17901
|
console.log(
|
|
@@ -17607,7 +17931,7 @@ ${usageFor("pet")}`);
|
|
|
17607
17931
|
path: r.path,
|
|
17608
17932
|
trunk: r.trunk,
|
|
17609
17933
|
pickup: r.pickup ?? false,
|
|
17610
|
-
exists:
|
|
17934
|
+
exists: fs73.existsSync(r.path)
|
|
17611
17935
|
}));
|
|
17612
17936
|
console.log(toonTable("repos", rows, ["key", "path", "trunk", "pickup", "exists"]));
|
|
17613
17937
|
break;
|
|
@@ -17698,7 +18022,8 @@ ${usageFor("pet")}`);
|
|
|
17698
18022
|
const key = pos[1];
|
|
17699
18023
|
if (!key && !has("--all")) throw new Error("watch release requires a key or --all");
|
|
17700
18024
|
const released = releaseHeldWatches(has("--all") ? void 0 : key);
|
|
17701
|
-
|
|
18025
|
+
const resumed = resetRepairStreaks(has("--all") ? void 0 : key);
|
|
18026
|
+
console.log(toonKV({ released: released.length, ...resumed.length ? { repairsResumed: resumed.join(", ") } : {} }));
|
|
17702
18027
|
break;
|
|
17703
18028
|
}
|
|
17704
18029
|
if (sub === "rm") {
|
|
@@ -17720,7 +18045,7 @@ ${usageFor("pet")}`);
|
|
|
17720
18045
|
break;
|
|
17721
18046
|
}
|
|
17722
18047
|
case "init": {
|
|
17723
|
-
if (!
|
|
18048
|
+
if (!fs73.existsSync(configPath())) {
|
|
17724
18049
|
const exampleRepo = has("--scan") ? "" : `[repos.example]
|
|
17725
18050
|
path = "~/src/example"
|
|
17726
18051
|
trunk = "main"
|
|
@@ -17729,7 +18054,7 @@ trunk = "main"
|
|
|
17729
18054
|
# pickup = true # opt into [pickup.github] multi-repo tracker pickup
|
|
17730
18055
|
|
|
17731
18056
|
`;
|
|
17732
|
-
|
|
18057
|
+
fs73.writeFileSync(
|
|
17733
18058
|
configPath(),
|
|
17734
18059
|
`# lobstah workspace definitions \u2014 the descriptor's repo key resolves here.
|
|
17735
18060
|
# Top-level keys must come before any [section].
|
|
@@ -17767,12 +18092,12 @@ wallClockSecs = 3600
|
|
|
17767
18092
|
let reference = "https://github.com/aequitas-labs/lobstah/blob/main/docs/configuration.md";
|
|
17768
18093
|
try {
|
|
17769
18094
|
const local = fileURLToPath6(new URL("../docs/configuration.md", import.meta.url));
|
|
17770
|
-
if (
|
|
18095
|
+
if (fs73.existsSync(local)) reference = local;
|
|
17771
18096
|
} catch {
|
|
17772
18097
|
}
|
|
17773
18098
|
console.log(
|
|
17774
18099
|
toonKV({
|
|
17775
|
-
home:
|
|
18100
|
+
home: path60.dirname(configPath()),
|
|
17776
18101
|
config: configPath(),
|
|
17777
18102
|
...added.length > 0 ? { added: added.join(", ") } : {},
|
|
17778
18103
|
...unmarked.length > 0 ? { note: `none marked pickable \u2014 set pickup = true per [repos.*] (or rerun with --pickup)` } : {},
|