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 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.dispatch && opts.replace !== false ? standingDecisions().filter((d) => d.key !== key && d.dispatch === opts.dispatch && d.lane === opts.lane).map((d) => d.key) : [];
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 finish = `Run the relevant tests. ${pushRule(pr.headRefName, opts.id)} Report done with the same PR URL.`;
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} Fetch the PR's base branch ${pr.baseRefName ?? "(read from PR)"}. Bring the PR branch up to date with that base by this repo's convention. ${finish}`;
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 gitShared(repo.path, "fetch", "origin", fromRemoteBranch);
5646
- await gitShared(repo.path, "worktree", "add", "--no-track", dir, "-b", `lobstah/${id}`, `origin/${fromRemoteBranch}`);
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 gitShared(dir, "fetch", "origin", repo.trunk);
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((resolve13) => this.waiters.push(resolve13));
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((resolve13) => this.waiter = resolve13);
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((resolve13) => {
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
- resolve13({ sessionId, error: err.message });
6380
+ resolve14({ sessionId, error: err.message });
6130
6381
  });
6131
6382
  child.on("exit", (code) => {
6132
6383
  events.close();
6133
6384
  if (code === 0)
6134
- resolve13({ sessionId });
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
- resolve13({ sessionId, error });
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 fs68 from "node:fs";
6482
+ import * as fs69 from "node:fs";
6232
6483
  import * as os10 from "node:os";
6233
- import * as path55 from "node:path";
6484
+ import * as path56 from "node:path";
6234
6485
  import { spawn as spawn6 } from "node:child_process";
6235
6486
  function isolatedCodexHome() {
6236
- const dir = path55.join(lobstahHome(), "codex-home");
6237
- fs68.mkdirSync(dir, { recursive: true });
6238
- const auth = path55.join(dir, "auth.json");
6239
- const realAuth = path55.join(os10.homedir(), ".codex", "auth.json");
6240
- if (!fs68.existsSync(auth) && fs68.existsSync(realAuth)) {
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
- fs68.symlinkSync(realAuth, auth);
6493
+ fs69.symlinkSync(realAuth, auth);
6243
6494
  } catch {
6244
- fs68.copyFileSync(realAuth, auth);
6495
+ fs69.copyFileSync(realAuth, auth);
6245
6496
  }
6246
6497
  }
6247
- const cfg = path55.join(dir, "config.toml");
6248
- if (!fs68.existsSync(cfg)) {
6249
- fs68.writeFileSync(cfg, "# lobstah-managed: auth inherited from ~/.codex, interactive config not\n");
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((resolve13) => {
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) => resolve13(err.message));
6583
+ child.on("error", (err) => resolve14(err.message));
6333
6584
  child.on("exit", (code) => {
6334
6585
  if (turnError)
6335
- return resolve13(turnError);
6586
+ return resolve14(turnError);
6336
6587
  if (code === 0 || killed)
6337
- return resolve13(void 0);
6588
+ return resolve14(void 0);
6338
6589
  const detail = stderrTail.trim().split("\n").at(-1);
6339
- resolve13(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
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((resolve13) => abandon = resolve13);
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((resolve13) => setTimeout(resolve13, ms));
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 += t - last;
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 fs69 from "node:fs";
6870
- import * as path56 from "node:path";
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 = path56.resolve(path56.dirname(fileURLToPath4(import.meta.url)), "..", "..", "..");
6904
- const localCli = path56.join(root, "apps", "cli", "dist", "main.js");
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 (fs69.existsSync(localCli))
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 fs70 from "node:fs";
7069
- import * as path57 from "node:path";
7070
- import { execFileSync as execFileSync8 } from "node:child_process";
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((resolve13) => timer = setTimeout(() => resolve13(fallback), ms));
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 = path57.basename(activeDir);
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(fs70.readFileSync(path57.join(activeDir, "descriptor.json"), "utf8"));
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 = path57.join(activeDir, "brief.md");
7101
- if (!fs70.existsSync(briefFile))
7102
- fs70.writeFileSync(briefFile, descriptor.brief);
7103
- const brief = fs70.readFileSync(briefFile, "utf8");
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
- fs70.writeFileSync(path57.join(activeDir, "runner.json"), JSON.stringify(runnerInfo, null, 2));
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 = path57.join(activeDir, "worktree.json");
7391
+ const wtFile = path58.join(activeDir, "worktree.json");
7121
7392
  let recorded;
7122
7393
  let choice;
7123
- if (fs70.existsSync(wtFile)) {
7124
- recorded = JSON.parse(fs70.readFileSync(wtFile, "utf8")).path;
7125
- if (!fs70.existsSync(recorded))
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 (fs70.existsSync(worktreePath(id))) {
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
- fs70.writeFileSync(wtFile, JSON.stringify({ path: cwd, of: choice.owner }, null, 2));
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
- fs70.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
7437
+ fs71.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
7167
7438
  }
7168
- const worktreeOf = JSON.parse(fs70.readFileSync(wtFile, "utf8")).of;
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 = path57.resolve(path57.dirname(fileURLToPath5(import.meta.url)), "..", "..", "..", "bin");
7203
- const workerPath = fs70.existsSync(path57.join(binDir, "lobstah")) ? `${binDir}${path57.delimiter}${process.env.PATH ?? ""}` : void 0;
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 = path57.join(activeDir, "wallclock.json");
7477
+ const wallFile = path58.join(activeDir, "wallclock.json");
7207
7478
  const wallState = (() => {
7208
7479
  try {
7209
- return JSON.parse(fs70.readFileSync(wallFile, "utf8"));
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 execFileSync8("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
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
- fs70.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
7226
- fs70.renameSync(tmp, wallFile);
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 execFileSync9 } from "node:child_process";
7676
+ import { execFileSync as execFileSync10 } from "node:child_process";
7405
7677
  function listProcesses() {
7406
7678
  if (process.platform === "win32") {
7407
- const out2 = execFileSync9("powershell.exe", [
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 = execFileSync9("ps", ["-A", "-o", "pid=,ppid=,pgid="], {
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
- execFileSync9("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 3e4 });
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 fs71 from "node:fs";
7513
- import * as path58 from "node:path";
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 = path58.basename(activeDirArg2);
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
- fs71.renameSync(activeDirArg2, path58.join(laneDirs(lane).done, id));
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 fs72 from "node:fs";
7822
+ import * as fs73 from "node:fs";
7551
7823
  import * as os11 from "node:os";
7552
- import * as path59 from "node:path";
7824
+ import * as path60 from "node:path";
7553
7825
  import { randomUUID as randomUUID6 } from "node:crypto";
7554
- import { spawnSync as spawnSync13 } from "node:child_process";
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 execFileSync3 } from "node:child_process";
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 = execFileSync3("powershell.exe", ["-NoProfile", "-Command", `(Get-Process -Id ${pid}).StartTime.Ticks`], { encoding: "utf8" }).trim();
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 execFileSync3("ps", ["-p", String(pid), "-o", "lstart="], { encoding: "utf8" }).trim() || void 0;
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 pidAlive(pid, recordedStart) {
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
- execFileSync3("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
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 ? pidAlive(st.runner.pid, st.runner.processStartTime) : void 0;
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) && !pidAlive(st.runner.pid, st.runner.processStartTime));
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 (pidAlive(prior.pid, prior.processStartTime)) {
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((resolve13) => {
8461
+ await new Promise((resolve14) => {
8186
8462
  const timer = setTimeout(() => {
8187
8463
  wake = void 0;
8188
- resolve13();
8464
+ resolve14();
8189
8465
  }, intervalMs);
8190
8466
  wake = () => {
8191
8467
  wake = void 0;
8192
8468
  clearTimeout(timer);
8193
- setTimeout(resolve13, 50);
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 execFileSync4 } from "node:child_process";
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 execFileSync4("git", args, { cwd: worktree, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).trim();
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
- `Rebase the branch ${pr.headRef} of ${pr.url} onto its base branch, resolving any conflicts`,
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
- pushRule(pr.headRef, id),
9206
- `Do not merge the PR. Do not change anything beyond conflict resolution. When pushed, report status done.`
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 brief = (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));
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 execFileSync5 } from "node:child_process";
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 = execFileSync5("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5e3 }).trim();
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
- if (!hasOpenCatch(reg)) continue;
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: reg.claimed,
10227
- lane: laneOf(reg.claimed) ?? "work",
10513
+ ...catchId ? { dispatchId: catchId } : {},
10514
+ lane,
10228
10515
  worktree: reg.worktree,
10229
- repo: storedDescriptor(reg.claimed, laneOf(reg.claimed) ?? "work")?.repo ?? reg.repo
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
- return { label: worker.label, dispatchId: worker.dispatchId, ...forgeRepo ? { forgeRepo } : {}, branches, prs };
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
- return { heldBy: worker.label, reason: `${worker.label} (dispatch ${worker.dispatchId.slice(0, 8)}) ${how}${where}` };
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 held = repairHold(pr, records ??= readPrs(), workers ??= (opts.workerHolds ?? readWorkerHolds)());
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 claim = readSessionClaim(chain.latest, "work");
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 = repairBrief(pr, kind, kind === "checks" && fresh.length > 0 ? { id, checks: fresh, gates } : { id, gates });
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
- stories.push({ key, dispatches: chain, prUrl, prState: prStateOf(chain), gate: gateFor(entry.uuid, prUrl), watch: watch2 });
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 pidAlive2(state.pid) ? state : void 0;
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((resolve13) => setTimeout(resolve13, 50));
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 pidAlive2(pid) {
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((resolve13) => {
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
- resolve13(info);
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((resolve13) => {
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
- resolve13(info);
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((resolve13) => setTimeout(resolve13, ms));
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 (pidAlive2(pid)) {
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 execFileSync6 } from "node:child_process";
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 = execFileSync6("du", ["-sk", target], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
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
- execFileSync6("git", ["worktree", "remove", "--force", dir], { cwd: repo.path, stdio: "ignore" });
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 = execFileSync6("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
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
- execFileSync6("git", ["--git-dir", common, "worktree", "remove", "--force", dir], { stdio: "ignore" });
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 execFileSync7 } from "node:child_process";
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 execFileSync7("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
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((resolve13, reject) => {
13321
- waiting.set(id, { resolve: resolve13, reject });
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 pidAlive3(pid) {
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 || pidAlive3(hb.pid));
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 legacyBefore = id && lane ? readEvidence(id, lane).pr : void 0;
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
- if (id && lane) mergeEvidence(id, lane, { pr });
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 waitedPr(id, lane, entry) {
14835
+ function waitedPrs(id, lane, entry) {
14445
14836
  const link = entry.link ? parsePrRef(entry.link) : void 0;
14446
- return link ?? ownPr(id, lane);
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 ownPr(id, lane) {
14841
+ function ownPrs(id, lane) {
14449
14842
  const evidence = readEvidence(id, lane);
14450
- const url = evidence.prUrl ?? evidence.pr?.url ?? chainPr(id, lane)?.url;
14451
- return url ? parsePrRef(url) : void 0;
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 registerWaitWatch(id, lane, entry) {
14855
+ function registerWaitWatches(id, lane, entry) {
14454
14856
  try {
14455
- if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return void 0;
14456
- const own = ownPr(id, lane);
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 (!own || link && link.key !== own.key) return void 0;
14459
- return autoRegisterPrWatch(id, own.url);
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 void 0;
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 ref = waitedPr(id, lane, last);
14478
- const record = ref ? readPr(ref.key) : void 0;
14479
- if (!ref || !record || record.state !== "MERGED" && record.state !== "CLOSED") continue;
14480
- const merged = record.state === "MERGED";
14481
- const verb = merged ? "done" : "failed";
14482
- appendStatus(id, lane, verb, merged ? `the PR merged: ${ref.url}` : `the PR closed without merge: ${ref.url}`);
14483
- if (!readEvidence(id, lane).prUrl) mergeEvidence(id, lane, { prUrl: ref.url });
14484
- out.push({ id, lane, pr: ref.key, verb });
14485
- log(`${id}: waited on ${ref.key}, which ${merged ? "merged" : "closed without merge"} \u2014 ${verb}`);
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 fs65 from "node:fs";
14495
- import { spawnSync as spawnSync11 } from "node:child_process";
15027
+ import * as fs66 from "node:fs";
15028
+ import { spawnSync as spawnSync12 } from "node:child_process";
14496
15029
  function branchExists(repo, branch) {
14497
- return spawnSync11("git", ["-C", repo.path, "rev-parse", "--verify", "-q", `refs/heads/${branch}`], { stdio: "ignore" }).status === 0;
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}`) || fs65.existsSync(worktreePath(`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 fs66 from "node:fs";
14602
- import * as path53 from "node:path";
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 spawnSync12 } from "node:child_process";
14607
- var defaultRun = (cmd, args, cwd) => {
14608
- const res = spawnSync12(cmd, args, { cwd, encoding: "utf8", timeout: cmd === "gh" ? 1e4 : 3e3, windowsHide: true });
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 ?? defaultRun;
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 = path53.join(lobstahHome(), "logs");
14647
- fs66.mkdirSync(dir, { recursive: true });
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
- fs66.appendFileSync(path53.join(dir, "beat.log"), `${(/* @__PURE__ */ new Date()).toISOString()} ${msg.replace(/\s+/g, " ").slice(0, 300)}
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. A newer ask on the same dispatch replaces it. The answer
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 fs67 from "node:fs";
15097
- import * as path54 from "node:path";
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 = () => path54.join(lobstahHome(), "watchers");
15101
- var watcherFile = (sessionId) => path54.join(watcherDir(), `${encodeURIComponent(sessionId)}.json`);
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(fs67.readFileSync(watcherFile(sessionId), "utf8"));
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((resolve13) => setTimeout(resolve13, Math.min(pollMs, Math.max(1, deadline - Date.now()))));
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
- fs67.mkdirSync(watcherDir(), { recursive: true });
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
- fs67.writeFileSync(file, `${JSON.stringify(own())}
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
- fs67.rmSync(file);
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(fs67.readFileSync(file, "utf8")).pid === process.pid;
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
- fs67.writeFileSync(tmp, `${JSON.stringify(own())}
15697
+ fs68.writeFileSync(tmp, `${JSON.stringify(own())}
15158
15698
  `);
15159
- fs67.renameSync(tmp, file);
15699
+ fs68.renameSync(tmp, file);
15160
15700
  } catch {
15161
15701
  try {
15162
- fs67.rmSync(tmp, { force: true });
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()) fs67.rmSync(file, { force: true });
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>). The answer wakes man
15322
- wait as a decision-answer event.
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
- --no-watch opts out. done|failed
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 (fs72.existsSync(path59.join(d.active, id)) || fs72.existsSync(path59.join(d.queue, `${id}.json`)) || fs72.existsSync(path59.join(d.done, id)) || fs72.existsSync(path59.join(d.state, `${id}.status`))) {
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) => fs72.readdirSync(dir).some((file) => !file.startsWith(".")));
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 fs72.readdirSync(dirs[bucket])) {
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 = path59.join(laneDirs(member.lane).active, member.id);
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(fs72.readFileSync(path59.join(dir, "runner.json"), "utf8"));
15609
- return pidAlive(runner.pid, runner.processStartTime);
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 = fs72.readdirSync(dir).filter((f) => !f.startsWith(".")).map((f) => ({ f, m: fs72.statSync(path59.join(dir, f)).mtimeMs })).sort((a, b) => b.m - a.m);
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(fs72.readFileSync(path59.join(laneDirs(lane)[bucket], id, "claim.json"), "utf8")).by;
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 ?? fs72.readFileSync(briefFile, "utf8"),
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 = fs72.existsSync(path59.join(laneDirs(lane).active, id));
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 (fs72.existsSync(file)) {
15933
- const raw = fs72.readFileSync(file, "utf8");
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 = fs72.existsSync(file) ? fs72.statSync(file).size : 0;
16490
+ let size = fs73.existsSync(file) ? fs73.statSync(file).size : 0;
15945
16491
  setInterval(() => {
15946
- if (!fs72.existsSync(file)) return;
15947
- const now2 = fs72.statSync(file).size;
16492
+ if (!fs73.existsSync(file)) return;
16493
+ const now2 = fs73.statSync(file).size;
15948
16494
  if (now2 > size) {
15949
- const fd = fs72.openSync(file, "r");
16495
+ const fd = fs73.openSync(file, "r");
15950
16496
  const buf = Buffer.alloc(now2 - size);
15951
- fs72.readSync(fd, buf, 0, buf.length, size);
15952
- fs72.closeSync(fd);
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 = path59.join(laneDirs(origin.lane)[origin.bucket], origin.id, "claim.json");
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(fs72.readFileSync(claimFile, "utf8")).by;
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 prUrl = opt("--pr");
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 && fs72.existsSync(claim.worktree)) {
16102
- const rel = path59.relative(canon(claim.worktree), canon(process.cwd()));
16103
- const inside = rel === "" || !rel.startsWith("..") && !path59.isAbsolute(rel);
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) => spawnSync13("git", args2, { cwd: claim.worktree, encoding: "utf8", timeout: 1e4 });
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
- if (prUrl) mergeEvidence(id, lane, { prUrl });
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 prWatch = verb !== "failed" && prUrl && !noWatch && !storedDescriptor(id, lane)?.systemRepair ? autoRegisterPrWatch(id, prUrl) : verb === "paused" && !noWatch ? registerWaitWatch(id, lane, entry) : void 0;
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
- ...prWatch ? { watch: prWatch.key } : {},
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 = fs72.existsSync(worktree) ? worktree : process.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 = spawnSync13(invocation.file, invocation.argv, { cwd, stdio: "inherit" });
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 = path59.join(laneDirs(lane).active, id);
16195
- if (!fs72.existsSync(activeDir)) throw new Error(`${id} is not active \u2014 swap only applies to in-flight dispatches`);
16196
- const descFile = path59.join(activeDir, "descriptor.json");
16197
- const descriptor = JSON.parse(fs72.readFileSync(descFile, "utf8"));
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
- fs72.writeFileSync(descFile, JSON.stringify(descriptor, null, 2));
16778
+ fs73.writeFileSync(descFile, JSON.stringify(descriptor, null, 2));
16206
16779
  const worktree = dispatchWorktree(id, lane).path;
16207
- fs72.writeFileSync(path59.join(activeDir, "handoff"), handoffNote(fromHarness, worktreeProgress(worktree)));
16208
- const runnerFile = path59.join(activeDir, "runner.json");
16209
- if (fs72.existsSync(runnerFile)) {
16210
- const runner = JSON.parse(fs72.readFileSync(runnerFile, "utf8"));
16211
- if (pidAlive(runner.pid, runner.processStartTime)) killGroup(runner.pid, "SIGKILL");
16212
- fs72.unlinkSync(runnerFile);
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 (fs72.existsSync(path59.join(laneDirs(lane).done, id))) {
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 (fs72.existsSync(path59.join(laneDirs(lane).active, id))) {
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 (fs72.existsSync(path59.join(laneDirs(lane).active, id))) {
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") ? path59.join(os11.homedir(), ".claude", "settings.json") : path59.join(".claude", has("--shared") ? "settings.json" : "settings.local.json");
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(fs72.readFileSync(file, "utf8"));
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
- fs72.mkdirSync(path59.dirname(file), { recursive: true });
16697
- fs72.writeFileSync(file, `${JSON.stringify(settings, null, 2)}
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")) fs72.writeFileSync(".lobstah-man", "");
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" && !fs72.existsSync(".lobstah-man")) break;
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 (!fs72.existsSync(mine.worktree)) {
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 = path59.relative(reg.worktree, canon(process.cwd()));
17040
- return rel === "" || !rel.startsWith("..") && !path59.isAbsolute(rel);
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 && fs72.existsSync(wtDir)) {
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 ${path59.join(lobstahHome(), "logs", "daemon.err")}` }
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((resolve13, reject) => {
17317
- server.once("listening", () => resolve13());
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: fs72.existsSync(r.path)
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
- console.log(toonKV({ released: released.length }));
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 (!fs72.existsSync(configPath())) {
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
- fs72.writeFileSync(
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 (fs72.existsSync(local)) reference = local;
18095
+ if (fs73.existsSync(local)) reference = local;
17514
18096
  } catch {
17515
18097
  }
17516
18098
  console.log(
17517
18099
  toonKV({
17518
- home: path59.dirname(configPath()),
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)` } : {},