lobstah 0.6.2 → 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 +854 -272
- package/dist/runner.js +253 -19
- package/docs/configuration.md +21 -1
- package/docs/man.md +72 -20
- package/docs/vocabulary.md +15 -6
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -649,6 +649,9 @@ function checkOptions(raw) {
|
|
|
649
649
|
return options;
|
|
650
650
|
}
|
|
651
651
|
function askDecision(opts) {
|
|
652
|
+
if (opts.replace !== void 0 && !standingDecisions().some((d) => d.key === opts.replace)) {
|
|
653
|
+
throw new DecisionError(`no standing decision ${opts.replace} to replace \u2014 \`lobstah man tend\` lists them`);
|
|
654
|
+
}
|
|
652
655
|
const title = checkTitle(opts.title);
|
|
653
656
|
const options = checkOptions(opts.options ?? []);
|
|
654
657
|
let detail = opts.detailText ?? "";
|
|
@@ -709,7 +712,7 @@ ${title}`).digest("hex").slice(0, 16)
|
|
|
709
712
|
writeAtomic(path6.join(dir, DETAIL_MD), detail);
|
|
710
713
|
writeAtomic(path6.join(dir, DECISION_JSON), `${JSON.stringify(meta, null, 2)}
|
|
711
714
|
`);
|
|
712
|
-
const replaced = opts.
|
|
715
|
+
const replaced = opts.replace !== void 0 && opts.replace !== key ? [opts.replace] : [];
|
|
713
716
|
for (const old of replaced)
|
|
714
717
|
fs6.rmSync(decisionDir(old), { recursive: true, force: true });
|
|
715
718
|
return { meta, replaced };
|
|
@@ -1635,6 +1638,23 @@ var init_dist = __esm({
|
|
|
1635
1638
|
import * as fs7 from "node:fs";
|
|
1636
1639
|
import * as os2 from "node:os";
|
|
1637
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
|
+
}
|
|
1638
1658
|
function parseAttentionKinds(raw) {
|
|
1639
1659
|
if (raw === void 0)
|
|
1640
1660
|
return [...DEFAULT_ATTENTION_KINDS];
|
|
@@ -1672,7 +1692,8 @@ function loadConfig() {
|
|
|
1672
1692
|
pushEarly: r.pushEarly === void 0 ? void 0 : Boolean(r.pushEarly),
|
|
1673
1693
|
draftPr: r.draftPr === void 0 ? void 0 : Boolean(r.draftPr),
|
|
1674
1694
|
checkpointOnStop: r.checkpointOnStop === void 0 ? void 0 : Boolean(r.checkpointOnStop),
|
|
1675
|
-
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)
|
|
1676
1697
|
};
|
|
1677
1698
|
}
|
|
1678
1699
|
const groundsRaw = raw.grounds ?? {};
|
|
@@ -1713,12 +1734,13 @@ function resolveDispatch(d, cfg) {
|
|
|
1713
1734
|
flags: d.flags ?? []
|
|
1714
1735
|
};
|
|
1715
1736
|
}
|
|
1716
|
-
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;
|
|
1717
1738
|
var init_config = __esm({
|
|
1718
1739
|
"packages/core/dist/config.js"() {
|
|
1719
1740
|
"use strict";
|
|
1720
1741
|
init_dist();
|
|
1721
1742
|
init_paths();
|
|
1743
|
+
BRIEF_KINDS = ["conflict", "checks", "review", "ciFix", "rebase"];
|
|
1722
1744
|
ATTENTION_KINDS = ["question", "decision", "landed", "pr:draft", "pr:review", "pr:checks", "pr:conflict", "pr:ready", "report"];
|
|
1723
1745
|
DEFAULT_ATTENTION_KINDS = ["question", "decision", "pr:ready", "pr:review", "pr:conflict", "pr:checks"];
|
|
1724
1746
|
DEFAULT_SOAK = {
|
|
@@ -1733,7 +1755,7 @@ var init_config = __esm({
|
|
|
1733
1755
|
armGraceSecs: 5
|
|
1734
1756
|
};
|
|
1735
1757
|
DEFAULT_GLASS = { port: 4949 };
|
|
1736
|
-
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 };
|
|
1737
1759
|
DEFAULT_LIMITS = {
|
|
1738
1760
|
maxConcurrent: 2,
|
|
1739
1761
|
choreConcurrent: 1,
|
|
@@ -4205,6 +4227,35 @@ function bounceTrapMessages(trapId) {
|
|
|
4205
4227
|
}
|
|
4206
4228
|
return msgs.length;
|
|
4207
4229
|
}
|
|
4230
|
+
function messageTexts(id, lane) {
|
|
4231
|
+
const dir = inboxDir(id, lane);
|
|
4232
|
+
const texts = [];
|
|
4233
|
+
for (const d of [dir, path19.join(dir, "handled")]) {
|
|
4234
|
+
let files;
|
|
4235
|
+
try {
|
|
4236
|
+
files = fs21.readdirSync(d).filter((f) => f.endsWith(".msg"));
|
|
4237
|
+
} catch {
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
for (const f of files) {
|
|
4241
|
+
try {
|
|
4242
|
+
texts.push(fs21.readFileSync(path19.join(d, f), "utf8"));
|
|
4243
|
+
} catch {
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
return texts;
|
|
4248
|
+
}
|
|
4249
|
+
function trapMessageTexts(trapId, sinceMs = 0) {
|
|
4250
|
+
return messageTexts(trapKey(trapId), "work").flatMap((raw) => {
|
|
4251
|
+
try {
|
|
4252
|
+
const m = JSON.parse(raw);
|
|
4253
|
+
return (Date.parse(m.at ?? "") || 0) >= sinceMs ? [m.text ?? ""] : [];
|
|
4254
|
+
} catch {
|
|
4255
|
+
return sinceMs > 0 ? [] : [raw];
|
|
4256
|
+
}
|
|
4257
|
+
});
|
|
4258
|
+
}
|
|
4208
4259
|
var metaName, trapKey;
|
|
4209
4260
|
var init_inbox = __esm({
|
|
4210
4261
|
"packages/core/dist/inbox.js"() {
|
|
@@ -4684,6 +4735,20 @@ var init_compiled = __esm({
|
|
|
4684
4735
|
// packages/core/dist/pr.js
|
|
4685
4736
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
4686
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
|
+
}
|
|
4687
4752
|
function parsePrRef(s) {
|
|
4688
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());
|
|
4689
4754
|
if (!m)
|
|
@@ -4737,11 +4802,14 @@ function prStandingKinds(pr) {
|
|
|
4737
4802
|
function prReview(view) {
|
|
4738
4803
|
const latest = /* @__PURE__ */ new Map();
|
|
4739
4804
|
let lastReviewAt;
|
|
4805
|
+
let lastApprovalAt;
|
|
4740
4806
|
const reviews = [...view.reviews ?? []].sort((a, b) => (a.submittedAt ?? "").localeCompare(b.submittedAt ?? ""));
|
|
4741
4807
|
for (const r of reviews) {
|
|
4742
4808
|
if (r.submittedAt && (!lastReviewAt || r.submittedAt > lastReviewAt))
|
|
4743
4809
|
lastReviewAt = r.submittedAt;
|
|
4744
4810
|
const state = (r.state ?? "").toUpperCase();
|
|
4811
|
+
if (state === "APPROVED" && r.submittedAt && (!lastApprovalAt || r.submittedAt > lastApprovalAt))
|
|
4812
|
+
lastApprovalAt = r.submittedAt;
|
|
4745
4813
|
if (state === "APPROVED" || state === "CHANGES_REQUESTED" || state === "DISMISSED")
|
|
4746
4814
|
latest.set(r.author?.login ?? "?", state);
|
|
4747
4815
|
}
|
|
@@ -4749,7 +4817,8 @@ function prReview(view) {
|
|
|
4749
4817
|
return {
|
|
4750
4818
|
...view.unresolvedThreads !== void 0 ? { unresolvedThreads: view.unresolvedThreads } : {},
|
|
4751
4819
|
changesRequested,
|
|
4752
|
-
...lastReviewAt ? { lastReviewAt } : {}
|
|
4820
|
+
...lastReviewAt ? { lastReviewAt } : {},
|
|
4821
|
+
...lastApprovalAt ? { lastApprovalAt } : {}
|
|
4753
4822
|
};
|
|
4754
4823
|
}
|
|
4755
4824
|
function prEvidence(ref, view, observedAt) {
|
|
@@ -5096,7 +5165,9 @@ function upsertPr(pr, dispatchId) {
|
|
|
5096
5165
|
headSince: sinceOf(before?.headSha === pr.headSha, before?.headSince, before?.observedAt, pr.observedAt),
|
|
5097
5166
|
baseSince: sinceOf(!!before && before.baseRefName === pr.baseRefName && before.baseSha === (pr.baseSha ?? before.baseSha), before?.baseSince, before?.observedAt, pr.observedAt),
|
|
5098
5167
|
...pr.failingChecks?.length ? { failingSince: sinceOf(!!before && failingKey(before) === failingKey(pr), before?.failingSince, before?.observedAt, pr.observedAt) } : { failingSince: void 0 },
|
|
5099
|
-
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 }
|
|
5100
5171
|
};
|
|
5101
5172
|
writePr(after);
|
|
5102
5173
|
return { before, after };
|
|
@@ -5125,6 +5196,23 @@ function removePr(key) {
|
|
|
5125
5196
|
fs27.rmSync(file, { force: true });
|
|
5126
5197
|
return existed;
|
|
5127
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
|
+
}
|
|
5128
5216
|
var init_prs = __esm({
|
|
5129
5217
|
"packages/core/dist/prs.js"() {
|
|
5130
5218
|
"use strict";
|
|
@@ -5243,24 +5331,43 @@ function repairKind(pr, gates = []) {
|
|
|
5243
5331
|
function repairLimit(n) {
|
|
5244
5332
|
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 2;
|
|
5245
5333
|
}
|
|
5246
|
-
function pushRule(branch, id = "<dispatch id>") {
|
|
5334
|
+
function pushRule(branch, id = "<dispatch id>", mode = "rebase") {
|
|
5247
5335
|
const b = branch ?? "the PR's head branch";
|
|
5248
5336
|
return [
|
|
5249
5337
|
`Push only to the existing branch ${b}.`,
|
|
5250
|
-
`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.`,
|
|
5251
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.",
|
|
5252
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.`,
|
|
5253
5341
|
"Never push to another branch. Never open a new PR."
|
|
5254
5342
|
].join(" ");
|
|
5255
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
|
+
}
|
|
5256
5361
|
function repairBrief(pr, kind, arg = {}) {
|
|
5257
5362
|
const opts = typeof arg === "string" ? { id: arg } : arg;
|
|
5258
5363
|
const intro = `Repair ${pr.url} on its existing branch ${pr.headRefName ?? "(see PR)"} at ${pr.headSha}. Do not open a new PR.`;
|
|
5259
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.";
|
|
5260
|
-
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}`;
|
|
5261
5368
|
if (kind === "conflict")
|
|
5262
5369
|
return `${intro}
|
|
5263
|
-
${resolution}
|
|
5370
|
+
${resolution} ${conflictUpdate(pr.baseRefName, pr.headRefName, standalone)} ${finish(standalone ? "merge" : "rebase")}`;
|
|
5264
5371
|
if (kind === "checks") {
|
|
5265
5372
|
const failing = (pr.failingChecks ?? []).filter((c) => !opts.checks || opts.checks.includes(c.name));
|
|
5266
5373
|
const checks = failing.map((c) => `- ${c.name}${c.detailsUrl ? ` \u2014 ${c.detailsUrl}` : ""}`).join("\n");
|
|
@@ -5271,10 +5378,10 @@ These failing checks are human gates. They pass only when a person approves. Do
|
|
|
5271
5378
|
${resolution}
|
|
5272
5379
|
Latest failing checks:
|
|
5273
5380
|
${checks || "- Read the failing check from GitHub"}${gates}
|
|
5274
|
-
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()}`;
|
|
5275
5382
|
}
|
|
5276
5383
|
return `${intro}
|
|
5277
|
-
${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()}`;
|
|
5278
5385
|
}
|
|
5279
5386
|
var PUSH_RETRIES, PUSH_REJECTED;
|
|
5280
5387
|
var init_pr_repair = __esm({
|
|
@@ -5602,7 +5709,7 @@ var init_dist2 = __esm({
|
|
|
5602
5709
|
import * as fs30 from "node:fs";
|
|
5603
5710
|
import * as path26 from "node:path";
|
|
5604
5711
|
import { createHash as createHash6 } from "node:crypto";
|
|
5605
|
-
import { exec, execFile } from "node:child_process";
|
|
5712
|
+
import { exec, execFile, execFileSync as execFileSync3 } from "node:child_process";
|
|
5606
5713
|
import { promisify } from "node:util";
|
|
5607
5714
|
async function git2(cwd, ...args) {
|
|
5608
5715
|
const { stdout } = await run("git", args, { cwd, env: process.env });
|
|
@@ -5632,6 +5739,140 @@ ${gitStderr(err)}`, { cause: err });
|
|
|
5632
5739
|
}
|
|
5633
5740
|
}
|
|
5634
5741
|
}
|
|
5742
|
+
function pidAlive(pid) {
|
|
5743
|
+
try {
|
|
5744
|
+
process.kill(pid, 0);
|
|
5745
|
+
return true;
|
|
5746
|
+
} catch (err) {
|
|
5747
|
+
return err.code === "EPERM";
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
function takeRepoLock(file, mine) {
|
|
5751
|
+
try {
|
|
5752
|
+
fs30.writeFileSync(file, mine, { flag: "wx" });
|
|
5753
|
+
return { state: "held" };
|
|
5754
|
+
} catch (err) {
|
|
5755
|
+
const code = err.code ?? "";
|
|
5756
|
+
if (code !== "EEXIST" && !BUSY.has(code))
|
|
5757
|
+
throw err;
|
|
5758
|
+
}
|
|
5759
|
+
let raw = "";
|
|
5760
|
+
let age2 = 0;
|
|
5761
|
+
try {
|
|
5762
|
+
raw = fs30.readFileSync(file, "utf8");
|
|
5763
|
+
age2 = Date.now() - fs30.statSync(file).mtimeMs;
|
|
5764
|
+
} catch {
|
|
5765
|
+
return { state: "retry" };
|
|
5766
|
+
}
|
|
5767
|
+
let holder;
|
|
5768
|
+
try {
|
|
5769
|
+
holder = JSON.parse(raw);
|
|
5770
|
+
} catch {
|
|
5771
|
+
holder = void 0;
|
|
5772
|
+
}
|
|
5773
|
+
const stale = age2 > REPO_LOCK_WAIT_MS || (holder ? !pidAlive(holder.pid) : age2 > 5e3);
|
|
5774
|
+
if (stale) {
|
|
5775
|
+
try {
|
|
5776
|
+
if (fs30.readFileSync(file, "utf8") === raw)
|
|
5777
|
+
fs30.rmSync(file, { force: true });
|
|
5778
|
+
} catch {
|
|
5779
|
+
}
|
|
5780
|
+
return { state: "retry" };
|
|
5781
|
+
}
|
|
5782
|
+
return { state: "wait", ...holder ? { holder: holder.pid } : {} };
|
|
5783
|
+
}
|
|
5784
|
+
function releaseRepoLock(file, mine) {
|
|
5785
|
+
try {
|
|
5786
|
+
if (fs30.readFileSync(file, "utf8") === mine)
|
|
5787
|
+
fs30.rmSync(file, { force: true, recursive: true, maxRetries: 5 });
|
|
5788
|
+
} catch {
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
async function withRepoLock(common, fn) {
|
|
5792
|
+
const file = path26.join(common, "lobstah-git.lock");
|
|
5793
|
+
const mine = JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5794
|
+
const deadline = Date.now() + REPO_LOCK_WAIT_MS;
|
|
5795
|
+
let held = false;
|
|
5796
|
+
while (Date.now() < deadline) {
|
|
5797
|
+
const took = takeRepoLock(file, mine);
|
|
5798
|
+
if (took.state === "held") {
|
|
5799
|
+
held = true;
|
|
5800
|
+
break;
|
|
5801
|
+
}
|
|
5802
|
+
if (took.state === "wait")
|
|
5803
|
+
await new Promise((r) => setTimeout(r, 50 + Math.random() * 50));
|
|
5804
|
+
}
|
|
5805
|
+
try {
|
|
5806
|
+
await fn();
|
|
5807
|
+
} finally {
|
|
5808
|
+
if (held)
|
|
5809
|
+
releaseRepoLock(file, mine);
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
function withRepoLockSync(cwd, fn) {
|
|
5813
|
+
let common;
|
|
5814
|
+
try {
|
|
5815
|
+
common = execFileSync3("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
5816
|
+
} catch {
|
|
5817
|
+
return fn();
|
|
5818
|
+
}
|
|
5819
|
+
const file = path26.join(common, "lobstah-git.lock");
|
|
5820
|
+
const mine = JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5821
|
+
const deadline = Date.now() + REPO_LOCK_WAIT_MS;
|
|
5822
|
+
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
5823
|
+
let held = false;
|
|
5824
|
+
while (Date.now() < deadline) {
|
|
5825
|
+
const took = takeRepoLock(file, mine);
|
|
5826
|
+
if (took.state === "held") {
|
|
5827
|
+
held = true;
|
|
5828
|
+
break;
|
|
5829
|
+
}
|
|
5830
|
+
if (took.state === "wait") {
|
|
5831
|
+
if (took.holder === process.pid)
|
|
5832
|
+
break;
|
|
5833
|
+
Atomics.wait(pause, 0, 0, 50 + Math.random() * 50);
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
try {
|
|
5837
|
+
return fn();
|
|
5838
|
+
} finally {
|
|
5839
|
+
if (held)
|
|
5840
|
+
releaseRepoLock(file, mine);
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
async function oneAtATime(cwd, fn) {
|
|
5844
|
+
const common = await git2(cwd, "rev-parse", "--path-format=absolute", "--git-common-dir");
|
|
5845
|
+
const key = realpath(common);
|
|
5846
|
+
const run4 = (repoQueues.get(key) ?? Promise.resolve()).then(() => withRepoLock(common, () => fn(common)));
|
|
5847
|
+
const tail = run4.catch(() => {
|
|
5848
|
+
});
|
|
5849
|
+
repoQueues.set(key, tail);
|
|
5850
|
+
try {
|
|
5851
|
+
await run4;
|
|
5852
|
+
} finally {
|
|
5853
|
+
if (repoQueues.get(key) === tail)
|
|
5854
|
+
repoQueues.delete(key);
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5857
|
+
function fetchStamps(common) {
|
|
5858
|
+
try {
|
|
5859
|
+
return JSON.parse(fs30.readFileSync(path26.join(common, "lobstah-fetch.json"), "utf8"));
|
|
5860
|
+
} catch {
|
|
5861
|
+
return {};
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
async function fetchShared(cwd, remote, ref) {
|
|
5865
|
+
const asked = Date.now();
|
|
5866
|
+
const key = `${remote} ${ref}`;
|
|
5867
|
+
await oneAtATime(cwd, async (common) => {
|
|
5868
|
+
if ((fetchStamps(common)[key] ?? -Infinity) >= asked)
|
|
5869
|
+
return;
|
|
5870
|
+
const started = Date.now();
|
|
5871
|
+
await gitShared(cwd, "fetch", remote, ref);
|
|
5872
|
+
const stamps = { ...fetchStamps(common), [key]: started };
|
|
5873
|
+
fs30.writeFileSync(path26.join(common, "lobstah-fetch.json"), JSON.stringify(stamps, null, 2));
|
|
5874
|
+
});
|
|
5875
|
+
}
|
|
5635
5876
|
async function allocate(repo, id, fromRemoteBranch = repo.trunk) {
|
|
5636
5877
|
const dir = worktreePath(id);
|
|
5637
5878
|
if (fs30.existsSync(dir)) {
|
|
@@ -5642,8 +5883,10 @@ async function allocate(repo, id, fromRemoteBranch = repo.trunk) {
|
|
|
5642
5883
|
throw new Error(`repo path ${repo.path} missing and no origin configured`);
|
|
5643
5884
|
await run("git", ["clone", repo.origin, repo.path], { env: process.env });
|
|
5644
5885
|
}
|
|
5645
|
-
await
|
|
5646
|
-
await
|
|
5886
|
+
await fetchShared(repo.path, "origin", fromRemoteBranch);
|
|
5887
|
+
await oneAtATime(repo.path, async () => {
|
|
5888
|
+
await gitShared(repo.path, "worktree", "add", "--no-track", dir, "-b", `lobstah/${id}`, `origin/${fromRemoteBranch}`);
|
|
5889
|
+
});
|
|
5647
5890
|
await runSetup(repo, dir);
|
|
5648
5891
|
return dir;
|
|
5649
5892
|
}
|
|
@@ -5760,7 +6003,7 @@ async function chooseWorktree(input) {
|
|
|
5760
6003
|
return { reuse: true, path: found.path, owner: found.owner, from: found.from };
|
|
5761
6004
|
}
|
|
5762
6005
|
async function prepareReuse(repo, dir) {
|
|
5763
|
-
await
|
|
6006
|
+
await fetchShared(dir, "origin", repo.trunk);
|
|
5764
6007
|
if (!repo.setup?.length)
|
|
5765
6008
|
return { setupRan: false };
|
|
5766
6009
|
if (recordedSetupHash(dir) === setupHash(repo, dir))
|
|
@@ -5876,7 +6119,7 @@ async function removeIfSafe(dir, opts = {}) {
|
|
|
5876
6119
|
}
|
|
5877
6120
|
return { removed: true, primary, deletedBranches, keptBranches };
|
|
5878
6121
|
}
|
|
5879
|
-
var run, shell, LOCK_CONTENTION, ATTEMPTS, LOCKFILES, short;
|
|
6122
|
+
var run, shell, LOCK_CONTENTION, ATTEMPTS, REPO_LOCK_WAIT_MS, repoQueues, BUSY, LOCKFILES, short;
|
|
5880
6123
|
var init_dist3 = __esm({
|
|
5881
6124
|
"packages/worktree/dist/index.js"() {
|
|
5882
6125
|
"use strict";
|
|
@@ -5901,9 +6144,17 @@ var init_dist3 = __esm({
|
|
|
5901
6144
|
// held by another process ("fetching ref <ref> failed: reference already
|
|
5902
6145
|
// exists"). The files backend maps the lock's EEXIST to this reason; older
|
|
5903
6146
|
// git printed "Unable to create '<ref>.lock': File exists" instead.
|
|
5904
|
-
/reference already exists
|
|
6147
|
+
/reference already exists/,
|
|
6148
|
+
// Windows: two fetches unpack the same loose object, and one cannot write
|
|
6149
|
+
// the file the other holds open ("unable to write file
|
|
6150
|
+
// .git/objects/<xx>/<rest>: Permission denied", then "unpack-objects
|
|
6151
|
+
// failed"). Only an object path matches, not any denied write.
|
|
6152
|
+
/unable to write file \S*objects[\\/][0-9a-f]{2}[\\/][0-9a-f]{38,}: Permission denied/
|
|
5905
6153
|
];
|
|
5906
6154
|
ATTEMPTS = 6;
|
|
6155
|
+
REPO_LOCK_WAIT_MS = 5 * 6e4;
|
|
6156
|
+
repoQueues = /* @__PURE__ */ new Map();
|
|
6157
|
+
BUSY = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
5907
6158
|
LOCKFILES = [
|
|
5908
6159
|
"pnpm-lock.yaml",
|
|
5909
6160
|
"package-lock.json",
|
|
@@ -5958,7 +6209,7 @@ var init_types2 = __esm({
|
|
|
5958
6209
|
return Promise.resolve({ value: item, done: false });
|
|
5959
6210
|
if (this.closed)
|
|
5960
6211
|
return Promise.resolve({ value: void 0, done: true });
|
|
5961
|
-
return new Promise((
|
|
6212
|
+
return new Promise((resolve14) => this.waiters.push(resolve14));
|
|
5962
6213
|
}
|
|
5963
6214
|
};
|
|
5964
6215
|
}
|
|
@@ -5983,7 +6234,7 @@ var init_types2 = __esm({
|
|
|
5983
6234
|
return msg;
|
|
5984
6235
|
if (this.ended)
|
|
5985
6236
|
return void 0;
|
|
5986
|
-
await new Promise((
|
|
6237
|
+
await new Promise((resolve14) => this.waiter = resolve14);
|
|
5987
6238
|
this.waiter = null;
|
|
5988
6239
|
}
|
|
5989
6240
|
}
|
|
@@ -6122,20 +6373,20 @@ function startClaudeCli(invocation, opts) {
|
|
|
6122
6373
|
}
|
|
6123
6374
|
}
|
|
6124
6375
|
});
|
|
6125
|
-
const done = new Promise((
|
|
6376
|
+
const done = new Promise((resolve14) => {
|
|
6126
6377
|
child.on("error", (err) => {
|
|
6127
6378
|
events.push({ at: now(), type: "error", data: { message: err.message } });
|
|
6128
6379
|
events.close();
|
|
6129
|
-
|
|
6380
|
+
resolve14({ sessionId, error: err.message });
|
|
6130
6381
|
});
|
|
6131
6382
|
child.on("exit", (code) => {
|
|
6132
6383
|
events.close();
|
|
6133
6384
|
if (code === 0)
|
|
6134
|
-
|
|
6385
|
+
resolve14({ sessionId });
|
|
6135
6386
|
else {
|
|
6136
6387
|
const detail = stderrTail.trim().split("\n").at(-1);
|
|
6137
6388
|
const error = `claude exited ${code}${detail ? ` \u2014 ${detail}` : ""}`;
|
|
6138
|
-
|
|
6389
|
+
resolve14({ sessionId, error });
|
|
6139
6390
|
}
|
|
6140
6391
|
});
|
|
6141
6392
|
});
|
|
@@ -6228,25 +6479,25 @@ var init_claude = __esm({
|
|
|
6228
6479
|
});
|
|
6229
6480
|
|
|
6230
6481
|
// packages/adapters/dist/codex.js
|
|
6231
|
-
import * as
|
|
6482
|
+
import * as fs69 from "node:fs";
|
|
6232
6483
|
import * as os10 from "node:os";
|
|
6233
|
-
import * as
|
|
6484
|
+
import * as path56 from "node:path";
|
|
6234
6485
|
import { spawn as spawn6 } from "node:child_process";
|
|
6235
6486
|
function isolatedCodexHome() {
|
|
6236
|
-
const dir =
|
|
6237
|
-
|
|
6238
|
-
const auth =
|
|
6239
|
-
const realAuth =
|
|
6240
|
-
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)) {
|
|
6241
6492
|
try {
|
|
6242
|
-
|
|
6493
|
+
fs69.symlinkSync(realAuth, auth);
|
|
6243
6494
|
} catch {
|
|
6244
|
-
|
|
6495
|
+
fs69.copyFileSync(realAuth, auth);
|
|
6245
6496
|
}
|
|
6246
6497
|
}
|
|
6247
|
-
const cfg =
|
|
6248
|
-
if (!
|
|
6249
|
-
|
|
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");
|
|
6250
6501
|
}
|
|
6251
6502
|
return dir;
|
|
6252
6503
|
}
|
|
@@ -6298,7 +6549,7 @@ function startCodexCli(invoke, opts) {
|
|
|
6298
6549
|
let current;
|
|
6299
6550
|
let killed = false;
|
|
6300
6551
|
function runTurn(prompt, resumeThread) {
|
|
6301
|
-
return new Promise((
|
|
6552
|
+
return new Promise((resolve14) => {
|
|
6302
6553
|
const inv = invoke(codexExecArgs(opts, prompt, resumeThread));
|
|
6303
6554
|
const child = spawn6(inv.file, inv.argv, {
|
|
6304
6555
|
cwd: opts.cwd,
|
|
@@ -6329,14 +6580,14 @@ function startCodexCli(invoke, opts) {
|
|
|
6329
6580
|
}
|
|
6330
6581
|
}
|
|
6331
6582
|
});
|
|
6332
|
-
child.on("error", (err) =>
|
|
6583
|
+
child.on("error", (err) => resolve14(err.message));
|
|
6333
6584
|
child.on("exit", (code) => {
|
|
6334
6585
|
if (turnError)
|
|
6335
|
-
return
|
|
6586
|
+
return resolve14(turnError);
|
|
6336
6587
|
if (code === 0 || killed)
|
|
6337
|
-
return
|
|
6588
|
+
return resolve14(void 0);
|
|
6338
6589
|
const detail = stderrTail.trim().split("\n").at(-1);
|
|
6339
|
-
|
|
6590
|
+
resolve14(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
6340
6591
|
});
|
|
6341
6592
|
});
|
|
6342
6593
|
}
|
|
@@ -6548,7 +6799,7 @@ async function drive(run4, opts) {
|
|
|
6548
6799
|
let finalAt = 0;
|
|
6549
6800
|
let exit;
|
|
6550
6801
|
let abandon;
|
|
6551
|
-
const abandoned = new Promise((
|
|
6802
|
+
const abandoned = new Promise((resolve14) => abandon = resolve14);
|
|
6552
6803
|
const clearExit = () => {
|
|
6553
6804
|
if (!exit)
|
|
6554
6805
|
return;
|
|
@@ -6721,7 +6972,7 @@ var init_drive = __esm({
|
|
|
6721
6972
|
"use strict";
|
|
6722
6973
|
init_dist2();
|
|
6723
6974
|
WAITING_VERBS = ["needs-decision", "blocked", "paused"];
|
|
6724
|
-
sleep = (ms) => new Promise((
|
|
6975
|
+
sleep = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
6725
6976
|
}
|
|
6726
6977
|
});
|
|
6727
6978
|
|
|
@@ -6813,6 +7064,7 @@ var init_plan = __esm({
|
|
|
6813
7064
|
function startWallClock(opts) {
|
|
6814
7065
|
const now2 = opts.now ?? Date.now;
|
|
6815
7066
|
const tickMs = Math.max(1, Math.min(opts.tickMs ?? 5e3, opts.limitMs));
|
|
7067
|
+
const sleepGapMs = opts.sleepGapMs ?? Math.max(6e4, tickMs * 6);
|
|
6816
7068
|
let elapsed = opts.initialElapsedMs ?? 0;
|
|
6817
7069
|
const maxMs = Math.max(opts.limitMs, opts.maxMs ?? opts.limitMs);
|
|
6818
7070
|
let windowMs = Math.min(maxMs, Math.max(opts.limitMs, opts.initialWindowMs ?? opts.limitMs));
|
|
@@ -6826,8 +7078,9 @@ function startWallClock(opts) {
|
|
|
6826
7078
|
} catch {
|
|
6827
7079
|
paused = false;
|
|
6828
7080
|
}
|
|
7081
|
+
const step = Math.max(0, t - last);
|
|
6829
7082
|
if (!paused)
|
|
6830
|
-
elapsed +=
|
|
7083
|
+
elapsed += step > sleepGapMs ? Math.min(step, tickMs) : step;
|
|
6831
7084
|
last = t;
|
|
6832
7085
|
if (!done && elapsed >= windowMs && elapsed < maxMs) {
|
|
6833
7086
|
let advanced = false;
|
|
@@ -6866,8 +7119,8 @@ var init_wallclock = __esm({
|
|
|
6866
7119
|
import { execFile as execFile4 } from "node:child_process";
|
|
6867
7120
|
import { promisify as promisify4 } from "node:util";
|
|
6868
7121
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6869
|
-
import * as
|
|
6870
|
-
import * as
|
|
7122
|
+
import * as fs70 from "node:fs";
|
|
7123
|
+
import * as path57 from "node:path";
|
|
6871
7124
|
function firstLine2(err) {
|
|
6872
7125
|
const stderr = err && typeof err === "object" && "stderr" in err ? String(err.stderr).trim() : "";
|
|
6873
7126
|
return (stderr || (err instanceof Error ? err.message : String(err))).replace(/\s+/g, " ").slice(0, 240);
|
|
@@ -6900,10 +7153,10 @@ function keepRemote(opts) {
|
|
|
6900
7153
|
if (existingPrUrl)
|
|
6901
7154
|
mergeEvidence(id, lane, { prUrl: existingPrUrl });
|
|
6902
7155
|
const registerWatch = async (url) => {
|
|
6903
|
-
const root =
|
|
6904
|
-
const localCli =
|
|
7156
|
+
const root = path57.resolve(path57.dirname(fileURLToPath4(import.meta.url)), "..", "..", "..");
|
|
7157
|
+
const localCli = path57.join(root, "apps", "cli", "dist", "main.js");
|
|
6905
7158
|
const args = ["watch", "add", url, "--for", id];
|
|
6906
|
-
if (
|
|
7159
|
+
if (fs70.existsSync(localCli))
|
|
6907
7160
|
await command(process.execPath, [localCli, ...args], 15e3);
|
|
6908
7161
|
else
|
|
6909
7162
|
await command("lobstah", args, 15e3);
|
|
@@ -7065,13 +7318,23 @@ var init_remote = __esm({
|
|
|
7065
7318
|
});
|
|
7066
7319
|
|
|
7067
7320
|
// packages/runner/dist/run.js
|
|
7068
|
-
import * as
|
|
7069
|
-
import * as
|
|
7070
|
-
import { execFileSync as
|
|
7321
|
+
import * as fs71 from "node:fs";
|
|
7322
|
+
import * as path58 from "node:path";
|
|
7323
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
7071
7324
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7325
|
+
function livePrState(url) {
|
|
7326
|
+
const ref = parsePrRef(url);
|
|
7327
|
+
if (!ref)
|
|
7328
|
+
return void 0;
|
|
7329
|
+
try {
|
|
7330
|
+
return ghPrView(ref).state;
|
|
7331
|
+
} catch {
|
|
7332
|
+
return void 0;
|
|
7333
|
+
}
|
|
7334
|
+
}
|
|
7072
7335
|
function within(p, ms, fallback) {
|
|
7073
7336
|
let timer;
|
|
7074
|
-
const late = new Promise((
|
|
7337
|
+
const late = new Promise((resolve14) => timer = setTimeout(() => resolve14(fallback), ms));
|
|
7075
7338
|
return Promise.race([p, late]).finally(() => clearTimeout(timer));
|
|
7076
7339
|
}
|
|
7077
7340
|
function boundPr(descriptor, lane) {
|
|
@@ -7089,18 +7352,18 @@ function coldNote(cold, cwd, trunk) {
|
|
|
7089
7352
|
}
|
|
7090
7353
|
async function main(activeDir, lane, seams = {}) {
|
|
7091
7354
|
const deps = { ...defaultDeps, ...seams };
|
|
7092
|
-
const id =
|
|
7355
|
+
const id = path58.basename(activeDir);
|
|
7093
7356
|
if (isTrapCatch(id, lane))
|
|
7094
7357
|
throw new Error(`refusing headless runner for trap catch ${id}`);
|
|
7095
7358
|
const status = (verb, note) => appendStatus(id, lane, verb, note);
|
|
7096
7359
|
const cfg = loadConfig();
|
|
7097
|
-
const descriptor = JSON.parse(
|
|
7360
|
+
const descriptor = JSON.parse(fs71.readFileSync(path58.join(activeDir, "descriptor.json"), "utf8"));
|
|
7098
7361
|
const resolved = resolveDispatch(descriptor, cfg);
|
|
7099
7362
|
const repo = cfg.repos[descriptor.repo];
|
|
7100
|
-
const briefFile =
|
|
7101
|
-
if (!
|
|
7102
|
-
|
|
7103
|
-
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");
|
|
7104
7367
|
const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
|
|
7105
7368
|
const wake = process.env.LOBSTAH_WAKE;
|
|
7106
7369
|
const runnerInfo = {
|
|
@@ -7108,7 +7371,15 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7108
7371
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7109
7372
|
attempts
|
|
7110
7373
|
};
|
|
7111
|
-
|
|
7374
|
+
fs71.writeFileSync(path58.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
|
|
7375
|
+
if (descriptor.systemRepair && descriptor.pr?.url) {
|
|
7376
|
+
const state = deps.prState(descriptor.pr.url) ?? readPr(parsePrRef(descriptor.pr.url)?.key ?? "")?.state;
|
|
7377
|
+
if (state === "MERGED" || state === "CLOSED") {
|
|
7378
|
+
status("failed", `cancelled: ${descriptor.pr.url} is ${state.toLowerCase()}; nothing to repair`);
|
|
7379
|
+
complete(id, lane);
|
|
7380
|
+
return;
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7112
7383
|
const plan = planStart({
|
|
7113
7384
|
id,
|
|
7114
7385
|
lane,
|
|
@@ -7117,14 +7388,14 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7117
7388
|
resolvedHarness: resolved.harness,
|
|
7118
7389
|
envResume: process.env.LOBSTAH_RESUME
|
|
7119
7390
|
});
|
|
7120
|
-
const wtFile =
|
|
7391
|
+
const wtFile = path58.join(activeDir, "worktree.json");
|
|
7121
7392
|
let recorded;
|
|
7122
7393
|
let choice;
|
|
7123
|
-
if (
|
|
7124
|
-
recorded = JSON.parse(
|
|
7125
|
-
if (!
|
|
7394
|
+
if (fs71.existsSync(wtFile)) {
|
|
7395
|
+
recorded = JSON.parse(fs71.readFileSync(wtFile, "utf8")).path;
|
|
7396
|
+
if (!fs71.existsSync(recorded))
|
|
7126
7397
|
throw new Error(`recorded worktree ${recorded} is gone`);
|
|
7127
|
-
} else if (
|
|
7398
|
+
} else if (fs71.existsSync(worktreePath(id))) {
|
|
7128
7399
|
throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
|
|
7129
7400
|
} else if (descriptor.followUp && cfg.limits.reuseWorktree !== false) {
|
|
7130
7401
|
choice = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
|
|
@@ -7159,13 +7430,13 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7159
7430
|
type: "runner",
|
|
7160
7431
|
data: { worktree: cwd, worktreeOf: choice.owner, setupRan }
|
|
7161
7432
|
});
|
|
7162
|
-
|
|
7433
|
+
fs71.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
|
|
7163
7434
|
} else {
|
|
7164
7435
|
cwd = await deps.allocate(repo, id, descriptor.systemRepair ? descriptor.pr?.headRefName : void 0);
|
|
7165
7436
|
acquireWorktreeLock(cwd, id, lane);
|
|
7166
|
-
|
|
7437
|
+
fs71.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
|
|
7167
7438
|
}
|
|
7168
|
-
const worktreeOf = JSON.parse(
|
|
7439
|
+
const worktreeOf = JSON.parse(fs71.readFileSync(wtFile, "utf8")).of;
|
|
7169
7440
|
mergeEvidence(id, lane, { worktree: cwd, worktreeOf });
|
|
7170
7441
|
const existingPr = boundPr(descriptor, lane);
|
|
7171
7442
|
if (existingPr)
|
|
@@ -7199,21 +7470,21 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7199
7470
|
const envNudge = [process.env.LOBSTAH_NUDGE, wakeNote()].filter(Boolean).join("\n\n") || void 0;
|
|
7200
7471
|
const planNudge = plan.cold && !envNudge ? coldNote(plan.cold, cwd, repo.trunk) : void 0;
|
|
7201
7472
|
const promptWith = (nudge) => buildPrompt(brief, { id, nudge, attachments: descriptor.attachments, existingPr });
|
|
7202
|
-
const binDir =
|
|
7203
|
-
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;
|
|
7204
7475
|
let wallClockHit = false;
|
|
7205
7476
|
let current;
|
|
7206
|
-
const wallFile =
|
|
7477
|
+
const wallFile = path58.join(activeDir, "wallclock.json");
|
|
7207
7478
|
const wallState = (() => {
|
|
7208
7479
|
try {
|
|
7209
|
-
return JSON.parse(
|
|
7480
|
+
return JSON.parse(fs71.readFileSync(wallFile, "utf8"));
|
|
7210
7481
|
} catch {
|
|
7211
7482
|
return { elapsedMs: 0, windowMs: resolved.limits.wallClockSecs * 1e3 };
|
|
7212
7483
|
}
|
|
7213
7484
|
})();
|
|
7214
7485
|
const head = () => {
|
|
7215
7486
|
try {
|
|
7216
|
-
return
|
|
7487
|
+
return execFileSync9("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
7217
7488
|
} catch {
|
|
7218
7489
|
return void 0;
|
|
7219
7490
|
}
|
|
@@ -7222,8 +7493,8 @@ async function main(activeDir, lane, seams = {}) {
|
|
|
7222
7493
|
const maxWallMs = Math.max(resolved.limits.wallClockSecs ?? 0, cfg.limits.maxWallClockSecs ?? (resolved.limits.wallClockSecs ?? 0) * 4) * 1e3;
|
|
7223
7494
|
const persistWall = (elapsedMs, windowMs) => {
|
|
7224
7495
|
const tmp = `${wallFile}.tmp-${process.pid}`;
|
|
7225
|
-
|
|
7226
|
-
|
|
7496
|
+
fs71.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
|
|
7497
|
+
fs71.renameSync(tmp, wallFile);
|
|
7227
7498
|
};
|
|
7228
7499
|
const madeProgress = () => {
|
|
7229
7500
|
const activity = readActivity(id, lane);
|
|
@@ -7393,7 +7664,8 @@ var init_run = __esm({
|
|
|
7393
7664
|
chooseWorktree,
|
|
7394
7665
|
prepareReuse,
|
|
7395
7666
|
collectEvidence,
|
|
7396
|
-
reap: async () => 0
|
|
7667
|
+
reap: async () => 0,
|
|
7668
|
+
prState: livePrState
|
|
7397
7669
|
};
|
|
7398
7670
|
DONE_WAIT_MS = 2e3;
|
|
7399
7671
|
short3 = (s) => s.slice(0, 8);
|
|
@@ -7401,10 +7673,10 @@ var init_run = __esm({
|
|
|
7401
7673
|
});
|
|
7402
7674
|
|
|
7403
7675
|
// packages/runner/dist/reap.js
|
|
7404
|
-
import { execFileSync as
|
|
7676
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
7405
7677
|
function listProcesses() {
|
|
7406
7678
|
if (process.platform === "win32") {
|
|
7407
|
-
const out2 =
|
|
7679
|
+
const out2 = execFileSync10("powershell.exe", [
|
|
7408
7680
|
"-NoProfile",
|
|
7409
7681
|
"-NonInteractive",
|
|
7410
7682
|
"-Command",
|
|
@@ -7412,7 +7684,7 @@ function listProcesses() {
|
|
|
7412
7684
|
], { encoding: "utf8", windowsHide: true, timeout: 3e4, stdio: ["ignore", "pipe", "ignore"] });
|
|
7413
7685
|
return out2.split(/\r?\n/).map((l) => l.trim().split(/\s+/).map(Number)).filter(([pid, ppid]) => Number.isInteger(pid) && Number.isInteger(ppid)).map(([pid, ppid]) => ({ pid, ppid }));
|
|
7414
7686
|
}
|
|
7415
|
-
const out =
|
|
7687
|
+
const out = execFileSync10("ps", ["-A", "-o", "pid=,ppid=,pgid="], {
|
|
7416
7688
|
encoding: "utf8",
|
|
7417
7689
|
timeout: 3e4,
|
|
7418
7690
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -7452,7 +7724,7 @@ async function stopPids(pids, graceMs = 2e3) {
|
|
|
7452
7724
|
if (process.platform === "win32") {
|
|
7453
7725
|
for (const pid of pids) {
|
|
7454
7726
|
try {
|
|
7455
|
-
|
|
7727
|
+
execFileSync10("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 3e4 });
|
|
7456
7728
|
} catch {
|
|
7457
7729
|
}
|
|
7458
7730
|
}
|
|
@@ -7509,17 +7781,17 @@ __export(dist_exports, {
|
|
|
7509
7781
|
startedBy: () => startedBy,
|
|
7510
7782
|
stopPids: () => stopPids
|
|
7511
7783
|
});
|
|
7512
|
-
import * as
|
|
7513
|
-
import * as
|
|
7784
|
+
import * as fs72 from "node:fs";
|
|
7785
|
+
import * as path59 from "node:path";
|
|
7514
7786
|
function runRunner(activeDirArg2, laneArg2) {
|
|
7515
7787
|
const lane = laneArg2 === "chore" ? "chore" : "work";
|
|
7516
|
-
const id =
|
|
7788
|
+
const id = path59.basename(activeDirArg2);
|
|
7517
7789
|
main(activeDirArg2, lane, { reap: () => reapStarted(process.pid) }).catch((err) => {
|
|
7518
7790
|
try {
|
|
7519
7791
|
if (!isFinished(id, lane)) {
|
|
7520
7792
|
appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
|
|
7521
7793
|
}
|
|
7522
|
-
|
|
7794
|
+
fs72.renameSync(activeDirArg2, path59.join(laneDirs(lane).done, id));
|
|
7523
7795
|
} catch {
|
|
7524
7796
|
}
|
|
7525
7797
|
process.exitCode = 1;
|
|
@@ -7547,16 +7819,16 @@ var init_dist5 = __esm({
|
|
|
7547
7819
|
// apps/cli/src/main.ts
|
|
7548
7820
|
init_dist2();
|
|
7549
7821
|
init_dist3();
|
|
7550
|
-
import * as
|
|
7822
|
+
import * as fs73 from "node:fs";
|
|
7551
7823
|
import * as os11 from "node:os";
|
|
7552
|
-
import * as
|
|
7824
|
+
import * as path60 from "node:path";
|
|
7553
7825
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
7554
|
-
import { spawnSync as
|
|
7826
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
7555
7827
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
7556
7828
|
|
|
7557
7829
|
// packages/supervisor/dist/liveness.js
|
|
7558
7830
|
init_dist2();
|
|
7559
|
-
import { execFileSync as
|
|
7831
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
7560
7832
|
function classify(input) {
|
|
7561
7833
|
const { hasRunner, alive: alive2, lastVerb, lastEventAt: lastEventAt2, startedAt, now: now2, wedgeThresholdMs } = input;
|
|
7562
7834
|
if (lastVerb && TERMINAL_VERBS.includes(lastVerb))
|
|
@@ -7577,15 +7849,15 @@ function classify(input) {
|
|
|
7577
7849
|
function processStartTime(pid) {
|
|
7578
7850
|
try {
|
|
7579
7851
|
if (process.platform === "win32") {
|
|
7580
|
-
const out =
|
|
7852
|
+
const out = execFileSync4("powershell.exe", ["-NoProfile", "-Command", `(Get-Process -Id ${pid}).StartTime.Ticks`], { encoding: "utf8" }).trim();
|
|
7581
7853
|
return out || void 0;
|
|
7582
7854
|
}
|
|
7583
|
-
return
|
|
7855
|
+
return execFileSync4("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8" }).trim() || void 0;
|
|
7584
7856
|
} catch {
|
|
7585
7857
|
return void 0;
|
|
7586
7858
|
}
|
|
7587
7859
|
}
|
|
7588
|
-
function
|
|
7860
|
+
function pidAlive2(pid, recordedStart) {
|
|
7589
7861
|
try {
|
|
7590
7862
|
process.kill(pid, 0);
|
|
7591
7863
|
} catch {
|
|
@@ -7599,7 +7871,7 @@ function pidAlive(pid, recordedStart) {
|
|
|
7599
7871
|
function killGroup(pid, signal = "SIGTERM") {
|
|
7600
7872
|
if (process.platform === "win32") {
|
|
7601
7873
|
try {
|
|
7602
|
-
|
|
7874
|
+
execFileSync4("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
7603
7875
|
} catch {
|
|
7604
7876
|
}
|
|
7605
7877
|
return;
|
|
@@ -7820,7 +8092,7 @@ function sessionOf(st) {
|
|
|
7820
8092
|
return void 0;
|
|
7821
8093
|
}
|
|
7822
8094
|
}
|
|
7823
|
-
function reconcileOne(st, cfg, log, spawnHeadless = spawnRunner) {
|
|
8095
|
+
function reconcileOne(st, cfg, log, spawnHeadless = spawnRunner, resumeGrace = false) {
|
|
7824
8096
|
const hasDescriptor = fs32.existsSync(path28.join(st.dir, "descriptor.json"));
|
|
7825
8097
|
if (!hasDescriptor) {
|
|
7826
8098
|
const age2 = Date.now() - fs32.statSync(st.dir).mtimeMs;
|
|
@@ -7830,7 +8102,7 @@ function reconcileOne(st, cfg, log, spawnHeadless = spawnRunner) {
|
|
|
7830
8102
|
}
|
|
7831
8103
|
const statusLog = readStatusLog(st.id, st.lane);
|
|
7832
8104
|
const lastVerb = statusLog.at(-1)?.verb;
|
|
7833
|
-
const alive2 = st.runner ?
|
|
8105
|
+
const alive2 = st.runner ? pidAlive2(st.runner.pid, st.runner.processStartTime) : void 0;
|
|
7834
8106
|
const sessionClaim = readSessionClaim(st.id, st.lane);
|
|
7835
8107
|
if (sessionClaim) {
|
|
7836
8108
|
if (lastVerb === "done" || lastVerb === "failed") {
|
|
@@ -7900,6 +8172,8 @@ function reconcileOne(st, cfg, log, spawnHeadless = spawnRunner) {
|
|
|
7900
8172
|
break;
|
|
7901
8173
|
}
|
|
7902
8174
|
case "wedged": {
|
|
8175
|
+
if (resumeGrace)
|
|
8176
|
+
break;
|
|
7903
8177
|
const attempts = (st.runner?.attempts ?? 0) + 1;
|
|
7904
8178
|
if (st.runner)
|
|
7905
8179
|
killGroup(st.runner.pid, "SIGKILL");
|
|
@@ -7933,7 +8207,7 @@ function wakeReason(id, lane, now2 = Date.now()) {
|
|
|
7933
8207
|
}
|
|
7934
8208
|
function wakeParked(lane, free, log, spawnHeadless = spawnRunner, now2 = Date.now()) {
|
|
7935
8209
|
let woke = 0;
|
|
7936
|
-
const parked = listActive(lane).filter((st) => st.runner && !isTrapCatch(st.id, lane) && isParked(st.id, lane) && !cancelRequested(st.id, lane) && !
|
|
8210
|
+
const parked = listActive(lane).filter((st) => st.runner && !isTrapCatch(st.id, lane) && isParked(st.id, lane) && !cancelRequested(st.id, lane) && !pidAlive2(st.runner.pid, st.runner.processStartTime));
|
|
7937
8211
|
for (const st of parked) {
|
|
7938
8212
|
if (woke >= free)
|
|
7939
8213
|
break;
|
|
@@ -8068,10 +8342,12 @@ function tick(log = () => {
|
|
|
8068
8342
|
ensureLayout();
|
|
8069
8343
|
const now2 = hooks.now?.() ?? Date.now();
|
|
8070
8344
|
const ttl = cfg.soak.ttlSecs * 1e3;
|
|
8345
|
+
const wedgeMs = cfg.limits.wedgeThresholdSecs * 1e3;
|
|
8071
8346
|
const home = lobstahHome();
|
|
8072
8347
|
const timing = tickTimes.get(home);
|
|
8073
|
-
const resumed = timing && now2 - timing.last > ttl ? now2 : timing?.resumed;
|
|
8348
|
+
const resumed = timing && now2 - timing.last > Math.min(ttl, wedgeMs) ? now2 : timing?.resumed;
|
|
8074
8349
|
tickTimes.set(home, { last: now2, resumed });
|
|
8350
|
+
const wedgeGrace = resumed !== void 0 && now2 - resumed <= wedgeMs;
|
|
8075
8351
|
writeHeartbeat(cfg);
|
|
8076
8352
|
hooks.prWatches?.(hooks.now?.() ?? Date.now(), log);
|
|
8077
8353
|
for (const action of resumed !== void 0 && now2 - resumed <= ttl ? [] : sweepGhostTraps(ttl, now2, cfg.soak.pausedTtlSecs * 1e3)) {
|
|
@@ -8086,7 +8362,7 @@ function tick(log = () => {
|
|
|
8086
8362
|
const skipFor = (lane) => lane === "work" ? workSkip : choreSkip;
|
|
8087
8363
|
for (const lane of ["chore", "work"]) {
|
|
8088
8364
|
for (const st of listActive(lane))
|
|
8089
|
-
reconcileOne(st, cfg, log, hooks.spawnRunner);
|
|
8365
|
+
reconcileOne(st, cfg, log, hooks.spawnRunner, wedgeGrace);
|
|
8090
8366
|
}
|
|
8091
8367
|
retentionPass(cfg, hooks, log);
|
|
8092
8368
|
const hasHeadlessSlot = (lane) => slotUsage(lane).headless < (lane === "work" ? cfg.limits.maxConcurrent : cfg.limits.choreConcurrent);
|
|
@@ -8115,7 +8391,7 @@ function tick(log = () => {
|
|
|
8115
8391
|
inFlight++;
|
|
8116
8392
|
const st = listActive(lane).find((s) => s.id === id);
|
|
8117
8393
|
if (st)
|
|
8118
|
-
reconcileOne(st, cfg, log, hooks.spawnRunner);
|
|
8394
|
+
reconcileOne(st, cfg, log, hooks.spawnRunner, wedgeGrace);
|
|
8119
8395
|
}
|
|
8120
8396
|
}
|
|
8121
8397
|
if (cfg.notifyCommand) {
|
|
@@ -8135,7 +8411,7 @@ function acquireDaemonLock() {
|
|
|
8135
8411
|
const lockPath = path28.join(lobstahHome(), "daemon.lock");
|
|
8136
8412
|
try {
|
|
8137
8413
|
const prior = JSON.parse(fs32.readFileSync(lockPath, "utf8"));
|
|
8138
|
-
if (
|
|
8414
|
+
if (pidAlive2(prior.pid, prior.processStartTime)) {
|
|
8139
8415
|
throw new Error(`another daemon (pid ${prior.pid}) already supervises ${lobstahHome()} \u2014 run a second instance against its own LOBSTAH_HOME instead`);
|
|
8140
8416
|
}
|
|
8141
8417
|
} catch (err) {
|
|
@@ -8182,15 +8458,15 @@ async function daemon(intervalMs = 5e3, log = console.log, hooks = {}) {
|
|
|
8182
8458
|
} catch (err) {
|
|
8183
8459
|
log(`tick error: ${err instanceof Error ? err.message : String(err)}`);
|
|
8184
8460
|
}
|
|
8185
|
-
await new Promise((
|
|
8461
|
+
await new Promise((resolve14) => {
|
|
8186
8462
|
const timer = setTimeout(() => {
|
|
8187
8463
|
wake = void 0;
|
|
8188
|
-
|
|
8464
|
+
resolve14();
|
|
8189
8465
|
}, intervalMs);
|
|
8190
8466
|
wake = () => {
|
|
8191
8467
|
wake = void 0;
|
|
8192
8468
|
clearTimeout(timer);
|
|
8193
|
-
setTimeout(
|
|
8469
|
+
setTimeout(resolve14, 50);
|
|
8194
8470
|
};
|
|
8195
8471
|
});
|
|
8196
8472
|
}
|
|
@@ -8414,6 +8690,9 @@ var PickupState = class {
|
|
|
8414
8690
|
}
|
|
8415
8691
|
};
|
|
8416
8692
|
|
|
8693
|
+
// apps/pick/dist/sources/github.js
|
|
8694
|
+
init_dist2();
|
|
8695
|
+
|
|
8417
8696
|
// apps/pick/dist/pr-evidence.js
|
|
8418
8697
|
init_dist2();
|
|
8419
8698
|
import * as fs35 from "node:fs";
|
|
@@ -8516,7 +8795,7 @@ When the change is complete, commit it, push the branch, and open a PR against $
|
|
|
8516
8795
|
repoKey: this.cfg.key,
|
|
8517
8796
|
title: pr.title,
|
|
8518
8797
|
followUp: uuid,
|
|
8519
|
-
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")
|
|
8520
8799
|
});
|
|
8521
8800
|
}
|
|
8522
8801
|
return items;
|
|
@@ -8641,6 +8920,7 @@ When the change is complete, commit it, push the branch, and open a PR against $
|
|
|
8641
8920
|
author: pr.user.login,
|
|
8642
8921
|
headSha: pr.head.sha,
|
|
8643
8922
|
headRef: pr.head.ref,
|
|
8923
|
+
baseRef: pr.base?.ref,
|
|
8644
8924
|
labels: pr.labels.map((l) => l.name),
|
|
8645
8925
|
assignees: pr.assignees.map((a) => a.login),
|
|
8646
8926
|
reviews: reviews.map((r) => ({ id: r.id, author: r.user.login, state: r.state, sha: r.commit_id })),
|
|
@@ -8862,7 +9142,7 @@ import * as path32 from "node:path";
|
|
|
8862
9142
|
init_dist2();
|
|
8863
9143
|
import * as fs36 from "node:fs";
|
|
8864
9144
|
import * as path31 from "node:path";
|
|
8865
|
-
import { execFileSync as
|
|
9145
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
8866
9146
|
function age(seconds) {
|
|
8867
9147
|
if (seconds < 60)
|
|
8868
9148
|
return `${seconds}s`;
|
|
@@ -8872,7 +9152,7 @@ function age(seconds) {
|
|
|
8872
9152
|
}
|
|
8873
9153
|
function git3(worktree, ...args) {
|
|
8874
9154
|
try {
|
|
8875
|
-
return
|
|
9155
|
+
return execFileSync5("git", args, { cwd: worktree, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).trim();
|
|
8876
9156
|
} catch {
|
|
8877
9157
|
return void 0;
|
|
8878
9158
|
}
|
|
@@ -9198,12 +9478,16 @@ function staleApprovals(policy, pr) {
|
|
|
9198
9478
|
function approvalDedupKey(pr, review) {
|
|
9199
9479
|
return `${pr.number}#${review.id}@${pr.headSha}`;
|
|
9200
9480
|
}
|
|
9201
|
-
function rebaseBrief(pr, id) {
|
|
9481
|
+
function rebaseBrief(pr, id, trunk) {
|
|
9482
|
+
const standalone = standalonePr(pr.baseRef, trunk);
|
|
9202
9483
|
return [
|
|
9203
|
-
`
|
|
9484
|
+
`Bring the branch ${pr.headRef} of ${pr.url} up to date with its base branch${pr.baseRef ? ` ${pr.baseRef}` : ""}, resolving any conflicts`,
|
|
9204
9485
|
`in a way that preserves the intent of both sides.`,
|
|
9205
|
-
|
|
9206
|
-
|
|
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.`
|
|
9207
9491
|
].join(" ");
|
|
9208
9492
|
}
|
|
9209
9493
|
function choreVerdict(uuid) {
|
|
@@ -9308,7 +9592,7 @@ async function mergeLoop(ms, policy, state, log = () => {
|
|
|
9308
9592
|
enqueue({
|
|
9309
9593
|
id: uuid,
|
|
9310
9594
|
repo: ms.repoKey(),
|
|
9311
|
-
brief: rebaseBrief(pr, uuid),
|
|
9595
|
+
brief: withBriefHooks(rebaseBrief(pr, uuid, loadConfig().repos[ms.repoKey()]?.trunk), loadConfig().repos[ms.repoKey()], "rebase"),
|
|
9312
9596
|
// The chore works on this PR: the runner pushes no other branch and opens no PR.
|
|
9313
9597
|
pr: { url: pr.url, headRefName: pr.headRef, headSha: pr.headSha }
|
|
9314
9598
|
}, "chore");
|
|
@@ -9382,7 +9666,8 @@ function spawnContinuation(w, pending, log, fixedBrief, targetOverride) {
|
|
|
9382
9666
|
return void 0;
|
|
9383
9667
|
}
|
|
9384
9668
|
const id = randomUUID4();
|
|
9385
|
-
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;
|
|
9386
9671
|
const claim = readSessionClaim(target, targetLane);
|
|
9387
9672
|
const deliveredTo = readEvidence(target, targetLane).deliveredTo;
|
|
9388
9673
|
const trapAddress = claim?.by.startsWith("wt:") ? claim.by : deliveredTo?.startsWith("wt:") ? deliveredTo : void 0;
|
|
@@ -9973,7 +10258,7 @@ function worktreeView(id, lane) {
|
|
|
9973
10258
|
init_dist2();
|
|
9974
10259
|
import * as fs45 from "node:fs";
|
|
9975
10260
|
import * as path39 from "node:path";
|
|
9976
|
-
import { execFileSync as
|
|
10261
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
9977
10262
|
var GIT_CACHE_MS = 6e4;
|
|
9978
10263
|
var gitCache = /* @__PURE__ */ new Map();
|
|
9979
10264
|
function headStamp(cwd) {
|
|
@@ -10001,7 +10286,7 @@ function git4(cwd, ...args) {
|
|
|
10001
10286
|
if (hit && stamp !== "" && hit.stamp === stamp && now2 - hit.at < GIT_CACHE_MS) return hit.out;
|
|
10002
10287
|
let out;
|
|
10003
10288
|
try {
|
|
10004
|
-
out =
|
|
10289
|
+
out = execFileSync6("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).trim();
|
|
10005
10290
|
} catch {
|
|
10006
10291
|
out = void 0;
|
|
10007
10292
|
}
|
|
@@ -10035,6 +10320,7 @@ function livenessView(id, lane) {
|
|
|
10035
10320
|
...lastCommit ? { lastCommit } : {},
|
|
10036
10321
|
...ahead !== void 0 && trunk ? { aheadTrunk: `${ahead} ahead of ${trunk}` } : {},
|
|
10037
10322
|
...ev.prUrl ? { draftPr: ev.prUrl } : {},
|
|
10323
|
+
...ev.prUrls && ev.prUrls.length > 1 ? { prs: ev.prUrls.join(", ") } : {},
|
|
10038
10324
|
...readStatusLog(id, lane).at(-1)?.at ? { updated: readStatusLog(id, lane).at(-1).at } : {}
|
|
10039
10325
|
};
|
|
10040
10326
|
}
|
|
@@ -10220,13 +10506,15 @@ function terminal2(id, lane) {
|
|
|
10220
10506
|
function liveWorkers() {
|
|
10221
10507
|
const out = [];
|
|
10222
10508
|
for (const reg of listTraps()) {
|
|
10223
|
-
|
|
10509
|
+
const catchId = hasOpenCatch(reg) ? reg.claimed : void 0;
|
|
10510
|
+
const lane = catchId ? laneOf(catchId) ?? "work" : "work";
|
|
10224
10511
|
out.push({
|
|
10225
10512
|
label: `wt:${reg.name ?? reg.trapId}`,
|
|
10226
|
-
dispatchId:
|
|
10227
|
-
lane
|
|
10513
|
+
...catchId ? { dispatchId: catchId } : {},
|
|
10514
|
+
lane,
|
|
10228
10515
|
worktree: reg.worktree,
|
|
10229
|
-
repo: storedDescriptor(
|
|
10516
|
+
repo: (catchId ? storedDescriptor(catchId, lane)?.repo : void 0) ?? reg.repo,
|
|
10517
|
+
trapId: reg.trapId
|
|
10230
10518
|
});
|
|
10231
10519
|
}
|
|
10232
10520
|
for (const lane of ["work", "chore"]) {
|
|
@@ -10254,6 +10542,7 @@ function git5(cwd, args) {
|
|
|
10254
10542
|
const res = spawnSync4("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4 });
|
|
10255
10543
|
return res.status === 0 && !res.error ? res.stdout.trim() : void 0;
|
|
10256
10544
|
}
|
|
10545
|
+
var TRAP_MESSAGE_HOLD_MS = 24 * 36e5;
|
|
10257
10546
|
var GIT_CACHE_MS2 = 6e4;
|
|
10258
10547
|
var gitCache2 = /* @__PURE__ */ new Map();
|
|
10259
10548
|
function gitFacts(worktree, branch, now2 = Date.now()) {
|
|
@@ -10271,7 +10560,7 @@ function gitFacts(worktree, branch, now2 = Date.now()) {
|
|
|
10271
10560
|
function workerHold(worker) {
|
|
10272
10561
|
const branches = /* @__PURE__ */ new Map();
|
|
10273
10562
|
const prs = /* @__PURE__ */ new Map();
|
|
10274
|
-
const evidence = readEvidence(worker.dispatchId, worker.lane);
|
|
10563
|
+
const evidence = worker.dispatchId ? readEvidence(worker.dispatchId, worker.lane) : {};
|
|
10275
10564
|
const origin = worker.repo ? loadConfig().repos[worker.repo]?.origin : void 0;
|
|
10276
10565
|
let forgeRepo = origin ? githubRepoFromOrigin(origin) : void 0;
|
|
10277
10566
|
if (worker.worktree && fs46.existsSync(worker.worktree)) {
|
|
@@ -10292,10 +10581,28 @@ function workerHold(worker) {
|
|
|
10292
10581
|
}
|
|
10293
10582
|
const named = evidence.prUrl ? parsePrRef(evidence.prUrl) : void 0;
|
|
10294
10583
|
if (named) prs.set(named.key, "works on this PR");
|
|
10295
|
-
const owned = chainPr(worker.dispatchId, worker.lane);
|
|
10584
|
+
const owned = worker.dispatchId ? chainPr(worker.dispatchId, worker.lane) : void 0;
|
|
10296
10585
|
const ownedRef = owned ? parsePrRef(owned.url) : void 0;
|
|
10297
10586
|
if (ownedRef && !prs.has(ownedRef.key)) prs.set(ownedRef.key, "its chain owns this PR");
|
|
10298
|
-
|
|
10587
|
+
const said = [
|
|
10588
|
+
...worker.dispatchId ? [storedDescriptor(worker.dispatchId, worker.lane)?.brief ?? "", ...messageTexts(worker.dispatchId, worker.lane)] : [],
|
|
10589
|
+
...worker.trapId ? trapMessageTexts(worker.trapId, Date.now() - TRAP_MESSAGE_HOLD_MS) : []
|
|
10590
|
+
].join("\n");
|
|
10591
|
+
return {
|
|
10592
|
+
label: worker.label,
|
|
10593
|
+
...worker.dispatchId ? { dispatchId: worker.dispatchId } : {},
|
|
10594
|
+
...worker.trapId ? { trapId: worker.trapId } : {},
|
|
10595
|
+
...forgeRepo ? { forgeRepo } : {},
|
|
10596
|
+
branches,
|
|
10597
|
+
prs,
|
|
10598
|
+
said
|
|
10599
|
+
};
|
|
10600
|
+
}
|
|
10601
|
+
function namesPr(text, pr, sameRepo) {
|
|
10602
|
+
if (!pr.repo || pr.number === void 0 || !text) return false;
|
|
10603
|
+
const repo = pr.repo.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10604
|
+
const n = String(pr.number);
|
|
10605
|
+
return new RegExp(`github\\.com/${repo}/pull/${n}(?![0-9])`, "i").test(text) || new RegExp(`(?<![\\w.-])${repo}#${n}(?![0-9])`, "i").test(text) || sameRepo && new RegExp(`(?<![\\w/#&])#${n}(?![0-9])`).test(text);
|
|
10299
10606
|
}
|
|
10300
10607
|
function readWorkerHolds() {
|
|
10301
10608
|
return liveWorkers().map(workerHold);
|
|
@@ -10308,10 +10615,11 @@ function repairHold(pr, records, workers) {
|
|
|
10308
10615
|
for (const target of targets) {
|
|
10309
10616
|
for (const worker of workers) {
|
|
10310
10617
|
if (worker.forgeRepo && worker.forgeRepo !== target.repo) continue;
|
|
10311
|
-
const how = worker.prs.get(target.key) ?? (target.head ? worker.branches.get(target.head) : void 0);
|
|
10618
|
+
const how = worker.prs.get(target.key) ?? (target.head ? worker.branches.get(target.head) : void 0) ?? (namesPr(worker.said, target, worker.forgeRepo === target.repo) ? "was told to work on this PR" : void 0);
|
|
10312
10619
|
if (!how) continue;
|
|
10313
10620
|
const where = target.below ? `; #${target.number} is below this PR` : "";
|
|
10314
|
-
|
|
10621
|
+
const dispatch = worker.dispatchId ? ` (dispatch ${worker.dispatchId.slice(0, 8)})` : "";
|
|
10622
|
+
return { heldBy: worker.label, reason: `${worker.label}${dispatch} ${how}${where}` };
|
|
10315
10623
|
}
|
|
10316
10624
|
}
|
|
10317
10625
|
return void 0;
|
|
@@ -10463,6 +10771,22 @@ function holdCancelledRepair(id, now2 = /* @__PURE__ */ new Date()) {
|
|
|
10463
10771
|
}
|
|
10464
10772
|
return held;
|
|
10465
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
|
+
}
|
|
10785
|
+
function trapAddressOf(id) {
|
|
10786
|
+
const claim = readSessionClaim(id, "work");
|
|
10787
|
+
const deliveredTo = readEvidence(id, "work").deliveredTo;
|
|
10788
|
+
return claim?.by.startsWith("wt:") ? claim.by : deliveredTo?.startsWith("wt:") ? deliveredTo : void 0;
|
|
10789
|
+
}
|
|
10466
10790
|
function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
10467
10791
|
const config = loadConfig();
|
|
10468
10792
|
const cfg = config.watch;
|
|
@@ -10565,17 +10889,27 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
|
10565
10889
|
});
|
|
10566
10890
|
};
|
|
10567
10891
|
if (watch2.heldAt) return wait(watchHold(watch2));
|
|
10568
|
-
const
|
|
10892
|
+
const ownerTrap = trapAddressOf(chainState(owner).latest);
|
|
10893
|
+
const held = repairHold(
|
|
10894
|
+
pr,
|
|
10895
|
+
records ??= readPrs(),
|
|
10896
|
+
(workers ??= (opts.workerHolds ?? readWorkerHolds)()).filter((w2) => !(w2.dispatchId === void 0 && w2.trapId !== void 0 && `wt:${w2.trapId}` === ownerTrap))
|
|
10897
|
+
);
|
|
10569
10898
|
if (held) return wait(held);
|
|
10570
10899
|
const changed = Math.max(
|
|
10571
10900
|
Date.parse(pr.headSince ?? pr.observedAt) || now2,
|
|
10572
10901
|
Date.parse(pr.baseSince ?? pr.observedAt) || now2,
|
|
10573
10902
|
kind === "checks" ? Date.parse(pr.failingSince ?? pr.observedAt) || now2 : 0
|
|
10574
10903
|
);
|
|
10904
|
+
const approvedAt = kind === "conflict" ? Date.parse(pr.review?.lastApprovalAt ?? "") || 0 : 0;
|
|
10575
10905
|
if (now2 < changed + settleMs) {
|
|
10576
10906
|
const until2 = new Date(changed + settleMs).toISOString();
|
|
10577
10907
|
return wait({ heldBy: "settle", reason: `settling: the head, base, or failing checks changed at ${new Date(changed).toISOString()}` }, until2);
|
|
10578
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
|
+
}
|
|
10579
10913
|
const chain = chainState(owner);
|
|
10580
10914
|
if (chain.busy) return;
|
|
10581
10915
|
if (kind === "checks") {
|
|
@@ -10592,15 +10926,33 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
|
10592
10926
|
}
|
|
10593
10927
|
const target = storedDescriptor(chain.latest, "work");
|
|
10594
10928
|
if (!target) return;
|
|
10595
|
-
const
|
|
10596
|
-
const deliveredTo = readEvidence(chain.latest, "work").deliveredTo;
|
|
10597
|
-
const address = claim?.by.startsWith("wt:") ? claim.by : deliveredTo?.startsWith("wt:") ? deliveredTo : void 0;
|
|
10929
|
+
const address = trapAddressOf(chain.latest);
|
|
10598
10930
|
const trap = address ? readTrap(address.slice(3)) : void 0;
|
|
10599
10931
|
const liveTrap2 = trap && now2 - trapLastSeen(trap) <= config.soak.ttlSecs * 1e3 ? trap : void 0;
|
|
10600
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
|
+
}
|
|
10601
10952
|
const id = randomUUID5();
|
|
10602
10953
|
writePr({
|
|
10603
10954
|
...pr,
|
|
10955
|
+
repairStreak: { count: streak.count, lastRepairId: id, startHead: pr.headSha },
|
|
10604
10956
|
repair: {
|
|
10605
10957
|
headSha: pr.headSha,
|
|
10606
10958
|
kind,
|
|
@@ -10615,7 +10967,16 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
|
|
|
10615
10967
|
}
|
|
10616
10968
|
});
|
|
10617
10969
|
try {
|
|
10618
|
-
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
|
+
);
|
|
10619
10980
|
enqueue({
|
|
10620
10981
|
id,
|
|
10621
10982
|
repo: target.repo,
|
|
@@ -10985,6 +11346,7 @@ function describeDispatch(id, lane, bucket, staleSecs) {
|
|
|
10985
11346
|
at: last?.at ?? (bucket === "queued" ? queuedAt(id, lane) : claimedAt),
|
|
10986
11347
|
...answered ? { answeredAt: answered } : {},
|
|
10987
11348
|
prUrl: evidence.prUrl,
|
|
11349
|
+
...evidence.prUrls && evidence.prUrls.length > 1 ? { prUrls: evidence.prUrls } : {},
|
|
10988
11350
|
pr: evidence.pr,
|
|
10989
11351
|
...bucket === "queued" ? {} : worktreeView(id, lane),
|
|
10990
11352
|
...bucket === "queued" ? {} : livenessView(id, lane),
|
|
@@ -10997,6 +11359,15 @@ function awaitingView(id) {
|
|
|
10997
11359
|
const e = awaitingReply(id);
|
|
10998
11360
|
return e ? { awaitingReply: { sentAt: e.sentAt, from: e.from, line: e.line } } : {};
|
|
10999
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
|
+
}
|
|
11000
11371
|
function prStateOf(chain) {
|
|
11001
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);
|
|
11002
11373
|
const record = records.sort(prNewestFirst(records))[0];
|
|
@@ -11156,12 +11527,14 @@ function buildTendReport(now2 = Date.now()) {
|
|
|
11156
11527
|
if (!fresh) continue;
|
|
11157
11528
|
const prUrl = chain.map((d) => d.prUrl).find((u) => u !== void 0);
|
|
11158
11529
|
const watch2 = chain.map((d) => watchByDispatch.get(d.id)).find((w) => w !== void 0);
|
|
11159
|
-
|
|
11530
|
+
const prs = prsOf(chain);
|
|
11531
|
+
stories.push({ key, dispatches: chain, prUrl, ...prs ? { prs } : {}, prState: prStateOf(chain), gate: gateFor(entry.uuid, prUrl), watch: watch2 });
|
|
11160
11532
|
}
|
|
11161
11533
|
const direct = (d) => ({
|
|
11162
11534
|
key: "(direct)",
|
|
11163
11535
|
dispatches: [d],
|
|
11164
11536
|
prUrl: d.prUrl,
|
|
11537
|
+
...prsOf([d]) ? { prs: prsOf([d]) } : {},
|
|
11165
11538
|
prState: prStateOf([d]),
|
|
11166
11539
|
gate: gateFor(d.id, d.prUrl),
|
|
11167
11540
|
watch: watchByDispatch.get(d.id)
|
|
@@ -11334,7 +11707,7 @@ function renderTend(r) {
|
|
|
11334
11707
|
dispatches: s.dispatches.map(
|
|
11335
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()))})` : "")
|
|
11336
11709
|
).join(" \u2192 "),
|
|
11337
|
-
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 ?? "",
|
|
11338
11711
|
gate: s.gate ?? "",
|
|
11339
11712
|
watch: s.watch ?? "",
|
|
11340
11713
|
// Under the verb and note: what the live dispatch is doing now.
|
|
@@ -11673,7 +12046,7 @@ function liveState(text) {
|
|
|
11673
12046
|
return void 0;
|
|
11674
12047
|
}
|
|
11675
12048
|
if (!state || !Number.isInteger(state.pid) || state.pid <= 0 || !Number.isInteger(state.port)) return void 0;
|
|
11676
|
-
return
|
|
12049
|
+
return pidAlive3(state.pid) ? state : void 0;
|
|
11677
12050
|
}
|
|
11678
12051
|
function readGlassState() {
|
|
11679
12052
|
return liveState(readStateText());
|
|
@@ -11682,7 +12055,7 @@ async function pruneGlassState() {
|
|
|
11682
12055
|
if (readStateText() === void 0) return void 0;
|
|
11683
12056
|
const first = readGlassState();
|
|
11684
12057
|
if (first) return first;
|
|
11685
|
-
await new Promise((
|
|
12058
|
+
await new Promise((resolve14) => setTimeout(resolve14, 50));
|
|
11686
12059
|
const second = readGlassState();
|
|
11687
12060
|
if (!second) removeGlassState();
|
|
11688
12061
|
return second;
|
|
@@ -11701,7 +12074,7 @@ function writeGlassState(state) {
|
|
|
11701
12074
|
function removeGlassState() {
|
|
11702
12075
|
fs51.rmSync(stateFile(), { force: true });
|
|
11703
12076
|
}
|
|
11704
|
-
function
|
|
12077
|
+
function pidAlive3(pid) {
|
|
11705
12078
|
try {
|
|
11706
12079
|
process.kill(pid, 0);
|
|
11707
12080
|
return true;
|
|
@@ -11710,12 +12083,12 @@ function pidAlive2(pid) {
|
|
|
11710
12083
|
}
|
|
11711
12084
|
}
|
|
11712
12085
|
function probeVersionEndpoint(port, timeoutMs) {
|
|
11713
|
-
return new Promise((
|
|
12086
|
+
return new Promise((resolve14) => {
|
|
11714
12087
|
let settled = false;
|
|
11715
12088
|
const finish = (info) => {
|
|
11716
12089
|
if (settled) return;
|
|
11717
12090
|
settled = true;
|
|
11718
|
-
|
|
12091
|
+
resolve14(info);
|
|
11719
12092
|
};
|
|
11720
12093
|
const req = http.get(`${glassUrl(port)}/api/version`, { timeout: timeoutMs }, (res) => {
|
|
11721
12094
|
if (res.statusCode !== 200) {
|
|
@@ -11747,12 +12120,12 @@ function probeVersionEndpoint(port, timeoutMs) {
|
|
|
11747
12120
|
});
|
|
11748
12121
|
}
|
|
11749
12122
|
function probeLegacyGlass(port, timeoutMs) {
|
|
11750
|
-
return new Promise((
|
|
12123
|
+
return new Promise((resolve14) => {
|
|
11751
12124
|
let settled = false;
|
|
11752
12125
|
const finish = (info) => {
|
|
11753
12126
|
if (settled) return;
|
|
11754
12127
|
settled = true;
|
|
11755
|
-
|
|
12128
|
+
resolve14(info);
|
|
11756
12129
|
};
|
|
11757
12130
|
const req = http.get(`${glassUrl(port)}/data`, { timeout: timeoutMs }, (res) => {
|
|
11758
12131
|
if (res.statusCode !== 200) {
|
|
@@ -11795,14 +12168,14 @@ function glassLines(port, info, already = false) {
|
|
|
11795
12168
|
...info.version !== lobstahVersion() ? { stale: `${info.version} (run lobstah glass stop && lobstah glass --detach)` } : {}
|
|
11796
12169
|
};
|
|
11797
12170
|
}
|
|
11798
|
-
var delay = (ms) => new Promise((
|
|
12171
|
+
var delay = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
11799
12172
|
async function terminate(pid, timeoutMs) {
|
|
11800
12173
|
try {
|
|
11801
12174
|
process.kill(pid, "SIGTERM");
|
|
11802
12175
|
} catch {
|
|
11803
12176
|
}
|
|
11804
12177
|
const deadline = Date.now() + timeoutMs;
|
|
11805
|
-
while (
|
|
12178
|
+
while (pidAlive3(pid)) {
|
|
11806
12179
|
if (Date.now() >= deadline) return false;
|
|
11807
12180
|
await delay(50);
|
|
11808
12181
|
}
|
|
@@ -11957,7 +12330,7 @@ ${help}`;
|
|
|
11957
12330
|
init_dist2();
|
|
11958
12331
|
import * as fs52 from "node:fs";
|
|
11959
12332
|
import * as path45 from "node:path";
|
|
11960
|
-
import { execFileSync as
|
|
12333
|
+
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
11961
12334
|
var DAY = 864e5;
|
|
11962
12335
|
function walkBytes(target) {
|
|
11963
12336
|
const stat = fs52.lstatSync(target);
|
|
@@ -11976,7 +12349,7 @@ var sizing = {
|
|
|
11976
12349
|
worktree(target, platform = process.platform) {
|
|
11977
12350
|
if (platform !== "win32") {
|
|
11978
12351
|
try {
|
|
11979
|
-
const out =
|
|
12352
|
+
const out = execFileSync7("du", ["-sk", target], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
11980
12353
|
const kb = Number(out.trim().split(/\s+/)[0]);
|
|
11981
12354
|
if (Number.isFinite(kb)) return kb * 1024;
|
|
11982
12355
|
} catch {
|
|
@@ -12202,20 +12575,20 @@ function removeWorktree(id, dir) {
|
|
|
12202
12575
|
const desc = JSON.parse(fs52.readFileSync(descFile, "utf8"));
|
|
12203
12576
|
const repo = loadConfig().repos[desc.repo];
|
|
12204
12577
|
if (repo) {
|
|
12205
|
-
|
|
12578
|
+
execFileSync7("git", ["worktree", "remove", "--force", dir], { cwd: repo.path, stdio: "ignore" });
|
|
12206
12579
|
return;
|
|
12207
12580
|
}
|
|
12208
12581
|
} catch {
|
|
12209
12582
|
}
|
|
12210
12583
|
}
|
|
12211
12584
|
try {
|
|
12212
|
-
const common =
|
|
12585
|
+
const common = execFileSync7("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
12213
12586
|
cwd: dir,
|
|
12214
12587
|
encoding: "utf8",
|
|
12215
12588
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12216
12589
|
}).trim();
|
|
12217
12590
|
if (common) {
|
|
12218
|
-
|
|
12591
|
+
execFileSync7("git", ["--git-dir", common, "worktree", "remove", "--force", dir], { stdio: "ignore" });
|
|
12219
12592
|
return;
|
|
12220
12593
|
}
|
|
12221
12594
|
} catch {
|
|
@@ -12266,8 +12639,9 @@ function runCull(days, apply, freeBytes = statfsFreeBytes, now2 = Date.now()) {
|
|
|
12266
12639
|
|
|
12267
12640
|
// apps/cli/src/release.ts
|
|
12268
12641
|
init_dist2();
|
|
12642
|
+
init_dist3();
|
|
12269
12643
|
import * as fs53 from "node:fs";
|
|
12270
|
-
import { execFileSync as
|
|
12644
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
12271
12645
|
var DAY2 = 864e5;
|
|
12272
12646
|
function listIds(dir) {
|
|
12273
12647
|
try {
|
|
@@ -12346,7 +12720,7 @@ function mergedPrs(all) {
|
|
|
12346
12720
|
}
|
|
12347
12721
|
function git7(cwd, ...args) {
|
|
12348
12722
|
try {
|
|
12349
|
-
return
|
|
12723
|
+
return execFileSync8("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
12350
12724
|
} catch {
|
|
12351
12725
|
return void 0;
|
|
12352
12726
|
}
|
|
@@ -12356,7 +12730,7 @@ function unsafe(dir, fetched) {
|
|
|
12356
12730
|
if (status === void 0) return "unpushed work: not a readable git checkout";
|
|
12357
12731
|
if (status !== "") return "unpushed work: uncommitted changes";
|
|
12358
12732
|
const common = git7(dir, "rev-parse", "--path-format=absolute", "--git-common-dir") ?? dir;
|
|
12359
|
-
if (!fetched.has(common)) fetched.set(common, git7(dir, "fetch", "--quiet", "origin") !== void 0);
|
|
12733
|
+
if (!fetched.has(common)) fetched.set(common, withRepoLockSync(dir, () => git7(dir, "fetch", "--quiet", "origin")) !== void 0);
|
|
12360
12734
|
if (!fetched.get(common)) return "unpushed work: fetch failed, remote state unknown";
|
|
12361
12735
|
const remote = git7(dir, "branch", "-r", "--contains", "HEAD");
|
|
12362
12736
|
if (remote === void 0 || remote === "") return "unpushed work: HEAD is not on the remote";
|
|
@@ -12377,8 +12751,14 @@ function planMergeRelease(now2 = Date.now(), batch = Infinity) {
|
|
|
12377
12751
|
return ownerOf.get(id);
|
|
12378
12752
|
};
|
|
12379
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
|
+
};
|
|
12380
12759
|
for (const pr of merged.values()) {
|
|
12381
12760
|
for (const id of pr.owners) {
|
|
12761
|
+
if (openSibling(id)) continue;
|
|
12382
12762
|
const chain = chains.get(id) ?? /* @__PURE__ */ new Set([id]);
|
|
12383
12763
|
const home = owner(id);
|
|
12384
12764
|
const add = (o) => {
|
|
@@ -12585,7 +12965,6 @@ function answerKey(key, req) {
|
|
|
12585
12965
|
lane: q.lane,
|
|
12586
12966
|
...q.repo ? { repo: q.repo } : {},
|
|
12587
12967
|
askedBy: "worker",
|
|
12588
|
-
replace: false,
|
|
12589
12968
|
maxBytes
|
|
12590
12969
|
});
|
|
12591
12970
|
try {
|
|
@@ -13270,7 +13649,7 @@ import { randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual2 } from
|
|
|
13270
13649
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13271
13650
|
|
|
13272
13651
|
// apps/cli/src/glass-page.generated.ts
|
|
13273
|
-
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';
|
|
13274
13653
|
|
|
13275
13654
|
// apps/cli/src/glass-snapshot-thread.ts
|
|
13276
13655
|
init_dist2();
|
|
@@ -13317,8 +13696,8 @@ function startSnapshotThread() {
|
|
|
13317
13696
|
const pending = inFlight.get(local);
|
|
13318
13697
|
if (pending) return pending;
|
|
13319
13698
|
const id = ++next;
|
|
13320
|
-
const promise = new Promise((
|
|
13321
|
-
waiting.set(id, { resolve:
|
|
13699
|
+
const promise = new Promise((resolve14, reject) => {
|
|
13700
|
+
waiting.set(id, { resolve: resolve14, reject });
|
|
13322
13701
|
worker.postMessage({ id, local });
|
|
13323
13702
|
}).finally(() => inFlight.delete(local));
|
|
13324
13703
|
inFlight.set(local, promise);
|
|
@@ -14115,7 +14494,7 @@ async function awaitHeartbeat(sinceMs, oldPid, timeoutMs = RESTART_WAIT_MS) {
|
|
|
14115
14494
|
}
|
|
14116
14495
|
return void 0;
|
|
14117
14496
|
}
|
|
14118
|
-
function
|
|
14497
|
+
function pidAlive4(pid) {
|
|
14119
14498
|
try {
|
|
14120
14499
|
process.kill(pid, 0);
|
|
14121
14500
|
return true;
|
|
@@ -14128,7 +14507,7 @@ function daemonStatus(installed, now2 = Date.now()) {
|
|
|
14128
14507
|
const hb = readHeartbeat();
|
|
14129
14508
|
const ageSecs = hb ? Math.max(0, Math.round((now2 - (Date.parse(hb.heartbeat) || 0)) / 1e3)) : void 0;
|
|
14130
14509
|
const fresh = ageSecs !== void 0 && ageSecs * 1e3 < HEARTBEAT_STALE_MS3;
|
|
14131
|
-
const running = fresh && (hb?.pid === void 0 ||
|
|
14510
|
+
const running = fresh && (hb?.pid === void 0 || pidAlive4(hb.pid));
|
|
14132
14511
|
const parked = parkedDispatches(now2);
|
|
14133
14512
|
const cfg = loadConfig();
|
|
14134
14513
|
const work = slotUsage("work");
|
|
@@ -14357,11 +14736,14 @@ function observePr(ref, view, opts = {}) {
|
|
|
14357
14736
|
const pr = prEvidence(ref, view, now2.toISOString());
|
|
14358
14737
|
const id = opts.dispatchId;
|
|
14359
14738
|
const lane = id ? laneOf4(id) : void 0;
|
|
14360
|
-
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;
|
|
14361
14741
|
const { before, after } = upsertPr(pr, lane ? id : void 0);
|
|
14362
|
-
|
|
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 });
|
|
14363
14744
|
const was = before?.state ?? legacyBefore?.state;
|
|
14364
14745
|
const terminal3 = pr.state === "MERGED" || pr.state === "CLOSED";
|
|
14746
|
+
if (terminal3) cancelQueuedRepairs(ref.key);
|
|
14365
14747
|
const endedAt = Date.parse(view.mergedAt ?? view.closedAt ?? "");
|
|
14366
14748
|
const recentEnd = Number.isFinite(endedAt) && now2.getTime() - endedAt <= FIRST_SIGHT_NOTICE_MS;
|
|
14367
14749
|
if (terminal3 && (was === "OPEN" || was === void 0 && recentEnd)) {
|
|
@@ -14378,6 +14760,15 @@ function observePr(ref, view, opts = {}) {
|
|
|
14378
14760
|
}
|
|
14379
14761
|
return after;
|
|
14380
14762
|
}
|
|
14763
|
+
function cancelQueuedRepairs(key) {
|
|
14764
|
+
const cancelled = [];
|
|
14765
|
+
for (const id of pendingIds("chore")) {
|
|
14766
|
+
const d = queuedDescriptor(id, "chore");
|
|
14767
|
+
if (!d?.systemRepair || !d.pr?.url || parsePrRef(d.pr.url)?.key !== key) continue;
|
|
14768
|
+
if (cancelQueued(id, "chore")) cancelled.push(id);
|
|
14769
|
+
}
|
|
14770
|
+
return cancelled;
|
|
14771
|
+
}
|
|
14381
14772
|
function rawConfig() {
|
|
14382
14773
|
try {
|
|
14383
14774
|
return parse(fs64.readFileSync(configPath(), "utf8"));
|
|
@@ -14441,25 +14832,80 @@ function cutTitle(title, max = 60) {
|
|
|
14441
14832
|
// apps/cli/src/pr-waits.ts
|
|
14442
14833
|
init_dist2();
|
|
14443
14834
|
var PR_WAITS = /* @__PURE__ */ new Set(["pr", "review"]);
|
|
14444
|
-
function
|
|
14835
|
+
function waitedPrs(id, lane, entry) {
|
|
14445
14836
|
const link = entry.link ? parsePrRef(entry.link) : void 0;
|
|
14446
|
-
|
|
14837
|
+
const own = ownPrs(id, lane);
|
|
14838
|
+
if (link && !own.some((r) => r.key === link.key)) return [link];
|
|
14839
|
+
return own;
|
|
14447
14840
|
}
|
|
14448
|
-
function
|
|
14841
|
+
function ownPrs(id, lane) {
|
|
14449
14842
|
const evidence = readEvidence(id, lane);
|
|
14450
|
-
|
|
14451
|
-
|
|
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.`;
|
|
14452
14854
|
}
|
|
14453
|
-
function
|
|
14855
|
+
function registerWaitWatches(id, lane, entry) {
|
|
14454
14856
|
try {
|
|
14455
|
-
if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return
|
|
14456
|
-
const own =
|
|
14857
|
+
if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return [];
|
|
14858
|
+
const own = ownPrs(id, lane);
|
|
14457
14859
|
const link = entry.link ? parsePrRef(entry.link) : void 0;
|
|
14458
|
-
if (
|
|
14459
|
-
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);
|
|
14460
14862
|
} catch {
|
|
14461
|
-
return
|
|
14863
|
+
return [];
|
|
14864
|
+
}
|
|
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
|
+
}
|
|
14462
14907
|
}
|
|
14908
|
+
return observed;
|
|
14463
14909
|
}
|
|
14464
14910
|
function finishResolvedWaits(log = () => {
|
|
14465
14911
|
}) {
|
|
@@ -14474,27 +14920,114 @@ function finishResolvedWaits(log = () => {
|
|
|
14474
14920
|
for (const id of ids) {
|
|
14475
14921
|
const last = readStatusLog(id, lane).at(-1);
|
|
14476
14922
|
if (last?.verb !== "paused" || !last.waitingOn || !PR_WAITS.has(last.waitingOn)) continue;
|
|
14477
|
-
const
|
|
14478
|
-
const
|
|
14479
|
-
if (
|
|
14480
|
-
const merged =
|
|
14481
|
-
const
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
|
|
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}`);
|
|
14486
14936
|
}
|
|
14487
14937
|
}
|
|
14488
14938
|
return out;
|
|
14489
14939
|
}
|
|
14490
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
|
+
|
|
14491
15024
|
// apps/cli/src/soak-worktree.ts
|
|
14492
15025
|
init_dist2();
|
|
14493
15026
|
init_dist3();
|
|
14494
|
-
import * as
|
|
14495
|
-
import { spawnSync as
|
|
15027
|
+
import * as fs66 from "node:fs";
|
|
15028
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
14496
15029
|
function branchExists(repo, branch) {
|
|
14497
|
-
return
|
|
15030
|
+
return spawnSync12("git", ["-C", repo.path, "rev-parse", "--verify", "-q", `refs/heads/${branch}`], { stdio: "ignore" }).status === 0;
|
|
14498
15031
|
}
|
|
14499
15032
|
async function createSoakWorktree(opts) {
|
|
14500
15033
|
const need = opts.minFreeGB * GB;
|
|
@@ -14511,7 +15044,7 @@ async function createSoakWorktree(opts) {
|
|
|
14511
15044
|
);
|
|
14512
15045
|
}
|
|
14513
15046
|
}
|
|
14514
|
-
const taken = (id2) => branchExists(opts.repo, `lobstah/soak-${id2}`) ||
|
|
15047
|
+
const taken = (id2) => branchExists(opts.repo, `lobstah/soak-${id2}`) || fs66.existsSync(worktreePath(`soak-${id2}`));
|
|
14515
15048
|
let trapId = opts.trapId ?? newTrapId();
|
|
14516
15049
|
if (opts.trapId !== void 0 && taken(trapId)) {
|
|
14517
15050
|
throw new Error(`could not create a worktree for repo ${opts.repoKey}: soak-${trapId} or its branch already exists`);
|
|
@@ -14598,14 +15131,14 @@ async function setTerminalTitle(win, title, options = {}) {
|
|
|
14598
15131
|
|
|
14599
15132
|
// apps/cli/src/beat.ts
|
|
14600
15133
|
init_dist2();
|
|
14601
|
-
import * as
|
|
14602
|
-
import * as
|
|
15134
|
+
import * as fs67 from "node:fs";
|
|
15135
|
+
import * as path54 from "node:path";
|
|
14603
15136
|
|
|
14604
15137
|
// apps/cli/src/beat-pr.ts
|
|
14605
15138
|
init_dist2();
|
|
14606
|
-
import { spawnSync as
|
|
14607
|
-
var
|
|
14608
|
-
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 });
|
|
14609
15142
|
return { status: res.error ? null : res.status, stdout: res.stdout ?? "" };
|
|
14610
15143
|
};
|
|
14611
15144
|
var PR_PROBE_INTERVAL_MS = 6e4;
|
|
@@ -14613,7 +15146,7 @@ function probeTrapPr(reg, opts = {}) {
|
|
|
14613
15146
|
if (!hasOpenCatch(reg)) return { recorded: false, reason: "no-catch" };
|
|
14614
15147
|
const id = reg.claimed;
|
|
14615
15148
|
const now2 = opts.now ?? Date.now();
|
|
14616
|
-
const run4 = opts.run ??
|
|
15149
|
+
const run4 = opts.run ?? defaultRun2;
|
|
14617
15150
|
const last = readTrapPrProbe(reg.trapId);
|
|
14618
15151
|
const sameDispatch = last?.dispatch === id;
|
|
14619
15152
|
if (sameDispatch && now2 - (Date.parse(last.checkedAt) || 0) < PR_PROBE_INTERVAL_MS) return { recorded: false, reason: "throttled" };
|
|
@@ -14630,6 +15163,12 @@ function probeTrapPr(reg, opts = {}) {
|
|
|
14630
15163
|
const upstream = git10(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
|
14631
15164
|
const tracked = upstream.status === 0 ? upstream.stdout.trim() : "";
|
|
14632
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
|
+
}
|
|
14633
15172
|
const view = run4("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], reg.worktree);
|
|
14634
15173
|
const prUrl = view.status === 0 ? view.stdout.trim() : "";
|
|
14635
15174
|
if (!parsePrRef(prUrl)) return { recorded: false, reason: "no-pr" };
|
|
@@ -14643,10 +15182,10 @@ function probeTrapPr(reg, opts = {}) {
|
|
|
14643
15182
|
// apps/cli/src/beat.ts
|
|
14644
15183
|
function logError(err) {
|
|
14645
15184
|
try {
|
|
14646
|
-
const dir =
|
|
14647
|
-
|
|
15185
|
+
const dir = path54.join(lobstahHome(), "logs");
|
|
15186
|
+
fs67.mkdirSync(dir, { recursive: true });
|
|
14648
15187
|
const msg = err instanceof Error ? err.message : String(err);
|
|
14649
|
-
|
|
15188
|
+
fs67.appendFileSync(path54.join(dir, "beat.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg.replace(/\s+/g, " ").slice(0, 300)}
|
|
14650
15189
|
`);
|
|
14651
15190
|
} catch {
|
|
14652
15191
|
}
|
|
@@ -14743,7 +15282,7 @@ var COMMANDS = {
|
|
|
14743
15282
|
cancel: { flags: { "--session": { value: "<id>" } }, positionals: "<uuid>" },
|
|
14744
15283
|
report: {
|
|
14745
15284
|
flags: {
|
|
14746
|
-
"--pr": { value: "<url>" },
|
|
15285
|
+
"--pr": { value: "<url>", repeatable: true },
|
|
14747
15286
|
"--no-watch": {},
|
|
14748
15287
|
"--waiting-on": { value: "review|pr|deploy|person|external" },
|
|
14749
15288
|
"--link": { value: "<url>" },
|
|
@@ -14849,6 +15388,7 @@ var COMMANDS = {
|
|
|
14849
15388
|
"--option": { value: "<label>", repeatable: true },
|
|
14850
15389
|
"--attach": { value: "<file>", repeatable: true },
|
|
14851
15390
|
"--withdraw": { value: "<key>" },
|
|
15391
|
+
"--replace": { value: "<key>" },
|
|
14852
15392
|
"--grounds": { value: "<name>" },
|
|
14853
15393
|
"--session": { value: "<id>" }
|
|
14854
15394
|
},
|
|
@@ -14984,7 +15524,7 @@ work in flight requires a live watcher or the hook blocks with the arm command.
|
|
|
14984
15524
|
--attach copies images the page names by bare filename; --title overrides its first # heading.
|
|
14985
15525
|
The glass shows it on the deck; \`lobstah attention ack <key>\` acks it.`,
|
|
14986
15526
|
"man:ask": `Put a decision to the human: a card in the glass (decisions/<rid>/) until
|
|
14987
|
-
answered or withdrawn.
|
|
15527
|
+
answered or withdrawn. Asks stand side by side; --replace <key> replaces one. The answer
|
|
14988
15528
|
wakes man wait as decision-answer. Reserved for the claimed helm.`,
|
|
14989
15529
|
"man:answer": `Answer a decision (or a raw question's <lane>:<id>) as the glass does.
|
|
14990
15530
|
Stores the answer; the helm's man wait receives it as decision-answer.`,
|
|
@@ -15093,15 +15633,15 @@ function detectHarness(opts) {
|
|
|
15093
15633
|
|
|
15094
15634
|
// apps/cli/src/watchers.ts
|
|
15095
15635
|
init_dist2();
|
|
15096
|
-
import * as
|
|
15097
|
-
import * as
|
|
15636
|
+
import * as fs68 from "node:fs";
|
|
15637
|
+
import * as path55 from "node:path";
|
|
15098
15638
|
var STALE_MS = 5e3;
|
|
15099
15639
|
var HEARTBEAT_MS = 1500;
|
|
15100
|
-
var watcherDir = () =>
|
|
15101
|
-
var watcherFile = (sessionId) =>
|
|
15640
|
+
var watcherDir = () => path55.join(lobstahHome(), "watchers");
|
|
15641
|
+
var watcherFile = (sessionId) => path55.join(watcherDir(), `${encodeURIComponent(sessionId)}.json`);
|
|
15102
15642
|
function liveRegistration(sessionId, now2 = Date.now()) {
|
|
15103
15643
|
try {
|
|
15104
|
-
const w = JSON.parse(
|
|
15644
|
+
const w = JSON.parse(fs68.readFileSync(watcherFile(sessionId), "utf8"));
|
|
15105
15645
|
const beat = Date.parse(w.heartbeatAt);
|
|
15106
15646
|
if (w.sessionId !== sessionId || !Number.isInteger(w.pid) || w.pid <= 0 || !Number.isFinite(beat) || now2 - beat > STALE_MS) return void 0;
|
|
15107
15647
|
return w;
|
|
@@ -15118,17 +15658,17 @@ async function awaitWatcher(sessionId, kind, graceMs, trapId, pollMs = 100) {
|
|
|
15118
15658
|
for (; ; ) {
|
|
15119
15659
|
const w = liveWatcher(sessionId, kind, trapId);
|
|
15120
15660
|
if (w || Date.now() >= deadline) return w;
|
|
15121
|
-
await new Promise((
|
|
15661
|
+
await new Promise((resolve14) => setTimeout(resolve14, Math.min(pollMs, Math.max(1, deadline - Date.now()))));
|
|
15122
15662
|
}
|
|
15123
15663
|
}
|
|
15124
15664
|
function armWatcher(sessionId, kind, trapId) {
|
|
15125
|
-
|
|
15665
|
+
fs68.mkdirSync(watcherDir(), { recursive: true });
|
|
15126
15666
|
const file = watcherFile(sessionId);
|
|
15127
15667
|
const own = () => ({ sessionId, kind, pid: process.pid, heartbeatAt: (/* @__PURE__ */ new Date()).toISOString(), ...trapId ? { trapId } : {} });
|
|
15128
15668
|
let claimed = false;
|
|
15129
15669
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
15130
15670
|
try {
|
|
15131
|
-
|
|
15671
|
+
fs68.writeFileSync(file, `${JSON.stringify(own())}
|
|
15132
15672
|
`, { flag: "wx" });
|
|
15133
15673
|
claimed = true;
|
|
15134
15674
|
break;
|
|
@@ -15137,7 +15677,7 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15137
15677
|
const live = liveRegistration(sessionId);
|
|
15138
15678
|
if (live) throw new Error(`watcher already armed for session ${sessionId} (pid ${live.pid})`);
|
|
15139
15679
|
try {
|
|
15140
|
-
|
|
15680
|
+
fs68.rmSync(file);
|
|
15141
15681
|
} catch {
|
|
15142
15682
|
}
|
|
15143
15683
|
}
|
|
@@ -15145,7 +15685,7 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15145
15685
|
if (!claimed) throw new Error(`could not arm watcher for session ${sessionId}`);
|
|
15146
15686
|
const ownsFile = () => {
|
|
15147
15687
|
try {
|
|
15148
|
-
return JSON.parse(
|
|
15688
|
+
return JSON.parse(fs68.readFileSync(file, "utf8")).pid === process.pid;
|
|
15149
15689
|
} catch {
|
|
15150
15690
|
return false;
|
|
15151
15691
|
}
|
|
@@ -15154,12 +15694,12 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15154
15694
|
if (!ownsFile()) return;
|
|
15155
15695
|
const tmp = `${file}.tmp-${process.pid}`;
|
|
15156
15696
|
try {
|
|
15157
|
-
|
|
15697
|
+
fs68.writeFileSync(tmp, `${JSON.stringify(own())}
|
|
15158
15698
|
`);
|
|
15159
|
-
|
|
15699
|
+
fs68.renameSync(tmp, file);
|
|
15160
15700
|
} catch {
|
|
15161
15701
|
try {
|
|
15162
|
-
|
|
15702
|
+
fs68.rmSync(tmp, { force: true });
|
|
15163
15703
|
} catch {
|
|
15164
15704
|
}
|
|
15165
15705
|
}
|
|
@@ -15171,7 +15711,7 @@ function armWatcher(sessionId, kind, trapId) {
|
|
|
15171
15711
|
if (stopped) return;
|
|
15172
15712
|
stopped = true;
|
|
15173
15713
|
clearInterval(timer);
|
|
15174
|
-
if (ownsFile())
|
|
15714
|
+
if (ownsFile()) fs68.rmSync(file, { force: true });
|
|
15175
15715
|
process.off("exit", stop);
|
|
15176
15716
|
process.off("SIGINT", interrupt);
|
|
15177
15717
|
process.off("SIGTERM", terminate2);
|
|
@@ -15266,7 +15806,9 @@ work (humans and agents):
|
|
|
15266
15806
|
hold ends when that dispatch ends
|
|
15267
15807
|
watch release <key>|--all end a hold: a watch held by the per-cycle
|
|
15268
15808
|
fork cap ([watch].maxForksPerCycle), by
|
|
15269
|
-
watch hold, or by a cancelled repair
|
|
15809
|
+
watch hold, or by a cancelled repair;
|
|
15810
|
+
resume repairs stopped by
|
|
15811
|
+
[watch].maxRepairsWithoutProgress
|
|
15270
15812
|
|
|
15271
15813
|
host processes:
|
|
15272
15814
|
daemon [--interval <ms>] the supervisor: claims, worktrees, liveness,
|
|
@@ -15315,11 +15857,12 @@ lobstah man (orchestrator sessions \u2014 bare \`lobstah man\` prints the manual
|
|
|
15315
15857
|
man file <file.md> [--attach <file> ...] [--title <text>]
|
|
15316
15858
|
file the helm's own report under its
|
|
15317
15859
|
grounds; the glass renders it on the deck.
|
|
15318
|
-
man ask [<dispatch-id>] --title <q> [--detail <f.md>] [--option <label> ...] [--attach <file> ...]
|
|
15860
|
+
man ask [<dispatch-id>] --title <q> [--detail <f.md>] [--option <label> ...] [--attach <file> ...] [--replace <key>]
|
|
15319
15861
|
put a decision to the human: a card in the
|
|
15320
15862
|
glass until answered or withdrawn
|
|
15321
|
-
(--withdraw <key>).
|
|
15322
|
-
|
|
15863
|
+
(--withdraw <key>). Asks stand side by side;
|
|
15864
|
+
--replace <key> replaces one. The answer
|
|
15865
|
+
wakes man wait as a decision-answer event.
|
|
15323
15866
|
man answer <key> [--option <label>] [--text <text>] [--attach <file> ...]
|
|
15324
15867
|
answer a decision from the terminal.
|
|
15325
15868
|
man helm [--session <id>] [--grounds <name>] [--take] [--harness claude|codex]
|
|
@@ -15348,12 +15891,15 @@ lobstah man (orchestrator sessions \u2014 bare \`lobstah man\` prints the manual
|
|
|
15348
15891
|
man wait (lobstah man).
|
|
15349
15892
|
|
|
15350
15893
|
workers (dispatched agents; injected into every brief):
|
|
15351
|
-
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]
|
|
15352
15895
|
the validated status write path
|
|
15353
15896
|
(${VERBS.join(" | ")}). --pr on any
|
|
15354
15897
|
verb but failed records the PR and
|
|
15355
15898
|
registers its pr: watch for this chain;
|
|
15356
|
-
|
|
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
|
|
15357
15903
|
--report files a markdown page as the
|
|
15358
15904
|
dispatch's report, images --attach'ed.
|
|
15359
15905
|
|
|
@@ -15549,7 +16095,7 @@ function emitWatchAttention(attns, sessionId) {
|
|
|
15549
16095
|
function findLane(id) {
|
|
15550
16096
|
for (const lane of ["work", "chore"]) {
|
|
15551
16097
|
const d = laneDirs(lane);
|
|
15552
|
-
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`))) {
|
|
15553
16099
|
return lane;
|
|
15554
16100
|
}
|
|
15555
16101
|
}
|
|
@@ -15558,7 +16104,7 @@ function findLane(id) {
|
|
|
15558
16104
|
function anythingInFlight() {
|
|
15559
16105
|
const dispatches = ["work", "chore"].some((lane) => {
|
|
15560
16106
|
const dirs = laneDirs(lane);
|
|
15561
|
-
return [dirs.queue, dirs.active].some((dir) =>
|
|
16107
|
+
return [dirs.queue, dirs.active].some((dir) => fs73.readdirSync(dir).some((file) => !file.startsWith(".")));
|
|
15562
16108
|
});
|
|
15563
16109
|
return dispatches || listWatches().some((watch2) => watch2.owner === "man");
|
|
15564
16110
|
}
|
|
@@ -15575,7 +16121,7 @@ function dispatchChain(target) {
|
|
|
15575
16121
|
for (const lane of ["work", "chore"]) {
|
|
15576
16122
|
const dirs = laneDirs(lane);
|
|
15577
16123
|
for (const bucket of ["queue", "active", "done"]) {
|
|
15578
|
-
for (const entry of
|
|
16124
|
+
for (const entry of fs73.readdirSync(dirs[bucket])) {
|
|
15579
16125
|
if (entry.startsWith(".")) continue;
|
|
15580
16126
|
const id = bucket === "queue" ? entry.replace(/\.json$/, "") : entry;
|
|
15581
16127
|
const descriptor = storedDescriptor(id, lane);
|
|
@@ -15597,7 +16143,7 @@ function dispatchChain(target) {
|
|
|
15597
16143
|
}
|
|
15598
16144
|
function liveChainMember(member) {
|
|
15599
16145
|
if (member.bucket !== "active") return false;
|
|
15600
|
-
const dir =
|
|
16146
|
+
const dir = path60.join(laneDirs(member.lane).active, member.id);
|
|
15601
16147
|
const claim = readSessionClaim(member.id, member.lane);
|
|
15602
16148
|
if (claim?.by.startsWith("wt:")) {
|
|
15603
16149
|
const reg = readTrap(claim.by.slice("wt:".length));
|
|
@@ -15605,8 +16151,8 @@ function liveChainMember(member) {
|
|
|
15605
16151
|
}
|
|
15606
16152
|
if (isParked(member.id, member.lane)) return true;
|
|
15607
16153
|
try {
|
|
15608
|
-
const runner = JSON.parse(
|
|
15609
|
-
return
|
|
16154
|
+
const runner = JSON.parse(fs73.readFileSync(path60.join(dir, "runner.json"), "utf8"));
|
|
16155
|
+
return pidAlive2(runner.pid, runner.processStartTime);
|
|
15610
16156
|
} catch {
|
|
15611
16157
|
return false;
|
|
15612
16158
|
}
|
|
@@ -15614,7 +16160,7 @@ function liveChainMember(member) {
|
|
|
15614
16160
|
function rowsFor(lane, bucket) {
|
|
15615
16161
|
const dir = laneDirs(lane)[bucket];
|
|
15616
16162
|
const staleSecs = wedgeSecs2();
|
|
15617
|
-
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);
|
|
15618
16164
|
const sliced = bucket === "done" ? entries.slice(0, 10) : entries;
|
|
15619
16165
|
return sliced.map(({ f, m }) => {
|
|
15620
16166
|
const id = f.replace(/\.json$/, "");
|
|
@@ -15639,7 +16185,7 @@ function rowsFor(lane, bucket) {
|
|
|
15639
16185
|
function dispatchTrap(id, lane, names) {
|
|
15640
16186
|
const claimedBy = (bucket) => {
|
|
15641
16187
|
try {
|
|
15642
|
-
return JSON.parse(
|
|
16188
|
+
return JSON.parse(fs73.readFileSync(path60.join(laneDirs(lane)[bucket], id, "claim.json"), "utf8")).by;
|
|
15643
16189
|
} catch {
|
|
15644
16190
|
return void 0;
|
|
15645
16191
|
}
|
|
@@ -15832,7 +16378,7 @@ ${usageFor("trap")}`);
|
|
|
15832
16378
|
const d = {
|
|
15833
16379
|
id: opt("--id") ?? randomUUID6(),
|
|
15834
16380
|
repo,
|
|
15835
|
-
brief: briefText ??
|
|
16381
|
+
brief: briefText ?? fs73.readFileSync(briefFile, "utf8"),
|
|
15836
16382
|
harness: opt("--harness"),
|
|
15837
16383
|
model: opt("--model"),
|
|
15838
16384
|
effort: opt("--effort"),
|
|
@@ -15889,7 +16435,7 @@ ${usageFor("trap")}`);
|
|
|
15889
16435
|
const since = queuedAt(id, lane);
|
|
15890
16436
|
const claimedAt = claim?.at;
|
|
15891
16437
|
const state = displayState({ log, lastEventAt: lastEventAt(id, lane), queued: since !== void 0, claimedAt });
|
|
15892
|
-
const live =
|
|
16438
|
+
const live = fs73.existsSync(path60.join(laneDirs(lane).active, id));
|
|
15893
16439
|
const activity = live ? activityView(readActivity(id, lane), wedgeSecs2()) : void 0;
|
|
15894
16440
|
const waitingNow = live ? waitingView(log.at(-1)) : void 0;
|
|
15895
16441
|
console.log(
|
|
@@ -15929,8 +16475,8 @@ ${usageFor("trap")}`);
|
|
|
15929
16475
|
if (!id) throw new Error("logs requires a dispatch id");
|
|
15930
16476
|
const lane = findLane(id);
|
|
15931
16477
|
const file = eventsPath(id, lane);
|
|
15932
|
-
if (
|
|
15933
|
-
const raw =
|
|
16478
|
+
if (fs73.existsSync(file)) {
|
|
16479
|
+
const raw = fs73.readFileSync(file, "utf8");
|
|
15934
16480
|
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
15935
16481
|
const LIMIT = 50;
|
|
15936
16482
|
if (!has("--full") && lines.length > LIMIT) {
|
|
@@ -15941,15 +16487,15 @@ ${usageFor("trap")}`);
|
|
|
15941
16487
|
}
|
|
15942
16488
|
}
|
|
15943
16489
|
if (has("--follow")) {
|
|
15944
|
-
let size =
|
|
16490
|
+
let size = fs73.existsSync(file) ? fs73.statSync(file).size : 0;
|
|
15945
16491
|
setInterval(() => {
|
|
15946
|
-
if (!
|
|
15947
|
-
const now2 =
|
|
16492
|
+
if (!fs73.existsSync(file)) return;
|
|
16493
|
+
const now2 = fs73.statSync(file).size;
|
|
15948
16494
|
if (now2 > size) {
|
|
15949
|
-
const fd =
|
|
16495
|
+
const fd = fs73.openSync(file, "r");
|
|
15950
16496
|
const buf = Buffer.alloc(now2 - size);
|
|
15951
|
-
|
|
15952
|
-
|
|
16497
|
+
fs73.readSync(fd, buf, 0, buf.length, size);
|
|
16498
|
+
fs73.closeSync(fd);
|
|
15953
16499
|
process.stdout.write(buf.toString("utf8"));
|
|
15954
16500
|
size = now2;
|
|
15955
16501
|
}
|
|
@@ -16025,10 +16571,10 @@ ${[text, block].filter(Boolean).join("\n\n")}`,
|
|
|
16025
16571
|
}
|
|
16026
16572
|
let address;
|
|
16027
16573
|
let headless = false;
|
|
16028
|
-
const claimFile =
|
|
16574
|
+
const claimFile = path60.join(laneDirs(origin.lane)[origin.bucket], origin.id, "claim.json");
|
|
16029
16575
|
let claimedBy;
|
|
16030
16576
|
try {
|
|
16031
|
-
claimedBy = JSON.parse(
|
|
16577
|
+
claimedBy = JSON.parse(fs73.readFileSync(claimFile, "utf8")).by;
|
|
16032
16578
|
} catch {
|
|
16033
16579
|
claimedBy = readEvidence(origin.id, origin.lane).deliveredTo;
|
|
16034
16580
|
}
|
|
@@ -16059,7 +16605,8 @@ ${text}`, from);
|
|
|
16059
16605
|
if (!id || !verb) throw new Error(`report requires an id and a verb (${VERBS.join("|")})`);
|
|
16060
16606
|
const lane = findLane(id);
|
|
16061
16607
|
const note = rest.join(" ") || void 0;
|
|
16062
|
-
const
|
|
16608
|
+
const reportedPrs = values("--pr");
|
|
16609
|
+
const prUrl = reportedPrs[0];
|
|
16063
16610
|
const noWatch = has("--no-watch");
|
|
16064
16611
|
const waiting = { waitingOn: opt("--waiting-on"), link: opt("--link"), until: opt("--until") };
|
|
16065
16612
|
const saysWaiting = waiting.waitingOn !== void 0 || waiting.link !== void 0 || waiting.until !== void 0;
|
|
@@ -16072,6 +16619,19 @@ ${text}`, from);
|
|
|
16072
16619
|
${usageFor("report")}`);
|
|
16073
16620
|
}
|
|
16074
16621
|
}
|
|
16622
|
+
if (verb === "done") {
|
|
16623
|
+
const unread = unhandled(id, lane);
|
|
16624
|
+
if (unread.length > 0) {
|
|
16625
|
+
for (const m of unread) {
|
|
16626
|
+
console.log(`--- message ${m.file}`);
|
|
16627
|
+
console.log(m.text);
|
|
16628
|
+
acknowledge(id, lane, m.file);
|
|
16629
|
+
}
|
|
16630
|
+
throw new Error(
|
|
16631
|
+
`not reported done: ${unread.length} unread message(s) for ${id} arrived while you worked. They are printed above and now marked read. Act on them, then report done again.`
|
|
16632
|
+
);
|
|
16633
|
+
}
|
|
16634
|
+
}
|
|
16075
16635
|
const reportFile = opt("--report");
|
|
16076
16636
|
const attachFiles = values("--attach");
|
|
16077
16637
|
if (reportFile !== void 0 && verb !== "done" && verb !== "failed")
|
|
@@ -16098,24 +16658,35 @@ ${usageFor("report")}`);
|
|
|
16098
16658
|
if (verb === "done") {
|
|
16099
16659
|
const claim = readSessionClaim(id, lane);
|
|
16100
16660
|
let ownCheckout = false;
|
|
16101
|
-
if (claim?.by.startsWith("wt:") && claim.worktree &&
|
|
16102
|
-
const rel =
|
|
16103
|
-
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);
|
|
16104
16664
|
const who = callerSession(opt("--session"))?.id;
|
|
16105
16665
|
const manning = readTrap(claim.by.slice("wt:".length))?.sessionId;
|
|
16106
16666
|
ownCheckout = inside || who !== void 0 && (who === claim.sessionId || who === manning);
|
|
16107
16667
|
}
|
|
16108
16668
|
if (ownCheckout) {
|
|
16109
|
-
const git10 = (args2) =>
|
|
16669
|
+
const git10 = (args2) => spawnSync14("git", args2, { cwd: claim.worktree, encoding: "utf8", timeout: 1e4 });
|
|
16110
16670
|
const head = git10(["rev-parse", "HEAD"]);
|
|
16111
16671
|
const branch = git10(["branch", "--show-current"]);
|
|
16112
16672
|
if (head.status === 0 && branch.status === 0)
|
|
16113
16673
|
mergeEvidence(id, lane, { commits: [head.stdout.trim()], branch: branch.stdout.trim() });
|
|
16114
16674
|
}
|
|
16115
16675
|
}
|
|
16116
|
-
|
|
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
|
+
}
|
|
16117
16687
|
const gatesNamed = recordReportedGates(id, lane, values("--human-gate"), prUrl);
|
|
16118
|
-
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);
|
|
16119
16690
|
console.log(
|
|
16120
16691
|
toonKV({
|
|
16121
16692
|
id,
|
|
@@ -16126,7 +16697,9 @@ ${usageFor("report")}`);
|
|
|
16126
16697
|
...entry.link ? { link: entry.link } : {},
|
|
16127
16698
|
...entry.until ? { until: entry.until } : {},
|
|
16128
16699
|
...prUrl ? { prUrl } : {},
|
|
16129
|
-
...
|
|
16700
|
+
...reportPrs.length > 1 ? { prs: reportPrs.join(", ") } : {},
|
|
16701
|
+
...prWatches.length ? { watch: prWatches.map((w) => w.key).join(", ") } : {},
|
|
16702
|
+
...warning ? { warning } : {},
|
|
16130
16703
|
...gatesNamed.length ? { humanGates: gatesNamed.join(", ") } : {},
|
|
16131
16704
|
...filed ? { report: reportMarkdownPath(filed.key), reportKey: filed.key, reportTitle: filed.title } : {}
|
|
16132
16705
|
})
|
|
@@ -16173,14 +16746,14 @@ ${usageFor("report")}`);
|
|
|
16173
16746
|
);
|
|
16174
16747
|
}
|
|
16175
16748
|
const worktree = dispatchWorktree(id, lane).path;
|
|
16176
|
-
const cwd =
|
|
16749
|
+
const cwd = fs73.existsSync(worktree) ? worktree : process.cwd();
|
|
16177
16750
|
const invocation = harness === "codex" ? codexInvocation(["resume", sessionId]) : { file: "claude", argv: ["--resume", sessionId] };
|
|
16178
16751
|
if (!invocation) throw new Error("no codex CLI found \u2014 neither on PATH nor vendored by @openai/codex-sdk");
|
|
16179
16752
|
if (has("--print")) {
|
|
16180
16753
|
console.log(toonKV({ id, harness, sessionId, cwd, command: `${invocation.file} ${invocation.argv.join(" ")}` }));
|
|
16181
16754
|
break;
|
|
16182
16755
|
}
|
|
16183
|
-
const res =
|
|
16756
|
+
const res = spawnSync14(invocation.file, invocation.argv, { cwd, stdio: "inherit" });
|
|
16184
16757
|
if (res.error) throw new Error(`could not launch ${invocation.file}: ${res.error.message}`);
|
|
16185
16758
|
break;
|
|
16186
16759
|
}
|
|
@@ -16191,10 +16764,10 @@ ${usageFor("report")}`);
|
|
|
16191
16764
|
gateHelm(callerSession(opt("--session")));
|
|
16192
16765
|
}
|
|
16193
16766
|
const lane = findLane(id);
|
|
16194
|
-
const activeDir =
|
|
16195
|
-
if (!
|
|
16196
|
-
const descFile =
|
|
16197
|
-
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"));
|
|
16198
16771
|
const fromHarness = resolveSessionHarness(id, loadConfig(), lane).harness ?? descriptor.harness ?? "default";
|
|
16199
16772
|
for (const key of ["harness", "model", "effort"]) {
|
|
16200
16773
|
const v = opt(`--${key}`);
|
|
@@ -16202,14 +16775,14 @@ ${usageFor("report")}`);
|
|
|
16202
16775
|
}
|
|
16203
16776
|
if (opt("--harness")) descriptor.harnessExplicit = true;
|
|
16204
16777
|
if (opt("--model")) descriptor.modelExplicit = true;
|
|
16205
|
-
|
|
16778
|
+
fs73.writeFileSync(descFile, JSON.stringify(descriptor, null, 2));
|
|
16206
16779
|
const worktree = dispatchWorktree(id, lane).path;
|
|
16207
|
-
|
|
16208
|
-
const runnerFile =
|
|
16209
|
-
if (
|
|
16210
|
-
const runner = JSON.parse(
|
|
16211
|
-
if (
|
|
16212
|
-
|
|
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"));
|
|
16784
|
+
if (pidAlive2(runner.pid, runner.processStartTime)) killGroup(runner.pid, "SIGKILL");
|
|
16785
|
+
fs73.unlinkSync(runnerFile);
|
|
16213
16786
|
}
|
|
16214
16787
|
appendStatus(id, lane, "working", `swapped to ${descriptor.harness ?? "default"} \u2014 awaiting respawn`);
|
|
16215
16788
|
console.log(toonKV({ id, harness: descriptor.harness ?? "default", model: descriptor.model, swapped: true }));
|
|
@@ -16233,6 +16806,12 @@ ${usageFor("report")}`);
|
|
|
16233
16806
|
}),
|
|
16234
16807
|
branch: ev.branch,
|
|
16235
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
|
+
} : {},
|
|
16236
16815
|
sessionId: ev.sessionId,
|
|
16237
16816
|
...worktreeView(id, lane),
|
|
16238
16817
|
...dispatchTrap(id, lane, names),
|
|
@@ -16264,7 +16843,7 @@ ${usageFor("report")}`);
|
|
|
16264
16843
|
console.log(`commits[${ev.commits.length}]:`);
|
|
16265
16844
|
for (const c of ev.commits) console.log(` ${c}`);
|
|
16266
16845
|
}
|
|
16267
|
-
if (
|
|
16846
|
+
if (fs73.existsSync(path60.join(laneDirs(lane).done, id))) {
|
|
16268
16847
|
console.log(toonHelp([`lobstah send ${id} "<instruction>" (wakes it as a follow-up in its worktree)`]));
|
|
16269
16848
|
}
|
|
16270
16849
|
const attachments = storedDescriptor(id, lane)?.attachments ?? [];
|
|
@@ -16381,6 +16960,7 @@ ${usageFor("man:ask")}`);
|
|
|
16381
16960
|
...repo ? { repo } : {},
|
|
16382
16961
|
...grounds ? { grounds } : {},
|
|
16383
16962
|
askedBy: "helm",
|
|
16963
|
+
...opt("--replace") !== void 0 ? { replace: opt("--replace") } : {},
|
|
16384
16964
|
maxBytes: loadConfig().limits.attachmentMaxBytes
|
|
16385
16965
|
});
|
|
16386
16966
|
} catch (err) {
|
|
@@ -16551,7 +17131,7 @@ ${errs.map((e) => `- ${e}`).join("\n")}`);
|
|
|
16551
17131
|
const keys = holdCancelledRepair(id);
|
|
16552
17132
|
return keys.length > 0 ? { repairHeld: `${keys.join(", ")} (lobstah watch release <key> frees it)` } : {};
|
|
16553
17133
|
};
|
|
16554
|
-
if (
|
|
17134
|
+
if (fs73.existsSync(path60.join(laneDirs(lane).active, id))) {
|
|
16555
17135
|
requestCancel(id, lane);
|
|
16556
17136
|
console.log(toonKV({ id, cancel: "requested", note: "the claimant (daemon or trap) winds it down at its next check", ...repairHeld() }));
|
|
16557
17137
|
break;
|
|
@@ -16560,7 +17140,7 @@ ${errs.map((e) => `- ${e}`).join("\n")}`);
|
|
|
16560
17140
|
console.log(toonKV({ id, cancel: "finalized", note: "cancelled before claim \u2014 recorded as failed in done/", ...repairHeld() }));
|
|
16561
17141
|
break;
|
|
16562
17142
|
}
|
|
16563
|
-
if (
|
|
17143
|
+
if (fs73.existsSync(path60.join(laneDirs(lane).active, id))) {
|
|
16564
17144
|
requestCancel(id, lane);
|
|
16565
17145
|
console.log(toonKV({ id, cancel: "requested", note: "claimed while cancelling \u2014 the claimant winds it down", ...repairHeld() }));
|
|
16566
17146
|
break;
|
|
@@ -16684,20 +17264,20 @@ ${usageFor("man:wait")}`);
|
|
|
16684
17264
|
}
|
|
16685
17265
|
}
|
|
16686
17266
|
case "man:init": {
|
|
16687
|
-
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");
|
|
16688
17268
|
let existing;
|
|
16689
17269
|
try {
|
|
16690
|
-
existing = JSON.parse(
|
|
17270
|
+
existing = JSON.parse(fs73.readFileSync(file, "utf8"));
|
|
16691
17271
|
} catch {
|
|
16692
17272
|
existing = void 0;
|
|
16693
17273
|
}
|
|
16694
17274
|
const { settings, changed } = mergeHaulHook(existing);
|
|
16695
17275
|
if (changed) {
|
|
16696
|
-
|
|
16697
|
-
|
|
17276
|
+
fs73.mkdirSync(path60.dirname(file), { recursive: true });
|
|
17277
|
+
fs73.writeFileSync(file, `${JSON.stringify(settings, null, 2)}
|
|
16698
17278
|
`);
|
|
16699
17279
|
}
|
|
16700
|
-
if (has("--marker"))
|
|
17280
|
+
if (has("--marker")) fs73.writeFileSync(".lobstah-man", "");
|
|
16701
17281
|
console.log(
|
|
16702
17282
|
toonKV({
|
|
16703
17283
|
hook: "lobstah man haul",
|
|
@@ -16743,7 +17323,7 @@ ${usageFor("man:wait")}`);
|
|
|
16743
17323
|
if (helm) releaseHeldQuestions(helm);
|
|
16744
17324
|
const cfgHaul = loadConfig();
|
|
16745
17325
|
if (!helm && liveHelms(cfgHaul.helm.ttlSecs * 1e3).length > 0) break;
|
|
16746
|
-
if (!helm && process.env.LOBSTAH_MAN !== "1" && !
|
|
17326
|
+
if (!helm && process.env.LOBSTAH_MAN !== "1" && !fs73.existsSync(".lobstah-man")) break;
|
|
16747
17327
|
const dueDigest = () => helm ? dueHelmDigest(helm, cfgHaul.helm.reportSecs) : void 0;
|
|
16748
17328
|
const blockDigest = (d) => {
|
|
16749
17329
|
advanceCursor(helm.grounds, d.now);
|
|
@@ -16960,7 +17540,7 @@ ${behind}` : "");
|
|
|
16960
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}.`
|
|
16961
17541
|
);
|
|
16962
17542
|
}
|
|
16963
|
-
if (!
|
|
17543
|
+
if (!fs73.existsSync(mine.worktree)) {
|
|
16964
17544
|
throw new Error(
|
|
16965
17545
|
`this session's trap wt:${mine.trapId} is anchored at ${mine.worktree}, which no longer exists. Run \`lobstah stow\`, then soak again.`
|
|
16966
17546
|
);
|
|
@@ -17036,8 +17616,8 @@ ${usageFor("soak")}`
|
|
|
17036
17616
|
if (reservation) dropReservation(reservation.trapId);
|
|
17037
17617
|
const titled = reg.name ? await setTerminalTitle(reg.window, reg.name) : void 0;
|
|
17038
17618
|
const inside = (() => {
|
|
17039
|
-
const rel =
|
|
17040
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
17619
|
+
const rel = path60.relative(reg.worktree, canon(process.cwd()));
|
|
17620
|
+
return rel === "" || !rel.startsWith("..") && !path60.isAbsolute(rel);
|
|
17041
17621
|
})();
|
|
17042
17622
|
const sessionFlag = inside ? "" : ` --session ${sessionId}`;
|
|
17043
17623
|
console.log(
|
|
@@ -17126,7 +17706,7 @@ ${usageFor("soak")}`
|
|
|
17126
17706
|
const anchor = wtDir !== void 0 ? readTrapAnchor(wtDir) : void 0;
|
|
17127
17707
|
const created = reg?.createdWorktree === true || anchor?.createdBy === "soak";
|
|
17128
17708
|
let worktreeOut = {};
|
|
17129
|
-
if (wtDir !== void 0 &&
|
|
17709
|
+
if (wtDir !== void 0 && fs73.existsSync(wtDir)) {
|
|
17130
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;
|
|
17131
17711
|
if (keepReason) {
|
|
17132
17712
|
worktreeOut = { worktree: "kept", path: wtDir, reason: keepReason };
|
|
@@ -17202,7 +17782,7 @@ ${usageFor("pick")}`);
|
|
|
17202
17782
|
restarted: res.command,
|
|
17203
17783
|
...kind === "daemon" ? { headless: usage.headless, trapCatches: `${usage.traps} unaffected` } : {},
|
|
17204
17784
|
...active > 0 ? { interrupted: `${active} headless dispatch(es) (--force)` } : {},
|
|
17205
|
-
...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")}` }
|
|
17206
17786
|
})
|
|
17207
17787
|
);
|
|
17208
17788
|
if (!hb) process.exitCode = 1;
|
|
@@ -17213,6 +17793,7 @@ ${usageFor("pick")}`);
|
|
|
17213
17793
|
culler: cliCuller,
|
|
17214
17794
|
prWatches: (now2, log) => {
|
|
17215
17795
|
observeDispatchPrWatches(pollSecs(), now2);
|
|
17796
|
+
observeWaitedPrs({ everySecs: pollSecs(), now: now2 });
|
|
17216
17797
|
finishResolvedWaits(log);
|
|
17217
17798
|
deliverPrRepairs(log, loadConfig().watch.maxForksPerCycle);
|
|
17218
17799
|
stampRepairerBeat(now2);
|
|
@@ -17313,8 +17894,8 @@ ${usageFor("pet")}`);
|
|
|
17313
17894
|
break;
|
|
17314
17895
|
}
|
|
17315
17896
|
const server = serveGlass(port);
|
|
17316
|
-
await new Promise((
|
|
17317
|
-
server.once("listening", () =>
|
|
17897
|
+
await new Promise((resolve14, reject) => {
|
|
17898
|
+
server.once("listening", () => resolve14());
|
|
17318
17899
|
server.once("error", reject);
|
|
17319
17900
|
});
|
|
17320
17901
|
console.log(
|
|
@@ -17350,7 +17931,7 @@ ${usageFor("pet")}`);
|
|
|
17350
17931
|
path: r.path,
|
|
17351
17932
|
trunk: r.trunk,
|
|
17352
17933
|
pickup: r.pickup ?? false,
|
|
17353
|
-
exists:
|
|
17934
|
+
exists: fs73.existsSync(r.path)
|
|
17354
17935
|
}));
|
|
17355
17936
|
console.log(toonTable("repos", rows, ["key", "path", "trunk", "pickup", "exists"]));
|
|
17356
17937
|
break;
|
|
@@ -17441,7 +18022,8 @@ ${usageFor("pet")}`);
|
|
|
17441
18022
|
const key = pos[1];
|
|
17442
18023
|
if (!key && !has("--all")) throw new Error("watch release requires a key or --all");
|
|
17443
18024
|
const released = releaseHeldWatches(has("--all") ? void 0 : key);
|
|
17444
|
-
|
|
18025
|
+
const resumed = resetRepairStreaks(has("--all") ? void 0 : key);
|
|
18026
|
+
console.log(toonKV({ released: released.length, ...resumed.length ? { repairsResumed: resumed.join(", ") } : {} }));
|
|
17445
18027
|
break;
|
|
17446
18028
|
}
|
|
17447
18029
|
if (sub === "rm") {
|
|
@@ -17463,7 +18045,7 @@ ${usageFor("pet")}`);
|
|
|
17463
18045
|
break;
|
|
17464
18046
|
}
|
|
17465
18047
|
case "init": {
|
|
17466
|
-
if (!
|
|
18048
|
+
if (!fs73.existsSync(configPath())) {
|
|
17467
18049
|
const exampleRepo = has("--scan") ? "" : `[repos.example]
|
|
17468
18050
|
path = "~/src/example"
|
|
17469
18051
|
trunk = "main"
|
|
@@ -17472,7 +18054,7 @@ trunk = "main"
|
|
|
17472
18054
|
# pickup = true # opt into [pickup.github] multi-repo tracker pickup
|
|
17473
18055
|
|
|
17474
18056
|
`;
|
|
17475
|
-
|
|
18057
|
+
fs73.writeFileSync(
|
|
17476
18058
|
configPath(),
|
|
17477
18059
|
`# lobstah workspace definitions \u2014 the descriptor's repo key resolves here.
|
|
17478
18060
|
# Top-level keys must come before any [section].
|
|
@@ -17510,12 +18092,12 @@ wallClockSecs = 3600
|
|
|
17510
18092
|
let reference = "https://github.com/aequitas-labs/lobstah/blob/main/docs/configuration.md";
|
|
17511
18093
|
try {
|
|
17512
18094
|
const local = fileURLToPath6(new URL("../docs/configuration.md", import.meta.url));
|
|
17513
|
-
if (
|
|
18095
|
+
if (fs73.existsSync(local)) reference = local;
|
|
17514
18096
|
} catch {
|
|
17515
18097
|
}
|
|
17516
18098
|
console.log(
|
|
17517
18099
|
toonKV({
|
|
17518
|
-
home:
|
|
18100
|
+
home: path60.dirname(configPath()),
|
|
17519
18101
|
config: configPath(),
|
|
17520
18102
|
...added.length > 0 ? { added: added.join(", ") } : {},
|
|
17521
18103
|
...unmarked.length > 0 ? { note: `none marked pickable \u2014 set pickup = true per [repos.*] (or rerun with --pickup)` } : {},
|