lobstah 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1638,6 +1638,23 @@ var init_dist = __esm({
1638
1638
  import * as fs7 from "node:fs";
1639
1639
  import * as os2 from "node:os";
1640
1640
  import * as path7 from "node:path";
1641
+ function parseBriefHooks(raw) {
1642
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1643
+ return void 0;
1644
+ const out = {};
1645
+ for (const kind of [...BRIEF_KINDS, "all"]) {
1646
+ const text = raw[kind];
1647
+ if (typeof text === "string" && text.trim())
1648
+ out[kind] = text.trim();
1649
+ }
1650
+ return Object.keys(out).length ? out : void 0;
1651
+ }
1652
+ function withBriefHooks(brief, repo, kind) {
1653
+ const hooks = [repo?.briefHooks?.[kind], repo?.briefHooks?.all].filter((t) => !!t);
1654
+ return hooks.length ? `${brief}
1655
+
1656
+ ${hooks.join("\n\n")}` : brief;
1657
+ }
1641
1658
  function parseAttentionKinds(raw) {
1642
1659
  if (raw === void 0)
1643
1660
  return [...DEFAULT_ATTENTION_KINDS];
@@ -1675,7 +1692,8 @@ function loadConfig() {
1675
1692
  pushEarly: r.pushEarly === void 0 ? void 0 : Boolean(r.pushEarly),
1676
1693
  draftPr: r.draftPr === void 0 ? void 0 : Boolean(r.draftPr),
1677
1694
  checkpointOnStop: r.checkpointOnStop === void 0 ? void 0 : Boolean(r.checkpointOnStop),
1678
- humanGateChecks: Array.isArray(r.humanGateChecks) ? r.humanGateChecks.map(String).filter(Boolean) : void 0
1695
+ humanGateChecks: Array.isArray(r.humanGateChecks) ? r.humanGateChecks.map(String).filter(Boolean) : void 0,
1696
+ briefHooks: parseBriefHooks(r.briefHooks)
1679
1697
  };
1680
1698
  }
1681
1699
  const groundsRaw = raw.grounds ?? {};
@@ -1716,12 +1734,13 @@ function resolveDispatch(d, cfg) {
1716
1734
  flags: d.flags ?? []
1717
1735
  };
1718
1736
  }
1719
- var ATTENTION_KINDS, DEFAULT_ATTENTION_KINDS, DEFAULT_SOAK, DEFAULT_HELM, DEFAULT_GLASS, DEFAULT_WATCH, DEFAULT_LIMITS;
1737
+ var BRIEF_KINDS, ATTENTION_KINDS, DEFAULT_ATTENTION_KINDS, DEFAULT_SOAK, DEFAULT_HELM, DEFAULT_GLASS, DEFAULT_WATCH, DEFAULT_LIMITS;
1720
1738
  var init_config = __esm({
1721
1739
  "packages/core/dist/config.js"() {
1722
1740
  "use strict";
1723
1741
  init_dist();
1724
1742
  init_paths();
1743
+ BRIEF_KINDS = ["conflict", "checks", "review", "ciFix", "rebase"];
1725
1744
  ATTENTION_KINDS = ["question", "decision", "landed", "pr:draft", "pr:review", "pr:checks", "pr:conflict", "pr:ready", "report"];
1726
1745
  DEFAULT_ATTENTION_KINDS = ["question", "decision", "pr:ready", "pr:review", "pr:conflict", "pr:checks"];
1727
1746
  DEFAULT_SOAK = {
@@ -1736,7 +1755,7 @@ var init_config = __esm({
1736
1755
  armGraceSecs: 5
1737
1756
  };
1738
1757
  DEFAULT_GLASS = { port: 4949 };
1739
- DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
1758
+ DEFAULT_WATCH = { maxForksPerCycle: 3, autoRepair: true, conflicts: true, checks: true, maxRepairsPerPr: 2, maxRepairsWithoutProgress: 2, repairSettleSecs: 600, repairTrapWaitSecs: 600 };
1740
1759
  DEFAULT_LIMITS = {
1741
1760
  maxConcurrent: 2,
1742
1761
  choreConcurrent: 1,
@@ -4716,6 +4735,20 @@ var init_compiled = __esm({
4716
4735
  // packages/core/dist/pr.js
4717
4736
  import { spawnSync as spawnSync2 } from "node:child_process";
4718
4737
  import { createHash as createHash5 } from "node:crypto";
4738
+ function dispatchPrUrls(ev) {
4739
+ const out = /* @__PURE__ */ new Map();
4740
+ for (const u of [ev.prUrl, ...ev.prUrls ?? []]) {
4741
+ const ref = u ? parsePrRef(u) : void 0;
4742
+ if (ref && !out.has(ref.key))
4743
+ out.set(ref.key, ref.url);
4744
+ }
4745
+ if (out.size === 0 && ev.pr?.url) {
4746
+ const ref = parsePrRef(ev.pr.url);
4747
+ if (ref)
4748
+ out.set(ref.key, ref.url);
4749
+ }
4750
+ return [...out.values()];
4751
+ }
4719
4752
  function parsePrRef(s) {
4720
4753
  const m = /^(?:pr:)?([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)#(\d+)$/.exec(s.trim()) ?? /^https?:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/(\d+)(?:[/?#].*)?$/.exec(s.trim());
4721
4754
  if (!m)
@@ -4769,11 +4802,14 @@ function prStandingKinds(pr) {
4769
4802
  function prReview(view) {
4770
4803
  const latest = /* @__PURE__ */ new Map();
4771
4804
  let lastReviewAt;
4805
+ let lastApprovalAt;
4772
4806
  const reviews = [...view.reviews ?? []].sort((a, b) => (a.submittedAt ?? "").localeCompare(b.submittedAt ?? ""));
4773
4807
  for (const r of reviews) {
4774
4808
  if (r.submittedAt && (!lastReviewAt || r.submittedAt > lastReviewAt))
4775
4809
  lastReviewAt = r.submittedAt;
4776
4810
  const state = (r.state ?? "").toUpperCase();
4811
+ if (state === "APPROVED" && r.submittedAt && (!lastApprovalAt || r.submittedAt > lastApprovalAt))
4812
+ lastApprovalAt = r.submittedAt;
4777
4813
  if (state === "APPROVED" || state === "CHANGES_REQUESTED" || state === "DISMISSED")
4778
4814
  latest.set(r.author?.login ?? "?", state);
4779
4815
  }
@@ -4781,7 +4817,8 @@ function prReview(view) {
4781
4817
  return {
4782
4818
  ...view.unresolvedThreads !== void 0 ? { unresolvedThreads: view.unresolvedThreads } : {},
4783
4819
  changesRequested,
4784
- ...lastReviewAt ? { lastReviewAt } : {}
4820
+ ...lastReviewAt ? { lastReviewAt } : {},
4821
+ ...lastApprovalAt ? { lastApprovalAt } : {}
4785
4822
  };
4786
4823
  }
4787
4824
  function prEvidence(ref, view, observedAt) {
@@ -5128,7 +5165,9 @@ function upsertPr(pr, dispatchId) {
5128
5165
  headSince: sinceOf(before?.headSha === pr.headSha, before?.headSince, before?.observedAt, pr.observedAt),
5129
5166
  baseSince: sinceOf(!!before && before.baseRefName === pr.baseRefName && before.baseSha === (pr.baseSha ?? before.baseSha), before?.baseSince, before?.observedAt, pr.observedAt),
5130
5167
  ...pr.failingChecks?.length ? { failingSince: sinceOf(!!before && failingKey(before) === failingKey(pr), before?.failingSince, before?.observedAt, pr.observedAt) } : { failingSince: void 0 },
5131
- repair: before?.headSha === pr.headSha ? before?.repair : void 0
5168
+ repair: before?.headSha === pr.headSha ? before?.repair : void 0,
5169
+ // A merged or closed PR ends the run of repairs without progress.
5170
+ ...pr.state === "OPEN" ? {} : { repairStreak: void 0 }
5132
5171
  };
5133
5172
  writePr(after);
5134
5173
  return { before, after };
@@ -5157,6 +5196,23 @@ function removePr(key) {
5157
5196
  fs27.rmSync(file, { force: true });
5158
5197
  return existed;
5159
5198
  }
5199
+ function resetRepairStreaks(key) {
5200
+ const reset = [];
5201
+ for (const pr of key === void 0 ? readPrs() : [readPr(key)].filter((p) => p !== void 0)) {
5202
+ if (!pr.repairStreak)
5203
+ continue;
5204
+ withPrLock(pr.key, () => {
5205
+ const current = readPr(pr.key);
5206
+ if (!current?.repairStreak)
5207
+ return;
5208
+ const stopped = current.repair?.status === "gave-up" && current.repairStreak.stoppedHead !== void 0;
5209
+ const { repairStreak: _streak, ...rest } = current;
5210
+ writePr({ ...rest, ...stopped ? { repair: void 0 } : {} });
5211
+ reset.push(pr.key);
5212
+ });
5213
+ }
5214
+ return reset;
5215
+ }
5160
5216
  var init_prs = __esm({
5161
5217
  "packages/core/dist/prs.js"() {
5162
5218
  "use strict";
@@ -5275,24 +5331,43 @@ function repairKind(pr, gates = []) {
5275
5331
  function repairLimit(n) {
5276
5332
  return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 2;
5277
5333
  }
5278
- function pushRule(branch, id = "<dispatch id>") {
5334
+ function pushRule(branch, id = "<dispatch id>", mode = "rebase") {
5279
5335
  const b = branch ?? "the PR's head branch";
5280
5336
  return [
5281
5337
  `Push only to the existing branch ${b}.`,
5282
- `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, rebase your commits onto the moved head again, and push with \`--force-with-lease=${b}:<the head you just fetched>\`. Retry at most ${PUSH_RETRIES} times.`,
5338
+ mode === "merge" ? `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, merge the moved head into your branch, and push again with a normal push. Retry at most ${PUSH_RETRIES} times. Never force-push.` : `If the push is rejected as non-fast-forward because ${b} moved, fetch ${b}, rebase your commits onto the moved head again, and push with \`--force-with-lease=${b}:<the head you just fetched>\`. Retry at most ${PUSH_RETRIES} times.`,
5283
5339
  "If a push hook fails with a real test or type error, do not retry the push: fix the error, commit, and push again.",
5284
5340
  `If you still cannot push, report \`lobstah report ${id} failed "${PUSH_REJECTED} <rejection text>; moved head <full sha of the head you fetched>"\` and leave the PR as it was.`,
5285
5341
  "Never push to another branch. Never open a new PR."
5286
5342
  ].join(" ");
5287
5343
  }
5344
+ function standalonePr(baseRefName, trunk) {
5345
+ return baseRefName !== void 0 && trunk !== void 0 && baseRefName === trunk;
5346
+ }
5347
+ function conflictUpdate(base, branch, standalone) {
5348
+ const b = base ?? "the PR's base branch";
5349
+ const head = branch ?? "the PR's head branch";
5350
+ return standalone ? `Fetch origin/${b} and merge it into ${head} with a merge commit (\`git merge origin/${b}\`), resolving the conflicts in that merge. Do not rebase, and do not force-push: push the merge commit with a normal push.` : `This PR is stacked on its base branch ${b}. Fetch origin/${b} and rebase ${head} onto it, resolving the conflicts. Push with \`--force-with-lease=${head}:<the head you started from>\`.`;
5351
+ }
5352
+ function prReviewed(pr) {
5353
+ return pr.reviewDecision === "APPROVED" || pr.reviewDecision === "CHANGES_REQUESTED" || pr.review?.lastReviewAt !== void 0 || pr.review?.changesRequested === true;
5354
+ }
5355
+ function rerequestReview(pr, id = "<dispatch id>", reviewers = []) {
5356
+ const n = pr.number !== void 0 ? String(pr.number) : pr.url;
5357
+ const repo = pr.repo ? ` --repo ${pr.repo}` : "";
5358
+ const who = reviewers.length ? `its reviewers: \`gh pr edit ${n}${repo} --add-reviewer ${reviewers.join(",")}\`` : `its current reviewers: list them with \`gh pr view ${n}${repo} --json reviews,reviewRequests\`, then \`gh pr edit ${n}${repo} --add-reviewer <login>,<login>\``;
5359
+ return `This PR had a review, and your push changes its head, which invalidates that review. After you push, re-request review from ${who}. Then report \`lobstah report ${id} paused "<note>" --waiting-on review --link ${pr.url}\` instead of done.`;
5360
+ }
5288
5361
  function repairBrief(pr, kind, arg = {}) {
5289
5362
  const opts = typeof arg === "string" ? { id: arg } : arg;
5290
5363
  const intro = `Repair ${pr.url} on its existing branch ${pr.headRefName ?? "(see PR)"} at ${pr.headSha}. Do not open a new PR.`;
5291
5364
  const resolution = "For code already on main, take main's version. Keep only this PR's own changes. Never change behavior. If resolving a conflict would change code behavior, stop and report needs-decision.";
5292
- const 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}`;
5293
5368
  if (kind === "conflict")
5294
5369
  return `${intro}
5295
- ${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")}`;
5296
5371
  if (kind === "checks") {
5297
5372
  const failing = (pr.failingChecks ?? []).filter((c) => !opts.checks || opts.checks.includes(c.name));
5298
5373
  const checks = failing.map((c) => `- ${c.name}${c.detailsUrl ? ` \u2014 ${c.detailsUrl}` : ""}`).join("\n");
@@ -5303,10 +5378,10 @@ These failing checks are human gates. They pass only when a person approves. Do
5303
5378
  ${resolution}
5304
5379
  Latest failing checks:
5305
5380
  ${checks || "- Read the failing check from GitHub"}${gates}
5306
- Read each check log. Fix a real failure. If it is a flake, rerun it at most once. If a check cannot pass until a person approves the change, it is a human gate: do not change code for it, and name it on your report with --human-gate "<check name>", once per check. ${finish}`;
5381
+ Read each check log. Fix a real failure. If it is a flake, rerun it at most once. If a check cannot pass until a person approves the change, it is a human gate: do not change code for it, and name it on your report with --human-gate "<check name>", once per check. ${finish()}`;
5307
5382
  }
5308
5383
  return `${intro}
5309
- ${resolution} Read the requested review changes and comments with gh pr view --comments. Address the feedback. If a comment needs a person's decision, report needs-decision instead of guessing. ${finish}`;
5384
+ ${resolution} Read the requested review changes and comments with gh pr view --comments. Address the feedback. If a comment needs a person's decision, report needs-decision instead of guessing. ${finish()}`;
5310
5385
  }
5311
5386
  var PUSH_RETRIES, PUSH_REJECTED;
5312
5387
  var init_pr_repair = __esm({
@@ -6134,7 +6209,7 @@ var init_types2 = __esm({
6134
6209
  return Promise.resolve({ value: item, done: false });
6135
6210
  if (this.closed)
6136
6211
  return Promise.resolve({ value: void 0, done: true });
6137
- return new Promise((resolve13) => this.waiters.push(resolve13));
6212
+ return new Promise((resolve14) => this.waiters.push(resolve14));
6138
6213
  }
6139
6214
  };
6140
6215
  }
@@ -6159,7 +6234,7 @@ var init_types2 = __esm({
6159
6234
  return msg;
6160
6235
  if (this.ended)
6161
6236
  return void 0;
6162
- await new Promise((resolve13) => this.waiter = resolve13);
6237
+ await new Promise((resolve14) => this.waiter = resolve14);
6163
6238
  this.waiter = null;
6164
6239
  }
6165
6240
  }
@@ -6298,20 +6373,20 @@ function startClaudeCli(invocation, opts) {
6298
6373
  }
6299
6374
  }
6300
6375
  });
6301
- const done = new Promise((resolve13) => {
6376
+ const done = new Promise((resolve14) => {
6302
6377
  child.on("error", (err) => {
6303
6378
  events.push({ at: now(), type: "error", data: { message: err.message } });
6304
6379
  events.close();
6305
- resolve13({ sessionId, error: err.message });
6380
+ resolve14({ sessionId, error: err.message });
6306
6381
  });
6307
6382
  child.on("exit", (code) => {
6308
6383
  events.close();
6309
6384
  if (code === 0)
6310
- resolve13({ sessionId });
6385
+ resolve14({ sessionId });
6311
6386
  else {
6312
6387
  const detail = stderrTail.trim().split("\n").at(-1);
6313
6388
  const error = `claude exited ${code}${detail ? ` \u2014 ${detail}` : ""}`;
6314
- resolve13({ sessionId, error });
6389
+ resolve14({ sessionId, error });
6315
6390
  }
6316
6391
  });
6317
6392
  });
@@ -6404,25 +6479,25 @@ var init_claude = __esm({
6404
6479
  });
6405
6480
 
6406
6481
  // packages/adapters/dist/codex.js
6407
- import * as fs68 from "node:fs";
6482
+ import * as fs69 from "node:fs";
6408
6483
  import * as os10 from "node:os";
6409
- import * as path55 from "node:path";
6484
+ import * as path56 from "node:path";
6410
6485
  import { spawn as spawn6 } from "node:child_process";
6411
6486
  function isolatedCodexHome() {
6412
- const dir = path55.join(lobstahHome(), "codex-home");
6413
- fs68.mkdirSync(dir, { recursive: true });
6414
- const auth = path55.join(dir, "auth.json");
6415
- const realAuth = path55.join(os10.homedir(), ".codex", "auth.json");
6416
- 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)) {
6417
6492
  try {
6418
- fs68.symlinkSync(realAuth, auth);
6493
+ fs69.symlinkSync(realAuth, auth);
6419
6494
  } catch {
6420
- fs68.copyFileSync(realAuth, auth);
6495
+ fs69.copyFileSync(realAuth, auth);
6421
6496
  }
6422
6497
  }
6423
- const cfg = path55.join(dir, "config.toml");
6424
- if (!fs68.existsSync(cfg)) {
6425
- 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");
6426
6501
  }
6427
6502
  return dir;
6428
6503
  }
@@ -6474,7 +6549,7 @@ function startCodexCli(invoke, opts) {
6474
6549
  let current;
6475
6550
  let killed = false;
6476
6551
  function runTurn(prompt, resumeThread) {
6477
- return new Promise((resolve13) => {
6552
+ return new Promise((resolve14) => {
6478
6553
  const inv = invoke(codexExecArgs(opts, prompt, resumeThread));
6479
6554
  const child = spawn6(inv.file, inv.argv, {
6480
6555
  cwd: opts.cwd,
@@ -6505,14 +6580,14 @@ function startCodexCli(invoke, opts) {
6505
6580
  }
6506
6581
  }
6507
6582
  });
6508
- child.on("error", (err) => resolve13(err.message));
6583
+ child.on("error", (err) => resolve14(err.message));
6509
6584
  child.on("exit", (code) => {
6510
6585
  if (turnError)
6511
- return resolve13(turnError);
6586
+ return resolve14(turnError);
6512
6587
  if (code === 0 || killed)
6513
- return resolve13(void 0);
6588
+ return resolve14(void 0);
6514
6589
  const detail = stderrTail.trim().split("\n").at(-1);
6515
- resolve13(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
6590
+ resolve14(`codex exited ${code}${detail ? ` \u2014 ${detail}` : ""}`);
6516
6591
  });
6517
6592
  });
6518
6593
  }
@@ -6724,7 +6799,7 @@ async function drive(run4, opts) {
6724
6799
  let finalAt = 0;
6725
6800
  let exit;
6726
6801
  let abandon;
6727
- const abandoned = new Promise((resolve13) => abandon = resolve13);
6802
+ const abandoned = new Promise((resolve14) => abandon = resolve14);
6728
6803
  const clearExit = () => {
6729
6804
  if (!exit)
6730
6805
  return;
@@ -6897,7 +6972,7 @@ var init_drive = __esm({
6897
6972
  "use strict";
6898
6973
  init_dist2();
6899
6974
  WAITING_VERBS = ["needs-decision", "blocked", "paused"];
6900
- sleep = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms));
6975
+ sleep = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
6901
6976
  }
6902
6977
  });
6903
6978
 
@@ -7044,8 +7119,8 @@ var init_wallclock = __esm({
7044
7119
  import { execFile as execFile4 } from "node:child_process";
7045
7120
  import { promisify as promisify4 } from "node:util";
7046
7121
  import { fileURLToPath as fileURLToPath4 } from "node:url";
7047
- import * as fs69 from "node:fs";
7048
- import * as path56 from "node:path";
7122
+ import * as fs70 from "node:fs";
7123
+ import * as path57 from "node:path";
7049
7124
  function firstLine2(err) {
7050
7125
  const stderr = err && typeof err === "object" && "stderr" in err ? String(err.stderr).trim() : "";
7051
7126
  return (stderr || (err instanceof Error ? err.message : String(err))).replace(/\s+/g, " ").slice(0, 240);
@@ -7078,10 +7153,10 @@ function keepRemote(opts) {
7078
7153
  if (existingPrUrl)
7079
7154
  mergeEvidence(id, lane, { prUrl: existingPrUrl });
7080
7155
  const registerWatch = async (url) => {
7081
- const root = path56.resolve(path56.dirname(fileURLToPath4(import.meta.url)), "..", "..", "..");
7082
- 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");
7083
7158
  const args = ["watch", "add", url, "--for", id];
7084
- if (fs69.existsSync(localCli))
7159
+ if (fs70.existsSync(localCli))
7085
7160
  await command(process.execPath, [localCli, ...args], 15e3);
7086
7161
  else
7087
7162
  await command("lobstah", args, 15e3);
@@ -7243,8 +7318,8 @@ var init_remote = __esm({
7243
7318
  });
7244
7319
 
7245
7320
  // packages/runner/dist/run.js
7246
- import * as fs70 from "node:fs";
7247
- import * as path57 from "node:path";
7321
+ import * as fs71 from "node:fs";
7322
+ import * as path58 from "node:path";
7248
7323
  import { execFileSync as execFileSync9 } from "node:child_process";
7249
7324
  import { fileURLToPath as fileURLToPath5 } from "node:url";
7250
7325
  function livePrState(url) {
@@ -7259,7 +7334,7 @@ function livePrState(url) {
7259
7334
  }
7260
7335
  function within(p, ms, fallback) {
7261
7336
  let timer;
7262
- const late = new Promise((resolve13) => timer = setTimeout(() => resolve13(fallback), ms));
7337
+ const late = new Promise((resolve14) => timer = setTimeout(() => resolve14(fallback), ms));
7263
7338
  return Promise.race([p, late]).finally(() => clearTimeout(timer));
7264
7339
  }
7265
7340
  function boundPr(descriptor, lane) {
@@ -7277,18 +7352,18 @@ function coldNote(cold, cwd, trunk) {
7277
7352
  }
7278
7353
  async function main(activeDir, lane, seams = {}) {
7279
7354
  const deps = { ...defaultDeps, ...seams };
7280
- const id = path57.basename(activeDir);
7355
+ const id = path58.basename(activeDir);
7281
7356
  if (isTrapCatch(id, lane))
7282
7357
  throw new Error(`refusing headless runner for trap catch ${id}`);
7283
7358
  const status = (verb, note) => appendStatus(id, lane, verb, note);
7284
7359
  const cfg = loadConfig();
7285
- const descriptor = JSON.parse(fs70.readFileSync(path57.join(activeDir, "descriptor.json"), "utf8"));
7360
+ const descriptor = JSON.parse(fs71.readFileSync(path58.join(activeDir, "descriptor.json"), "utf8"));
7286
7361
  const resolved = resolveDispatch(descriptor, cfg);
7287
7362
  const repo = cfg.repos[descriptor.repo];
7288
- const briefFile = path57.join(activeDir, "brief.md");
7289
- if (!fs70.existsSync(briefFile))
7290
- fs70.writeFileSync(briefFile, descriptor.brief);
7291
- 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");
7292
7367
  const attempts = Number(process.env.LOBSTAH_ATTEMPTS ?? "1");
7293
7368
  const wake = process.env.LOBSTAH_WAKE;
7294
7369
  const runnerInfo = {
@@ -7296,7 +7371,7 @@ async function main(activeDir, lane, seams = {}) {
7296
7371
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
7297
7372
  attempts
7298
7373
  };
7299
- 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));
7300
7375
  if (descriptor.systemRepair && descriptor.pr?.url) {
7301
7376
  const state = deps.prState(descriptor.pr.url) ?? readPr(parsePrRef(descriptor.pr.url)?.key ?? "")?.state;
7302
7377
  if (state === "MERGED" || state === "CLOSED") {
@@ -7313,14 +7388,14 @@ async function main(activeDir, lane, seams = {}) {
7313
7388
  resolvedHarness: resolved.harness,
7314
7389
  envResume: process.env.LOBSTAH_RESUME
7315
7390
  });
7316
- const wtFile = path57.join(activeDir, "worktree.json");
7391
+ const wtFile = path58.join(activeDir, "worktree.json");
7317
7392
  let recorded;
7318
7393
  let choice;
7319
- if (fs70.existsSync(wtFile)) {
7320
- recorded = JSON.parse(fs70.readFileSync(wtFile, "utf8")).path;
7321
- if (!fs70.existsSync(recorded))
7394
+ if (fs71.existsSync(wtFile)) {
7395
+ recorded = JSON.parse(fs71.readFileSync(wtFile, "utf8")).path;
7396
+ if (!fs71.existsSync(recorded))
7322
7397
  throw new Error(`recorded worktree ${recorded} is gone`);
7323
- } else if (fs70.existsSync(worktreePath(id))) {
7398
+ } else if (fs71.existsSync(worktreePath(id))) {
7324
7399
  throw new Error(`unrecorded worktree already exists for ${id} \u2014 refusing to proceed`);
7325
7400
  } else if (descriptor.followUp && cfg.limits.reuseWorktree !== false) {
7326
7401
  choice = await deps.chooseWorktree({ id, lane, repoKey: descriptor.repo, repo, followUp: descriptor.followUp });
@@ -7355,13 +7430,13 @@ async function main(activeDir, lane, seams = {}) {
7355
7430
  type: "runner",
7356
7431
  data: { worktree: cwd, worktreeOf: choice.owner, setupRan }
7357
7432
  });
7358
- 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));
7359
7434
  } else {
7360
7435
  cwd = await deps.allocate(repo, id, descriptor.systemRepair ? descriptor.pr?.headRefName : void 0);
7361
7436
  acquireWorktreeLock(cwd, id, lane);
7362
- fs70.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
7437
+ fs71.writeFileSync(wtFile, JSON.stringify({ path: cwd }, null, 2));
7363
7438
  }
7364
- const worktreeOf = JSON.parse(fs70.readFileSync(wtFile, "utf8")).of;
7439
+ const worktreeOf = JSON.parse(fs71.readFileSync(wtFile, "utf8")).of;
7365
7440
  mergeEvidence(id, lane, { worktree: cwd, worktreeOf });
7366
7441
  const existingPr = boundPr(descriptor, lane);
7367
7442
  if (existingPr)
@@ -7395,14 +7470,14 @@ async function main(activeDir, lane, seams = {}) {
7395
7470
  const envNudge = [process.env.LOBSTAH_NUDGE, wakeNote()].filter(Boolean).join("\n\n") || void 0;
7396
7471
  const planNudge = plan.cold && !envNudge ? coldNote(plan.cold, cwd, repo.trunk) : void 0;
7397
7472
  const promptWith = (nudge) => buildPrompt(brief, { id, nudge, attachments: descriptor.attachments, existingPr });
7398
- const binDir = path57.resolve(path57.dirname(fileURLToPath5(import.meta.url)), "..", "..", "..", "bin");
7399
- 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;
7400
7475
  let wallClockHit = false;
7401
7476
  let current;
7402
- const wallFile = path57.join(activeDir, "wallclock.json");
7477
+ const wallFile = path58.join(activeDir, "wallclock.json");
7403
7478
  const wallState = (() => {
7404
7479
  try {
7405
- return JSON.parse(fs70.readFileSync(wallFile, "utf8"));
7480
+ return JSON.parse(fs71.readFileSync(wallFile, "utf8"));
7406
7481
  } catch {
7407
7482
  return { elapsedMs: 0, windowMs: resolved.limits.wallClockSecs * 1e3 };
7408
7483
  }
@@ -7418,8 +7493,8 @@ async function main(activeDir, lane, seams = {}) {
7418
7493
  const maxWallMs = Math.max(resolved.limits.wallClockSecs ?? 0, cfg.limits.maxWallClockSecs ?? (resolved.limits.wallClockSecs ?? 0) * 4) * 1e3;
7419
7494
  const persistWall = (elapsedMs, windowMs) => {
7420
7495
  const tmp = `${wallFile}.tmp-${process.pid}`;
7421
- fs70.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
7422
- fs70.renameSync(tmp, wallFile);
7496
+ fs71.writeFileSync(tmp, JSON.stringify({ ...wallState, elapsedMs, windowMs }));
7497
+ fs71.renameSync(tmp, wallFile);
7423
7498
  };
7424
7499
  const madeProgress = () => {
7425
7500
  const activity = readActivity(id, lane);
@@ -7706,17 +7781,17 @@ __export(dist_exports, {
7706
7781
  startedBy: () => startedBy,
7707
7782
  stopPids: () => stopPids
7708
7783
  });
7709
- import * as fs71 from "node:fs";
7710
- import * as path58 from "node:path";
7784
+ import * as fs72 from "node:fs";
7785
+ import * as path59 from "node:path";
7711
7786
  function runRunner(activeDirArg2, laneArg2) {
7712
7787
  const lane = laneArg2 === "chore" ? "chore" : "work";
7713
- const id = path58.basename(activeDirArg2);
7788
+ const id = path59.basename(activeDirArg2);
7714
7789
  main(activeDirArg2, lane, { reap: () => reapStarted(process.pid) }).catch((err) => {
7715
7790
  try {
7716
7791
  if (!isFinished(id, lane)) {
7717
7792
  appendStatus(id, lane, "failed", String(err instanceof Error ? err.message : err).slice(0, 500));
7718
7793
  }
7719
- fs71.renameSync(activeDirArg2, path58.join(laneDirs(lane).done, id));
7794
+ fs72.renameSync(activeDirArg2, path59.join(laneDirs(lane).done, id));
7720
7795
  } catch {
7721
7796
  }
7722
7797
  process.exitCode = 1;
@@ -7744,11 +7819,11 @@ var init_dist5 = __esm({
7744
7819
  // apps/cli/src/main.ts
7745
7820
  init_dist2();
7746
7821
  init_dist3();
7747
- import * as fs72 from "node:fs";
7822
+ import * as fs73 from "node:fs";
7748
7823
  import * as os11 from "node:os";
7749
- import * as path59 from "node:path";
7824
+ import * as path60 from "node:path";
7750
7825
  import { randomUUID as randomUUID6 } from "node:crypto";
7751
- import { spawnSync as spawnSync13 } from "node:child_process";
7826
+ import { spawnSync as spawnSync14 } from "node:child_process";
7752
7827
  import { fileURLToPath as fileURLToPath6 } from "node:url";
7753
7828
 
7754
7829
  // packages/supervisor/dist/liveness.js
@@ -8383,15 +8458,15 @@ async function daemon(intervalMs = 5e3, log = console.log, hooks = {}) {
8383
8458
  } catch (err) {
8384
8459
  log(`tick error: ${err instanceof Error ? err.message : String(err)}`);
8385
8460
  }
8386
- await new Promise((resolve13) => {
8461
+ await new Promise((resolve14) => {
8387
8462
  const timer = setTimeout(() => {
8388
8463
  wake = void 0;
8389
- resolve13();
8464
+ resolve14();
8390
8465
  }, intervalMs);
8391
8466
  wake = () => {
8392
8467
  wake = void 0;
8393
8468
  clearTimeout(timer);
8394
- setTimeout(resolve13, 50);
8469
+ setTimeout(resolve14, 50);
8395
8470
  };
8396
8471
  });
8397
8472
  }
@@ -8615,6 +8690,9 @@ var PickupState = class {
8615
8690
  }
8616
8691
  };
8617
8692
 
8693
+ // apps/pick/dist/sources/github.js
8694
+ init_dist2();
8695
+
8618
8696
  // apps/pick/dist/pr-evidence.js
8619
8697
  init_dist2();
8620
8698
  import * as fs35 from "node:fs";
@@ -8717,7 +8795,7 @@ When the change is complete, commit it, push the branch, and open a PR against $
8717
8795
  repoKey: this.cfg.key,
8718
8796
  title: pr.title,
8719
8797
  followUp: uuid,
8720
- brief: `Address the review feedback on ${pr.html_url} (branch ${pr.head.ref}). Check out that branch in this worktree, read the full review discussion with \`gh pr view ${pr.number} --comments\`, implement what the feedback asks, and push. Reply to review threads where a reply is warranted. When the feedback is addressed, report status done.`
8798
+ brief: withBriefHooks(`Address the review feedback on ${pr.html_url} (branch ${pr.head.ref}). Check out that branch in this worktree, read the full review discussion with \`gh pr view ${pr.number} --comments\`, implement what the feedback asks, and push. Reply to review threads where a reply is warranted. When the feedback is addressed, report status done.`, loadConfig().repos[this.cfg.key], "review")
8721
8799
  });
8722
8800
  }
8723
8801
  return items;
@@ -8842,6 +8920,7 @@ When the change is complete, commit it, push the branch, and open a PR against $
8842
8920
  author: pr.user.login,
8843
8921
  headSha: pr.head.sha,
8844
8922
  headRef: pr.head.ref,
8923
+ baseRef: pr.base?.ref,
8845
8924
  labels: pr.labels.map((l) => l.name),
8846
8925
  assignees: pr.assignees.map((a) => a.login),
8847
8926
  reviews: reviews.map((r) => ({ id: r.id, author: r.user.login, state: r.state, sha: r.commit_id })),
@@ -9399,12 +9478,16 @@ function staleApprovals(policy, pr) {
9399
9478
  function approvalDedupKey(pr, review) {
9400
9479
  return `${pr.number}#${review.id}@${pr.headSha}`;
9401
9480
  }
9402
- function rebaseBrief(pr, id) {
9481
+ function rebaseBrief(pr, id, trunk) {
9482
+ const standalone = standalonePr(pr.baseRef, trunk);
9403
9483
  return [
9404
- `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`,
9405
9485
  `in a way that preserves the intent of both sides.`,
9406
- pushRule(pr.headRef, id),
9407
- `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.`
9408
9491
  ].join(" ");
9409
9492
  }
9410
9493
  function choreVerdict(uuid) {
@@ -9509,7 +9592,7 @@ async function mergeLoop(ms, policy, state, log = () => {
9509
9592
  enqueue({
9510
9593
  id: uuid,
9511
9594
  repo: ms.repoKey(),
9512
- brief: rebaseBrief(pr, uuid),
9595
+ brief: withBriefHooks(rebaseBrief(pr, uuid, loadConfig().repos[ms.repoKey()]?.trunk), loadConfig().repos[ms.repoKey()], "rebase"),
9513
9596
  // The chore works on this PR: the runner pushes no other branch and opens no PR.
9514
9597
  pr: { url: pr.url, headRefName: pr.headRef, headSha: pr.headSha }
9515
9598
  }, "chore");
@@ -9583,7 +9666,8 @@ function spawnContinuation(w, pending, log, fixedBrief, targetOverride) {
9583
9666
  return void 0;
9584
9667
  }
9585
9668
  const id = randomUUID4();
9586
- const 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;
9587
9671
  const claim = readSessionClaim(target, targetLane);
9588
9672
  const deliveredTo = readEvidence(target, targetLane).deliveredTo;
9589
9673
  const trapAddress = claim?.by.startsWith("wt:") ? claim.by : deliveredTo?.startsWith("wt:") ? deliveredTo : void 0;
@@ -10236,6 +10320,7 @@ function livenessView(id, lane) {
10236
10320
  ...lastCommit ? { lastCommit } : {},
10237
10321
  ...ahead !== void 0 && trunk ? { aheadTrunk: `${ahead} ahead of ${trunk}` } : {},
10238
10322
  ...ev.prUrl ? { draftPr: ev.prUrl } : {},
10323
+ ...ev.prUrls && ev.prUrls.length > 1 ? { prs: ev.prUrls.join(", ") } : {},
10239
10324
  ...readStatusLog(id, lane).at(-1)?.at ? { updated: readStatusLog(id, lane).at(-1).at } : {}
10240
10325
  };
10241
10326
  }
@@ -10686,6 +10771,17 @@ function holdCancelledRepair(id, now2 = /* @__PURE__ */ new Date()) {
10686
10771
  }
10687
10772
  return held;
10688
10773
  }
10774
+ function progressStreak(pr) {
10775
+ const s = pr.repairStreak ?? { count: 0 };
10776
+ if (s.stoppedHead !== void 0) return s.stoppedHead === pr.headSha ? s : { count: 0 };
10777
+ const id = s.lastRepairId;
10778
+ if (!id) return { count: s.count };
10779
+ const lane = laneOf(id) ?? "chore";
10780
+ if (readStatusLog(id, lane).at(-1)?.verb !== "done") return { count: s.count };
10781
+ const own = (readEvidence(id, lane).commits ?? []).some((c) => pr.headSha.startsWith(c.split(" ")[0]));
10782
+ if (!own && s.startHead !== void 0 && s.startHead !== pr.headSha) return { count: 0 };
10783
+ return { count: s.count + 1 };
10784
+ }
10689
10785
  function trapAddressOf(id) {
10690
10786
  const claim = readSessionClaim(id, "work");
10691
10787
  const deliveredTo = readEvidence(id, "work").deliveredTo;
@@ -10805,10 +10901,15 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
10805
10901
  Date.parse(pr.baseSince ?? pr.observedAt) || now2,
10806
10902
  kind === "checks" ? Date.parse(pr.failingSince ?? pr.observedAt) || now2 : 0
10807
10903
  );
10904
+ const approvedAt = kind === "conflict" ? Date.parse(pr.review?.lastApprovalAt ?? "") || 0 : 0;
10808
10905
  if (now2 < changed + settleMs) {
10809
10906
  const until2 = new Date(changed + settleMs).toISOString();
10810
10907
  return wait({ heldBy: "settle", reason: `settling: the head, base, or failing checks changed at ${new Date(changed).toISOString()}` }, until2);
10811
10908
  }
10909
+ if (now2 < approvedAt + settleMs) {
10910
+ const until2 = new Date(approvedAt + settleMs).toISOString();
10911
+ return wait({ heldBy: "settle", reason: `settling: the PR was approved at ${new Date(approvedAt).toISOString()}` }, until2);
10912
+ }
10812
10913
  const chain = chainState(owner);
10813
10914
  if (chain.busy) return;
10814
10915
  if (kind === "checks") {
@@ -10829,9 +10930,29 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
10829
10930
  const trap = address ? readTrap(address.slice(3)) : void 0;
10830
10931
  const liveTrap2 = trap && now2 - trapLastSeen(trap) <= config.soak.ttlSecs * 1e3 ? trap : void 0;
10831
10932
  const waitSecs = Math.max(0, Number.isFinite(cfg.repairTrapWaitSecs) ? cfg.repairTrapWaitSecs : 600);
10933
+ const streak = progressStreak(pr);
10934
+ const maxNoProgress = Number.isFinite(cfg.maxRepairsWithoutProgress) ? Math.max(1, Math.floor(cfg.maxRepairsWithoutProgress)) : 2;
10935
+ if (streak.count >= maxNoProgress) {
10936
+ const reason = `no merge progress after ${streak.count} repair(s) in a row ([watch].maxRepairsWithoutProgress = ${maxNoProgress}); \`lobstah watch release ${watch2.key}\` repairs again`;
10937
+ writePr({
10938
+ ...pr,
10939
+ repairStreak: { count: streak.count, stoppedHead: pr.headSha },
10940
+ repair: { headSha: pr.headSha, kind, attempts, maxAttempts: limit, status: "gave-up", reason }
10941
+ });
10942
+ postNotice({
10943
+ kind: "repair-stopped",
10944
+ text: `${pr.key}: repairs stopped \u2014 ${reason} (${pr.url})`,
10945
+ refId: owner,
10946
+ repo: target.repo,
10947
+ dedupeKey: `repair-stopped-${pr.key}-${pr.headSha}`
10948
+ });
10949
+ log(`repair ${watch2.key}: stopped \u2014 ${reason}`);
10950
+ return;
10951
+ }
10832
10952
  const id = randomUUID5();
10833
10953
  writePr({
10834
10954
  ...pr,
10955
+ repairStreak: { count: streak.count, lastRepairId: id, startHead: pr.headSha },
10835
10956
  repair: {
10836
10957
  headSha: pr.headSha,
10837
10958
  kind,
@@ -10846,7 +10967,16 @@ function deliverPrRepairs(log, cap = 3, opts = {}) {
10846
10967
  }
10847
10968
  });
10848
10969
  try {
10849
- const brief = 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
+ );
10850
10980
  enqueue({
10851
10981
  id,
10852
10982
  repo: target.repo,
@@ -11216,6 +11346,7 @@ function describeDispatch(id, lane, bucket, staleSecs) {
11216
11346
  at: last?.at ?? (bucket === "queued" ? queuedAt(id, lane) : claimedAt),
11217
11347
  ...answered ? { answeredAt: answered } : {},
11218
11348
  prUrl: evidence.prUrl,
11349
+ ...evidence.prUrls && evidence.prUrls.length > 1 ? { prUrls: evidence.prUrls } : {},
11219
11350
  pr: evidence.pr,
11220
11351
  ...bucket === "queued" ? {} : worktreeView(id, lane),
11221
11352
  ...bucket === "queued" ? {} : livenessView(id, lane),
@@ -11228,6 +11359,15 @@ function awaitingView(id) {
11228
11359
  const e = awaitingReply(id);
11229
11360
  return e ? { awaitingReply: { sentAt: e.sentAt, from: e.from, line: e.line } } : {};
11230
11361
  }
11362
+ function prsOf(chain) {
11363
+ const urls = [...new Set(chain.flatMap((d) => d.prUrls ?? []))];
11364
+ if (urls.length < 2) return void 0;
11365
+ return urls.map((u) => {
11366
+ const ref = parsePrRef(u);
11367
+ const record = ref ? readPr(ref.key) : void 0;
11368
+ return record ? `${prBadge(record).text} ${u}` : u;
11369
+ });
11370
+ }
11231
11371
  function prStateOf(chain) {
11232
11372
  const records = chain.map((d) => parsePrRef(d.prUrl ?? d.pr?.url ?? "")).filter((ref) => ref !== void 0).map((ref) => readPr(ref.key)).filter((pr) => pr !== void 0);
11233
11373
  const record = records.sort(prNewestFirst(records))[0];
@@ -11387,12 +11527,14 @@ function buildTendReport(now2 = Date.now()) {
11387
11527
  if (!fresh) continue;
11388
11528
  const prUrl = chain.map((d) => d.prUrl).find((u) => u !== void 0);
11389
11529
  const watch2 = chain.map((d) => watchByDispatch.get(d.id)).find((w) => w !== void 0);
11390
- 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 });
11391
11532
  }
11392
11533
  const direct = (d) => ({
11393
11534
  key: "(direct)",
11394
11535
  dispatches: [d],
11395
11536
  prUrl: d.prUrl,
11537
+ ...prsOf([d]) ? { prs: prsOf([d]) } : {},
11396
11538
  prState: prStateOf([d]),
11397
11539
  gate: gateFor(d.id, d.prUrl),
11398
11540
  watch: watchByDispatch.get(d.id)
@@ -11565,7 +11707,7 @@ function renderTend(r) {
11565
11707
  dispatches: s.dispatches.map(
11566
11708
  (d) => `${d.id.slice(0, 8)}:${d.outOfTimeWorkSaved ? "out of time, work saved" : d.state}` + (d.state === "held" && d.note ? ` (${named(d.note.replace(/^held: /, ""))})` : "") + (d.answeredAt ? ` (answered ${Math.max(0, Math.round((Date.now() - Date.parse(d.answeredAt)) / 6e4))}m ago)` : "") + (d.waiting ? ` (${waitingText(d.waiting)})` : "") + (d.awaitingReply ? ` (awaiting reply \xB7 ${ageLabel(Date.now() - (Date.parse(d.awaitingReply.sentAt) || Date.now()))})` : "")
11567
11709
  ).join(" \u2192 "),
11568
- pr: s.prState ? `${s.prState} ${s.prUrl ?? ""}`.trim() : s.prUrl ?? "",
11710
+ pr: s.prs ? s.prs.join("; ") : s.prState ? `${s.prState} ${s.prUrl ?? ""}`.trim() : s.prUrl ?? "",
11569
11711
  gate: s.gate ?? "",
11570
11712
  watch: s.watch ?? "",
11571
11713
  // Under the verb and note: what the live dispatch is doing now.
@@ -11913,7 +12055,7 @@ async function pruneGlassState() {
11913
12055
  if (readStateText() === void 0) return void 0;
11914
12056
  const first = readGlassState();
11915
12057
  if (first) return first;
11916
- await new Promise((resolve13) => setTimeout(resolve13, 50));
12058
+ await new Promise((resolve14) => setTimeout(resolve14, 50));
11917
12059
  const second = readGlassState();
11918
12060
  if (!second) removeGlassState();
11919
12061
  return second;
@@ -11941,12 +12083,12 @@ function pidAlive3(pid) {
11941
12083
  }
11942
12084
  }
11943
12085
  function probeVersionEndpoint(port, timeoutMs) {
11944
- return new Promise((resolve13) => {
12086
+ return new Promise((resolve14) => {
11945
12087
  let settled = false;
11946
12088
  const finish = (info) => {
11947
12089
  if (settled) return;
11948
12090
  settled = true;
11949
- resolve13(info);
12091
+ resolve14(info);
11950
12092
  };
11951
12093
  const req = http.get(`${glassUrl(port)}/api/version`, { timeout: timeoutMs }, (res) => {
11952
12094
  if (res.statusCode !== 200) {
@@ -11978,12 +12120,12 @@ function probeVersionEndpoint(port, timeoutMs) {
11978
12120
  });
11979
12121
  }
11980
12122
  function probeLegacyGlass(port, timeoutMs) {
11981
- return new Promise((resolve13) => {
12123
+ return new Promise((resolve14) => {
11982
12124
  let settled = false;
11983
12125
  const finish = (info) => {
11984
12126
  if (settled) return;
11985
12127
  settled = true;
11986
- resolve13(info);
12128
+ resolve14(info);
11987
12129
  };
11988
12130
  const req = http.get(`${glassUrl(port)}/data`, { timeout: timeoutMs }, (res) => {
11989
12131
  if (res.statusCode !== 200) {
@@ -12026,7 +12168,7 @@ function glassLines(port, info, already = false) {
12026
12168
  ...info.version !== lobstahVersion() ? { stale: `${info.version} (run lobstah glass stop && lobstah glass --detach)` } : {}
12027
12169
  };
12028
12170
  }
12029
- var delay = (ms) => new Promise((resolve13) => setTimeout(resolve13, ms));
12171
+ var delay = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
12030
12172
  async function terminate(pid, timeoutMs) {
12031
12173
  try {
12032
12174
  process.kill(pid, "SIGTERM");
@@ -12609,8 +12751,14 @@ function planMergeRelease(now2 = Date.now(), batch = Infinity) {
12609
12751
  return ownerOf.get(id);
12610
12752
  };
12611
12753
  const candidates = /* @__PURE__ */ new Map();
12754
+ const openSibling = (id) => {
12755
+ const k = all.get(id);
12756
+ const urls = k ? dispatchPrUrls(readEvidence(id, k.lane)) : [];
12757
+ return urls.length > 1 && urls.some((u) => readPr(parsePrRef(u).key)?.state === "OPEN");
12758
+ };
12612
12759
  for (const pr of merged.values()) {
12613
12760
  for (const id of pr.owners) {
12761
+ if (openSibling(id)) continue;
12614
12762
  const chain = chains.get(id) ?? /* @__PURE__ */ new Set([id]);
12615
12763
  const home = owner(id);
12616
12764
  const add = (o) => {
@@ -13501,7 +13649,7 @@ import { randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual2 } from
13501
13649
  import { fileURLToPath as fileURLToPath3 } from "node:url";
13502
13650
 
13503
13651
  // apps/cli/src/glass-page.generated.ts
13504
- var GLASS_PAGE = '<!doctype html>\n<html>\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width,initial-scale=1" />\n <title>spyglass</title>\n <link rel="icon" type="image/png" href="/icon.png" />\n <style>:root{--bg: #0e1116;--card: #161b22;--line: #2b3240;--line-hover: #3a455a;--row-hover: #1c2330;--inset: #10151c;--seg-on: #26436b;--scrim: rgba(4, 7, 11, 0.72);--fg: #dbe2ea;--dim: #8b96a5;--ok: #4fc17c;--warn: #e2b93d;--bad: #e26d5c;--link: #6cb2e2;--on-fill: #fff;--pr-merged: #8957e5;--pr-merged-fg: #ab7df8;--pr-open: #238636;--pr-draft: #6e7681;--pr-closed: #da3633;--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;--font-size: 13px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--page-pad-block: 14px;--radius: 6px;--card-radius: 8px;--card-pad: 12px 14px;--card-min: 300px;--deck-card-min: 240px;--badge-radius: 5px;--badge-pad: 1px 7px;--badge-font: 11px;--modal-radius: 10px;--modal-width: 780px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:var(--font-size)/1.45 var(--font-mono);padding-block:var(--page-pad-block);padding-inline:var(--space-4);display:flex;flex-direction:column;min-height:100vh}h1{font-size:15px;margin:0 0 10px}h1 .dim{color:var(--dim);font-weight:normal}h2{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;margin:20px 0 6px}.chips,.controls{display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center}.controls{margin:0 0 14px}.chips{margin-bottom:8px}.chip{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:3px 9px}.controls select,.newtrap select,.controls input[type=search]{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);color:var(--fg);font:inherit;padding:4px 8px}.controls input[type=search]{flex:1 1 140px;min-width:120px;max-width:340px}#chain-control{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}#f-chain{margin:0;width:14px;height:14px;accent-color:var(--link)}.tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--line);margin:8px 0 10px}.tabs a{color:var(--dim);padding:6px 11px;border-bottom:2px solid transparent}.tabs a.on{color:var(--fg);border-color:var(--link)}.tabpage{display:none}.tabpage.on{display:block;flex:1 0 auto}.headerline{display:flex;align-items:center;gap:var(--space-2)}.headerline h1{flex:1}#settings-slot{min-width:34px;text-align:right;color:var(--dim)}#settings-slot #gearbtn{margin-left:0}.deckgrid{display:flex;flex-direction:column;gap:var(--space-3)}.deckgrid h2{margin:5px 0}.deckgrid section{width:100%;min-width:0}.deckline{padding:2px 0;border-top:1px solid var(--line);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.deckline.acked,.card.acked,.rowhead.acked,.badge.acked{opacity:0.55}.deckmore{color:var(--dim);font-size:11px;display:inline-block;margin-top:4px}.deckline.click{cursor:pointer}.deckline.click:hover{background:var(--row-hover)}.deckgrid h2 a{color:inherit}.deckgrid h2 a:hover{color:var(--fg)}.deckgrid .cards{grid-template-columns:repeat(auto-fill,minmax(min(var(--deck-card-min),100%),1fr));gap:10px}.deckstack{margin-bottom:9px}.deckstack>.dim{font-size:11px;margin:3px 0 5px}.seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}.seg button{background:var(--card);border:none;color:var(--dim);font:inherit;padding:4px 11px;cursor:pointer}.seg button.on{background:var(--seg-on);color:var(--fg)}.wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}th,td{text-align:left;padding:4px 9px;border-top:1px solid var(--line);vertical-align:top;white-space:nowrap}td.grow{white-space:normal;word-break:break-word;min-width:140px}th{color:var(--dim);font-weight:normal;border-top:none;font-size:11px}tr.rowhead{cursor:pointer}tr.rowhead:hover{background:var(--row-hover)}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--card-min),100%),1fr));gap:var(--space-3)}.card{background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad);cursor:pointer;min-width:0;overflow:hidden;overflow-wrap:anywhere}.card:hover{border-color:var(--line-hover)}.card .top{display:flex;justify-content:space-between;gap:var(--space-3);align-items:center;min-height:22px}.card .top>b,.card .top>.prname{flex:1 1000000 auto;min-width:min(10ch,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .top .badge{flex:0 0 auto}.card .top .badge.long{flex:0 1 auto;min-width:min(12ch,100%);max-width:min(26ch,100%);overflow:hidden;text-overflow:ellipsis}.card .meta{color:var(--dim);font-size:12px;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.card .note{margin-top:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.activity{margin-top:4px;font-size:12px;color:var(--fg);opacity:0.85}.waiting{margin-top:4px;font-size:12px;color:var(--dim)}.activity.stale{color:var(--dim);opacity:1}.card .foot{margin-top:8px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;color:var(--dim);font-size:12px}.card .foot>*{min-width:0}.card .foot .footact{margin-left:auto}.btn{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:1px 8px;cursor:pointer;white-space:nowrap}.btn:hover{color:var(--fg);border-color:var(--line-hover)}.badge{border-radius:var(--badge-radius);padding:var(--badge-pad);font-size:var(--badge-font);line-height:1.5;border:1px solid var(--line);white-space:nowrap;display:inline-block}.badge.pr-merged{background:var(--pr-merged);border-color:var(--pr-merged-fg);color:var(--on-fill)}.badge.pr-open{background:var(--pr-open);border-color:var(--pr-open);color:var(--on-fill)}.badge.pr-draft{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.pr-closed{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-conflicts{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-behind{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.unreported{font-weight:normal;font-size:10px;padding:0 5px;vertical-align:1px}.v-done{color:var(--ok)}.v-working,.v-queued{color:var(--fg)}.v-needs-decision,.v-blocked{color:var(--bad);font-weight:bold}.v-failed{color:var(--bad)}.v-paused,.v-unknown{color:var(--dim)}.dim{color:var(--dim)}.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)}.detail{display:none}.detail.open{display:table-row}.detail td{background:var(--inset);white-space:pre-wrap;word-break:break-word;font-size:12px;color:var(--dim)}.detail b{color:var(--fg)}.empty{color:var(--dim);padding:6px 2px}#stale{display:none;color:var(--bad)}a{color:var(--link);text-decoration:none}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle}.dot.ok{background:var(--ok)}.dot.warn{background:var(--warn)}.dot.bad{background:var(--bad)}.dot.dim{background:var(--dim)}#overlay{position:fixed;inset:0;background:var(--scrim);display:none;z-index:10;padding:28px 16px;overflow:auto}#overlay.open{display:block}.modal{max-width:var(--modal-width);margin:0 auto;background:var(--card);border:1px solid var(--line);border-radius:var(--modal-radius);padding:16px 18px;position:relative}.modal .x{position:absolute;top:8px;right:12px;cursor:pointer;color:var(--dim);font-size:18px;line-height:1}.modal .x:hover{color:var(--fg)}.modal h3{margin:0 24px 2px 0;font-size:14px}.modal .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.modal .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.modal pre{white-space:pre-wrap;word-break:break-word;margin:0;font:inherit;color:var(--fg)}.msg{border-left:3px solid var(--line);padding:3px 10px;margin:8px 0;white-space:pre-wrap;word-break:break-word}.msg.from-helm{border-color:var(--link)}.msg .hdr{color:var(--dim);font-size:11px}.catch{border:1px solid var(--line);border-radius:var(--radius);padding:6px 10px;margin:8px 0}.catch .hdr{display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}.loglines{color:var(--dim);font-size:12px;white-space:pre-wrap;word-break:break-word;margin-top:4px}.cmd{display:flex;gap:var(--space-2);align-items:center;margin:5px 0;flex-wrap:wrap}.cmd code{background:var(--inset);border:1px solid var(--line);border-radius:5px;padding:3px 8px;word-break:break-all;cursor:pointer}.cmd code:hover{border-color:var(--line-hover)}.cmd code.copied{border-color:var(--ok)}.cmd button{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:2px 8px;cursor:pointer}.cmd button:hover{color:var(--fg)}.chip.click{cursor:pointer}.chip.click:hover{border-color:var(--line-hover)}footer{margin-top:26px;padding-top:10px;border-top:1px solid var(--line);color:var(--dim);font-size:12px;display:flex;gap:var(--space-2);flex-wrap:wrap}.lob{position:fixed;bottom:6px;left:0;z-index:5;cursor:pointer;font-size:34px;line-height:1;user-select:none;animation:crawl 18s linear infinite}a.lob{color:inherit}.lob .bub .badge{display:inline-block;margin:0 0 3px}.lob .sprite{width:72px;height:56px;background:url(/lob-sprite.png) 0 0 no-repeat;background-size:400% 100%;image-rendering:pixelated;animation:step 0.5s steps(4) infinite}.lob .fallback{display:inline-block;animation:waddle 0.45s ease-in-out infinite alternate}.lob .bub{display:none;position:absolute;bottom:58px;right:-8px;z-index:2;background:var(--card);border:1px solid var(--line);border-radius:9px;padding:6px 9px 5px;font-size:11px;line-height:1.35;color:var(--fg);width:max-content;max-width:150px;box-shadow:0 2px 8px rgba(0,0,0,0.4)}.lob:hover .bub{display:block}.lob .bub span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;white-space:normal;word-break:break-word}.lob .star{position:absolute;left:56px;top:-9px;height:14px;image-rendering:pixelated;z-index:3}.lob:hover{animation-play-state:paused}@keyframes crawl{0%{transform:translateX(-90px)}100%{transform:translateX(100vw)}}@keyframes step{to{background-position-x:-288px}}@keyframes waddle{from{transform:rotate(-8deg) translateY(0)}to{transform:rotate(8deg) translateY(-3px)}}#gearbtn{background:none;border:1px solid var(--line);border-radius:var(--radius);color:var(--dim);font:inherit;font-size:13px;padding:1px 7px;margin-left:8px;cursor:pointer;vertical-align:1px}#gearbtn:hover,#gearbtn.on{color:var(--fg);border-color:var(--line-hover)}.settings .row{display:flex;justify-content:space-between;align-items:center;gap:14px;margin:12px 0}.settings .lbl{color:var(--fg);font-size:12px}.settings .hint{display:block;color:var(--dim);font-size:11px;margin-top:2px}.dcards{display:flex;flex-direction:column;gap:10px}.dcard{width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad)}.dcard.focus{animation:dflash 1.6s ease-out 1}@keyframes dflash{0%,35%{border-color:var(--link);box-shadow:0 0 0 3px var(--link)}100%{border-color:var(--line);box-shadow:0 0 0 0 transparent}}.dcard.answered{opacity:0.7}.dcard .dtop{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2)}.dcard .dtop .badge{margin-top:0}.dcard .dtitle{white-space:pre-wrap;overflow-wrap:anywhere}.dcard .dmeta{font-size:11px;margin-left:auto}.dcard .dlink{color:var(--link)}.dfiles{display:flex;flex-wrap:wrap;gap:var(--space-2);margin:6px 0}.dfiles .dimg img{max-height:160px;max-width:320px;border:1px solid var(--line);border-radius:var(--radius);display:block}.dfiles .dfile{width:100%;font-size:11px}.doptions{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 6px}.btn.dopt{font-size:12px;padding:3px 12px;color:var(--fg)}.btn.dopt.on{background:var(--seg-on);border-color:var(--link)}.danswer{display:block;width:100%;min-height:3.2em;max-height:50vh;margin:6px 0;padding:6px 8px;resize:vertical;overflow-y:auto;field-sizing:content;background:var(--inset);color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);font:inherit}.danswer:focus{outline:none;border-color:var(--line-hover)}.dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.dfoot .dattach input{display:none}.dfoot .dchip{font-size:11px;border:1px solid var(--line);border-radius:var(--badge-radius);padding:0 0 0 6px}.dfoot .dx{background:none;border:none;color:var(--dim);cursor:pointer;font:inherit}.dfoot .derr{font-size:11px}.btn.dsend{margin-left:auto;color:var(--fg);padding:2px 14px}.btn:disabled{cursor:default;opacity:0.6}.danswered{margin-top:6px}.thumb,.dimg,.mdimg{background:none;border:none;padding:0;cursor:zoom-in;font:inherit}.thumb{display:block;margin:4px 0}.thumb img{max-height:120px;max-width:240px;border:1px solid var(--line);border-radius:var(--radius);display:block}.lightbox{position:fixed;inset:0;z-index:20;background:rgba(0,0,0,0.85);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2)}.lightbox .lbimg{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:var(--radius);box-shadow:0 8px 40px rgba(0,0,0,0.6)}.lightbox .lbclose{position:absolute;top:12px;right:16px;background:none;border:none;color:var(--fg);font-size:28px;line-height:1;cursor:pointer}.lightbox .lboriginal{color:var(--link);font-size:12px}.mdpage{background:var(--inset);border:1px solid var(--line);border-radius:6px;padding:8px 14px;margin:6px 0;line-height:1.5;overflow-wrap:anywhere}.mdpage h1,.mdpage h2,.mdpage h3,.mdpage h4{margin:12px 0 6px;line-height:1.25}.mdpage h1{font-size:18px}.mdpage h2{font-size:15px}.mdpage h3,.mdpage h4{font-size:13px}.mdpage p,.mdpage ul,.mdpage ol,.mdpage blockquote{margin:6px 0}.mdpage ul,.mdpage ol{padding-left:22px}.mdpage a{color:var(--link)}.mdpage code{background:var(--card);border-radius:3px;padding:0 3px}.mdpage pre.mdcode{background:var(--card);border:1px solid var(--line);border-radius:4px;padding:8px;overflow-x:auto;white-space:pre}.mdpage pre.mdcode code{padding:0;background:none}.mdpage blockquote{border-left:3px solid var(--line);padding-left:10px;color:var(--dim)}.mdpage img{max-width:100%;height:auto;border-radius:4px;display:block;margin:6px 0}.mdpage hr{border:0;border-top:1px solid var(--line)}.mdpage .mdtable{overflow-x:auto}.mdpage table{border-collapse:collapse;margin:6px 0}.mdpage th,.mdpage td{border:1px solid var(--line);padding:3px 8px;text-align:left}.tabhead{margin:0 0 8px}.newtrap{display:inline-flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12px;font-weight:normal;text-transform:none;letter-spacing:normal}.startcmds{margin:6px 0;font-size:11px;cursor:default}.reportview{max-width:1100px;margin:0 auto;padding:20px 16px 48px}.reportview h1{font-size:18px;margin:0 0 4px}.reportview .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.reportview .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.reportview .mdpage{padding:12px 20px}</style>\n </head>\n <body>\n <!-- The page is rendered by Preact (src/components/app.ts): header, tabs, controls, the five tab pages, footer, lobs, modal overlay. -->\n <script>"use strict";(()=>{var Ae,b,Bt,Bs,O,Nt,Ot,qt,Je,Ge,le,Ft,Ze,Xe,Qe,Os,Se={},Ce=[],qs=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Re=Array.isArray;function E(e,t){for(var n in t)e[n]=t[n];return e}function Ye(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Q(e,t,n){var s,r,i,o={};for(i in t)i=="key"?s=t[i]:i=="ref"?r=t[i]:o[i]=t[i];if(arguments.length>2&&(o.children=arguments.length>3?Ae.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)o[i]===void 0&&(o[i]=e.defaultProps[i]);return xe(e,o,s,r,null)}function xe(e,t,n,s,r){var i={type:e,props:t,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:r??++Bt,__i:-1,__u:0};return r==null&&b.vnode!=null&&b.vnode(i),i}function Z(e){return e.children}function Te(e,t){this.props=e,this.context=t}function V(e,t){if(t==null)return e.__?V(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?V(e):null}function Fs(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,s=[],r=[],i=E({},t);i.__v=t.__v+1,b.vnode&&b.vnode(i),et(e.__P,i,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,s,n??V(t),!!(32&t.__u),r),i.__v=t.__v,i.__.__k[i.__i]=i,Jt(s,i,r),t.__e=t.__=null,i.__e!=n&&Wt(i)}}function Wt(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),Wt(e)}function Ht(e){(!e.__d&&(e.__d=!0)&&O.push(e)&&!De.__r++||Nt!=b.debounceRendering)&&((Nt=b.debounceRendering)||Ot)(De)}function De(){try{for(var e,t=1;O.length;)O.length>t&&O.sort(qt),e=O.shift(),t=O.length,Fs(e)}finally{O.length=De.__r=0}}function Vt(e,t,n,s,r,i,o,c,u,l,f){var h,d,m,p,g,x,G=s&&s.__k||Ce,$=t.length;for(u=Ws(n,t,G,u,$),h=0;h<$;h++)(m=n.__k[h])!=null&&(d=m.__i!=-1&&G[m.__i]||Se,m.__i=h,x=et(e,m,d,r,i,o,c,u,l,f),p=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&tt(d.ref,null,m),f.push(m.ref,m.__c||p,m)),g==null&&p!=null&&(g=p),4&m.__u?(u=Kt(m,u,e),d.__e&&(d.__e=null)):typeof m.type=="function"&&x!==void 0?u=x:p&&(u=p.nextSibling),m.__u&=-7);return n.__e=g,u}function Ws(e,t,n,s,r){var i,o,c,u,l,f=n.length,h=f,d=0;for(e.__k=new Array(r),i=0;i<r;i++)(o=t[i])!=null&&typeof o!="boolean"&&typeof o!="function"?(typeof o=="string"||typeof o=="number"||typeof o=="bigint"||o.constructor==String?o=e.__k[i]=xe(null,o,null,null,null):Re(o)?o=e.__k[i]=xe(Z,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[i]=xe(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[i]=o,u=i+d,o.__=e,o.__b=e.__b+1,c=null,(l=o.__i=Vs(o,n,u,h))!=-1&&(h--,(c=n[l])&&(c.__u|=2)),c==null||c.__v==null?(l==-1&&(r>f?d--:r<f&&d++),typeof o.type!="function"&&(o.__u|=4)):l!=u&&(l==u-1?d--:l==u+1?d++:(l>u?d--:d++,o.__u|=4))):e.__k[i]=null;if(h)for(i=0;i<f;i++)(c=n[i])!=null&&(2&c.__u)==0&&(c.__e==s&&(s=V(c)),Qt(c,c));return s}function Kt(e,t,n){var s,r;if(typeof e.type=="function"){for(s=e.__k,r=0;s&&r<s.length;r++)s[r]&&(s[r].__=e,t=Kt(s[r],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=V(e)),t=n.insertBefore(e.__e,t||null));do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function Vs(e,t,n,s){var r,i,o,c=e.key,u=e.type,l=t[n],f=l!=null&&(2&l.__u)==0;if(l===null&&c==null||f&&c==l.key&&u==l.type)return n;if(s>(f?1:0)){for(r=n-1,i=n+1;r>=0||i<t.length;)if((l=t[o=r>=0?r--:i++])!=null&&(2&l.__u)==0&&c==l.key&&u==l.type)return o}return-1}function Ut(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||qs.test(t)?n:n+"px"}function we(e,t,n,s,r){var i,o;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof s=="string"&&(e.style.cssText=s=""),s)for(t in s)n&&t in n||Ut(e.style,t,"");if(n)for(t in n)s&&n[t]==s[t]||Ut(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(Ft,"$1")),o=t.toLowerCase(),t=o in e||t=="onFocusOut"||t=="onFocusIn"?o.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=n,n?s?n[le]=s[le]:(n[le]=Ze,e.addEventListener(t,i?Qe:Xe,i)):e.removeEventListener(t,i?Qe:Xe,i);else{if(r=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function jt(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t[Ge]==null)t[Ge]=Ze++;else if(t[Ge]<n[le])return;return n(b.event?b.event(t):t)}}}function et(e,t,n,s,r,i,o,c,u,l){var f,h,d,m,p,g,x,G,$,P,ie,W,ae,Lt,ye,ze,I=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(u=!!(32&n.__u),i=[c=t.__e=n.__e]),(f=b.__b)&&f(t);e:if(typeof I=="function"){h=o.length;try{if($=t.props,P=I.prototype&&I.prototype.render,ie=(f=I.contextType)&&s[f.__c],W=f?ie?ie.props.value:f.__:s,n.__c?G=(d=t.__c=n.__c).__=d.__E:(P?t.__c=d=new I($,W):(t.__c=d=new Te($,W),d.constructor=I,d.render=zs),ie&&ie.sub(d),d.state||(d.state={}),d.__n=s,m=d.__d=!0,d.__h=[],d._sb=[]),P&&d.__s==null&&(d.__s=d.state),P&&I.getDerivedStateFromProps!=null&&(d.__s==d.state&&(d.__s=E({},d.__s)),E(d.__s,I.getDerivedStateFromProps($,d.__s))),p=d.props,g=d.state,d.__v=t,m)P&&I.getDerivedStateFromProps==null&&d.componentWillMount!=null&&d.componentWillMount(),P&&d.componentDidMount!=null&&d.__h.push(d.componentDidMount);else{if(P&&I.getDerivedStateFromProps==null&&$!==p&&d.componentWillReceiveProps!=null&&d.componentWillReceiveProps($,W),t.__v==n.__v||!d.__e&&d.shouldComponentUpdate!=null&&d.shouldComponentUpdate($,d.__s,W)===!1){t.__v!=n.__v&&(d.props=$,d.state=d.__s,d.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(X){X&&(X.__=t)}),Ce.push.apply(d.__h,d._sb),d._sb=[],d.__h.length&&o.push(d),c=V(n);break e}d.componentWillUpdate!=null&&d.componentWillUpdate($,d.__s,W),P&&d.componentDidUpdate!=null&&d.__h.push(function(){d.componentDidUpdate(p,g,x)})}if(d.context=W,d.props=$,d.__P=e,d.__e=!1,ae=b.__r,Lt=0,P)d.state=d.__s,d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),Ce.push.apply(d.__h,d._sb),d._sb=[];else do d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),d.state=d.__s;while(d.__d&&++Lt<25);d.state=d.__s,d.getChildContext!=null&&(s=E(E({},s),d.getChildContext())),P&&!m&&d.getSnapshotBeforeUpdate!=null&&(x=d.getSnapshotBeforeUpdate(p,g)),ye=f!=null&&f.type===Z&&f.key==null?Xt(f.props.children):f,c=Vt(e,Re(ye)?ye:[ye],t,n,s,r,i,o,c,u,l),d.base=t.__e,t.__u&=-161,d.__h.length&&o.push(d),G&&(d.__E=d.__=null)}catch(X){if(o.length=h,t.__v=null,u||i!=null){if(X.then){for(t.__u|=u?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;i!=null&&(i[i.indexOf(c)]=null),t.__e=c}else if(i!=null)for(ze=i.length;ze--;)Ye(i[ze])}else t.__e=n.__e;t.__k==null&&(t.__k=n.__k||[]),X.then||zt(t),b.__e(X,t,n)}}else i==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):c=t.__e=Ks(n.__e,t,n,s,r,i,o,u,l);return(f=b.diffed)&&f(t),128&t.__u?void 0:c}function zt(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(zt))}function Jt(e,t,n){for(var s=0;s<n.length;s++)tt(n[s],n[++s],n[++s]);b.__c&&b.__c(t,e),e.some(function(r){try{e=r.__h,r.__h=[],e.some(function(i){i.call(r)})}catch(i){b.__e(i,r.__v)}})}function Xt(e){return typeof e!="object"||e==null||e.__b>0?e:Re(e)?e.map(Xt):e.constructor!==void 0?null:E({},e)}function Ks(e,t,n,s,r,i,o,c,u){var l,f,h,d,m,p,g,x=n.props||Se,G=t.props,$=t.type;if($=="svg"?r="http://www.w3.org/2000/svg":$=="math"?r="http://www.w3.org/1998/Math/MathML":r||(r="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l<i.length;l++)if((m=i[l])&&"setAttribute"in m==!!$&&($?m.localName==$:m.nodeType==3)){e=m,i[l]=null;break}}if(e==null){if($==null)return document.createTextNode(G);e=document.createElementNS(r,$,G.is&&G),c&&(b.__m&&b.__m(t,i),c=!1),i=null}if($==null)x===G||c&&e.data==G||(e.data=G);else{if(i=$=="textarea"&&G.defaultValue!=null?null:i&&Ae.call(e.childNodes),!c&&i!=null)for(x={},l=0;l<e.attributes.length;l++)x[(m=e.attributes[l]).name]=m.value;for(l in x)m=x[l],l=="dangerouslySetInnerHTML"?h=m:l=="children"||l in G||l=="value"&&"defaultValue"in G||l=="checked"&&"defaultChecked"in G||we(e,l,null,m,r);for(l in G)m=G[l],l=="children"?d=m:l=="dangerouslySetInnerHTML"?f=m:l=="value"?p=m:l=="checked"?g=m:c&&typeof m!="function"||x[l]===m||we(e,l,m,x[l],r);if(f)c||h&&(f.__html==h.__html||f.__html==e.innerHTML)||(e.innerHTML=f.__html),t.__k=[];else if(h&&(e.innerHTML=""),Vt(t.type=="template"?e.content:e,Re(d)?d:[d],t,n,s,$=="foreignObject"?"http://www.w3.org/1999/xhtml":r,i,o,i?i[0]:n.__k&&V(n,0),c,u),i!=null)for(l=i.length;l--;)Ye(i[l]);c&&$!="textarea"||(l="value",$=="progress"&&p==null?e.removeAttribute("value"):p!=null&&(p!==e[l]||$=="progress"&&!p||$=="option"&&p!=x[l])&&we(e,l,p,x[l],r),l="checked",g!=null&&g!=e[l]&&we(e,l,g,x[l],r))}return e}function tt(e,t,n){try{if(typeof e=="function"){var s=typeof e.__u=="function";s&&e.__u(),s&&t==null||(e.__u=e(t))}else e.current=t}catch(r){b.__e(r,n)}}function Qt(e,t,n){var s,r;if(b.unmount&&b.unmount(e),(s=e.ref)&&(s.current&&s.current!=e.__e||tt(s,null,t)),(s=e.__c)!=null){if(s.componentWillUnmount)try{s.componentWillUnmount()}catch(i){b.__e(i,t)}s.base=s.__P=s.__n=null}if(s=e.__k)for(r=0;r<s.length;r++)s[r]&&Qt(s[r],t,n||typeof e.type!="function");n||Ye(e.__e),e.__c=e.__=e.__e=void 0}function zs(e,t,n){return this.constructor(e,n)}function nt(e,t,n){var s,r,i,o;t==document&&(t=document.documentElement),b.__&&b.__(e,t),r=(s=typeof n=="function")?null:n&&n.__k||t.__k,i=[],o=[],et(t,e=(!s&&n||t).__k=Q(Z,null,[e]),r||Se,Se,t.namespaceURI,!s&&n?[n]:r?null:t.firstChild?Ae.call(t.childNodes):null,i,!s&&n?n:r?r.__e:t.firstChild,s,o),Jt(i,e,o),e.props.children=null}Ae=Ce.slice,b={__e:function(e,t,n,s){for(var r,i,o;t=t.__;)if((r=t.__c)&&!r.__)try{if((i=r.constructor)&&i.getDerivedStateFromError!=null&&(r.setState(i.getDerivedStateFromError(e)),o=r.__d),r.componentDidCatch!=null&&(r.componentDidCatch(e,s||{}),o=r.__d),o)return r.__E=r}catch(c){e=c}throw e}},Bt=0,Bs=function(e){return e!=null&&e.constructor===void 0},Te.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=E({},this.state),typeof e=="function"&&(e=e(E({},n),this.props)),e&&E(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),Ht(this))},Te.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Ht(this))},Te.prototype.render=Z,O=[],Ot=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,qt=function(e,t){return e.__v.__b-t.__v.__b},De.__r=0,Je=Math.random().toString(8),Ge="__d"+Je,le="__a"+Je,Ft=/(PointerCapture)$|Capture$/i,Ze=0,Xe=jt(!1),Qe=jt(!0),Os=0;function Js(e){return{id:e,lane:"work",bucket:"done",repo:"",brief:"",attachments:[],messageAttachments:[],verb:"unknown",log:[],inbox:[],sort:0}}var Zt=new WeakMap;function st(e,t){let n=e.dispatches||[],s=Zt.get(n);return s||Zt.set(n,s=new Map(n.map(r=>[r.id,r]))),{...t,catches:(t.catches||[]).map(r=>s.get(r)??Js(r))}}function L(e){if(!e)return"dim";let t=e.state||"open";return t==="open"&&e.merge?"pr-"+e.merge:t==="open"&&e.tone&&e.tone!=="ok"?e.tone:"pr-"+t}var ce=["deck","dispatches","traps","prs","reports","notices"];function nn(e){let t=String(e||"").replace(/^#/,"");return ce.includes(t)?t:"deck"}var rt=e=>`#decision/${encodeURIComponent(e)}`;function ot(e){let t=/^#?decision\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var it=(e,t,n)=>`/attachment/dispatch/${encodeURIComponent(e)}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,sn=(e,t)=>`/attachment/trap/${encodeURIComponent(e)}/${encodeURIComponent(t)}`,rn=e=>/\\.(png|jpe?g|gif|webp)$/i.test(e),at=(e,t)=>`/decision/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function lt(e,t){let n=new Map(t.map(r=>[r.key,r])),s=[];for(let r of e)if(r.kind==="decision"){let i=n.get(r.key);if(!i)continue;s.push({kind:"decision",key:i.key,title:i.title,detail:i.detail,options:i.options,attachments:i.attachments,...i.dispatch?{dispatch:i.dispatch,lane:i.lane??"work"}:{},...i.repo?{repo:i.repo}:{},at:i.askedAt})}else r.kind==="question"&&s.push({kind:"question",key:r.key,verb:r.verb,note:r.note||r.verb,dispatch:r.id,lane:r.lane,...r.repo?{repo:r.repo}:{},at:r.at??""});return s.sort((r,i)=>i.at.localeCompare(r.at)||r.key.localeCompare(i.key))}function on(e){let t=(e.text??"").split(`\n`).map(n=>n.trim()).find(Boolean)??"";return[e.option,t&&(t.length>80?t.slice(0,79)+"\\u2026":t),e.files?`${e.files} file${e.files===1?"":"s"}`:""].filter(Boolean).join(" \\xB7 ")}function an(e,t){return(e.reports||[]).find(n=>n.key===`report:${t.lane}:${t.id}`)}function de(e){return e.author==="helm"?"":e.trap?e.trap:e.author==="headless"?e.dispatch?e.dispatch.slice(0,8):"":e.author}var Yt=(e,t)=>+!!e.acked-+!!t.acked||t.filedAt.localeCompare(e.filedAt),C=e=>`/report/${encodeURIComponent(e)}`;function ln(e){let t=/^#?report\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}function cn(e){let t=/^\\/report\\/([^/?#]+)\\/?$/.exec(e);if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var dn=e=>`/report/${encodeURIComponent(e)}/md`,ct=(e,t)=>`/report/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function en(e,t,n){return!!e&&n-Date.parse(e)>t}function tn(e,t){if(t.lane&&e.lane!==t.lane||t.repo&&e.repo!==t.repo||t.verb&&e.verb!==t.verb)return!1;if(t.q){let n=t.q.toLowerCase();if(!(e.id+" "+(e.note||"")+" "+(e.brief||"")+" "+(e.repo||"")+" "+(e.for||"")).toLowerCase().includes(n))return!1}return!0}function Y(e,t){if(!t)return null;if(t.type==="helm")return e.helms.find(s=>s.grounds===t.key)||null;if(t.type==="dispatch")return e.dispatches.find(s=>s.lane+":"+s.id===t.key)||null;if(t.type==="pr")return(e.prs||[]).find(s=>s.key===t.key)||null;if(t.type==="settings")return{attentionKinds:e.attentionKinds||[],attentionError:e.attentionError};let n=e.traps.find(s=>s.trapId===t.key);return n?st(e,n):null}function pn(e,t,n){let s=t.st,r=String(s.q||"").toLowerCase(),i=(...p)=>!r||p.join(" ").toLowerCase().includes(r),o=p=>({x:p,stale:en(p.heartbeatAt,18e5,n)}),c=Y(e,t.modal),u=(p,g)=>!!p&&n-Date.parse(p)<=g,l=p=>Math.max(0,...(p.notices||[]).filter(g=>g.kind==="trap-stowed"||g.kind==="trap-ghosted").map(g=>Date.parse(g.at)||0)),f=p=>p.live||!!p.starting||!!p.requested,h=[...e.traps||[]].sort((p,g)=>Number(f(g))-Number(f(p))||Number(g.live)-Number(p.live)||(p.live?(Date.parse(p.signedOnAt||"")||0)-(Date.parse(g.signedOnAt||"")||0):l(g)-l(p))||(p.name??p.trapId).localeCompare(g.name??g.trapId)),d=h.filter(p=>(f(p)||(p.notices||[]).some(g=>(g.kind==="trap-stowed"||g.kind==="trap-ghosted")&&u(g.at,36e5)))&&i(p.name,p.trapId,p.repo,p.worktree)),m=({ageSecs:p,...g})=>g;return{chips:{daemon:e.daemon,daemonStale:!!e.daemon&&en(e.daemon.heartbeat,9e4,n),helms:e.helms.map(o)},deck:{view:s.view,decisions:lt(e.attention||[],e.decisions||[]).filter(p=>p.kind==="decision"?i(p.kind,p.repo,p.title,p.dispatch,p.detail):i(p.kind,p.repo,p.note,p.dispatch)),prAttention:(e.attention||[]).filter(p=>p.kind&&p.kind.startsWith("pr:")).map(m),landed:(e.landed||[]).filter(p=>u(p.at,864e5)&&i(p.repo,p.note,p.id)).sort((p,g)=>Date.parse(g.at)-Date.parse(p.at)).slice(0,8),reports:(e.reports||[]).filter(p=>i(p.title,p.author,p.dispatch,p.repo)).sort(Yt),inflight:e.dispatches.filter(p=>p.bucket!=="done"&&tn(p,{...s,lane:"",repo:"",verb:""})),traps:d.map(p=>o(st(e,p))),stacks:(e.stacks||[]).filter(p=>p.open&&i(p.repo,p.numbers.join(" "))),prs:(e.prs||[]).filter(p=>p.state==="OPEN"),error:e.attentionError},dispatches:{view:s.view,chain:s.chain,list:e.dispatches.filter(p=>tn(p,s))},traps:{view:s.view,list:h.filter(p=>(!s.repo||p.repo===s.repo)&&i(p.name,p.trapId,p.repo,p.worktree,p.harness)).map(p=>o(st(e,p)))},prs:{view:s.view,stacks:(e.stacks||[]).filter(p=>!s.repo||p.repo===s.repo),prs:(e.prs||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.number,p.title,p.url,p.state,p.baseRefName,p.headRefName)),watches:(e.watches||[]).filter(p=>!String(p.key).startsWith("pr:"))},reports:{view:s.view,list:(e.reports||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.title,p.author,p.dispatch,p.repo)).sort(Yt)},notices:{list:(e.notices||[]).filter(p=>(!s.repo||!p.repo||p.repo===s.repo)&&(!s.noticeKind||p.kind===s.noticeKind)&&i(p.kind,p.text,p.repo))},foot:{version:e.version,repoUrl:e.repoUrl},modal:{modal:t.modal,item:c&&o(c),prefs:t.modal&&t.modal.type==="settings"?{view:s.view,lobs:s.lobs}:void 0}}}function un(e,t){let n=(e.prs||[]).find(c=>c.key===t);if(!n)return null;let s=(e.stacks||[]).find(c=>c.id===n.stackId),r=new Map((e.dispatches||[]).map(c=>[c.id,c])),i=(n.dispatchIds||[]).map(c=>{let u=r.get(c);return u?{id:c,verb:u.verb,modalKey:u.lane+":"+u.id}:{id:c,culled:!0}}),o=n.watch;return{pr:n,stack:s?{numbers:s.numbers,titles:s.numbers.map(c=>(e.prs||[]).find(u=>u.stackId===s.id&&u.number===c)?.title||""),position:n.position+1,size:s.numbers.length,floor:s.floor,nextNumber:s.nextNumber,nextMergeable:!!n.nextMergeable,blockedBy:n.blockedBy}:null,chain:i,watch:o?{key:o.key,owner:o.owner||"",lastCheckedAt:o.lastCheckedAt||null,lastError:o.lastError||null,cursor:o.cursor}:null}}function fn(e){return e?{text:e.lastError?"failing":"watching",at:e.lastCheckedAt||null}:{text:"no watch",at:null}}function mn(){let e={view:"table",lane:"",repo:"",verb:"",q:"",lobs:!0,chain:!1,noticeKind:""};try{Object.assign(e,JSON.parse(localStorage.getItem("spyglass")||"{}"))}catch{}return e.view!=="table"&&e.view!=="cards"&&(e.view="table"),e.lobs=e.lobs!==!1,e}function hn(e){try{localStorage.setItem("spyglass",JSON.stringify(e))}catch{}}function gn(){try{return JSON.parse(localStorage.getItem("spyglass-lob-hidden")||"{}")||{}}catch{return{}}}function vn(e){try{localStorage.setItem("spyglass-lob-hidden",JSON.stringify(e))}catch{}}var pe,_n=new Set,_=()=>pe;function dt(e){pe=e}function T(e){pe={...pe,...e};for(let t of _n)t(pe)}var pt=e=>{_n.add(e)};function ut(e){let{modal:t,drafts:n}=_(),s=new Set(lt(e.attention||[],e.decisions||[]).map(i=>i.key)),r=Object.fromEntries(Object.entries(n).filter(([i])=>s.has(i)));T({snapshot:e,stale:!1,modal:t&&Y(e,t)?t:null,...Object.keys(r).length!==Object.keys(n).length?{drafts:r}:{}})}var $n=()=>({text:"",files:[]});function A(e,t){let n=_().drafts;T({drafts:{...n,[e]:{...n[e]??$n(),...t}}})}function bn(e,t){let n=_().drafts[e]?.option;A(e,{option:n===t?void 0:t,error:void 0})}var Xs=e=>new Promise((t,n)=>{let s=new FileReader;s.onload=()=>t(String(s.result).replace(/^data:[^,]*,/,"")),s.onerror=()=>n(s.error),s.readAsDataURL(e)});async function ft(e,t){let n=_().snapshot?.answerLimits,s=[...t??[]],r=_().drafts[e]?.files??[],i=[],o=[];for(let c of s){let u=(/\\.[^.]+$/.exec(c.name)?.[0]??"").toLowerCase();if(n&&!n.extensions.includes(u))i.push(`${c.name}: type not accepted`);else if(n&&c.size>n.maxBytes)i.push(`${c.name}: larger than ${n.maxBytes} bytes`);else if(n&&r.length+o.length>=n.maxFiles)i.push(`${c.name}: at most ${n.maxFiles} files`);else try{o.push({name:c.name,bytes:c.size,data:await Xs(c)})}catch{i.push(`${c.name}: could not be read`)}}A(e,{files:[..._().drafts[e]?.files??[],...o],error:i.length?i.join("; "):void 0})}var Qs={"image/png":".png","image/jpeg":".jpg","image/gif":".gif","image/webp":".webp"};async function kn(e,t){let n=[...t.clipboardData?.items??[]],s=new Date().toISOString().replace(/[:.]/g,"-").replace(/-\\d{3}Z$/,"Z"),r=n.filter(i=>i.kind==="file"&&i.type.startsWith("image/")).map(i=>i.getAsFile()).filter(i=>i!==null).map((i,o,c)=>{let u=`pasted-${s}${c.length>1?`-${o+1}`:""}${Qs[i.type]??".png"}`;return new File([i],u,{type:i.type})});r.length&&await ft(e,r)}function yn(e,t){let n=(_().drafts[e]?.files??[]).filter((s,r)=>r!==t);A(e,{files:n})}async function wn(e){let t=_().snapshot?.focusToken,n=_().drafts[e]??$n();if(!(n.sending||n.sent)){if(!n.option&&!n.text.trim()&&n.files.length===0){A(e,{error:"Choose an option, write an answer, or attach a file."});return}if(!t){A(e,{error:"This page has no answer token; reload it."});return}A(e,{sending:!0,error:void 0});try{let s=await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":t},body:JSON.stringify({kind:"decision-answer",payload:{key:e,...n.option?{option:n.option}:{},...n.text.trim()?{text:n.text}:{},files:n.files.map(i=>({name:i.name,data:i.data}))}})}),r=await s.json();if(!s.ok||!r.ok){A(e,{sending:!1,error:r.reason??`The answer was refused (${s.status}).`});return}A(e,{sending:!1,sent:on({option:n.option,text:n.text,files:n.files.length})})}catch{A(e,{sending:!1,error:"The answer could not be sent."})}}}var Gn=()=>T({stale:!0}),xn=(e,t)=>T({lightbox:{src:e,name:t}}),ee=()=>T({lightbox:null});function M(e,t){let{snapshot:n}=_(),s={type:e,key:t};T({modal:!n||Y(n,s)?s:null})}function te(){T({modal:null})}async function Tn(e){let t=_().snapshot?.focusToken;if(t)try{let s=await(await fetch(`/api/focus/${encodeURIComponent(e)}`,{method:"POST",headers:{"x-lobstah-focus-token":t}})).json();T({focusResults:{..._().focusResults,[e]:s.message??s.reason??"Window focus failed."}})}catch{T({focusResults:{..._().focusResults,[e]:"Window focus failed."}})}}async function Sn(e,t){let n=_().snapshot?.focusToken;if(!n)return"The glass has no page token yet.";try{let r=await(await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":n},body:JSON.stringify({kind:"trap-request",payload:{repo:e,harness:t}})})).json();if(!r.ok)return r.reason??"The request was refused."}catch{return"The request could not be sent."}try{ut(await(await fetch("/data")).json())}catch{}}function N(e){let t={..._().prefs,...e};hn(t),T({prefs:t})}function Cn(e){e!=="table"&&e!=="cards"||N({view:e})}var Dn=e=>N({lobs:e==="on"});function An(e,t){let n={..._().lobHidden,[e]:t};vn(n),setTimeout(()=>T({lobHidden:n}),0)}async function Rn(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}var In=function(e,t,n,s){var r;t[0]=0;for(var i=1;i<t.length;i++){var o=t[i++],c=t[i]?(t[0]|=o?1:2,n[t[i++]]):t[++i];o===3?s[0]=c:o===4?s[1]=Object.assign(s[1]||{},c):o===5?(s[1]=s[1]||{})[t[++i]]=c:o===6?s[1][t[++i]]+=c+"":o?(r=e.apply(c,In(e,c,n,["",null])),s.push(r),c[0]?t[0]|=2:(t[i-2]=0,t[i]=r)):s.push(c)}return s},Pn=new Map;function Mn(e){var t=Pn.get(this);return t||(t=new Map,Pn.set(this,t)),(t=In(this,t.get(e)||(t.set(e,t=(function(n){for(var s,r,i=1,o="",c="",u=[0],l=function(d){i===1&&(d||(o=o.replace(/^\\s*\\n\\s*|\\s*\\n\\s*$/g,"")))?u.push(0,d,o):i===3&&(d||o)?(u.push(3,d,o),i=2):i===2&&o==="..."&&d?u.push(4,d,0):i===2&&o&&!d?u.push(5,0,!0,o):i>=5&&((o||!d&&i===5)&&(u.push(i,0,o,r),i=6),d&&(u.push(i,d,0,r),i=6)),o=""},f=0;f<n.length;f++){f&&(i===1&&l(),l(f));for(var h=0;h<n[f].length;h++)s=n[f][h],i===1?s==="<"?(l(),u=[u],i=3):o+=s:i===4?o==="--"&&s===">"?(i=1,o=""):o=s+o[0]:c?s===c?c="":o+=s:s===\'"\'||s==="\'"?c=s:s===">"?(l(),i=1):i&&(s==="="?(i=5,r=o,o=""):s==="/"&&(i<5||n[f][h+1]===">")?(l(),i===3&&(u=u[0]),i=u,(u=u[0]).push(2,0,i),i=0):s===" "||s===" "||s===`\n`||s==="\\r"?(l(),i=2):o+=s),i===3&&o==="!--"&&(i=4,u=u[0])}return l(),u})(e)),t),arguments,[])).length>1?t:t[0]}var En=e=>Array.isArray(e)?e.map(En):e===""?null:e,Zs=(e,t,...n)=>Q(e,t,...n.map(En)),a=Mn.bind(Zs);var Ie,k,mt,Ln,ht=0,Fn=[],y=b,Nn=y.__b,Hn=y.__r,Un=y.diffed,jn=y.__c,Bn=y.unmount,On=y.__;function Wn(e,t){y.__h&&y.__h(k,e,ht||t),ht=0;var n=k.__H||(k.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function ne(e){return ht=1,Ys(Kn,e)}function Ys(e,t,n){var s=Wn(Ie++,2);if(s.t=e,!s.__c&&(s.__=[n?n(t):Kn(void 0,t),function(c){var u=s.__N?s.__N[0]:s.__[0],l=s.t(u,c);u!==l&&(s.__N=[l,s.__[1]],s.__c.setState({}))}],s.__c=k,!k.__f)){var r=function(c,u,l){if(!s.__c.__H)return!0;var f=!1,h=s.__c.props!==c;if(s.__c.__H.__.some(function(m){if(m.__N){f=!0;var p=m.__[0];m.__=m.__N,m.__N=void 0,p!==m.__[0]&&(h=!0)}}),i){var d=i.call(this,c,u,l);return f?d||h:d}return!f||h};k.__f=!0;var i=k.shouldComponentUpdate,o=k.componentWillUpdate;k.componentWillUpdate=function(c,u,l){if(this.__e){var f=i;i=void 0,r(c,u,l),i=f}o&&o.call(this,c,u,l)},k.shouldComponentUpdate=r}return s.__N||s.__}function Vn(e,t){var n=Wn(Ie++,7);return nr(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function er(){for(var e;e=Fn.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Pe),t.__h.some(gt),t.__h=[]}catch(n){t.__h=[],y.__e(n,e.__v)}}}y.__b=function(e){k=null,Nn&&Nn(e)},y.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),On&&On(e,t)},y.__r=function(e){Hn&&Hn(e),Ie=0;var t=(k=e.__c).__H;t&&(mt===k?(t.__h=[],k.__h=[],t.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.some(Pe),t.__h.some(gt),t.__h=[],Ie=0)),mt=k},y.diffed=function(e){Un&&Un(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(Fn.push(t)!==1&&Ln===y.requestAnimationFrame||((Ln=y.requestAnimationFrame)||tr)(er)),t.__H.__.some(function(n){n.u&&(n.__H=n.u,n.u=void 0)})),mt=k=null},y.__c=function(e,t){t.some(function(n){try{n.__h.some(Pe),n.__h=n.__h.filter(function(s){return!s.__||gt(s)})}catch(s){t.some(function(r){r.__h&&(r.__h=[])}),t=[],y.__e(s,n.__v)}}),jn&&jn(e,t)},y.unmount=function(e){Bn&&Bn(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(s){try{Pe(s)}catch(r){t=r}}),n.__H=void 0,t&&y.__e(t,n.__v))};var qn=typeof requestAnimationFrame=="function";function tr(e){var t,n=function(){clearTimeout(s),qn&&cancelAnimationFrame(t),setTimeout(e)},s=setTimeout(n,35);qn&&(t=requestAnimationFrame(n))}function Pe(e){var t=k,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),k=t}function gt(e){var t=k;e.__c=e.__(),k=t}function nr(e,t){return!e||e.length!==t.length||t.some(function(n,s){return n!==e[s]})}function Kn(e,t){return typeof t=="function"?t(e):t}var K=e=>{if(!e)return"";let t=Math.max(0,(Date.now()-Date.parse(e))/1e3);return t<90?Math.round(t)+"s":t<5400?Math.round(t/60)+"m":t<172800?(t/3600).toFixed(1)+"h":Math.round(t/86400)+"d"},v=e=>a`<span data-age=${e??""}>${K(e)}</span>`,Ee=e=>e.activity&&a`<div class=${"activity"+(e.activity.stale?" stale":"")} title=${e.activity.stale?"no activity past the wedge threshold":e.activity.kind}>${e.activity.stale?"stale \\xB7 ":""}${e.activity.summary} \xB7 ${v(e.activity.at)} ago</div>`,sr=e=>e&&/^https?:\\/\\//i.test(e)?e:void 0,Le=e=>{let t=e.waiting;if(!t)return;let n=sr(t.link),s=n;try{n&&(s=new URL(n).host+new URL(n).pathname.replace(/\\/$/,""))}catch{s=n}return a`<div class="waiting">${e.verb}: waiting on ${t.on} \xB7 ${v(t.since)}${n&&[" \\xB7 ",a`<a href=${n} target="_blank" rel="noopener noreferrer" onClick=${D}>${s}</a>`]}</div>`},rr=12,zn=e=>e!==void 0&&e.length>rr,H=e=>zn(e)?e:void 0,U=e=>zn(e)?" long":"",w=(e,t)=>()=>M(e,t),D=e=>e.stopPropagation(),or=e=>e.sessionId?(e.harness==="codex"?"codex resume ":"claude --resume ")+e.sessionId:void 0;function z(e){if(e.requested)return a`<span class="dim">Requested \u2014 no window yet</span>`;if(e.starting)return a`<span class="dim">${e.starting.failedAt?"Did not start":"Starting \\u2014 no window yet"}</span>`;if(!e.live){let s=or(e);return s?a`<span class="dim">Resume: <code onClick=${D}>${s}</code></span>`:a`<span class="dim">Resume command unavailable</span>`}let t=_();return!t.snapshot?.focusSupported&&!e.link?a`<span class="dim">Window focus is not supported here</span>`:a`<span class="winaction"><button class="btn open" title="open this trap\'s window" onClick=${s=>{D(s),Tn(e.trapId)}}>\u2197 open</button>${e.link&&[" ",a`<a href=${e.link} onClick=${D}>Session link</a>`]}${t.focusResults[e.trapId]&&[" ",a`<span class="dim">${t.focusResults[e.trapId]}</span>`]}</span>`}function R(e,t,n){return t.length?a`<div class="wrap"><table><tbody><tr>${e.map(s=>a`<th>${s}</th>`)}</tr>${t}</tbody></table></div>`:a`<div class="empty">${n}</div>`}function ir({text:e}){let[t,n]=ne(null),s=r=>async()=>{await Rn(e)&&(n(r),setTimeout(()=>n(null),1e3))};return a`<div class="cmd"><code class=${t==="code"?"copied":void 0} title="click to copy" onClick=${s("code")}>${e}</code><button title="copy" onClick=${s("button")}>${t==="button"?"\\u2713":"\\u29C9"}</button></div>`}var S=(e,t)=>a`<${ir} key=${t} text=${e} />`,ue=(e,t,n="thumb")=>a`<button\n type="button"\n class=${n}\n title=${"view "+t}\n onClick=${s=>{D(s),xn(e,t)}}\n >\n <img src=${e} alt=${t} loading="lazy" />\n </button>`,se=(e,t)=>e.map(n=>[a`<div class="sub">${n.name} \xB7 ${n.type} \xB7 ${n.bytes} bytes</div>`,t&&rn(n.name)&&ue(t(n.name),n.name),S(n.path)]),Jn=/\\bwt:([a-z0-9][a-z0-9-]*)/g,vt=e=>_().snapshot?.trapNames?.[e],j=e=>e.replace(Jn,(t,n,s)=>{let r=vt(n);return r&&!Xn(e,s,t.length,r)?r:t}),Xn=(e,t,n,s)=>e.slice(Math.max(0,t-s.length-2),t)===`${s} (`&&e[t+n]===")";function Ne(e){let t="wt:"+e,n=vt(e)??t;return _().snapshot?.traps.some(r=>r.trapId===e)?a`<a class="trapname" href="#traps" title=${t} onClick=${r=>{r.preventDefault(),D(r),M("trap",e)}}>${n}</a>`:a`<span class="trapname" title=${t}>${n}</span>`}var Me=e=>{let t=/^wt:([a-z0-9][a-z0-9-]*)$/.exec(e);return t?Ne(t[1]):e};function B(e,t=1/0){let n=[],s=0;for(let c of e.matchAll(Jn)){c.index>s&&n.push({text:e.slice(s,c.index)});let u=vt(c[1]);n.push(u&&Xn(e,c.index,c[0].length,u)?{text:c[0]}:{text:u??c[0],trapId:c[1]}),s=c.index+c[0].length}s<e.length&&n.push({text:e.slice(s)});let r=n.reduce((c,u)=>c+u.text.length,0),i=r>t?t-1:1/0,o=[];for(let c of n){if(i<=0)break;c.text.length<=i?o.push(c.trapId?Ne(c.trapId):c.text):o.push(c.text.slice(0,i)),i-=c.text.length}return r>t&&o.push("\\u2026"),o}var _t=e=>e.log.flatMap((t,n)=>[n?`\n`:"",t.at+" "+t.verb,...t.note?[" ",...B(t.note)]:[]]);function Qn(e){let t=[e.elapsed&&`elapsed: ${e.elapsed}`,e.attempt&&`attempt: ${e.attempt}`,e.branch&&`branch: ${e.branch}`,e.lastCommit&&`last commit: ${e.lastCommit}`,e.aheadTrunk&&`commits: ${e.aheadTrunk}`,e.draftPr&&`draft PR: ${e.draftPr}`,e.updated&&`updated: ${e.updated}`].filter(Boolean).join(`\n`);return a`${e.waiting&&[a`<div class="sec">waiting</div>`,Le(e)]}${e.activity&&[a`<div class="sec">activity</div>`,Ee(e)]}${t&&[a`<div class="sec">progress</div>`,a`<pre>${t}</pre>`]}<div class="sec">brief</div><pre>${e.brief}</pre>${e.attachments.length>0&&[a`<div class="sec">attachments (${e.attachments.length})</div>`,se(e.attachments,n=>it(e.lane,e.id,n))]}${e.messageAttachments.length>0&&[a`<div class="sec">message attachments (${e.messageAttachments.length})</div>`,se(e.messageAttachments,n=>it(e.lane,e.id,n))]}${e.followUp&&[a`<div class="sec">forks</div>`,a`<pre>${e.followUp}</pre>`]}<div class="sec">log</div><div class="loglines">${e.log.length?_t(e):"no entries yet"}</div>${e.inbox.length>0&&[a`<div class="sec">inbox</div>`,a`<div class="loglines">${e.inbox.join(`\n---\n`)}</div>`]}${e.awaitingReply&&[a`<div class="sec">awaiting reply</div>`,a`<div>${e.awaitingReply.line} \xB7 from ${e.awaitingReply.from} \xB7 ${v(e.awaitingReply.sentAt)} ago</div>`]}${e.evidence&&[a`<div class="sec">evidence</div>`,a`<div class="loglines">${JSON.stringify(e.evidence)}</div>`]}`}function fe(e){return e.for?[Me(e.for),e.evidence&&e.evidence.deliveredTo?[" ",a`<span class="ok">\u2713delivered</span>`]:[" ",a`<span class="warn">waiting</span>`]]:e.claimedBy?Me(e.claimedBy):""}function re(e){let t=e.evidence&&(e.evidence.prUrl||e.evidence.pr&&e.evidence.pr.url);if(!t)return"";let n=e.prBadge;return[a`<a href=${t} target="_blank" rel="noopener" onClick=${D}>PR</a>`,n&&[" ",a`<span class=${"badge "+L(n)} title=${"observed "+n.observedAt}>${n.text}</span>`],e.prGate&&[" ",a`<span class="badge dim" title="merge gate (pick)">${e.prGate}</span>`]]}var ar={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch"},$t={"pr:review":"bad","pr:checks":"bad","pr:conflict":"pr-conflicts","pr:ready":"ok","pr:draft":"dim",watch:"warn"},Zn=e=>ar[e]||"";var He=e=>a`<a href=${e.url} target="_blank" rel="noopener" onClick=${D}>#${e.number}</a>`,Ue=e=>a`<span class="prname" title=${e.title||void 0}><b>#${e.number}</b>${e.title&&[" ",e.title]}</span>`;function oe(e,t,n){return e.flatMap((s,r)=>{let i=t.get(s)||void 0,o=s===n?a`<b title=${i}>#${s}</b>`:a`<span title=${i}>#${s}</span>`;return r?[" \\u2192 ",o]:[o]})}function me(e){let t=e.checks;return t.passed+"/"+t.total+" passed"+(t.failed?" \\xB7 "+t.failed+" failed":"")+(t.pending?" \\xB7 "+t.pending+" pending":"")}function je(e){let t=e.review||{};return(e.reviewDecision||"")+(t.unresolvedThreads?" \\xB7 "+t.unresolvedThreads+" unresolved":"")+(t.changesRequested?" \\xB7 changes requested":"")}function he(e){return[e.mergeStateStatus,e.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:e.blockedBy?" \\xB7 blocked by #"+e.blockedBy:""]}function bt(e){let t=fn(e);return e?[t.text," \\xB7 ",t.at?[v(t.at)," ago"]:"never checked"]:a`<span class="dim">${t.text}</span>`}function ge(e){if(e.requested)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">requested</span>`],hb:a`<span class="dim">\u2014</span>`};if(e.starting)return{stale:!1,listen:e.starting.failedAt?[a`<span class="dot bad"></span>`,a`<span class="bad">start failed</span>`]:[a`<span class="dot warn"></span>`,"starting"],hb:a`<span class="dim">\u2014</span>`};if(!e.live)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">signed off</span>`],hb:a`<span class="dim">\u2014</span>`};let t=Date.now()-Date.parse(e.heartbeatAt)>18e5,n=e.listening??(!!e.firstParkedAt&&!t);return{stale:t,listen:n?[a`<span class="dot ok"></span>`,"listening"]:[a`<span class="dot warn"></span>`,"not listening"],hb:a`<span class=${t?"bad":"ok"}>${t&&"stale "}${v(e.heartbeatAt)} ago</span>`}}function kt(e){if(e.requested)return{kind:"requested",helmOn:!!_().snapshot?.helmOn};if(e.starting)return e.starting.failedAt?{kind:"start failed",reason:e.starting.reason??"no session signed on in time"}:{kind:"starting",deadline:e.starting.deadline};if(!e.live)return{kind:"signed off"};let t=e.claimed?e.catches.find(n=>n.id===e.claimed&&n.bucket==="active"):void 0;return t?t.waiting||t.verb==="paused"?{kind:"parked",current:t}:{kind:"working",current:t}:{kind:"idle",listening:e.listening??(!!e.firstParkedAt&&Date.now()-Date.parse(e.heartbeatAt??"")<=18e5)}}function lr(e){let t=kt(e);return t.kind==="working"||t.kind==="idle"&&t.listening?"ok":t.kind==="start failed"?"bad":t.kind==="parked"||t.kind==="starting"?"warn":"dim"}var Yn=e=>e.waiting?`waiting on ${e.waiting.on}`:e.note||"waiting",es=e=>e.brief.split(/\\r?\\n/,1)[0]?.trim().slice(0,40)||"(no title)";function ve(e){let t=kt(e),n=a`<span class=${"dot trapdot "+lr(e)}></span>`;if(t.kind==="requested")return[n,a`<span class="dim">${ts(e,t.helmOn)}</span>`];if(t.kind==="starting")return[n,"starting \\xB7 waiting for its session to sign on"];if(t.kind==="start failed")return[n,a`<span class="bad">start failed</span>`," \\xB7 ",t.reason];if(t.kind==="signed off")return[n,a`<span class="dim">signed off</span>`];if(t.kind==="idle")return[n,"idle \\xB7 ",t.listening?"listening":"not listening"];let s=t.current;return t.kind==="parked"?[n,"parked \\xB7 ",Yn(s)]:[n,"working \\xB7 ",a`<a href="#dispatches" onClick=${i=>{i.preventDefault(),D(i),M("dispatch",`${s.lane}:${s.id}`)}}>${s.id.slice(0,8)}</a>`," \\xB7 ",es(s),s.activity&&[" \\xB7 ",s.activity.summary," ",v(s.activity.at)," ago"]]}var ts=(e,t)=>`requested \\xB7 ${e.repo??""} \\xB7 ${e.harness??""} \\xB7 ${t?"waiting for the helm":"waiting for a helm"}`;function J(e){let t=e.starting?.commands;return t?.length?a`<div class="startcmds" onClick=${D}><div class="dim">start it: run one in a terminal</div>${t.map(n=>S(n.command,n.harness))}</div>`:null}function Be(){let t=_().snapshot?.repoKeys??[],[n,s]=ne(!1),[r,i]=ne(""),[o,c]=ne("claude"),[u,l]=ne(null),f=r||t[0]||"",h=async d=>{d.preventDefault();let m=await Sn(f,o);l(m??null),m||s(!1)};return n?a`<form class="newtrap" onClick=${D} onSubmit=${h}>\n <label>repo <select name="repo" value=${f} onChange=${d=>i(d.target.value)}>${t.map(d=>a`<option value=${d}>${d}</option>`)}</select></label>\n <label>harness <select name="harness" value=${o} onChange=${d=>c(d.target.value)}><option value="claude">claude</option><option value="codex">codex</option></select></label>\n <button class="btn" type="submit" disabled=${!f}>Request</button>\n <button class="btn" type="button" onClick=${()=>s(!1)}>Cancel</button>\n ${u&&a`<span class="bad">${u}</span>`}\n </form>`:a`<span class="newtrap"><button class="btn" onClick=${d=>(D(d),s(!0),l(null))}>+ New trap</button></span>`}function Oe(e){let t=kt(e);if(t.kind==="requested")return ts(e,t.helmOn);if(t.kind==="starting")return`starting \\xB7 sign-on due by ${t.deadline}`;if(t.kind==="start failed")return`start failed \\xB7 ${t.reason}`;if(t.kind==="signed off")return"signed off";if(t.kind==="idle")return`idle \\xB7 ${t.listening?"listening":"not listening"}`;if(t.kind==="parked")return`parked \\xB7 ${Yn(t.current)}`;let n=t.current;return[`working \\xB7 ${n.id.slice(0,8)} \\xB7 ${es(n)}`,n.activity&&`${n.activity.summary} ${K(n.activity.at)} ago`].filter(Boolean).join(" \\xB7 ")}function yt(e){let t=e.messages.filter(n=>n.state==="pending").length;return e.messages.length?["\\u2709 "+e.messages.length,t>0&&[" ",a`<span class="warn">(${t} pending)</span>`]]:null}var cr=e=>()=>{window.open(C(e.key),"_blank","noopener")};function wt(e){let t=de(e);return{nodes:[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked?" \\xB7 acked":""],text:[t,`${K(e.filedAt)} ago`,e.acked?"acked":""].filter(Boolean).join(" \\xB7 ")}}function dr(e){let t=wt(e);return a`<a key=${e.key} class=${"card"+(e.acked?" acked":"")} href=${C(e.key)} target="_blank" rel="noopener"><div class="top"><b title=${e.title}>${e.title}</b></div><div class="meta" title=${t.text}>${t.nodes}</div></a>`}function pr(e){return a`<tr key=${e.key} class=${"rowhead"+(e.acked?" acked":"")} onClick=${cr(e)}><td class="grow"><a href=${C(e.key)} target="_blank" rel="noopener" onClick=${t=>t.stopPropagation()}>${e.title}</a></td><td>${de(e)}</td><td>${v(e.filedAt)}</td><td>${e.acked?"acked":""}</td></tr>`}function ns({inp:e}){return e.view==="cards"?e.list.length?a`<div class="cards">${e.list.map(dr)}</div>`:a`<div class="empty">no reports match</div>`:R(["title","from","filed","acked"],e.list.map(pr),"no reports match")}function os(e){let t=e.trim();return/^(https?:\\/\\/|mailto:|#)/i.test(t)?t:void 0}function is(e){let t=e.trim();return/^[^/\\\\:?#]+$/.test(t)&&t!=="."&&t!==".."?t:void 0}function ss(e,t,n,s){let r=0;for(let i=t;i<e.length;i++){if(e[i]==="\\\\"){i++;continue}if(e[i]===n)r++;else if(e[i]===s){if(r===0)return i;r--}}return-1}function rs(e,t){let n=ss(e,t+1,"[","]");if(n<0||e[n+1]!=="(")return;let s=ss(e,n+2,"(",")");if(s<0)return;let r=e.slice(n+2,s).trim().replace(/\\s+"[^"]*"$/,"").replace(/^<(.*)>$/,"$1");return{text:e.slice(t+1,n),target:r,end:s+1}}function q(e){let t=[],n="",s=()=>{n&&t.push({t:"text",v:n}),n=""};for(let r=0;r<e.length;){let i=e[r];if(i==="\\\\"&&r+1<e.length&&/[\\\\`*_{}[\\]()#+\\-.!|<>~]/.test(e[r+1])){n+=e[r+1],r+=2;continue}if(i==="`"){let o=/^`+/.exec(e.slice(r))[0],c=e.indexOf(o,r+o.length);if(c>0){s(),t.push({t:"code",v:e.slice(r+o.length,c).replace(/^ (.*) $/,"$1")}),r=c+o.length;continue}}if(i==="!"&&e[r+1]==="["){let o=rs(e,r+1);if(o){s(),t.push({t:"img",alt:o.text,src:o.target}),r=o.end;continue}}if(i==="["){let o=rs(e,r);if(o){s(),t.push({t:"a",href:o.target,c:q(o.text)}),r=o.end;continue}}if((i==="*"||i==="_")&&e[r+1]===i){let o=e.indexOf(i+i,r+2);if(o>r+2){s(),t.push({t:"b",c:q(e.slice(r+2,o))}),r=o+2;continue}}if(i==="*"||i==="_"&&(r===0||!/\\w/.test(e[r-1]))){let o=r+1;for(;(o=e.indexOf(i,o))>0&&(e[o+1]===i||i==="_"&&/\\w/.test(e[o+1]??""));)o+=2;if(o>r+1&&e[r+1]!==" "){s(),t.push({t:"i",c:q(e.slice(r+1,o))}),r=o+1;continue}}if(i===`\n`){/ {2,}$/.test(n)||n.endsWith("\\\\")?(n=n.replace(/( {2,}|\\\\)$/,""),s(),t.push({t:"br"})):n+=" ",r++;continue}n+=i,r++}return s(),t}var as=/^ {0,3}(`{3,}|~{3,})\\s*([^`\\s]*)/,ls=/^ {0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/,cs=/^ {0,3}([-*_])(\\s*\\1){2,}\\s*$/,Tt=/^( {0,3})([-*+])\\s+(.*)$/,St=/^( {0,3})(\\d{1,9})[.)]\\s+(.*)$/,qe=/^ {0,3}>\\s?(.*)$/,ur=/^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/;function Gt(e){let t=e.trim();t.startsWith("|")&&(t=t.slice(1)),t.endsWith("|")&&!t.endsWith("\\\\|")&&(t=t.slice(0,-1));let n=[],s="";for(let r=0;r<t.length;r++)t[r]==="\\\\"&&t[r+1]==="|"?(s+="|",r++):t[r]==="|"?(n.push(s.trim()),s=""):s+=t[r];return n.push(s.trim()),n}var xt=e=>as.test(e)||ls.test(e)||cs.test(e)||Tt.test(e)||St.test(e)||qe.test(e);function Fe(e){let t=e.replace(/\\r\\n?/g,`\n`).replace(/\\t/g," ").split(`\n`),n=[],s=0;for(;s<t.length;){let r=t[s];if(!r.trim()){s++;continue}let i=as.exec(r);if(i){let f=i[1],h=[];for(s++;s<t.length&&!new RegExp(`^ {0,3}${f[0]}{${f.length},}\\\\s*$`).test(t[s]);)h.push(t[s++]);s++,n.push({t:"code",lang:i[2]??"",v:h.join(`\n`)});continue}let o=ls.exec(r);if(o){n.push({t:"h",level:o[1].length,c:q(o[2])}),s++;continue}if(cs.test(r)){n.push({t:"hr"}),s++;continue}if(qe.test(r)){let f=[];for(;s<t.length&&t[s].trim()&&(qe.test(t[s])||!xt(t[s]));)f.push(qe.exec(t[s])?.[1]??t[s]),s++;n.push({t:"quote",c:Fe(f.join(`\n`))});continue}let c=Tt.exec(r),u=St.exec(r);if(c||u){let f=!c,h=f?St:Tt,d=[];for(;s<t.length;){let m=t[s],p=h.exec(m);if(p&&p[1].length<=3){d.push([p[3]]),s++;continue}if(!m.trim()){if(s+1<t.length&&/^ {2,}\\S/.test(t[s+1])){d.at(-1).push(""),s++;continue}break}if(/^ {2,}\\S/.test(m)){d.at(-1).push(m.replace(/^ {2,4}/,"")),s++;continue}if(xt(m))break;d.at(-1).push(m.trim()),s++}n.push({t:"list",ordered:f,start:f?Number(u[2]):1,items:d.map(m=>Fe(m.join(`\n`)))});continue}if(r.includes("|")&&s+1<t.length&&ur.test(t[s+1])&&t[s+1].includes("-")){let f=Gt(r),h=Gt(t[s+1]).map(m=>m.startsWith(":")&&m.endsWith(":")?"center":m.endsWith(":")?"right":m.startsWith(":")?"left":"");s+=2;let d=[];for(;s<t.length&&t[s].trim()&&t[s].includes("|");){let m=Gt(t[s++]);d.push(f.map((p,g)=>q(m[g]??"")))}n.push({t:"table",align:f.map((m,p)=>h[p]??""),head:f.map(q),rows:d});continue}let l=[];for(;s<t.length&&t[s].trim()&&!xt(t[s])&&!(l.length&&/^ {0,3}(=+|-+)\\s*$/.test(t[s]));)l.push(t[s++].replace(/^ +/,""));if(l.length===0){n.push({t:"p",c:q(r.trim())}),s++;continue}n.push({t:"p",c:q(l.join(`\n`))})}return n}function F(e,t){return t.map(n=>{switch(n.t){case"text":return n.v;case"code":return a`<code>${n.v}</code>`;case"b":return a`<strong>${F(e,n.c)}</strong>`;case"i":return a`<em>${F(e,n.c)}</em>`;case"br":return a`<br />`;case"a":{let s=os(n.href);return s?a`<a href=${s} target="_blank" rel="noopener noreferrer">${F(e,n.c)}</a>`:F(e,n.c)}case"img":{let s=is(n.src);return s?ue(e(s),n.alt||s,"mdimg"):a`<span class="dim">[image not shown: ${n.alt||n.src}]</span>`}}})}function We(e,t){return t.map(n=>{switch(n.t){case"h":{let s=F(e,n.c);return n.level===1?a`<h1>${s}</h1>`:n.level===2?a`<h2>${s}</h2>`:n.level===3?a`<h3>${s}</h3>`:a`<h4>${s}</h4>`}case"p":return a`<p>${F(e,n.c)}</p>`;case"code":return a`<pre class="mdcode"><code>${n.v}</code></pre>`;case"hr":return a`<hr />`;case"quote":return a`<blockquote>${We(e,n.c)}</blockquote>`;case"list":{let s=n.items.map(r=>a`<li>${We(e,r)}</li>`);return n.ordered?a`<ol start=${n.start}>${s}</ol>`:a`<ul>${s}</ul>`}case"table":return a`<div class="mdtable"><table><thead><tr>${n.head.map((s,r)=>a`<th style=${n.align[r]?"text-align:"+n.align[r]:void 0}>${F(e,s)}</th>`)}</tr></thead><tbody>${n.rows.map(s=>a`<tr>${s.map((r,i)=>a`<td style=${n.align[i]?"text-align:"+n.align[i]:void 0}>${F(e,r)}</td>`)}</tr>`)}</tbody></table></div>`}})}var _e=new Map;function ds(e){let t=_e.get(e);return t||(t=Fe(e),_e.set(e,t),_e.size>8&&_e.delete(_e.keys().next().value)),t}function fr({r:e,text:t}){let n=t?.text!==void 0?We(s=>ct(e.key,s),ds(t.text)):t?.error?a`<div class="bad">${t.error}</div>`:a`<div class="dim">loading\u2026</div>`;return a`<div class="mdpage" data-report=${e.key}>${n}</div>`}function ps({text:e,fileUrl:t}){return a`<div class="mdpage">${We(t,ds(e))}</div>`}function Ct(e){let t=de(e);return[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked&&[" \\xB7 ",a`<span class="ok">acked</span>`]]}function us({r:e,text:t,heading:n}){return[n&&a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)}</div>`,a`<${fr} r=${e} text=${t} />`,e.attachments.length>0&&[a`<div class="sec">report attachments (${e.attachments.length})</div>`,se(e.attachments,s=>ct(e.key,s))],!e.acked&&[a`<div class="sec">ack</div>`,S("lobstah attention ack "+e.key)]]}var mr=/\\.(png|jpe?g|gif|webp)$/i,hr=e=>{e.style.height="auto",e.style.height=e.scrollHeight+"px"};function gr(e,t){return t.length?a`<div class="dfiles">${t.map(n=>mr.test(n.name)?a`<span key=${n.name}>${ue(at(e,n.name),n.name,"dimg")}</span>`:a`<div key=${n.name} class="dfile"><span>${n.name}</span> <span class="dim">\xB7 ${n.type} \xB7 ${n.bytes} bytes \xB7 </span><code>${n.path}</code></div>`)}</div>`:null}function vr(e,t,n){let s=e.key,r=e.kind==="decision"?e.options:[],i=t?.text??"",o=t?.files??[],c=!!t?.sending;return[r.length>0&&a`<div class="doptions">${r.map(u=>a`<button key=${u} type="button" class=${"btn dopt"+(t?.option===u?" on":"")} aria-pressed=${t?.option===u?"true":"false"} disabled=${c} onClick=${()=>bn(s,u)}>${u}</button>`)}</div>`,a`<textarea\n class="danswer"\n rows="2"\n placeholder=${r.length?"Add to your answer, or answer in your own words":"Your answer"}\n value=${i}\n disabled=${c}\n onInput=${u=>{let l=u.currentTarget;hr(l),A(s,{text:l.value,error:void 0})}}\n onPaste=${u=>{kn(s,u)}}\n ></textarea>`,a`<div class="dfoot">\n <label class="btn dattach" title="attach images or files">attach<input type="file" multiple accept=${n} disabled=${c} onChange=${u=>{let l=u.currentTarget;ft(s,l.files).then(()=>{l.value=""})}} /></label>\n ${o.map((u,l)=>a`<span key=${u.name+l} class="dchip">${u.name}<button type="button" class="dx" title="remove" disabled=${c} onClick=${()=>yn(s,l)}>\xD7</button></span>`)}\n ${t?.error&&a`<span class="bad derr">${t.error}</span>`}\n <button type="button" class="btn dsend" disabled=${c} onClick=${()=>{wn(s)}}>${c?"Sending\\u2026":"Send"}</button>\n </div>`]}function _r(e,t,n,s){let r=e.dispatch,i=e.lane??"work",c=[r&&a`<a class="dlink" href="#" onClick=${l=>{l.preventDefault(),w("dispatch",i+":"+r)()}}>${r.slice(0,8)}</a>`,e.repo,[v(e.at)," ago"]].filter(Boolean).flatMap((l,f)=>f?[" \\xB7 ",l]:[l]),u="dcard"+(e.kind==="question"?" plain":"")+(t?.sent?" answered":"")+(n?" focus":"");return a`<div key=${e.key} class=${u} data-decision=${e.key}>\n <div class="dtop">\n ${e.kind==="question"&&a`<span class=${"badge "+(e.verb==="blocked"?"bad":"warn")}>${e.verb}</span>`}\n <b class="dtitle">${e.kind==="decision"?e.title:B(e.note)}</b>\n <span class="dmeta dim">${c}</span>\n </div>\n ${e.kind==="decision"&&e.detail.trim()&&a`<${ps} text=${e.detail} fileUrl=${l=>at(e.key,l)} />`}\n ${e.kind==="decision"&&gr(e.key,e.attachments)}\n ${t?.sent?a`<div class="danswered ok">answered \xB7 ${t.sent}</div>`:vr(e,t,s)}\n </div>`}function fs({cards:e,drafts:t,focus:n,extensions:s}){let r=s.join(","),i=e.length?a`<div class="dcards">${e.map(o=>_r(o,t[o.key],o.key===n,r))}</div>`:a`<div class="empty">none</div>`;return a`<section class="decisions"><h2>decisions</h2>${i}</section>`}function yr(e,t){let n=e.badge&&a`<span class=${"badge "+(e.badge.tone||"dim")+U(e.badge.text)} title=${H(e.badge.text)}>${e.badge.text}</span>`;if(e.href){let s=(t==="cards"?"card":"deckline click")+(e.acked?" acked":"");return t==="cards"?a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener"><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}</a>`:a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener">${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}</a>`}return t==="cards"?a`<div key=${e.key} class=${"card"+(e.acked?" acked":"")} onClick=${e.open} style=${e.open?void 0:"cursor:default"}><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}${e.extra}${e.action&&a`<div class="foot"><span class="footact">${e.action}</span></div>`}</div>`:a`<div key=${e.key} class=${"deckline"+(e.open?" click":"")+(e.acked?" acked":"")} onClick=${e.open}>${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}${e.action&&[" ",a`<span class="dim">\xB7 </span>`,e.action]}${e.extra}</div>`}var ms=(e,t)=>e>0&&a`<a class="deckmore" href=${"#"+t}>+${e} more \u2192</a>`;function Ve(e,t,n,s,r,i){let o=t.slice(0,s),c=o.map(l=>yr(l,r)),u=o.length?r==="cards"?a`<div class="cards">${c}</div>`:c:a`<div class="empty">none</div>`;return a`<section><h2><a href=${"#"+n}>${e} \u2192</a></h2>${i&&a`<div class="tabhead">${i}</div>`}${u}${ms(t.length-o.length,n)}</section>`}function wr(e,t,n,s){let r=t.find(l=>l.number===e.nextNumber)||t[0],i=oe(e.numbers,new Map(t.map(l=>[l.number,l.title]))),o=r?"next #"+r.number:"nothing mergeable";if(s==="cards"){let l=f=>{let h=n.get(f.key)||[],d=h.length>0&&h.every(g=>g.acked),m=h.length?h.map(g=>a`<span class=${"badge "+($t[g.kind]||"dim")}>${Zn(g.kind)}</span>`):a`<span class=${"badge "+L(f.badge)+U(f.badge.text)} title=${H(f.badge.text)}>${f.badge.text}</span>`,p=`${f.repo} \\xB7 ${f.badge.text}${d?" \\xB7 acked":""}`;return a`<div key=${f.key} class=${"card"+(d?" acked":"")} onClick=${w("pr",f.key)}><div class="top">${Ue(f)}${m}</div><div class="meta" title=${p}>${p}</div></div>`};return a`<div key=${e.id} class="deckstack"><div class="dim">${i} \xB7 ${o}</div><div class="cards">${t.map(l)}</div></div>`}let c=t.flatMap(l=>(n.get(l.key)||[]).map(f=>a`<span class=${"badge "+($t[f.kind]||L(l.badge))+(f.acked?" acked":"")}>#${l.number} ${l.badge.text}</span>`)),u=c.flatMap((l,f)=>f?[" ",l]:[l]);return a`<div key=${e.id} class=${"deckline"+(r?" click":"")} onClick=${r&&w("pr",r.key)}><b>${i}</b> <span class="dim">\xB7 ${o}</span>${c.length>0&&[" \\xB7 ",u]}</div>`}function Gr(e,t){let n=e.stacks.slice(0,3),s=new Map;for(let i of e.prAttention){let o=s.get(i.key)||[];o.push(i),s.set(i.key,o)}let r=n.length?n.map(i=>wr(i,e.prs.filter(o=>o.stackId===i.id).sort((o,c)=>o.position-c.position),s,t)):a`<div class="empty">none</div>`;return a`<section><h2><a href="#prs">PRs \u2192</a></h2>${r}${ms(e.stacks.length-n.length,"prs")}</section>`}function hs({inp:e,drafts:t,focus:n,extensions:s}){let r=e.view,i=e.inflight.map(l=>({key:l.lane+":"+l.id,title:[l.id.slice(0,8)," ",l.repo||""],badge:{text:l.verb,tone:l.verb==="needs-decision"||l.verb==="blocked"?"bad":"dim"},meta:[B(l.note||"",90),l.verbAt&&[" \\xB7 ",v(l.verbAt)," ago"],(l.for||l.claimedBy)&&[" \\xB7 ",Me(l.for||l.claimedBy)]],metaText:[j(l.note||""),l.verbAt&&`${K(l.verbAt)} ago`,j(l.for||l.claimedBy||"")].filter(Boolean).join(" \\xB7 "),open:w("dispatch",l.lane+":"+l.id)})),o=e.landed.map(l=>({key:l.key,title:[l.id.slice(0,8)," ",l.repo||"",l.unreported&&[" ",a`<span class="badge warn unreported">unreported</span>`]],badge:{text:l.verb,tone:l.verb==="failed"?"bad":"ok"},meta:[B(l.note||"",90)," \\xB7 ",v(l.at)," ago"],metaText:`${j(l.note||"")} \\xB7 ${K(l.at)} ago`,open:w("dispatch",l.lane+":"+l.id)})),c=e.reports.map(l=>{let f=wt(l);return{key:l.key,title:l.title,meta:f.nodes,metaText:f.text,href:C(l.key),acked:!!l.acked}}),u=e.traps.map(({x:l})=>({key:l.trapId,title:"\\u{1FAA4} "+(l.label??`wt:${l.trapId}`),badge:l.requested?{text:"requested",tone:"dim"}:l.starting?{text:l.starting.failedAt?"start failed":"starting",tone:l.starting.failedAt?"bad":"warn"}:{text:l.live?l.harness||"live":"signed off",tone:l.live?"ok":"dim"},meta:[l.repo||""," \\xB7 ",ve(l)],metaText:`${l.repo||""} \\xB7 ${Oe(l)}`,action:z(l),extra:J(l),open:w("trap",l.trapId)}));return a`<div class="deckgrid"><${fs} cards=${e.decisions} drafts=${t} focus=${n} extensions=${s} />${Ve("in flight",i,"dispatches",4,r)}${Ve("Landed \\xB7 24h",o,"dispatches",8,r)}${Ve("reports",c,"reports",8,r)}${Ve("traps",u,"traps",8,r,a`<${Be} />`)}${Gr(e,r)}</div>`}function xr(e,t){if(!t)return e;let n=new Map(e.map(i=>[i.id,i])),s=i=>{let o=i,c=new Set;for(;o.followUp&&n.has(o.followUp)&&!c.has(o.id);)c.add(o.id),o=n.get(o.followUp);return o.id},r=new Map;for(let i of e){let o=s(i),c=r.get(o)||[];c.push(i),r.set(o,c)}return[...r.values()].sort((i,o)=>o[0].sort-i[0].sort).flatMap(i=>i.sort((o,c)=>o.id===s(o)?-1:c.id===s(c)?1:o.sort-c.sort))}function Tr(e,t){let n=e.lane+":"+e.id;return a`<tr key=${n} class="rowhead" onClick=${w("dispatch",n)}><td>${t&&e.followUp&&a`<span class="dim">\u21B3 </span>`}${e.id.slice(0,8)}</td><td>${e.lane} / ${e.bucket}</td><td>${e.repo}</td><td class=${"v-"+e.verb}>${e.verb}</td><td class="grow">${B(e.note??"",90)}${Le(e)}${Ee(e)}</td><td>${v(e.verbAt)}</td><td>${fe(e)}</td><td>${re(e)}</td></tr>`}function Sr(e){let t=e.lane+":"+e.id;return a`<div key=${t} class="card" onClick=${w("dispatch",t)}><div class="top"><b>${e.id.slice(0,8)}</b><span class=${"badge v-"+e.verb+U(e.verb)} title=${H(e.verb)}>${e.verb}</span></div><div class="meta" title=${`${e.repo} \\xB7 ${e.lane} ${e.bucket}`}>${e.repo} \xB7 ${e.lane} ${e.bucket}${e.verbAt&&[" \\xB7 ",v(e.verbAt)]}</div>${e.note&&a`<div class="note" title=${j(e.note)}>${B(e.note)}</div>`}${Le(e)}${Ee(e)}<div class="foot">${fe(e)} ${re(e)}</div></div>`}function gs({inp:e}){let t=e.list;return e.view==="cards"?t.length?a`<div class="cards">${t.map(Sr)}</div>`:a`<div class="empty">no dispatches match</div>`:R(["id","lane / bucket","repo","verb","note","age","addressed","pr"],xr(t,e.chain).map(n=>Tr(n,e.chain)),"no dispatches match")}var Cr={deck:"On deck",dispatches:"Dispatches",traps:"Traps",prs:"PRs",reports:"Reports",notices:"Notices"},Dr=["working","needs-decision","blocked","paused","done","failed","unknown"];function Ar(e,t){let n=t.daemonStale,s=e.daemon?[a`<span class=${n?"bad":"ok"}>${n&&"stale "}${v(e.daemon.heartbeat)} ago</span>`," ",a`<span class="dim">v${e.daemon.version}</span>`]:a`<span class="bad">down</span>`;return[a`<span class="chip">daemon ${s}</span>`,e.slots&&a`<span class="chip">active headless: ${e.slots.headless} of ${e.slots.limit}; traps: ${e.slots.traps}${e.slots.parked?`; parked: ${e.slots.parked} (no slot)`:""}</span>`,t.helms.map(({x:r,stale:i})=>a`<span key=${r.grounds} class="chip click" onClick=${w("helm",r.grounds)}>\u26F5 <b>${r.man}</b> <span class="dim">helm ${r.grounds}</span> <span class=${i?"warn":"ok"}>${i&&"stale "}${v(r.heartbeatAt)} ago</span></span>`)]}var Dt=[];function Rr(e){let t=[...new Set([...e.dispatches.map(n=>n.repo),...e.notices.map(n=>n.repo),...e.prs.map(n=>n.repo)].filter(Boolean))].sort();return t.length!==Dt.length&&(Dt=t),Dt}var At=[];function Pr(e){let t=[...new Set(e.notices.map(n=>n.kind))].sort();return t.length!==At.length&&(At=t),At}var $e=e=>e.currentTarget.value;function Ir(e,t,n){let s=o=>t===o?"":"display:none",r=e?Rr(e):[],i=e?Pr(e):[];return a`<div class="controls">\n <select id="f-lane" style=${e&&s("dispatches")} value=${n.lane} onChange=${o=>N({lane:$e(o)})}>\n <option value="">all lanes</option>\n <option value="work">work</option>\n <option value="chore">chore</option>\n </select>\n <select id="f-repo" value=${n.repo} onChange=${o=>N({repo:$e(o)})}>\n <option value="">all repos</option>\n ${r.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <select id="f-verb" style=${e&&s("dispatches")} value=${n.verb} onChange=${o=>N({verb:$e(o)})}>\n <option value="">all verbs</option>\n ${Dr.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <label id="chain-control" style=${e&&s("dispatches")}><input id="f-chain" type="checkbox" checked=${!!n.chain} onChange=${o=>N({chain:o.currentTarget.checked})} /> group by chain</label>\n <select id="f-kind" style=${e&&s("notices")} value=${n.noticeKind} onChange=${o=>N({noticeKind:$e(o)})}>\n <option value="">all notice kinds</option>\n ${i.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <input id="f-q" type="search" placeholder="search id \xB7 note \xB7 brief" value=${n.q} onInput=${o=>N({q:$e(o)})} />\n </div>`}function vs({d:e,inp:t,tab:n,st:s,stale:r}){return[a`<div class="headerline"><h1>\u{1F99E}\u2728 spyglass<span id="stale" style=${e||r?r?"display:inline":"display:none":void 0}> \xB7 STALE FEED</span></h1><span id="settings-slot"><button id="gearbtn" title="settings" aria-label="settings" onClick=${()=>M("settings","browser")}>\u2699</button></span></div>`,a`<div class="chips"><span id="chips" style="display:contents">${e&&t&&Ar(e,t.chips)}</span><span class="chip dim" id="clock">${e&&new Date(e.now).toLocaleTimeString("en-GB")}</span></div>`,a`<nav class="tabs" id="tabs" aria-label="Spyglass views">${ce.map(i=>a`<a href=${"#"+i} data-tab=${i} class=${e?i===n?"on":"":void 0}>${Cr[i]}</a>`)}</nav>`,Ir(e,n,s)]}var _s=({d:e})=>e&&["\\u{1F99E}\\u2728 lobstah v"+e.version+" \\xB7 ",a`<a href=${e.repoUrl} target="_blank">${e.repoUrl.replace("https://github.com/","")}</a>`];function $s(e,t){if(!t.lobs)return[];let n={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch",report:"report"},s=t.hidden??{},r=e.filter(o=>!o.acked&&!(o.key!==void 0&&s[o.key]===o.stateHash)).map(o=>{let c=n[o.kind??""]??"",u=o.key!==void 0?{hideKey:o.key,hideHash:o.stateHash??""}:{};return o.kind==="decision"&&o.key?{key:"decision:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:(o.kind??"question")==="question"&&o.key?{key:"question:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:o.kind==="report"&&o.key?{key:"report:"+o.key,text:o.note||o.verb,label:c,href:C(o.key),...u}:typeof o.kind=="string"&&o.kind.startsWith("pr:")&&o.prUrl?{key:o.kind+":"+(o.key??o.prUrl),text:o.note||o.kind,href:o.prUrl,label:c,...u}:{key:(o.kind??"question")+":"+(o.key??o.lane+":"+o.id),text:o.note||o.verb,label:c,...o.kind==="watch"?{}:{open:{type:"dispatch",key:o.lane+":"+o.id}},...u}});!r.length&&t.preview&&(r=[{key:"preview",text:"attention questions crawl in here",...t.previewHelm?{open:{type:"helm",key:t.previewHelm}}:{}}]);let i=r.length>4?r.length-4:0;return r=r.slice(0,4),i&&(r[3]={...r[3],text:"\\u2026and "+i+" more \\u2014 see attention",label:""}),r}var bs=!0;function Mr(e,t,n){let s="animation-duration:"+((innerWidth+180)/(100+t*12)).toFixed(1)+"s;animation-delay:-"+t*9%14+"s",r=()=>{e.hideKey&&An(e.hideKey,e.hideHash??"")},i=[a`<div class="bub">${e.label&&[a`<span class="badge dim">${e.label}</span>`," "]}<span>${e.text.length>48?e.text.slice(0,47)+"\\u2026":e.text}</span></div>`,n===!1?a`<span class="fallback">\u{1F99E}</span>`:a`<div class="sprite"></div>`,bs&&a`<img\n class="star"\n src="/star.png"\n alt=""\n onError=${c=>{bs=!1,c.currentTarget.remove()}}\n />`],o=e.open;return e.hash?a`<a key=${e.key} class="lob" style=${s} title="open the decision" href=${e.hash} onClick=${r}>${i}</a>`:e.href?a`<a key=${e.key} class="lob" style=${s} title=${e.label==="report"?"open the report":"open the PR"} href=${e.href} target="_blank" rel="noopener" onClick=${r}>${i}</a>`:a`<div\n key=${e.key}\n class="lob"\n style=${s}\n title="click to open"\n onClick=${()=>{r(),o&&M(o.type,o.key)}}\n >\n ${i}\n </div>`}function ks({state:e}){let t=e.snapshot,n=$s(t?.attention||[],{lobs:e.prefs.lobs,hidden:e.lobHidden,preview:e.preview,previewHelm:t&&t.helms.length?t.helms[0].grounds:void 0}),s=n.map(r=>r.key).join("|")+(e.spriteOk===null?"?":e.spriteOk?"s":"e");return Vn(()=>n.map((r,i)=>Mr(r,i,e.spriteOk)),[s])}function Ke({box:e}){if(!e)return null;let t=n=>{n.target===n.currentTarget&&ee()};return a`<div id="lightbox" class="lightbox" role="dialog" aria-label=${e.name} onClick=${t}>\n <button type="button" class="lbclose" title="close" onClick=${ee}>\xD7</button>\n <img class="lbimg" src=${e.src} alt=${e.name} />\n <a class="lboriginal" href=${e.src} target="_blank" rel="noopener">open original \xB7 ${e.name}</a>\n </div>`}var be=a`<span class="x" onClick=${te}>\xD7</span>`,ys=(e,t,n)=>a`<span class="seg">${t.map(([s,r])=>a`<button class=${s===n?"on":""} onClick=${()=>e(s)}>${r}</button>`)}</span>`;function Er(e,t){let n=ys(Cn,[["table","table"],["cards","cards"]],t.view),s=ys(Dn,[["on","on"],["off","off"]],t.lobs?"on":"off"),r=e.attentionError?e.attentionError:(e.attentionKinds||[]).join(" \\xB7 ");return[be,a`<h3>\u2699 settings <span class="dim" style="font-weight:normal">\xB7 this browser only</span></h3>`,a`<div class="settings"><div class="row"><span class="lbl">view<span class="hint">cards or table (notices stay a table)</span></span>${n}</div><div class="row"><span class="lbl">lobs<span class="hint">crawling lobsters in this page</span></span>${s}</div><div class="row"><span class="lbl">attention<span class="hint">kinds shown \u2014 attentionKinds in config.toml (read-only here)</span></span><span class="dim" style="font-size:11px;text-align:right;max-width:260px">${r}</span></div></div>`]}function Lr(e,t){let n=oe(t.numbers,new Map(t.numbers.map((r,i)=>[r,t.titles[i]])),e.number),s=t.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:t.blockedBy?" \\xB7 blocked by #"+t.blockedBy:t.nextNumber?" \\xB7 next is #"+t.nextNumber:"";return[n," \\xB7 ",t.position," of ",t.size," \\xB7 floor ",t.floor,s]}var Nr=e=>t=>{t.preventDefault(),M("dispatch",e)};function Hr(e,t){let n=un(e,t),s=n.pr,r=n.stack,i=n.watch,o=je(s),c=n.chain.length?n.chain.flatMap((l,f)=>[f?" \\u2192 ":"",l.culled?a`<span class="dim">${l.id.slice(0,8)} (culled)</span>`:[a`<a href="#" onClick=${Nr(l.modalKey)}>${l.id.slice(0,8)}</a>`," ",a`<span class="dim">${l.verb}</span>`]]):a`<span class="dim">none</span>`,u=i?[a`<div>${i.key} \xB7 owner ${i.owner||"?"} \xB7 ${i.lastCheckedAt?["checked ",v(i.lastCheckedAt)," ago"]:"never checked"}</div>`,i.lastError&&a`<div class="bad">${i.lastError}</div>`,a`<div class="dim" style="font-size:11px;margin-top:4px">cursor</div>`,S(i.cursor)]:a`<div class="dim">no watch \u2014 <code>lobstah watch add ${s.url}</code> registers one</div>`;return[be,a`<h3>${He(s)} ${s.title||""} <span class=${"badge "+L(s.badge)}>${s.badge.text}</span>${s.draft&&s.badge.text!=="draft"&&[" ",a`<span class="badge pr-draft">draft</span>`]}</h3>`,a`<div class="sub">${s.repo} \xB7 ${s.state} \xB7 observed ${v(s.observedAt)} ago${s.gate&&" \\xB7 gate "+s.gate}</div>`,a`<div class="sec">checks</div><div>${me(s)}</div><div class="sec">review</div><div>${o||a`<span class="dim">no review yet</span>`}</div><div class="sec">merge</div><div>${he(s)}</div>${s.repairWait&&a`<div class="sec">repair waits</div><div>${s.repairWait.heldBy}: ${s.repairWait.reason}${s.repairWait.until?" \\xB7 until "+s.repairWait.until:""}</div>`}<div class="sec">refs</div><div>${s.headRefName||"?"} \u2192 ${s.baseRefName||"?"}</div><div class="sec">stack</div><div>${r?Lr(s,r):a`<span class="dim">not stacked</span>`}</div><div class="sec">dispatch chain</div><div>${c}</div><div class="sec">watch</div>`,u]}var ws=(e,t)=>(e==="codex"?"codex resume ":"claude --resume ")+t;function Ur(e){let t=Date.now()-Date.parse(e.heartbeatAt)>18e5;return[be,a`<h3>\u26F5 ${e.man}</h3><div class="sub">helm of <b>${e.grounds}</b> (${(e.repos||[]).join(", ")})</div><div class="sub">${e.harness??"?"} \xB7 ${e.cwd??"?"}${e.host&&" \\xB7 "+e.host}</div><div class="sub">session ${e.sessionId??""} \xB7 signed on ${v(e.signedOnAt)} ago \xB7 heartbeat <span class=${t?"warn":"ok"}>${v(e.heartbeatAt)} ago</span></div>`,e.sessionId&&[a`<div class="sec">open this session</div>`,S(ws(e.harness,e.sessionId))],e.transcript&&[a`<div class="sec">transcript</div>`,S(e.transcript)]]}function jr(e){return[a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)} \xB7 <a href=${C(e.key)} target="_blank" rel="noopener">open the report \u2197</a></div>`]}function Br(e,t){let n=e.claimedBy&&e.claimedBy.startsWith("wt:")?[a`<div class="sec">worked by trap</div>`,a`<div class="claimedby">claimed by ${Ne(e.claimedBy.slice(3))}</div>`,S(j(e.claimedBy)),a`<div class="dim" style="font-size:11px">an opted-in interactive session mans this seat \u2014 attach would resume someone\'s live thread. Message it instead: lobstah send ${j(e.claimedBy)} "\u2026"</div>`]:[a`<div class="sec">open this session</div>`,S("lobstah attach "+e.id)];return[be,a`<h3>${e.id.slice(0,8)} <span class=${"badge v-"+e.verb}>${e.verb}</span></h3>`,a`<div class="sub">${e.repo} \xB7 ${e.lane} ${e.bucket} \xB7 ${v(e.verbAt)}${e.for&&[" \\xB7 ",fe(e)]} ${re(e)}</div>`,n,e.worktree&&[a`<div class="sec">worktree${e.worktreeOf?" (reused from "+e.worktreeOf.slice(0,8)+")":""}</div>`,S(e.worktree),e.worktreeKept&&a`<div class="dim" style="font-size:11px">kept: ${e.worktreeKept}</div>`],e.transcript&&[a`<div class="sec">transcript</div>`,S(e.transcript)],t&&jr(t),Qn(e)]}function Or(e){let t=ge(e),n=e.requested?[e.repo??""," \\xB7 ",e.harness??""," \\xB7 requested ",v(e.requested.at)," ago from the glass; the helm reserves it and starts its session."]:e.starting?e.starting.failedAt?[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 start failed: ",e.starting.reason??"",". Its addressed work stays queued; a session can still redeem the ticket, or `lobstah stow --wt ",e.name??e.trapId,"` withdraws it."]:[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 starting: waiting for a session to redeem the ticket (due by ",e.starting.deadline,")"]:e.live?[e.repo??"addressed bait only"," \\xB7 session ",e.sessionId??""," \\xB7 signed on ",v(e.signedOnAt)," ago \\xB7 ",t.listen," \\xB7 heartbeat ",t.hb]:"signed off \\u2014 registration gone; the lifecycle, messages, and catches are the surviving record. Re-soaking the same worktree restores this address.",s=e.notices.length?e.notices.map(o=>a`<div key=${o.seq} class="loglines">${v(o.at)} ago \xB7 <b>${o.kind}</b> \u2014 ${o.text}</div>`):a`<div class="empty">none recorded</div>`,r=e.messages.length?e.messages.map(o=>a`<div key=${o.file} class=${"msg"+(o.from==="helm"?" from-helm":"")}><div class="hdr">from ${o.from} \xB7 ${o.at&&[v(o.at)," ago"]} \xB7 ${o.state==="pending"?a`<span class="warn">pending</span>`:a`<span class="ok">delivered</span>`}</div>${o.text}${o.attachments?.length?se(o.attachments,c=>sn(e.trapId,c)):""}</div>`):a`<div class="empty">none</div>`,i=e.catches.length?e.catches.map(o=>a`<div key=${o.lane+":"+o.id} class="catch"><div class="hdr"><b>${o.id.slice(0,8)}</b><span class=${"badge v-"+o.verb}>${o.verb}</span><span class="dim">${v(o.verbAt)}</span>${re(o)}</div><div class="loglines">${_t(o)}</div></div>`):a`<div class="empty">none yet</div>`;return[be,a`<h3>\u{1FAA4} ${e.label??`wt:${e.trapId}`} <span class="badge">${e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??"signed off"}</span></h3>`,e.worktree&&a`<div class="sub">${e.worktree}</div>`,a`<div class="sub">${n}</div>`,a`<div class="sec">window</div>`,z(e),J(e),e.live&&e.sessionId&&[a`<div class="sec">open this session</div>`,S(ws(e.harness,e.sessionId)),a`<div class="dim" style="font-size:11px">as registered at sign-on \u2014 a hookless enlistment may hold a made-up id</div>`],a`<div class="sec">lifecycle (${e.notices.length})</div>`,s,a`<div class="sec">messages (${e.messages.length})</div>`,r,a`<div class="sec">catches (${e.catches.length})</div>`,i]}function Gs({snapshot:e,modal:t,prefs:n}){if(!e||!t)return null;let s=Y(e,t);if(!s)return null;if(t.type==="settings")return Er(s,n);if(t.type==="pr")return Hr(e,t.key);if(t.type==="helm")return Ur(s);if(t.type==="dispatch"){let r=an(e,s);return Br(s,r)}return Or(s)}var qr=e=>a`<tr key=${e.seq}><td class="dim">${v(e.at)}</td><td>${e.kind}</td><td class="grow">${e.text}</td><td class="dim">${e.repo??""}</td></tr>`,xs=({inp:e})=>R(["at","kind","text","repo"],e.list.map(qr),"no notices");function Ts(e){let t=new Map;for(let n of e.prs){let s=t.get(n.stackId)||[];s.push(n),t.set(n.stackId,s)}return e.stacks.map(n=>({s:n,prs:t.get(n.id)||[]})).filter(n=>n.prs.length)}var Ss=(e,t)=>oe(e.numbers,new Map(t.map(n=>[n.number,n.title]))),Rt=e=>e.state+(e.draft?" \\xB7 draft":"")+(e.state==="MERGED"&&e.mergedAt?" \\xB7 "+e.mergedAt:"");function Cs(e){let t=s=>{let r=String(s??"");return r.length>24?r.slice(0,23)+"\\u2026":r},n=e.watches.map(s=>a`<tr key=${s.key}><td>${s.key}</td><td>${s.owner}</td><td title=${s.cursor}>${t(s.cursor)}</td><td>${s.lastCheckedAt&&v(s.lastCheckedAt)}</td><td>${s.lastError||""}</td></tr>`);return[a`<h2>other watches</h2>`,R(["key","owner","cursor","last check","error"],n,"no other watches")]}function Fr(e){let t=Ts(e).flatMap(({s:n,prs:s})=>[a`<tr key=${"stack:"+n.id}><th colspan="8">${Ss(n,s)} \xB7 floor ${n.floor}${n.open?" \\xB7 open":" \\xB7 history"}</th></tr>`,...s.map(r=>a`<tr key=${r.key} class="rowhead" onClick=${w("pr",r.key)}><td>${He(r)}</td><td class="grow">${r.title||""}</td><td>${Rt(r)}</td><td>${me(r)}</td><td>${je(r)}</td><td>${he(r)}</td><td>${bt(r.watch)}</td><td>${r.gate||""}</td></tr>`)]);return[R(["PR","title","state","checks","review","merge","watch","gate"],t,"no PR evidence"),Cs(e)]}function Wr(e){let t=Ts(e),n=r=>a`<div key=${r.key} class="card" onClick=${w("pr",r.key)}><div class="top">${Ue(r)}<span class=${"badge "+L(r.badge)+U(r.badge.text)} title=${H(r.badge.text)}>${r.badge.text}</span></div><div class="meta" title=${`${r.repo} \\xB7 ${Rt(r)}`}>${r.repo} \xB7 ${Rt(r)} \xB7 ${he(r)}</div><div class="foot"><span>${me(r)}</span><span>${bt(r.watch)}</span>${r.gate&&a`<span>gate ${r.gate}</span>`}</div></div>`;return[t.length?t.map(({s:r,prs:i})=>a`<${Z} key=${"stack:"+r.id}><h2>${Ss(r,i)} \xB7 floor ${r.floor}${r.open?"":" \\xB7 history"}</h2><div class="cards">${i.map(n)}</div><//>`):a`<div class="empty">no PR evidence</div>`,Cs(e)]}var Ds=({inp:e})=>e.view==="cards"?Wr(e):Fr(e);var Pt=e=>e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??(e.live?"":"signed off"),As=e=>e.live||e.starting?"":" dim";function Vr(e){let t=ge(e);return a`<tr key=${e.trapId} class=${"rowhead"+As(e)} onClick=${w("trap",e.trapId)}><td><b>${e.label??`wt:${e.trapId}`}</b></td><td>${e.repo??"\\u2014"}</td><td class="grow">${e.worktree??""}</td><td>${e.harness??""}</td><td>${(e.sessionId??"").slice(0,8)}</td><td>${ve(e)}${J(e)}</td><td>${t.listen}</td><td>${t.hb}</td><td>${yt(e)}</td><td>${e.catches.length}</td><td>${z(e)}</td></tr>`}function Kr(e){let t=ge(e),n=yt(e),s=`${e.repo??(e.live?"addressed bait only":"history")}${e.sessionId?" \\xB7 session "+e.sessionId.slice(0,8):""}`;return a`<div key=${e.trapId} class=${"card"+As(e)} onClick=${w("trap",e.trapId)}><div class="top"><b>\u{1FAA4} ${e.label??`wt:${e.trapId}`}</b><span class=${"badge"+U(Pt(e))} title=${H(Pt(e))}>${Pt(e)}</span></div><div class="meta" title=${s}>${s}</div>${e.worktree&&a`<div class="note" title=${e.worktree}>${e.worktree}</div>`}<div class="note" title=${Oe(e)}>${ve(e)}</div>${J(e)}<div class="foot"><span>${t.listen}${e.live&&[" \\xB7 heartbeat ",t.hb]} \xB7 ${e.catches.length} catch${e.catches.length===1?"":"es"}${n&&[" \\xB7 ",n]}</span><span class="footact">${z(e)}</span></div></div>`}function Rs({inp:e}){let t=e.list.map(s=>s.x),n=a`<div class="tabhead"><${Be} /></div>`;return e.view==="cards"?[n,t.length?a`<div class="cards">${t.map(Kr)}</div>`:a`<div class="empty">no traps soaking</div>`]:[n,R(["address","repo","worktree","harness","session","now","listening","heartbeat","mail","catches","window"],t.map(Vr),"no traps soaking")]}function Ps({state:e}){let t=e.snapshot,n=e.route,s=t&&pn(t,{st:e.prefs,modal:e.modal},Date.now()),r=o=>!s||o!==n?null:o==="deck"?a`<${hs} inp=${s.deck} drafts=${e.drafts} focus=${e.focusDecision} extensions=${t?.answerLimits?.extensions??[]} />`:o==="dispatches"?a`<${gs} inp=${s.dispatches} />`:o==="traps"?a`<${Rs} inp=${s.traps} />`:o==="prs"?a`<${Ds} inp=${s.prs} />`:o==="reports"?a`<${ns} inp=${s.reports} />`:a`<${xs} inp=${s.notices} />`,i=o=>{o.target===o.currentTarget&&te()};return[a`<${vs} d=${t} inp=${s} tab=${n} st=${e.prefs} stale=${e.stale} />`,ce.map(o=>a`<main key=${o} id=${"page-"+o} class=${t&&o===n?"tabpage on":"tabpage"}><div id=${o}>${r(o)}</div></main>`),a`<footer id="foot"><${_s} d=${t} /></footer>`,a`<div id="lobs"><${ks} state=${e} /></div>`,a`<${Ke} box=${e.lightbox} />`,a`<div id="overlay" class=${e.modal&&t?"open":t?"":void 0} onClick=${i}><div class="modal" id="modalbox"><${Gs} snapshot=${t} modal=${e.modal} prefs=${e.prefs} /></div></div>`]}function zr({view:e}){if(e.state==="loading")return a`<main class="reportview"><div class="dim">loading\u2026</div></main>`;if(e.state==="error")return a`<main class="reportview"><div class="bad">${e.error}</div></main>`;let t=e.report;return a`<main class="reportview">\n <h1>${t.title}</h1>\n <${us} r=${t} text=${e.text!==void 0?{text:e.text}:{error:e.error}} />\n </main>`}function Is({view:e,box:t}){return[a`<${zr} view=${e} />`,a`<${Ke} box=${t} />`]}async function Ms(e){let t=encodeURIComponent(e),n;try{let s=await fetch(`/report/${t}/meta`);if(!s.ok)return{state:"error",error:`report not found (${s.status})`};n=JSON.parse(await s.text())}catch{return{state:"error",error:"report could not be read"}}try{let s=await fetch(dn(e));return s.ok?{state:"ready",report:n,text:await s.text()}:{state:"ready",report:n,error:`report not found (${s.status})`}}catch{return{state:"ready",report:n,error:"report could not be read"}}}var It=!1,ke=null;async function Mt(){if(!It){It=!0;try{let e=await fetch("/data");if(!e.ok)throw new Error(`/data answered ${e.status}`);ut(await e.json())}catch{Gn()}finally{It=!1}}}var Es=()=>{ke||(ke=setInterval(Mt,2e3))},Jr=()=>{ke&&clearInterval(ke),ke=null};function Ls(){document.addEventListener("visibilitychange",()=>{document.hidden?Jr():(Mt(),Es())}),Mt(),document.hidden||Es()}var Et=()=>nn(location.hash),Ns=e=>window.addEventListener("hashchange",()=>e(Et()));function Us(){let e=ln(location.hash);return e===null?!1:(location.replace(C(e)),!0)}var js=()=>({snapshot:void 0,route:Et(),prefs:mn(),modal:null,stale:!1,lobHidden:gn(),spriteOk:null,focusResults:{},preview:new URLSearchParams(location.search).has("lob"),drafts:{},focusDecision:ot(location.hash),lightbox:null}),Hs=cn(location.pathname);Hs!==null?Xr(Hs):Us()||Qr();function Xr(e){dt(js());let t={state:"loading"},n=()=>nt(Q(Is,{view:t,box:_().lightbox}),document.body);document.title="report \\xB7 lobstah glass",pt(n),n(),Ms(e).then(s=>{t=s,s.state==="ready"&&(document.title=`${s.report.title} \\xB7 lobstah glass`),n()}),document.addEventListener("keydown",s=>{s.key==="Escape"&&_().lightbox&&ee()})}function Qr(){dt(js());let e,t=!1,n=()=>{let o=_(),c=o.route!==e||!t&&!!o.snapshot;nt(Q(Ps,{state:o}),document.body),e=o.route,t||(t=!!o.snapshot),c&&window.scrollTo(0,0),o.focusDecision||(s=null),r(o.focusDecision)},s=null;function r(o){if(!o||o===s)return;let c=[...document.querySelectorAll("[data-decision]")].find(u=>u.getAttribute("data-decision")===o);c&&(s=o,c.scrollIntoView?.({block:"center"}))}pt(n),n();let i=new Image;i.onload=()=>T({spriteOk:!0}),i.onerror=()=>T({spriteOk:!1}),i.src="/lob-sprite.png",Ns(o=>{if(Us())return;let c=ot(location.hash);c!==_().focusDecision&&(s=null),T({route:o,focusDecision:c})}),document.addEventListener("keydown",o=>{o.key==="Escape"&&(_().lightbox?ee():te())}),Ls()}})();</script>\n </body>\n</html>\n';
13652
+ var GLASS_PAGE = '<!doctype html>\n<html>\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width,initial-scale=1" />\n <title>spyglass</title>\n <link rel="icon" type="image/png" href="/icon.png" />\n <style>:root{--bg: #0e1116;--card: #161b22;--line: #2b3240;--line-hover: #3a455a;--row-hover: #1c2330;--inset: #10151c;--seg-on: #26436b;--scrim: rgba(4, 7, 11, 0.72);--fg: #dbe2ea;--dim: #8b96a5;--ok: #4fc17c;--warn: #e2b93d;--bad: #e26d5c;--link: #6cb2e2;--on-fill: #fff;--pr-merged: #8957e5;--pr-merged-fg: #ab7df8;--pr-open: #238636;--pr-draft: #6e7681;--pr-closed: #da3633;--font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;--font-size: 13px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--page-pad-block: 14px;--radius: 6px;--card-radius: 8px;--card-pad: 12px 14px;--card-min: 300px;--deck-card-min: 240px;--badge-radius: 5px;--badge-pad: 1px 7px;--badge-font: 11px;--modal-radius: 10px;--modal-width: 780px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--fg);font:var(--font-size)/1.45 var(--font-mono);padding-block:var(--page-pad-block);padding-inline:var(--space-4);display:flex;flex-direction:column;min-height:100vh}h1{font-size:15px;margin:0 0 10px}h1 .dim{color:var(--dim);font-weight:normal}h2{font-size:12px;color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;margin:20px 0 6px}.chips,.controls{display:flex;gap:var(--space-2);flex-wrap:wrap;align-items:center}.controls{margin:0 0 14px}.chips{margin-bottom:8px}.chip{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);padding:3px 9px}.controls select,.newtrap select,.controls input[type=search]{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);color:var(--fg);font:inherit;padding:4px 8px}.controls input[type=search]{flex:1 1 140px;min-width:120px;max-width:340px}#chain-control{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}#f-chain{margin:0;width:14px;height:14px;accent-color:var(--link)}.tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--line);margin:8px 0 10px}.tabs a{color:var(--dim);padding:6px 11px;border-bottom:2px solid transparent}.tabs a.on{color:var(--fg);border-color:var(--link)}.tabpage{display:none}.tabpage.on{display:block;flex:1 0 auto}.headerline{display:flex;align-items:center;gap:var(--space-2)}.headerline h1{flex:1}#settings-slot{min-width:34px;text-align:right;color:var(--dim)}#settings-slot #gearbtn{margin-left:0}.deckgrid{display:flex;flex-direction:column;gap:var(--space-3)}.deckgrid h2{margin:5px 0}.deckgrid section{width:100%;min-width:0}.deckline{padding:2px 0;border-top:1px solid var(--line);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.deckline.acked,.card.acked,.rowhead.acked,.badge.acked{opacity:0.55}.deckmore{color:var(--dim);font-size:11px;display:inline-block;margin-top:4px}.deckline.click{cursor:pointer}.deckline.click:hover{background:var(--row-hover)}.deckgrid h2 a{color:inherit}.deckgrid h2 a:hover{color:var(--fg)}.deckgrid .cards{grid-template-columns:repeat(auto-fill,minmax(min(var(--deck-card-min),100%),1fr));gap:10px}.deckstack{margin-bottom:9px}.deckstack>.dim{font-size:11px;margin:3px 0 5px}.seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}.seg button{background:var(--card);border:none;color:var(--dim);font:inherit;padding:4px 11px;cursor:pointer}.seg button.on{background:var(--seg-on);color:var(--fg)}.wrap{overflow-x:auto}table{border-collapse:collapse;width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}th,td{text-align:left;padding:4px 9px;border-top:1px solid var(--line);vertical-align:top;white-space:nowrap}td.grow{white-space:normal;word-break:break-word;min-width:140px}th{color:var(--dim);font-weight:normal;border-top:none;font-size:11px}tr.rowhead{cursor:pointer}tr.rowhead:hover{background:var(--row-hover)}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(var(--card-min),100%),1fr));gap:var(--space-3)}.card{background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad);cursor:pointer;min-width:0;overflow:hidden;overflow-wrap:anywhere}.card:hover{border-color:var(--line-hover)}.card .top{display:flex;justify-content:space-between;gap:var(--space-3);align-items:center;min-height:22px}.card .top>b,.card .top>.prname{flex:1 1000000 auto;min-width:min(10ch,100%);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card .top .badge{flex:0 0 auto}.card .top .badge.long{flex:0 1 auto;min-width:min(12ch,100%);max-width:min(26ch,100%);overflow:hidden;text-overflow:ellipsis}.card .meta{color:var(--dim);font-size:12px;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.card .note{margin-top:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.activity{margin-top:4px;font-size:12px;color:var(--fg);opacity:0.85}.waiting{margin-top:4px;font-size:12px;color:var(--dim)}.activity.stale{color:var(--dim);opacity:1}.card .foot{margin-top:8px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;color:var(--dim);font-size:12px}.card .foot>*{min-width:0}.card .foot .footact{margin-left:auto}.btn{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:1px 8px;cursor:pointer;white-space:nowrap}.btn:hover{color:var(--fg);border-color:var(--line-hover)}.badge{border-radius:var(--badge-radius);padding:var(--badge-pad);font-size:var(--badge-font);line-height:1.5;border:1px solid var(--line);white-space:nowrap;display:inline-block}.badge.pr-merged{background:var(--pr-merged);border-color:var(--pr-merged-fg);color:var(--on-fill)}.badge.pr-open{background:var(--pr-open);border-color:var(--pr-open);color:var(--on-fill)}.badge.pr-draft{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.pr-closed{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-conflicts{background:var(--pr-closed);border-color:var(--pr-closed);color:var(--on-fill)}.badge.pr-behind{background:var(--pr-draft);border-color:var(--pr-draft);color:var(--on-fill)}.badge.unreported{font-weight:normal;font-size:10px;padding:0 5px;vertical-align:1px}.v-done{color:var(--ok)}.v-working,.v-queued{color:var(--fg)}.v-needs-decision,.v-blocked{color:var(--bad);font-weight:bold}.v-failed{color:var(--bad)}.v-paused,.v-unknown{color:var(--dim)}.dim{color:var(--dim)}.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)}.detail{display:none}.detail.open{display:table-row}.detail td{background:var(--inset);white-space:pre-wrap;word-break:break-word;font-size:12px;color:var(--dim)}.detail b{color:var(--fg)}.empty{color:var(--dim);padding:6px 2px}#stale{display:none;color:var(--bad)}a{color:var(--link);text-decoration:none}.dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:5px;vertical-align:middle}.dot.ok{background:var(--ok)}.dot.warn{background:var(--warn)}.dot.bad{background:var(--bad)}.dot.dim{background:var(--dim)}#overlay{position:fixed;inset:0;background:var(--scrim);display:none;z-index:10;padding:28px 16px;overflow:auto}#overlay.open{display:block}.modal{max-width:var(--modal-width);margin:0 auto;background:var(--card);border:1px solid var(--line);border-radius:var(--modal-radius);padding:16px 18px;position:relative}.modal .x{position:absolute;top:8px;right:12px;cursor:pointer;color:var(--dim);font-size:18px;line-height:1}.modal .x:hover{color:var(--fg)}.modal h3{margin:0 24px 2px 0;font-size:14px}.modal .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.modal .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.modal pre{white-space:pre-wrap;word-break:break-word;margin:0;font:inherit;color:var(--fg)}.msg{border-left:3px solid var(--line);padding:3px 10px;margin:8px 0;white-space:pre-wrap;word-break:break-word}.msg.from-helm{border-color:var(--link)}.msg .hdr{color:var(--dim);font-size:11px}.catch{border:1px solid var(--line);border-radius:var(--radius);padding:6px 10px;margin:8px 0}.catch .hdr{display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}.loglines{color:var(--dim);font-size:12px;white-space:pre-wrap;word-break:break-word;margin-top:4px}.cmd{display:flex;gap:var(--space-2);align-items:center;margin:5px 0;flex-wrap:wrap}.cmd code{background:var(--inset);border:1px solid var(--line);border-radius:5px;padding:3px 8px;word-break:break-all;cursor:pointer}.cmd code:hover{border-color:var(--line-hover)}.cmd code.copied{border-color:var(--ok)}.cmd button{background:var(--card);border:1px solid var(--line);border-radius:5px;color:var(--dim);font:inherit;font-size:11px;padding:2px 8px;cursor:pointer}.cmd button:hover{color:var(--fg)}.chip.click{cursor:pointer}.chip.click:hover{border-color:var(--line-hover)}footer{margin-top:26px;padding-top:10px;border-top:1px solid var(--line);color:var(--dim);font-size:12px;display:flex;gap:var(--space-2);flex-wrap:wrap}.lob{position:fixed;bottom:6px;left:0;z-index:5;cursor:pointer;font-size:34px;line-height:1;user-select:none;animation:crawl 18s linear infinite}a.lob{color:inherit}.lob .bub .badge{display:inline-block;margin:0 0 3px}.lob .sprite{width:72px;height:56px;background:url(/lob-sprite.png) 0 0 no-repeat;background-size:400% 100%;image-rendering:pixelated;animation:step 0.5s steps(4) infinite}.lob .fallback{display:inline-block;animation:waddle 0.45s ease-in-out infinite alternate}.lob .bub{display:none;position:absolute;bottom:58px;right:-8px;z-index:2;background:var(--card);border:1px solid var(--line);border-radius:9px;padding:6px 9px 5px;font-size:11px;line-height:1.35;color:var(--fg);width:max-content;max-width:150px;box-shadow:0 2px 8px rgba(0,0,0,0.4)}.lob:hover .bub{display:block}.lob .bub span{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;white-space:normal;word-break:break-word}.lob .star{position:absolute;left:56px;top:-9px;height:14px;image-rendering:pixelated;z-index:3}.lob:hover{animation-play-state:paused}@keyframes crawl{0%{transform:translateX(-90px)}100%{transform:translateX(100vw)}}@keyframes step{to{background-position-x:-288px}}@keyframes waddle{from{transform:rotate(-8deg) translateY(0)}to{transform:rotate(8deg) translateY(-3px)}}#gearbtn{background:none;border:1px solid var(--line);border-radius:var(--radius);color:var(--dim);font:inherit;font-size:13px;padding:1px 7px;margin-left:8px;cursor:pointer;vertical-align:1px}#gearbtn:hover,#gearbtn.on{color:var(--fg);border-color:var(--line-hover)}.settings .row{display:flex;justify-content:space-between;align-items:center;gap:14px;margin:12px 0}.settings .lbl{color:var(--fg);font-size:12px}.settings .hint{display:block;color:var(--dim);font-size:11px;margin-top:2px}.dcards{display:flex;flex-direction:column;gap:10px}.dcard{width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--card-radius);padding:var(--card-pad)}.dcard.focus{animation:dflash 1.6s ease-out 1}@keyframes dflash{0%,35%{border-color:var(--link);box-shadow:0 0 0 3px var(--link)}100%{border-color:var(--line);box-shadow:0 0 0 0 transparent}}.dcard.answered{opacity:0.7}.dcard .dtop{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2)}.dcard .dtop .badge{margin-top:0}.dcard .dtitle{white-space:pre-wrap;overflow-wrap:anywhere}.dcard .dmeta{font-size:11px;margin-left:auto}.dcard .dlink{color:var(--link)}.dfiles{display:flex;flex-wrap:wrap;gap:var(--space-2);margin:6px 0}.dfiles .dimg img{max-height:160px;max-width:320px;border:1px solid var(--line);border-radius:var(--radius);display:block}.dfiles .dfile{width:100%;font-size:11px}.doptions{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0 6px}.btn.dopt{font-size:12px;padding:3px 12px;color:var(--fg)}.btn.dopt.on{background:var(--seg-on);border-color:var(--link)}.danswer{display:block;width:100%;min-height:3.2em;max-height:50vh;margin:6px 0;padding:6px 8px;resize:vertical;overflow-y:auto;field-sizing:content;background:var(--inset);color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);font:inherit}.danswer:focus{outline:none;border-color:var(--line-hover)}.dfoot{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.dfoot .dattach input{display:none}.dfoot .dchip{font-size:11px;border:1px solid var(--line);border-radius:var(--badge-radius);padding:0 0 0 6px}.dfoot .dx{background:none;border:none;color:var(--dim);cursor:pointer;font:inherit}.dfoot .derr{font-size:11px}.btn.dsend{margin-left:auto;color:var(--fg);padding:2px 14px}.btn:disabled{cursor:default;opacity:0.6}.danswered{margin-top:6px}.thumb,.dimg,.mdimg{background:none;border:none;padding:0;cursor:zoom-in;font:inherit}.thumb{display:block;margin:4px 0}.thumb img{max-height:120px;max-width:240px;border:1px solid var(--line);border-radius:var(--radius);display:block}.lightbox{position:fixed;inset:0;z-index:20;background:rgba(0,0,0,0.85);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2)}.lightbox .lbimg{max-width:90vw;max-height:90vh;object-fit:contain;border-radius:var(--radius);box-shadow:0 8px 40px rgba(0,0,0,0.6)}.lightbox .lbclose{position:absolute;top:12px;right:16px;background:none;border:none;color:var(--fg);font-size:28px;line-height:1;cursor:pointer}.lightbox .lboriginal{color:var(--link);font-size:12px}.mdpage{background:var(--inset);border:1px solid var(--line);border-radius:6px;padding:8px 14px;margin:6px 0;line-height:1.5;overflow-wrap:anywhere}.mdpage h1,.mdpage h2,.mdpage h3,.mdpage h4{margin:12px 0 6px;line-height:1.25}.mdpage h1{font-size:18px}.mdpage h2{font-size:15px}.mdpage h3,.mdpage h4{font-size:13px}.mdpage p,.mdpage ul,.mdpage ol,.mdpage blockquote{margin:6px 0}.mdpage ul,.mdpage ol{padding-left:22px}.mdpage a{color:var(--link)}.mdpage code{background:var(--card);border-radius:3px;padding:0 3px}.mdpage pre.mdcode{background:var(--card);border:1px solid var(--line);border-radius:4px;padding:8px;overflow-x:auto;white-space:pre}.mdpage pre.mdcode code{padding:0;background:none}.mdpage blockquote{border-left:3px solid var(--line);padding-left:10px;color:var(--dim)}.mdpage img{max-width:100%;height:auto;border-radius:4px;display:block;margin:6px 0}.mdpage hr{border:0;border-top:1px solid var(--line)}.mdpage .mdtable{overflow-x:auto}.mdpage table{border-collapse:collapse;margin:6px 0}.mdpage th,.mdpage td{border:1px solid var(--line);padding:3px 8px;text-align:left}.tabhead{margin:0 0 8px}.newtrap{display:inline-flex;flex-wrap:wrap;gap:6px;align-items:center;font-size:12px;font-weight:normal;text-transform:none;letter-spacing:normal}.startcmds{margin:6px 0;font-size:11px;cursor:default}.reportview{max-width:1100px;margin:0 auto;padding:20px 16px 48px}.reportview h1{font-size:18px;margin:0 0 4px}.reportview .sub{color:var(--dim);font-size:12px;margin-bottom:10px}.reportview .sec{color:var(--dim);text-transform:uppercase;letter-spacing:0.08em;font-size:11px;margin:14px 0 4px}.reportview .mdpage{padding:12px 20px}</style>\n </head>\n <body>\n <!-- The page is rendered by Preact (src/components/app.ts): header, tabs, controls, the five tab pages, footer, lobs, modal overlay. -->\n <script>"use strict";(()=>{var Ae,b,Bt,Bs,O,Nt,Ot,qt,Je,Ge,le,Ft,Ze,Xe,Qe,Os,xe={},Ce=[],qs=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,Re=Array.isArray;function E(e,t){for(var n in t)e[n]=t[n];return e}function Ye(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function Q(e,t,n){var s,r,i,o={};for(i in t)i=="key"?s=t[i]:i=="ref"?r=t[i]:o[i]=t[i];if(arguments.length>2&&(o.children=arguments.length>3?Ae.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(i in e.defaultProps)o[i]===void 0&&(o[i]=e.defaultProps[i]);return Te(e,o,s,r,null)}function Te(e,t,n,s,r){var i={type:e,props:t,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:r??++Bt,__i:-1,__u:0};return r==null&&b.vnode!=null&&b.vnode(i),i}function Z(e){return e.children}function Se(e,t){this.props=e,this.context=t}function V(e,t){if(t==null)return e.__?V(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?V(e):null}function Fs(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,s=[],r=[],i=E({},t);i.__v=t.__v+1,b.vnode&&b.vnode(i),et(e.__P,i,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,s,n??V(t),!!(32&t.__u),r),i.__v=t.__v,i.__.__k[i.__i]=i,Jt(s,i,r),t.__e=t.__=null,i.__e!=n&&Wt(i)}}function Wt(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),Wt(e)}function Ht(e){(!e.__d&&(e.__d=!0)&&O.push(e)&&!De.__r++||Nt!=b.debounceRendering)&&((Nt=b.debounceRendering)||Ot)(De)}function De(){try{for(var e,t=1;O.length;)O.length>t&&O.sort(qt),e=O.shift(),t=O.length,Fs(e)}finally{O.length=De.__r=0}}function Vt(e,t,n,s,r,i,o,c,u,l,f){var h,d,m,p,g,T,G=s&&s.__k||Ce,$=t.length;for(u=Ws(n,t,G,u,$),h=0;h<$;h++)(m=n.__k[h])!=null&&(d=m.__i!=-1&&G[m.__i]||xe,m.__i=h,T=et(e,m,d,r,i,o,c,u,l,f),p=m.__e,m.ref&&d.ref!=m.ref&&(d.ref&&tt(d.ref,null,m),f.push(m.ref,m.__c||p,m)),g==null&&p!=null&&(g=p),4&m.__u?(u=Kt(m,u,e),d.__e&&(d.__e=null)):typeof m.type=="function"&&T!==void 0?u=T:p&&(u=p.nextSibling),m.__u&=-7);return n.__e=g,u}function Ws(e,t,n,s,r){var i,o,c,u,l,f=n.length,h=f,d=0;for(e.__k=new Array(r),i=0;i<r;i++)(o=t[i])!=null&&typeof o!="boolean"&&typeof o!="function"?(typeof o=="string"||typeof o=="number"||typeof o=="bigint"||o.constructor==String?o=e.__k[i]=Te(null,o,null,null,null):Re(o)?o=e.__k[i]=Te(Z,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[i]=Te(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[i]=o,u=i+d,o.__=e,o.__b=e.__b+1,c=null,(l=o.__i=Vs(o,n,u,h))!=-1&&(h--,(c=n[l])&&(c.__u|=2)),c==null||c.__v==null?(l==-1&&(r>f?d--:r<f&&d++),typeof o.type!="function"&&(o.__u|=4)):l!=u&&(l==u-1?d--:l==u+1?d++:(l>u?d--:d++,o.__u|=4))):e.__k[i]=null;if(h)for(i=0;i<f;i++)(c=n[i])!=null&&(2&c.__u)==0&&(c.__e==s&&(s=V(c)),Qt(c,c));return s}function Kt(e,t,n){var s,r;if(typeof e.type=="function"){for(s=e.__k,r=0;s&&r<s.length;r++)s[r]&&(s[r].__=e,t=Kt(s[r],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=V(e)),t=n.insertBefore(e.__e,t||null));do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function Vs(e,t,n,s){var r,i,o,c=e.key,u=e.type,l=t[n],f=l!=null&&(2&l.__u)==0;if(l===null&&c==null||f&&c==l.key&&u==l.type)return n;if(s>(f?1:0)){for(r=n-1,i=n+1;r>=0||i<t.length;)if((l=t[o=r>=0?r--:i++])!=null&&(2&l.__u)==0&&c==l.key&&u==l.type)return o}return-1}function Ut(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||qs.test(t)?n:n+"px"}function we(e,t,n,s,r){var i,o;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof s=="string"&&(e.style.cssText=s=""),s)for(t in s)n&&t in n||Ut(e.style,t,"");if(n)for(t in n)s&&n[t]==s[t]||Ut(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")i=t!=(t=t.replace(Ft,"$1")),o=t.toLowerCase(),t=o in e||t=="onFocusOut"||t=="onFocusIn"?o.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+i]=n,n?s?n[le]=s[le]:(n[le]=Ze,e.addEventListener(t,i?Qe:Xe,i)):e.removeEventListener(t,i?Qe:Xe,i);else{if(r=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function jt(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t[Ge]==null)t[Ge]=Ze++;else if(t[Ge]<n[le])return;return n(b.event?b.event(t):t)}}}function et(e,t,n,s,r,i,o,c,u,l){var f,h,d,m,p,g,T,G,$,P,ie,W,ae,Lt,ye,ze,I=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(u=!!(32&n.__u),i=[c=t.__e=n.__e]),(f=b.__b)&&f(t);e:if(typeof I=="function"){h=o.length;try{if($=t.props,P=I.prototype&&I.prototype.render,ie=(f=I.contextType)&&s[f.__c],W=f?ie?ie.props.value:f.__:s,n.__c?G=(d=t.__c=n.__c).__=d.__E:(P?t.__c=d=new I($,W):(t.__c=d=new Se($,W),d.constructor=I,d.render=zs),ie&&ie.sub(d),d.state||(d.state={}),d.__n=s,m=d.__d=!0,d.__h=[],d._sb=[]),P&&d.__s==null&&(d.__s=d.state),P&&I.getDerivedStateFromProps!=null&&(d.__s==d.state&&(d.__s=E({},d.__s)),E(d.__s,I.getDerivedStateFromProps($,d.__s))),p=d.props,g=d.state,d.__v=t,m)P&&I.getDerivedStateFromProps==null&&d.componentWillMount!=null&&d.componentWillMount(),P&&d.componentDidMount!=null&&d.__h.push(d.componentDidMount);else{if(P&&I.getDerivedStateFromProps==null&&$!==p&&d.componentWillReceiveProps!=null&&d.componentWillReceiveProps($,W),t.__v==n.__v||!d.__e&&d.shouldComponentUpdate!=null&&d.shouldComponentUpdate($,d.__s,W)===!1){t.__v!=n.__v&&(d.props=$,d.state=d.__s,d.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(X){X&&(X.__=t)}),Ce.push.apply(d.__h,d._sb),d._sb=[],d.__h.length&&o.push(d),c=V(n);break e}d.componentWillUpdate!=null&&d.componentWillUpdate($,d.__s,W),P&&d.componentDidUpdate!=null&&d.__h.push(function(){d.componentDidUpdate(p,g,T)})}if(d.context=W,d.props=$,d.__P=e,d.__e=!1,ae=b.__r,Lt=0,P)d.state=d.__s,d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),Ce.push.apply(d.__h,d._sb),d._sb=[];else do d.__d=!1,ae&&ae(t),f=d.render(d.props,d.state,d.context),d.state=d.__s;while(d.__d&&++Lt<25);d.state=d.__s,d.getChildContext!=null&&(s=E(E({},s),d.getChildContext())),P&&!m&&d.getSnapshotBeforeUpdate!=null&&(T=d.getSnapshotBeforeUpdate(p,g)),ye=f!=null&&f.type===Z&&f.key==null?Xt(f.props.children):f,c=Vt(e,Re(ye)?ye:[ye],t,n,s,r,i,o,c,u,l),d.base=t.__e,t.__u&=-161,d.__h.length&&o.push(d),G&&(d.__E=d.__=null)}catch(X){if(o.length=h,t.__v=null,u||i!=null){if(X.then){for(t.__u|=u?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;i!=null&&(i[i.indexOf(c)]=null),t.__e=c}else if(i!=null)for(ze=i.length;ze--;)Ye(i[ze])}else t.__e=n.__e;t.__k==null&&(t.__k=n.__k||[]),X.then||zt(t),b.__e(X,t,n)}}else i==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):c=t.__e=Ks(n.__e,t,n,s,r,i,o,u,l);return(f=b.diffed)&&f(t),128&t.__u?void 0:c}function zt(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(zt))}function Jt(e,t,n){for(var s=0;s<n.length;s++)tt(n[s],n[++s],n[++s]);b.__c&&b.__c(t,e),e.some(function(r){try{e=r.__h,r.__h=[],e.some(function(i){i.call(r)})}catch(i){b.__e(i,r.__v)}})}function Xt(e){return typeof e!="object"||e==null||e.__b>0?e:Re(e)?e.map(Xt):e.constructor!==void 0?null:E({},e)}function Ks(e,t,n,s,r,i,o,c,u){var l,f,h,d,m,p,g,T=n.props||xe,G=t.props,$=t.type;if($=="svg"?r="http://www.w3.org/2000/svg":$=="math"?r="http://www.w3.org/1998/Math/MathML":r||(r="http://www.w3.org/1999/xhtml"),i!=null){for(l=0;l<i.length;l++)if((m=i[l])&&"setAttribute"in m==!!$&&($?m.localName==$:m.nodeType==3)){e=m,i[l]=null;break}}if(e==null){if($==null)return document.createTextNode(G);e=document.createElementNS(r,$,G.is&&G),c&&(b.__m&&b.__m(t,i),c=!1),i=null}if($==null)T===G||c&&e.data==G||(e.data=G);else{if(i=$=="textarea"&&G.defaultValue!=null?null:i&&Ae.call(e.childNodes),!c&&i!=null)for(T={},l=0;l<e.attributes.length;l++)T[(m=e.attributes[l]).name]=m.value;for(l in T)m=T[l],l=="dangerouslySetInnerHTML"?h=m:l=="children"||l in G||l=="value"&&"defaultValue"in G||l=="checked"&&"defaultChecked"in G||we(e,l,null,m,r);for(l in G)m=G[l],l=="children"?d=m:l=="dangerouslySetInnerHTML"?f=m:l=="value"?p=m:l=="checked"?g=m:c&&typeof m!="function"||T[l]===m||we(e,l,m,T[l],r);if(f)c||h&&(f.__html==h.__html||f.__html==e.innerHTML)||(e.innerHTML=f.__html),t.__k=[];else if(h&&(e.innerHTML=""),Vt(t.type=="template"?e.content:e,Re(d)?d:[d],t,n,s,$=="foreignObject"?"http://www.w3.org/1999/xhtml":r,i,o,i?i[0]:n.__k&&V(n,0),c,u),i!=null)for(l=i.length;l--;)Ye(i[l]);c&&$!="textarea"||(l="value",$=="progress"&&p==null?e.removeAttribute("value"):p!=null&&(p!==e[l]||$=="progress"&&!p||$=="option"&&p!=T[l])&&we(e,l,p,T[l],r),l="checked",g!=null&&g!=e[l]&&we(e,l,g,T[l],r))}return e}function tt(e,t,n){try{if(typeof e=="function"){var s=typeof e.__u=="function";s&&e.__u(),s&&t==null||(e.__u=e(t))}else e.current=t}catch(r){b.__e(r,n)}}function Qt(e,t,n){var s,r;if(b.unmount&&b.unmount(e),(s=e.ref)&&(s.current&&s.current!=e.__e||tt(s,null,t)),(s=e.__c)!=null){if(s.componentWillUnmount)try{s.componentWillUnmount()}catch(i){b.__e(i,t)}s.base=s.__P=s.__n=null}if(s=e.__k)for(r=0;r<s.length;r++)s[r]&&Qt(s[r],t,n||typeof e.type!="function");n||Ye(e.__e),e.__c=e.__=e.__e=void 0}function zs(e,t,n){return this.constructor(e,n)}function nt(e,t,n){var s,r,i,o;t==document&&(t=document.documentElement),b.__&&b.__(e,t),r=(s=typeof n=="function")?null:n&&n.__k||t.__k,i=[],o=[],et(t,e=(!s&&n||t).__k=Q(Z,null,[e]),r||xe,xe,t.namespaceURI,!s&&n?[n]:r?null:t.firstChild?Ae.call(t.childNodes):null,i,!s&&n?n:r?r.__e:t.firstChild,s,o),Jt(i,e,o),e.props.children=null}Ae=Ce.slice,b={__e:function(e,t,n,s){for(var r,i,o;t=t.__;)if((r=t.__c)&&!r.__)try{if((i=r.constructor)&&i.getDerivedStateFromError!=null&&(r.setState(i.getDerivedStateFromError(e)),o=r.__d),r.componentDidCatch!=null&&(r.componentDidCatch(e,s||{}),o=r.__d),o)return r.__E=r}catch(c){e=c}throw e}},Bt=0,Bs=function(e){return e!=null&&e.constructor===void 0},Se.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=E({},this.state),typeof e=="function"&&(e=e(E({},n),this.props)),e&&E(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),Ht(this))},Se.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Ht(this))},Se.prototype.render=Z,O=[],Ot=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,qt=function(e,t){return e.__v.__b-t.__v.__b},De.__r=0,Je=Math.random().toString(8),Ge="__d"+Je,le="__a"+Je,Ft=/(PointerCapture)$|Capture$/i,Ze=0,Xe=jt(!1),Qe=jt(!0),Os=0;function Js(e){return{id:e,lane:"work",bucket:"done",repo:"",brief:"",attachments:[],messageAttachments:[],verb:"unknown",log:[],inbox:[],sort:0}}var Zt=new WeakMap;function st(e,t){let n=e.dispatches||[],s=Zt.get(n);return s||Zt.set(n,s=new Map(n.map(r=>[r.id,r]))),{...t,catches:(t.catches||[]).map(r=>s.get(r)??Js(r))}}function L(e){if(!e)return"dim";let t=e.state||"open";return t==="open"&&e.merge?"pr-"+e.merge:t==="open"&&e.tone&&e.tone!=="ok"?e.tone:"pr-"+t}var ce=["deck","dispatches","traps","prs","reports","notices"];function nn(e){let t=String(e||"").replace(/^#/,"");return ce.includes(t)?t:"deck"}var rt=e=>`#decision/${encodeURIComponent(e)}`;function ot(e){let t=/^#?decision\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var it=(e,t,n)=>`/attachment/dispatch/${encodeURIComponent(e)}/${encodeURIComponent(t)}/${encodeURIComponent(n)}`,sn=(e,t)=>`/attachment/trap/${encodeURIComponent(e)}/${encodeURIComponent(t)}`,rn=e=>/\\.(png|jpe?g|gif|webp)$/i.test(e),at=(e,t)=>`/decision/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function lt(e,t){let n=new Map(t.map(r=>[r.key,r])),s=[];for(let r of e)if(r.kind==="decision"){let i=n.get(r.key);if(!i)continue;s.push({kind:"decision",key:i.key,title:i.title,detail:i.detail,options:i.options,attachments:i.attachments,...i.dispatch?{dispatch:i.dispatch,lane:i.lane??"work"}:{},...i.repo?{repo:i.repo}:{},at:i.askedAt})}else r.kind==="question"&&s.push({kind:"question",key:r.key,verb:r.verb,note:r.note||r.verb,dispatch:r.id,lane:r.lane,...r.repo?{repo:r.repo}:{},at:r.at??""});return s.sort((r,i)=>i.at.localeCompare(r.at)||r.key.localeCompare(i.key))}function on(e){let t=(e.text??"").split(`\n`).map(n=>n.trim()).find(Boolean)??"";return[e.option,t&&(t.length>80?t.slice(0,79)+"\\u2026":t),e.files?`${e.files} file${e.files===1?"":"s"}`:""].filter(Boolean).join(" \\xB7 ")}function an(e,t){return(e.reports||[]).find(n=>n.key===`report:${t.lane}:${t.id}`)}function de(e){return e.author==="helm"?"":e.trap?e.trap:e.author==="headless"?e.dispatch?e.dispatch.slice(0,8):"":e.author}var Yt=(e,t)=>+!!e.acked-+!!t.acked||t.filedAt.localeCompare(e.filedAt),D=e=>`/report/${encodeURIComponent(e)}`;function ln(e){let t=/^#?report\\/(.+)$/.exec(String(e||""));if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}function cn(e){let t=/^\\/report\\/([^/?#]+)\\/?$/.exec(e);if(!t)return null;try{return decodeURIComponent(t[1])}catch{return null}}var dn=e=>`/report/${encodeURIComponent(e)}/md`,ct=(e,t)=>`/report/${encodeURIComponent(e)}/files/${encodeURIComponent(t)}`;function en(e,t,n){return!!e&&n-Date.parse(e)>t}function tn(e,t){if(t.lane&&e.lane!==t.lane||t.repo&&e.repo!==t.repo||t.verb&&e.verb!==t.verb)return!1;if(t.q){let n=t.q.toLowerCase();if(!(e.id+" "+(e.note||"")+" "+(e.brief||"")+" "+(e.repo||"")+" "+(e.for||"")).toLowerCase().includes(n))return!1}return!0}function Y(e,t){if(!t)return null;if(t.type==="helm")return e.helms.find(s=>s.grounds===t.key)||null;if(t.type==="dispatch")return e.dispatches.find(s=>s.lane+":"+s.id===t.key)||null;if(t.type==="pr")return(e.prs||[]).find(s=>s.key===t.key)||null;if(t.type==="settings")return{attentionKinds:e.attentionKinds||[],attentionError:e.attentionError};let n=e.traps.find(s=>s.trapId===t.key);return n?st(e,n):null}function pn(e,t,n){let s=t.st,r=String(s.q||"").toLowerCase(),i=(...p)=>!r||p.join(" ").toLowerCase().includes(r),o=p=>({x:p,stale:en(p.heartbeatAt,18e5,n)}),c=Y(e,t.modal),u=(p,g)=>!!p&&n-Date.parse(p)<=g,l=p=>Math.max(0,...(p.notices||[]).filter(g=>g.kind==="trap-stowed"||g.kind==="trap-ghosted").map(g=>Date.parse(g.at)||0)),f=p=>p.live||!!p.starting||!!p.requested,h=[...e.traps||[]].sort((p,g)=>Number(f(g))-Number(f(p))||Number(g.live)-Number(p.live)||(p.live?(Date.parse(p.signedOnAt||"")||0)-(Date.parse(g.signedOnAt||"")||0):l(g)-l(p))||(p.name??p.trapId).localeCompare(g.name??g.trapId)),d=h.filter(p=>(f(p)||(p.notices||[]).some(g=>(g.kind==="trap-stowed"||g.kind==="trap-ghosted")&&u(g.at,36e5)))&&i(p.name,p.trapId,p.repo,p.worktree)),m=({ageSecs:p,...g})=>g;return{chips:{daemon:e.daemon,daemonStale:!!e.daemon&&en(e.daemon.heartbeat,9e4,n),helms:e.helms.map(o)},deck:{view:s.view,decisions:lt(e.attention||[],e.decisions||[]).filter(p=>p.kind==="decision"?i(p.kind,p.repo,p.title,p.dispatch,p.detail):i(p.kind,p.repo,p.note,p.dispatch)),prAttention:(e.attention||[]).filter(p=>p.kind&&p.kind.startsWith("pr:")).map(m),landed:(e.landed||[]).filter(p=>u(p.at,864e5)&&i(p.repo,p.note,p.id)).sort((p,g)=>Date.parse(g.at)-Date.parse(p.at)).slice(0,8),reports:(e.reports||[]).filter(p=>i(p.title,p.author,p.dispatch,p.repo)).sort(Yt),inflight:e.dispatches.filter(p=>p.bucket!=="done"&&tn(p,{...s,lane:"",repo:"",verb:""})),traps:d.map(p=>o(st(e,p))),stacks:(e.stacks||[]).filter(p=>p.open&&i(p.repo,p.numbers.join(" "))),prs:(e.prs||[]).filter(p=>p.state==="OPEN"),error:e.attentionError},dispatches:{view:s.view,chain:s.chain,list:e.dispatches.filter(p=>tn(p,s))},traps:{view:s.view,list:h.filter(p=>(!s.repo||p.repo===s.repo)&&i(p.name,p.trapId,p.repo,p.worktree,p.harness)).map(p=>o(st(e,p)))},prs:{view:s.view,stacks:(e.stacks||[]).filter(p=>!s.repo||p.repo===s.repo),prs:(e.prs||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.number,p.title,p.url,p.state,p.baseRefName,p.headRefName)),watches:(e.watches||[]).filter(p=>!String(p.key).startsWith("pr:"))},reports:{view:s.view,list:(e.reports||[]).filter(p=>(!s.repo||p.repo===s.repo)&&i(p.title,p.author,p.dispatch,p.repo)).sort(Yt)},notices:{list:(e.notices||[]).filter(p=>(!s.repo||!p.repo||p.repo===s.repo)&&(!s.noticeKind||p.kind===s.noticeKind)&&i(p.kind,p.text,p.repo))},foot:{version:e.version,repoUrl:e.repoUrl},modal:{modal:t.modal,item:c&&o(c),prefs:t.modal&&t.modal.type==="settings"?{view:s.view,lobs:s.lobs}:void 0}}}function un(e,t){let n=(e.prs||[]).find(c=>c.key===t);if(!n)return null;let s=(e.stacks||[]).find(c=>c.id===n.stackId),r=new Map((e.dispatches||[]).map(c=>[c.id,c])),i=(n.dispatchIds||[]).map(c=>{let u=r.get(c);return u?{id:c,verb:u.verb,modalKey:u.lane+":"+u.id}:{id:c,culled:!0}}),o=n.watch;return{pr:n,stack:s?{numbers:s.numbers,titles:s.numbers.map(c=>(e.prs||[]).find(u=>u.stackId===s.id&&u.number===c)?.title||""),position:n.position+1,size:s.numbers.length,floor:s.floor,nextNumber:s.nextNumber,nextMergeable:!!n.nextMergeable,blockedBy:n.blockedBy}:null,chain:i,watch:o?{key:o.key,owner:o.owner||"",lastCheckedAt:o.lastCheckedAt||null,lastError:o.lastError||null,cursor:o.cursor}:null}}function fn(e){return e?{text:e.lastError?"failing":"watching",at:e.lastCheckedAt||null}:{text:"no watch",at:null}}function mn(){let e={view:"table",lane:"",repo:"",verb:"",q:"",lobs:!0,chain:!1,noticeKind:""};try{Object.assign(e,JSON.parse(localStorage.getItem("spyglass")||"{}"))}catch{}return e.view!=="table"&&e.view!=="cards"&&(e.view="table"),e.lobs=e.lobs!==!1,e}function hn(e){try{localStorage.setItem("spyglass",JSON.stringify(e))}catch{}}function gn(){try{return JSON.parse(localStorage.getItem("spyglass-lob-hidden")||"{}")||{}}catch{return{}}}function vn(e){try{localStorage.setItem("spyglass-lob-hidden",JSON.stringify(e))}catch{}}var pe,_n=new Set,_=()=>pe;function dt(e){pe=e}function S(e){pe={...pe,...e};for(let t of _n)t(pe)}var pt=e=>{_n.add(e)};function ut(e){let{modal:t,drafts:n}=_(),s=new Set(lt(e.attention||[],e.decisions||[]).map(i=>i.key)),r=Object.fromEntries(Object.entries(n).filter(([i])=>s.has(i)));S({snapshot:e,stale:!1,modal:t&&Y(e,t)?t:null,...Object.keys(r).length!==Object.keys(n).length?{drafts:r}:{}})}var $n=()=>({text:"",files:[]});function A(e,t){let n=_().drafts;S({drafts:{...n,[e]:{...n[e]??$n(),...t}}})}function bn(e,t){let n=_().drafts[e]?.option;A(e,{option:n===t?void 0:t,error:void 0})}var Xs=e=>new Promise((t,n)=>{let s=new FileReader;s.onload=()=>t(String(s.result).replace(/^data:[^,]*,/,"")),s.onerror=()=>n(s.error),s.readAsDataURL(e)});async function ft(e,t){let n=_().snapshot?.answerLimits,s=[...t??[]],r=_().drafts[e]?.files??[],i=[],o=[];for(let c of s){let u=(/\\.[^.]+$/.exec(c.name)?.[0]??"").toLowerCase();if(n&&!n.extensions.includes(u))i.push(`${c.name}: type not accepted`);else if(n&&c.size>n.maxBytes)i.push(`${c.name}: larger than ${n.maxBytes} bytes`);else if(n&&r.length+o.length>=n.maxFiles)i.push(`${c.name}: at most ${n.maxFiles} files`);else try{o.push({name:c.name,bytes:c.size,data:await Xs(c)})}catch{i.push(`${c.name}: could not be read`)}}A(e,{files:[..._().drafts[e]?.files??[],...o],error:i.length?i.join("; "):void 0})}var Qs={"image/png":".png","image/jpeg":".jpg","image/gif":".gif","image/webp":".webp"};async function kn(e,t){let n=[...t.clipboardData?.items??[]],s=new Date().toISOString().replace(/[:.]/g,"-").replace(/-\\d{3}Z$/,"Z"),r=n.filter(i=>i.kind==="file"&&i.type.startsWith("image/")).map(i=>i.getAsFile()).filter(i=>i!==null).map((i,o,c)=>{let u=`pasted-${s}${c.length>1?`-${o+1}`:""}${Qs[i.type]??".png"}`;return new File([i],u,{type:i.type})});r.length&&await ft(e,r)}function yn(e,t){let n=(_().drafts[e]?.files??[]).filter((s,r)=>r!==t);A(e,{files:n})}async function wn(e){let t=_().snapshot?.focusToken,n=_().drafts[e]??$n();if(!(n.sending||n.sent)){if(!n.option&&!n.text.trim()&&n.files.length===0){A(e,{error:"Choose an option, write an answer, or attach a file."});return}if(!t){A(e,{error:"This page has no answer token; reload it."});return}A(e,{sending:!0,error:void 0});try{let s=await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":t},body:JSON.stringify({kind:"decision-answer",payload:{key:e,...n.option?{option:n.option}:{},...n.text.trim()?{text:n.text}:{},files:n.files.map(i=>({name:i.name,data:i.data}))}})}),r=await s.json();if(!s.ok||!r.ok){A(e,{sending:!1,error:r.reason??`The answer was refused (${s.status}).`});return}A(e,{sending:!1,sent:on({option:n.option,text:n.text,files:n.files.length})})}catch{A(e,{sending:!1,error:"The answer could not be sent."})}}}var Gn=()=>S({stale:!0}),Tn=(e,t)=>S({lightbox:{src:e,name:t}}),ee=()=>S({lightbox:null});function M(e,t){let{snapshot:n}=_(),s={type:e,key:t};S({modal:!n||Y(n,s)?s:null})}function te(){S({modal:null})}async function Sn(e){let t=_().snapshot?.focusToken;if(t)try{let s=await(await fetch(`/api/focus/${encodeURIComponent(e)}`,{method:"POST",headers:{"x-lobstah-focus-token":t}})).json();S({focusResults:{..._().focusResults,[e]:s.message??s.reason??"Window focus failed."}})}catch{S({focusResults:{..._().focusResults,[e]:"Window focus failed."}})}}async function xn(e,t){let n=_().snapshot?.focusToken;if(!n)return"The glass has no page token yet.";try{let r=await(await fetch("/requests",{method:"POST",headers:{"content-type":"application/json","x-lobstah-token":n},body:JSON.stringify({kind:"trap-request",payload:{repo:e,harness:t}})})).json();if(!r.ok)return r.reason??"The request was refused."}catch{return"The request could not be sent."}try{ut(await(await fetch("/data")).json())}catch{}}function N(e){let t={..._().prefs,...e};hn(t),S({prefs:t})}function Cn(e){e!=="table"&&e!=="cards"||N({view:e})}var Dn=e=>N({lobs:e==="on"});function An(e,t){let n={..._().lobHidden,[e]:t};vn(n),setTimeout(()=>S({lobHidden:n}),0)}async function Rn(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}var In=function(e,t,n,s){var r;t[0]=0;for(var i=1;i<t.length;i++){var o=t[i++],c=t[i]?(t[0]|=o?1:2,n[t[i++]]):t[++i];o===3?s[0]=c:o===4?s[1]=Object.assign(s[1]||{},c):o===5?(s[1]=s[1]||{})[t[++i]]=c:o===6?s[1][t[++i]]+=c+"":o?(r=e.apply(c,In(e,c,n,["",null])),s.push(r),c[0]?t[0]|=2:(t[i-2]=0,t[i]=r)):s.push(c)}return s},Pn=new Map;function Mn(e){var t=Pn.get(this);return t||(t=new Map,Pn.set(this,t)),(t=In(this,t.get(e)||(t.set(e,t=(function(n){for(var s,r,i=1,o="",c="",u=[0],l=function(d){i===1&&(d||(o=o.replace(/^\\s*\\n\\s*|\\s*\\n\\s*$/g,"")))?u.push(0,d,o):i===3&&(d||o)?(u.push(3,d,o),i=2):i===2&&o==="..."&&d?u.push(4,d,0):i===2&&o&&!d?u.push(5,0,!0,o):i>=5&&((o||!d&&i===5)&&(u.push(i,0,o,r),i=6),d&&(u.push(i,d,0,r),i=6)),o=""},f=0;f<n.length;f++){f&&(i===1&&l(),l(f));for(var h=0;h<n[f].length;h++)s=n[f][h],i===1?s==="<"?(l(),u=[u],i=3):o+=s:i===4?o==="--"&&s===">"?(i=1,o=""):o=s+o[0]:c?s===c?c="":o+=s:s===\'"\'||s==="\'"?c=s:s===">"?(l(),i=1):i&&(s==="="?(i=5,r=o,o=""):s==="/"&&(i<5||n[f][h+1]===">")?(l(),i===3&&(u=u[0]),i=u,(u=u[0]).push(2,0,i),i=0):s===" "||s===" "||s===`\n`||s==="\\r"?(l(),i=2):o+=s),i===3&&o==="!--"&&(i=4,u=u[0])}return l(),u})(e)),t),arguments,[])).length>1?t:t[0]}var En=e=>Array.isArray(e)?e.map(En):e===""?null:e,Zs=(e,t,...n)=>Q(e,t,...n.map(En)),a=Mn.bind(Zs);var Ie,k,mt,Ln,ht=0,Fn=[],y=b,Nn=y.__b,Hn=y.__r,Un=y.diffed,jn=y.__c,Bn=y.unmount,On=y.__;function Wn(e,t){y.__h&&y.__h(k,e,ht||t),ht=0;var n=k.__H||(k.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function ne(e){return ht=1,Ys(Kn,e)}function Ys(e,t,n){var s=Wn(Ie++,2);if(s.t=e,!s.__c&&(s.__=[n?n(t):Kn(void 0,t),function(c){var u=s.__N?s.__N[0]:s.__[0],l=s.t(u,c);u!==l&&(s.__N=[l,s.__[1]],s.__c.setState({}))}],s.__c=k,!k.__f)){var r=function(c,u,l){if(!s.__c.__H)return!0;var f=!1,h=s.__c.props!==c;if(s.__c.__H.__.some(function(m){if(m.__N){f=!0;var p=m.__[0];m.__=m.__N,m.__N=void 0,p!==m.__[0]&&(h=!0)}}),i){var d=i.call(this,c,u,l);return f?d||h:d}return!f||h};k.__f=!0;var i=k.shouldComponentUpdate,o=k.componentWillUpdate;k.componentWillUpdate=function(c,u,l){if(this.__e){var f=i;i=void 0,r(c,u,l),i=f}o&&o.call(this,c,u,l)},k.shouldComponentUpdate=r}return s.__N||s.__}function Vn(e,t){var n=Wn(Ie++,7);return nr(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function er(){for(var e;e=Fn.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Pe),t.__h.some(gt),t.__h=[]}catch(n){t.__h=[],y.__e(n,e.__v)}}}y.__b=function(e){k=null,Nn&&Nn(e)},y.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),On&&On(e,t)},y.__r=function(e){Hn&&Hn(e),Ie=0;var t=(k=e.__c).__H;t&&(mt===k?(t.__h=[],k.__h=[],t.__.some(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.some(Pe),t.__h.some(gt),t.__h=[],Ie=0)),mt=k},y.diffed=function(e){Un&&Un(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(Fn.push(t)!==1&&Ln===y.requestAnimationFrame||((Ln=y.requestAnimationFrame)||tr)(er)),t.__H.__.some(function(n){n.u&&(n.__H=n.u,n.u=void 0)})),mt=k=null},y.__c=function(e,t){t.some(function(n){try{n.__h.some(Pe),n.__h=n.__h.filter(function(s){return!s.__||gt(s)})}catch(s){t.some(function(r){r.__h&&(r.__h=[])}),t=[],y.__e(s,n.__v)}}),jn&&jn(e,t)},y.unmount=function(e){Bn&&Bn(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(s){try{Pe(s)}catch(r){t=r}}),n.__H=void 0,t&&y.__e(t,n.__v))};var qn=typeof requestAnimationFrame=="function";function tr(e){var t,n=function(){clearTimeout(s),qn&&cancelAnimationFrame(t),setTimeout(e)},s=setTimeout(n,35);qn&&(t=requestAnimationFrame(n))}function Pe(e){var t=k,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),k=t}function gt(e){var t=k;e.__c=e.__(),k=t}function nr(e,t){return!e||e.length!==t.length||t.some(function(n,s){return n!==e[s]})}function Kn(e,t){return typeof t=="function"?t(e):t}var K=e=>{if(!e)return"";let t=Math.max(0,(Date.now()-Date.parse(e))/1e3);return t<90?Math.round(t)+"s":t<5400?Math.round(t/60)+"m":t<172800?(t/3600).toFixed(1)+"h":Math.round(t/86400)+"d"},v=e=>a`<span data-age=${e??""}>${K(e)}</span>`,Ee=e=>e.activity&&a`<div class=${"activity"+(e.activity.stale?" stale":"")} title=${e.activity.stale?"no activity past the wedge threshold":e.activity.kind}>${e.activity.stale?"stale \\xB7 ":""}${e.activity.summary} \xB7 ${v(e.activity.at)} ago</div>`,sr=e=>e&&/^https?:\\/\\//i.test(e)?e:void 0,Le=e=>{let t=e.waiting;if(!t)return;let n=sr(t.link),s=n;try{n&&(s=new URL(n).host+new URL(n).pathname.replace(/\\/$/,""))}catch{s=n}return a`<div class="waiting">${e.verb}: waiting on ${t.on} \xB7 ${v(t.since)}${n&&[" \\xB7 ",a`<a href=${n} target="_blank" rel="noopener noreferrer" onClick=${x}>${s}</a>`]}</div>`},rr=12,zn=e=>e!==void 0&&e.length>rr,H=e=>zn(e)?e:void 0,U=e=>zn(e)?" long":"",w=(e,t)=>()=>M(e,t),x=e=>e.stopPropagation(),or=e=>e.sessionId?(e.harness==="codex"?"codex resume ":"claude --resume ")+e.sessionId:void 0;function z(e){if(e.requested)return a`<span class="dim">Requested \u2014 no window yet</span>`;if(e.starting)return a`<span class="dim">${e.starting.failedAt?"Did not start":"Starting \\u2014 no window yet"}</span>`;if(!e.live){let s=or(e);return s?a`<span class="dim">Resume: <code onClick=${x}>${s}</code></span>`:a`<span class="dim">Resume command unavailable</span>`}let t=_();return!t.snapshot?.focusSupported&&!e.link?a`<span class="dim">Window focus is not supported here</span>`:a`<span class="winaction"><button class="btn open" title="open this trap\'s window" onClick=${s=>{x(s),Sn(e.trapId)}}>\u2197 open</button>${e.link&&[" ",a`<a href=${e.link} onClick=${x}>Session link</a>`]}${t.focusResults[e.trapId]&&[" ",a`<span class="dim">${t.focusResults[e.trapId]}</span>`]}</span>`}function R(e,t,n){return t.length?a`<div class="wrap"><table><tbody><tr>${e.map(s=>a`<th>${s}</th>`)}</tr>${t}</tbody></table></div>`:a`<div class="empty">${n}</div>`}function ir({text:e}){let[t,n]=ne(null),s=r=>async()=>{await Rn(e)&&(n(r),setTimeout(()=>n(null),1e3))};return a`<div class="cmd"><code class=${t==="code"?"copied":void 0} title="click to copy" onClick=${s("code")}>${e}</code><button title="copy" onClick=${s("button")}>${t==="button"?"\\u2713":"\\u29C9"}</button></div>`}var C=(e,t)=>a`<${ir} key=${t} text=${e} />`,ue=(e,t,n="thumb")=>a`<button\n type="button"\n class=${n}\n title=${"view "+t}\n onClick=${s=>{x(s),Tn(e,t)}}\n >\n <img src=${e} alt=${t} loading="lazy" />\n </button>`,se=(e,t)=>e.map(n=>[a`<div class="sub">${n.name} \xB7 ${n.type} \xB7 ${n.bytes} bytes</div>`,t&&rn(n.name)&&ue(t(n.name),n.name),C(n.path)]),Jn=/\\bwt:([a-z0-9][a-z0-9-]*)/g,vt=e=>_().snapshot?.trapNames?.[e],j=e=>e.replace(Jn,(t,n,s)=>{let r=vt(n);return r&&!Xn(e,s,t.length,r)?r:t}),Xn=(e,t,n,s)=>e.slice(Math.max(0,t-s.length-2),t)===`${s} (`&&e[t+n]===")";function Ne(e){let t="wt:"+e,n=vt(e)??t;return _().snapshot?.traps.some(r=>r.trapId===e)?a`<a class="trapname" href="#traps" title=${t} onClick=${r=>{r.preventDefault(),x(r),M("trap",e)}}>${n}</a>`:a`<span class="trapname" title=${t}>${n}</span>`}var Me=e=>{let t=/^wt:([a-z0-9][a-z0-9-]*)$/.exec(e);return t?Ne(t[1]):e};function B(e,t=1/0){let n=[],s=0;for(let c of e.matchAll(Jn)){c.index>s&&n.push({text:e.slice(s,c.index)});let u=vt(c[1]);n.push(u&&Xn(e,c.index,c[0].length,u)?{text:c[0]}:{text:u??c[0],trapId:c[1]}),s=c.index+c[0].length}s<e.length&&n.push({text:e.slice(s)});let r=n.reduce((c,u)=>c+u.text.length,0),i=r>t?t-1:1/0,o=[];for(let c of n){if(i<=0)break;c.text.length<=i?o.push(c.trapId?Ne(c.trapId):c.text):o.push(c.text.slice(0,i)),i-=c.text.length}return r>t&&o.push("\\u2026"),o}var _t=e=>e.log.flatMap((t,n)=>[n?`\n`:"",t.at+" "+t.verb,...t.note?[" ",...B(t.note)]:[]]);function Qn(e){let t=[e.elapsed&&`elapsed: ${e.elapsed}`,e.attempt&&`attempt: ${e.attempt}`,e.branch&&`branch: ${e.branch}`,e.lastCommit&&`last commit: ${e.lastCommit}`,e.aheadTrunk&&`commits: ${e.aheadTrunk}`,e.draftPr&&`draft PR: ${e.draftPr}`,e.updated&&`updated: ${e.updated}`].filter(Boolean).join(`\n`);return a`${e.waiting&&[a`<div class="sec">waiting</div>`,Le(e)]}${e.activity&&[a`<div class="sec">activity</div>`,Ee(e)]}${t&&[a`<div class="sec">progress</div>`,a`<pre>${t}</pre>`]}<div class="sec">brief</div><pre>${e.brief}</pre>${e.attachments.length>0&&[a`<div class="sec">attachments (${e.attachments.length})</div>`,se(e.attachments,n=>it(e.lane,e.id,n))]}${e.messageAttachments.length>0&&[a`<div class="sec">message attachments (${e.messageAttachments.length})</div>`,se(e.messageAttachments,n=>it(e.lane,e.id,n))]}${e.followUp&&[a`<div class="sec">forks</div>`,a`<pre>${e.followUp}</pre>`]}<div class="sec">log</div><div class="loglines">${e.log.length?_t(e):"no entries yet"}</div>${e.inbox.length>0&&[a`<div class="sec">inbox</div>`,a`<div class="loglines">${e.inbox.join(`\n---\n`)}</div>`]}${e.awaitingReply&&[a`<div class="sec">awaiting reply</div>`,a`<div>${e.awaitingReply.line} \xB7 from ${e.awaitingReply.from} \xB7 ${v(e.awaitingReply.sentAt)} ago</div>`]}${e.evidence&&[a`<div class="sec">evidence</div>`,a`<div class="loglines">${JSON.stringify(e.evidence)}</div>`]}`}function fe(e){return e.for?[Me(e.for),e.evidence&&e.evidence.deliveredTo?[" ",a`<span class="ok">\u2713delivered</span>`]:[" ",a`<span class="warn">waiting</span>`]]:e.claimedBy?Me(e.claimedBy):""}function re(e){let t=e.evidence&&(e.evidence.prUrl||e.evidence.pr&&e.evidence.pr.url);if(!t)return"";let n=e.prBadge,s=e.evidence&&e.evidence.prUrls&&e.evidence.prUrls.length>1?e.evidence.prUrls:void 0;return[s?s.map((r,i)=>[i?" ":"",a`<a href=${r} target="_blank" rel="noopener" onClick=${x}>#${r.split("/").pop()}</a>`]):a`<a href=${t} target="_blank" rel="noopener" onClick=${x}>PR</a>`,n&&[" ",a`<span class=${"badge "+L(n)} title=${"observed "+n.observedAt}>${n.text}</span>`],e.prGate&&[" ",a`<span class="badge dim" title="merge gate (pick)">${e.prGate}</span>`]]}var ar={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch"},$t={"pr:review":"bad","pr:checks":"bad","pr:conflict":"pr-conflicts","pr:ready":"ok","pr:draft":"dim",watch:"warn"},Zn=e=>ar[e]||"";var He=e=>a`<a href=${e.url} target="_blank" rel="noopener" onClick=${x}>#${e.number}</a>`,Ue=e=>a`<span class="prname" title=${e.title||void 0}><b>#${e.number}</b>${e.title&&[" ",e.title]}</span>`;function oe(e,t,n){return e.flatMap((s,r)=>{let i=t.get(s)||void 0,o=s===n?a`<b title=${i}>#${s}</b>`:a`<span title=${i}>#${s}</span>`;return r?[" \\u2192 ",o]:[o]})}function me(e){let t=e.checks;return t.passed+"/"+t.total+" passed"+(t.failed?" \\xB7 "+t.failed+" failed":"")+(t.pending?" \\xB7 "+t.pending+" pending":"")}function je(e){let t=e.review||{};return(e.reviewDecision||"")+(t.unresolvedThreads?" \\xB7 "+t.unresolvedThreads+" unresolved":"")+(t.changesRequested?" \\xB7 changes requested":"")}function he(e){return[e.mergeStateStatus,e.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:e.blockedBy?" \\xB7 blocked by #"+e.blockedBy:""]}function bt(e){let t=fn(e);return e?[t.text," \\xB7 ",t.at?[v(t.at)," ago"]:"never checked"]:a`<span class="dim">${t.text}</span>`}function ge(e){if(e.requested)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">requested</span>`],hb:a`<span class="dim">\u2014</span>`};if(e.starting)return{stale:!1,listen:e.starting.failedAt?[a`<span class="dot bad"></span>`,a`<span class="bad">start failed</span>`]:[a`<span class="dot warn"></span>`,"starting"],hb:a`<span class="dim">\u2014</span>`};if(!e.live)return{stale:!1,listen:[a`<span class="dot"></span>`,a`<span class="dim">signed off</span>`],hb:a`<span class="dim">\u2014</span>`};let t=Date.now()-Date.parse(e.heartbeatAt)>18e5,n=e.listening??(!!e.firstParkedAt&&!t);return{stale:t,listen:n?[a`<span class="dot ok"></span>`,"listening"]:[a`<span class="dot warn"></span>`,"not listening"],hb:a`<span class=${t?"bad":"ok"}>${t&&"stale "}${v(e.heartbeatAt)} ago</span>`}}function kt(e){if(e.requested)return{kind:"requested",helmOn:!!_().snapshot?.helmOn};if(e.starting)return e.starting.failedAt?{kind:"start failed",reason:e.starting.reason??"no session signed on in time"}:{kind:"starting",deadline:e.starting.deadline};if(!e.live)return{kind:"signed off"};let t=e.claimed?e.catches.find(n=>n.id===e.claimed&&n.bucket==="active"):void 0;return t?t.waiting||t.verb==="paused"?{kind:"parked",current:t}:{kind:"working",current:t}:{kind:"idle",listening:e.listening??(!!e.firstParkedAt&&Date.now()-Date.parse(e.heartbeatAt??"")<=18e5)}}function lr(e){let t=kt(e);return t.kind==="working"||t.kind==="idle"&&t.listening?"ok":t.kind==="start failed"?"bad":t.kind==="parked"||t.kind==="starting"?"warn":"dim"}var Yn=e=>e.waiting?`waiting on ${e.waiting.on}`:e.note||"waiting",es=e=>e.brief.split(/\\r?\\n/,1)[0]?.trim().slice(0,40)||"(no title)";function ve(e){let t=kt(e),n=a`<span class=${"dot trapdot "+lr(e)}></span>`;if(t.kind==="requested")return[n,a`<span class="dim">${ts(e,t.helmOn)}</span>`];if(t.kind==="starting")return[n,"starting \\xB7 waiting for its session to sign on"];if(t.kind==="start failed")return[n,a`<span class="bad">start failed</span>`," \\xB7 ",t.reason];if(t.kind==="signed off")return[n,a`<span class="dim">signed off</span>`];if(t.kind==="idle")return[n,"idle \\xB7 ",t.listening?"listening":"not listening"];let s=t.current;return t.kind==="parked"?[n,"parked \\xB7 ",Yn(s)]:[n,"working \\xB7 ",a`<a href="#dispatches" onClick=${i=>{i.preventDefault(),x(i),M("dispatch",`${s.lane}:${s.id}`)}}>${s.id.slice(0,8)}</a>`," \\xB7 ",es(s),s.activity&&[" \\xB7 ",s.activity.summary," ",v(s.activity.at)," ago"]]}var ts=(e,t)=>`requested \\xB7 ${e.repo??""} \\xB7 ${e.harness??""} \\xB7 ${t?"waiting for the helm":"waiting for a helm"}`;function J(e){let t=e.starting?.commands;return t?.length?a`<div class="startcmds" onClick=${x}><div class="dim">start it: run one in a terminal</div>${t.map(n=>C(n.command,n.harness))}</div>`:null}function Be(){let t=_().snapshot?.repoKeys??[],[n,s]=ne(!1),[r,i]=ne(""),[o,c]=ne("claude"),[u,l]=ne(null),f=r||t[0]||"",h=async d=>{d.preventDefault();let m=await xn(f,o);l(m??null),m||s(!1)};return n?a`<form class="newtrap" onClick=${x} onSubmit=${h}>\n <label>repo <select name="repo" value=${f} onChange=${d=>i(d.target.value)}>${t.map(d=>a`<option value=${d}>${d}</option>`)}</select></label>\n <label>harness <select name="harness" value=${o} onChange=${d=>c(d.target.value)}><option value="claude">claude</option><option value="codex">codex</option></select></label>\n <button class="btn" type="submit" disabled=${!f}>Request</button>\n <button class="btn" type="button" onClick=${()=>s(!1)}>Cancel</button>\n ${u&&a`<span class="bad">${u}</span>`}\n </form>`:a`<span class="newtrap"><button class="btn" onClick=${d=>(x(d),s(!0),l(null))}>+ New trap</button></span>`}function Oe(e){let t=kt(e);if(t.kind==="requested")return ts(e,t.helmOn);if(t.kind==="starting")return`starting \\xB7 sign-on due by ${t.deadline}`;if(t.kind==="start failed")return`start failed \\xB7 ${t.reason}`;if(t.kind==="signed off")return"signed off";if(t.kind==="idle")return`idle \\xB7 ${t.listening?"listening":"not listening"}`;if(t.kind==="parked")return`parked \\xB7 ${Yn(t.current)}`;let n=t.current;return[`working \\xB7 ${n.id.slice(0,8)} \\xB7 ${es(n)}`,n.activity&&`${n.activity.summary} ${K(n.activity.at)} ago`].filter(Boolean).join(" \\xB7 ")}function yt(e){let t=e.messages.filter(n=>n.state==="pending").length;return e.messages.length?["\\u2709 "+e.messages.length,t>0&&[" ",a`<span class="warn">(${t} pending)</span>`]]:null}var cr=e=>()=>{window.open(D(e.key),"_blank","noopener")};function wt(e){let t=de(e);return{nodes:[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked?" \\xB7 acked":""],text:[t,`${K(e.filedAt)} ago`,e.acked?"acked":""].filter(Boolean).join(" \\xB7 ")}}function dr(e){let t=wt(e);return a`<a key=${e.key} class=${"card"+(e.acked?" acked":"")} href=${D(e.key)} target="_blank" rel="noopener"><div class="top"><b title=${e.title}>${e.title}</b></div><div class="meta" title=${t.text}>${t.nodes}</div></a>`}function pr(e){return a`<tr key=${e.key} class=${"rowhead"+(e.acked?" acked":"")} onClick=${cr(e)}><td class="grow"><a href=${D(e.key)} target="_blank" rel="noopener" onClick=${t=>t.stopPropagation()}>${e.title}</a></td><td>${de(e)}</td><td>${v(e.filedAt)}</td><td>${e.acked?"acked":""}</td></tr>`}function ns({inp:e}){return e.view==="cards"?e.list.length?a`<div class="cards">${e.list.map(dr)}</div>`:a`<div class="empty">no reports match</div>`:R(["title","from","filed","acked"],e.list.map(pr),"no reports match")}function os(e){let t=e.trim();return/^(https?:\\/\\/|mailto:|#)/i.test(t)?t:void 0}function is(e){let t=e.trim();return/^[^/\\\\:?#]+$/.test(t)&&t!=="."&&t!==".."?t:void 0}function ss(e,t,n,s){let r=0;for(let i=t;i<e.length;i++){if(e[i]==="\\\\"){i++;continue}if(e[i]===n)r++;else if(e[i]===s){if(r===0)return i;r--}}return-1}function rs(e,t){let n=ss(e,t+1,"[","]");if(n<0||e[n+1]!=="(")return;let s=ss(e,n+2,"(",")");if(s<0)return;let r=e.slice(n+2,s).trim().replace(/\\s+"[^"]*"$/,"").replace(/^<(.*)>$/,"$1");return{text:e.slice(t+1,n),target:r,end:s+1}}function q(e){let t=[],n="",s=()=>{n&&t.push({t:"text",v:n}),n=""};for(let r=0;r<e.length;){let i=e[r];if(i==="\\\\"&&r+1<e.length&&/[\\\\`*_{}[\\]()#+\\-.!|<>~]/.test(e[r+1])){n+=e[r+1],r+=2;continue}if(i==="`"){let o=/^`+/.exec(e.slice(r))[0],c=e.indexOf(o,r+o.length);if(c>0){s(),t.push({t:"code",v:e.slice(r+o.length,c).replace(/^ (.*) $/,"$1")}),r=c+o.length;continue}}if(i==="!"&&e[r+1]==="["){let o=rs(e,r+1);if(o){s(),t.push({t:"img",alt:o.text,src:o.target}),r=o.end;continue}}if(i==="["){let o=rs(e,r);if(o){s(),t.push({t:"a",href:o.target,c:q(o.text)}),r=o.end;continue}}if((i==="*"||i==="_")&&e[r+1]===i){let o=e.indexOf(i+i,r+2);if(o>r+2){s(),t.push({t:"b",c:q(e.slice(r+2,o))}),r=o+2;continue}}if(i==="*"||i==="_"&&(r===0||!/\\w/.test(e[r-1]))){let o=r+1;for(;(o=e.indexOf(i,o))>0&&(e[o+1]===i||i==="_"&&/\\w/.test(e[o+1]??""));)o+=2;if(o>r+1&&e[r+1]!==" "){s(),t.push({t:"i",c:q(e.slice(r+1,o))}),r=o+1;continue}}if(i===`\n`){/ {2,}$/.test(n)||n.endsWith("\\\\")?(n=n.replace(/( {2,}|\\\\)$/,""),s(),t.push({t:"br"})):n+=" ",r++;continue}n+=i,r++}return s(),t}var as=/^ {0,3}(`{3,}|~{3,})\\s*([^`\\s]*)/,ls=/^ {0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/,cs=/^ {0,3}([-*_])(\\s*\\1){2,}\\s*$/,St=/^( {0,3})([-*+])\\s+(.*)$/,xt=/^( {0,3})(\\d{1,9})[.)]\\s+(.*)$/,qe=/^ {0,3}>\\s?(.*)$/,ur=/^\\s*\\|?\\s*:?-+:?\\s*(\\|\\s*:?-+:?\\s*)*\\|?\\s*$/;function Gt(e){let t=e.trim();t.startsWith("|")&&(t=t.slice(1)),t.endsWith("|")&&!t.endsWith("\\\\|")&&(t=t.slice(0,-1));let n=[],s="";for(let r=0;r<t.length;r++)t[r]==="\\\\"&&t[r+1]==="|"?(s+="|",r++):t[r]==="|"?(n.push(s.trim()),s=""):s+=t[r];return n.push(s.trim()),n}var Tt=e=>as.test(e)||ls.test(e)||cs.test(e)||St.test(e)||xt.test(e)||qe.test(e);function Fe(e){let t=e.replace(/\\r\\n?/g,`\n`).replace(/\\t/g," ").split(`\n`),n=[],s=0;for(;s<t.length;){let r=t[s];if(!r.trim()){s++;continue}let i=as.exec(r);if(i){let f=i[1],h=[];for(s++;s<t.length&&!new RegExp(`^ {0,3}${f[0]}{${f.length},}\\\\s*$`).test(t[s]);)h.push(t[s++]);s++,n.push({t:"code",lang:i[2]??"",v:h.join(`\n`)});continue}let o=ls.exec(r);if(o){n.push({t:"h",level:o[1].length,c:q(o[2])}),s++;continue}if(cs.test(r)){n.push({t:"hr"}),s++;continue}if(qe.test(r)){let f=[];for(;s<t.length&&t[s].trim()&&(qe.test(t[s])||!Tt(t[s]));)f.push(qe.exec(t[s])?.[1]??t[s]),s++;n.push({t:"quote",c:Fe(f.join(`\n`))});continue}let c=St.exec(r),u=xt.exec(r);if(c||u){let f=!c,h=f?xt:St,d=[];for(;s<t.length;){let m=t[s],p=h.exec(m);if(p&&p[1].length<=3){d.push([p[3]]),s++;continue}if(!m.trim()){if(s+1<t.length&&/^ {2,}\\S/.test(t[s+1])){d.at(-1).push(""),s++;continue}break}if(/^ {2,}\\S/.test(m)){d.at(-1).push(m.replace(/^ {2,4}/,"")),s++;continue}if(Tt(m))break;d.at(-1).push(m.trim()),s++}n.push({t:"list",ordered:f,start:f?Number(u[2]):1,items:d.map(m=>Fe(m.join(`\n`)))});continue}if(r.includes("|")&&s+1<t.length&&ur.test(t[s+1])&&t[s+1].includes("-")){let f=Gt(r),h=Gt(t[s+1]).map(m=>m.startsWith(":")&&m.endsWith(":")?"center":m.endsWith(":")?"right":m.startsWith(":")?"left":"");s+=2;let d=[];for(;s<t.length&&t[s].trim()&&t[s].includes("|");){let m=Gt(t[s++]);d.push(f.map((p,g)=>q(m[g]??"")))}n.push({t:"table",align:f.map((m,p)=>h[p]??""),head:f.map(q),rows:d});continue}let l=[];for(;s<t.length&&t[s].trim()&&!Tt(t[s])&&!(l.length&&/^ {0,3}(=+|-+)\\s*$/.test(t[s]));)l.push(t[s++].replace(/^ +/,""));if(l.length===0){n.push({t:"p",c:q(r.trim())}),s++;continue}n.push({t:"p",c:q(l.join(`\n`))})}return n}function F(e,t){return t.map(n=>{switch(n.t){case"text":return n.v;case"code":return a`<code>${n.v}</code>`;case"b":return a`<strong>${F(e,n.c)}</strong>`;case"i":return a`<em>${F(e,n.c)}</em>`;case"br":return a`<br />`;case"a":{let s=os(n.href);return s?a`<a href=${s} target="_blank" rel="noopener noreferrer">${F(e,n.c)}</a>`:F(e,n.c)}case"img":{let s=is(n.src);return s?ue(e(s),n.alt||s,"mdimg"):a`<span class="dim">[image not shown: ${n.alt||n.src}]</span>`}}})}function We(e,t){return t.map(n=>{switch(n.t){case"h":{let s=F(e,n.c);return n.level===1?a`<h1>${s}</h1>`:n.level===2?a`<h2>${s}</h2>`:n.level===3?a`<h3>${s}</h3>`:a`<h4>${s}</h4>`}case"p":return a`<p>${F(e,n.c)}</p>`;case"code":return a`<pre class="mdcode"><code>${n.v}</code></pre>`;case"hr":return a`<hr />`;case"quote":return a`<blockquote>${We(e,n.c)}</blockquote>`;case"list":{let s=n.items.map(r=>a`<li>${We(e,r)}</li>`);return n.ordered?a`<ol start=${n.start}>${s}</ol>`:a`<ul>${s}</ul>`}case"table":return a`<div class="mdtable"><table><thead><tr>${n.head.map((s,r)=>a`<th style=${n.align[r]?"text-align:"+n.align[r]:void 0}>${F(e,s)}</th>`)}</tr></thead><tbody>${n.rows.map(s=>a`<tr>${s.map((r,i)=>a`<td style=${n.align[i]?"text-align:"+n.align[i]:void 0}>${F(e,r)}</td>`)}</tr>`)}</tbody></table></div>`}})}var _e=new Map;function ds(e){let t=_e.get(e);return t||(t=Fe(e),_e.set(e,t),_e.size>8&&_e.delete(_e.keys().next().value)),t}function fr({r:e,text:t}){let n=t?.text!==void 0?We(s=>ct(e.key,s),ds(t.text)):t?.error?a`<div class="bad">${t.error}</div>`:a`<div class="dim">loading\u2026</div>`;return a`<div class="mdpage" data-report=${e.key}>${n}</div>`}function ps({text:e,fileUrl:t}){return a`<div class="mdpage">${We(t,ds(e))}</div>`}function Ct(e){let t=de(e);return[t&&t+" \\xB7 ",v(e.filedAt)," ago",e.acked&&[" \\xB7 ",a`<span class="ok">acked</span>`]]}function us({r:e,text:t,heading:n}){return[n&&a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)}</div>`,a`<${fr} r=${e} text=${t} />`,e.attachments.length>0&&[a`<div class="sec">report attachments (${e.attachments.length})</div>`,se(e.attachments,s=>ct(e.key,s))],!e.acked&&[a`<div class="sec">ack</div>`,C("lobstah attention ack "+e.key)]]}var mr=/\\.(png|jpe?g|gif|webp)$/i,hr=e=>{e.style.height="auto",e.style.height=e.scrollHeight+"px"};function gr(e,t){return t.length?a`<div class="dfiles">${t.map(n=>mr.test(n.name)?a`<span key=${n.name}>${ue(at(e,n.name),n.name,"dimg")}</span>`:a`<div key=${n.name} class="dfile"><span>${n.name}</span> <span class="dim">\xB7 ${n.type} \xB7 ${n.bytes} bytes \xB7 </span><code>${n.path}</code></div>`)}</div>`:null}function vr(e,t,n){let s=e.key,r=e.kind==="decision"?e.options:[],i=t?.text??"",o=t?.files??[],c=!!t?.sending;return[r.length>0&&a`<div class="doptions">${r.map(u=>a`<button key=${u} type="button" class=${"btn dopt"+(t?.option===u?" on":"")} aria-pressed=${t?.option===u?"true":"false"} disabled=${c} onClick=${()=>bn(s,u)}>${u}</button>`)}</div>`,a`<textarea\n class="danswer"\n rows="2"\n placeholder=${r.length?"Add to your answer, or answer in your own words":"Your answer"}\n value=${i}\n disabled=${c}\n onInput=${u=>{let l=u.currentTarget;hr(l),A(s,{text:l.value,error:void 0})}}\n onPaste=${u=>{kn(s,u)}}\n ></textarea>`,a`<div class="dfoot">\n <label class="btn dattach" title="attach images or files">attach<input type="file" multiple accept=${n} disabled=${c} onChange=${u=>{let l=u.currentTarget;ft(s,l.files).then(()=>{l.value=""})}} /></label>\n ${o.map((u,l)=>a`<span key=${u.name+l} class="dchip">${u.name}<button type="button" class="dx" title="remove" disabled=${c} onClick=${()=>yn(s,l)}>\xD7</button></span>`)}\n ${t?.error&&a`<span class="bad derr">${t.error}</span>`}\n <button type="button" class="btn dsend" disabled=${c} onClick=${()=>{wn(s)}}>${c?"Sending\\u2026":"Send"}</button>\n </div>`]}function _r(e,t,n,s){let r=e.dispatch,i=e.lane??"work",c=[r&&a`<a class="dlink" href="#" onClick=${l=>{l.preventDefault(),w("dispatch",i+":"+r)()}}>${r.slice(0,8)}</a>`,e.repo,[v(e.at)," ago"]].filter(Boolean).flatMap((l,f)=>f?[" \\xB7 ",l]:[l]),u="dcard"+(e.kind==="question"?" plain":"")+(t?.sent?" answered":"")+(n?" focus":"");return a`<div key=${e.key} class=${u} data-decision=${e.key}>\n <div class="dtop">\n ${e.kind==="question"&&a`<span class=${"badge "+(e.verb==="blocked"?"bad":"warn")}>${e.verb}</span>`}\n <b class="dtitle">${e.kind==="decision"?e.title:B(e.note)}</b>\n <span class="dmeta dim">${c}</span>\n </div>\n ${e.kind==="decision"&&e.detail.trim()&&a`<${ps} text=${e.detail} fileUrl=${l=>at(e.key,l)} />`}\n ${e.kind==="decision"&&gr(e.key,e.attachments)}\n ${t?.sent?a`<div class="danswered ok">answered \xB7 ${t.sent}</div>`:vr(e,t,s)}\n </div>`}function fs({cards:e,drafts:t,focus:n,extensions:s}){let r=s.join(","),i=e.length?a`<div class="dcards">${e.map(o=>_r(o,t[o.key],o.key===n,r))}</div>`:a`<div class="empty">none</div>`;return a`<section class="decisions"><h2>decisions</h2>${i}</section>`}function yr(e,t){let n=e.badge&&a`<span class=${"badge "+(e.badge.tone||"dim")+U(e.badge.text)} title=${H(e.badge.text)}>${e.badge.text}</span>`;if(e.href){let s=(t==="cards"?"card":"deckline click")+(e.acked?" acked":"");return t==="cards"?a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener"><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}</a>`:a`<a key=${e.key} class=${s} href=${e.href} target="_blank" rel="noopener">${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}</a>`}return t==="cards"?a`<div key=${e.key} class=${"card"+(e.acked?" acked":"")} onClick=${e.open} style=${e.open?void 0:"cursor:default"}><div class="top"><b>${e.title}</b>${n}</div>${e.meta&&a`<div class="meta" title=${e.metaText}>${e.meta}</div>`}${e.extra}${e.action&&a`<div class="foot"><span class="footact">${e.action}</span></div>`}</div>`:a`<div key=${e.key} class=${"deckline"+(e.open?" click":"")+(e.acked?" acked":"")} onClick=${e.open}>${n&&[n," "]}<b>${e.title}</b>${e.meta&&[" ",a`<span class="dim">\xB7 ${e.meta}</span>`]}${e.action&&[" ",a`<span class="dim">\xB7 </span>`,e.action]}${e.extra}</div>`}var ms=(e,t)=>e>0&&a`<a class="deckmore" href=${"#"+t}>+${e} more \u2192</a>`;function Ve(e,t,n,s,r,i){let o=t.slice(0,s),c=o.map(l=>yr(l,r)),u=o.length?r==="cards"?a`<div class="cards">${c}</div>`:c:a`<div class="empty">none</div>`;return a`<section><h2><a href=${"#"+n}>${e} \u2192</a></h2>${i&&a`<div class="tabhead">${i}</div>`}${u}${ms(t.length-o.length,n)}</section>`}function wr(e,t,n,s){let r=t.find(l=>l.number===e.nextNumber)||t[0],i=oe(e.numbers,new Map(t.map(l=>[l.number,l.title]))),o=r?"next #"+r.number:"nothing mergeable";if(s==="cards"){let l=f=>{let h=n.get(f.key)||[],d=h.length>0&&h.every(g=>g.acked),m=h.length?h.map(g=>a`<span class=${"badge "+($t[g.kind]||"dim")}>${Zn(g.kind)}</span>`):a`<span class=${"badge "+L(f.badge)+U(f.badge.text)} title=${H(f.badge.text)}>${f.badge.text}</span>`,p=`${f.repo} \\xB7 ${f.badge.text}${d?" \\xB7 acked":""}`;return a`<div key=${f.key} class=${"card"+(d?" acked":"")} onClick=${w("pr",f.key)}><div class="top">${Ue(f)}${m}</div><div class="meta" title=${p}>${p}</div></div>`};return a`<div key=${e.id} class="deckstack"><div class="dim">${i} \xB7 ${o}</div><div class="cards">${t.map(l)}</div></div>`}let c=t.flatMap(l=>(n.get(l.key)||[]).map(f=>a`<span class=${"badge "+($t[f.kind]||L(l.badge))+(f.acked?" acked":"")}>#${l.number} ${l.badge.text}</span>`)),u=c.flatMap((l,f)=>f?[" ",l]:[l]);return a`<div key=${e.id} class=${"deckline"+(r?" click":"")} onClick=${r&&w("pr",r.key)}><b>${i}</b> <span class="dim">\xB7 ${o}</span>${c.length>0&&[" \\xB7 ",u]}</div>`}function Gr(e,t){let n=e.stacks.slice(0,3),s=new Map;for(let i of e.prAttention){let o=s.get(i.key)||[];o.push(i),s.set(i.key,o)}let r=n.length?n.map(i=>wr(i,e.prs.filter(o=>o.stackId===i.id).sort((o,c)=>o.position-c.position),s,t)):a`<div class="empty">none</div>`;return a`<section><h2><a href="#prs">PRs \u2192</a></h2>${r}${ms(e.stacks.length-n.length,"prs")}</section>`}function hs({inp:e,drafts:t,focus:n,extensions:s}){let r=e.view,i=e.inflight.map(l=>({key:l.lane+":"+l.id,title:[l.id.slice(0,8)," ",l.repo||""],badge:{text:l.verb,tone:l.verb==="needs-decision"||l.verb==="blocked"?"bad":"dim"},meta:[B(l.note||"",90),l.verbAt&&[" \\xB7 ",v(l.verbAt)," ago"],(l.for||l.claimedBy)&&[" \\xB7 ",Me(l.for||l.claimedBy)]],metaText:[j(l.note||""),l.verbAt&&`${K(l.verbAt)} ago`,j(l.for||l.claimedBy||"")].filter(Boolean).join(" \\xB7 "),open:w("dispatch",l.lane+":"+l.id)})),o=e.landed.map(l=>({key:l.key,title:[l.id.slice(0,8)," ",l.repo||"",l.unreported&&[" ",a`<span class="badge warn unreported">unreported</span>`]],badge:{text:l.verb,tone:l.verb==="failed"?"bad":"ok"},meta:[B(l.note||"",90)," \\xB7 ",v(l.at)," ago"],metaText:`${j(l.note||"")} \\xB7 ${K(l.at)} ago`,open:w("dispatch",l.lane+":"+l.id)})),c=e.reports.map(l=>{let f=wt(l);return{key:l.key,title:l.title,meta:f.nodes,metaText:f.text,href:D(l.key),acked:!!l.acked}}),u=e.traps.map(({x:l})=>({key:l.trapId,title:"\\u{1FAA4} "+(l.label??`wt:${l.trapId}`),badge:l.requested?{text:"requested",tone:"dim"}:l.starting?{text:l.starting.failedAt?"start failed":"starting",tone:l.starting.failedAt?"bad":"warn"}:{text:l.live?l.harness||"live":"signed off",tone:l.live?"ok":"dim"},meta:[l.repo||""," \\xB7 ",ve(l)],metaText:`${l.repo||""} \\xB7 ${Oe(l)}`,action:z(l),extra:J(l),open:w("trap",l.trapId)}));return a`<div class="deckgrid"><${fs} cards=${e.decisions} drafts=${t} focus=${n} extensions=${s} />${Ve("in flight",i,"dispatches",4,r)}${Ve("Landed \\xB7 24h",o,"dispatches",8,r)}${Ve("reports",c,"reports",8,r)}${Ve("traps",u,"traps",8,r,a`<${Be} />`)}${Gr(e,r)}</div>`}function Tr(e,t){if(!t)return e;let n=new Map(e.map(i=>[i.id,i])),s=i=>{let o=i,c=new Set;for(;o.followUp&&n.has(o.followUp)&&!c.has(o.id);)c.add(o.id),o=n.get(o.followUp);return o.id},r=new Map;for(let i of e){let o=s(i),c=r.get(o)||[];c.push(i),r.set(o,c)}return[...r.values()].sort((i,o)=>o[0].sort-i[0].sort).flatMap(i=>i.sort((o,c)=>o.id===s(o)?-1:c.id===s(c)?1:o.sort-c.sort))}function Sr(e,t){let n=e.lane+":"+e.id;return a`<tr key=${n} class="rowhead" onClick=${w("dispatch",n)}><td>${t&&e.followUp&&a`<span class="dim">\u21B3 </span>`}${e.id.slice(0,8)}</td><td>${e.lane} / ${e.bucket}</td><td>${e.repo}</td><td class=${"v-"+e.verb}>${e.verb}</td><td class="grow">${B(e.note??"",90)}${Le(e)}${Ee(e)}</td><td>${v(e.verbAt)}</td><td>${fe(e)}</td><td>${re(e)}</td></tr>`}function xr(e){let t=e.lane+":"+e.id;return a`<div key=${t} class="card" onClick=${w("dispatch",t)}><div class="top"><b>${e.id.slice(0,8)}</b><span class=${"badge v-"+e.verb+U(e.verb)} title=${H(e.verb)}>${e.verb}</span></div><div class="meta" title=${`${e.repo} \\xB7 ${e.lane} ${e.bucket}`}>${e.repo} \xB7 ${e.lane} ${e.bucket}${e.verbAt&&[" \\xB7 ",v(e.verbAt)]}</div>${e.note&&a`<div class="note" title=${j(e.note)}>${B(e.note)}</div>`}${Le(e)}${Ee(e)}<div class="foot">${fe(e)} ${re(e)}</div></div>`}function gs({inp:e}){let t=e.list;return e.view==="cards"?t.length?a`<div class="cards">${t.map(xr)}</div>`:a`<div class="empty">no dispatches match</div>`:R(["id","lane / bucket","repo","verb","note","age","addressed","pr"],Tr(t,e.chain).map(n=>Sr(n,e.chain)),"no dispatches match")}var Cr={deck:"On deck",dispatches:"Dispatches",traps:"Traps",prs:"PRs",reports:"Reports",notices:"Notices"},Dr=["working","needs-decision","blocked","paused","done","failed","unknown"];function Ar(e,t){let n=t.daemonStale,s=e.daemon?[a`<span class=${n?"bad":"ok"}>${n&&"stale "}${v(e.daemon.heartbeat)} ago</span>`," ",a`<span class="dim">v${e.daemon.version}</span>`]:a`<span class="bad">down</span>`;return[a`<span class="chip">daemon ${s}</span>`,e.slots&&a`<span class="chip">active headless: ${e.slots.headless} of ${e.slots.limit}; traps: ${e.slots.traps}${e.slots.parked?`; parked: ${e.slots.parked} (no slot)`:""}</span>`,t.helms.map(({x:r,stale:i})=>a`<span key=${r.grounds} class="chip click" onClick=${w("helm",r.grounds)}>\u26F5 <b>${r.man}</b> <span class="dim">helm ${r.grounds}</span> <span class=${i?"warn":"ok"}>${i&&"stale "}${v(r.heartbeatAt)} ago</span></span>`)]}var Dt=[];function Rr(e){let t=[...new Set([...e.dispatches.map(n=>n.repo),...e.notices.map(n=>n.repo),...e.prs.map(n=>n.repo)].filter(Boolean))].sort();return t.length!==Dt.length&&(Dt=t),Dt}var At=[];function Pr(e){let t=[...new Set(e.notices.map(n=>n.kind))].sort();return t.length!==At.length&&(At=t),At}var $e=e=>e.currentTarget.value;function Ir(e,t,n){let s=o=>t===o?"":"display:none",r=e?Rr(e):[],i=e?Pr(e):[];return a`<div class="controls">\n <select id="f-lane" style=${e&&s("dispatches")} value=${n.lane} onChange=${o=>N({lane:$e(o)})}>\n <option value="">all lanes</option>\n <option value="work">work</option>\n <option value="chore">chore</option>\n </select>\n <select id="f-repo" value=${n.repo} onChange=${o=>N({repo:$e(o)})}>\n <option value="">all repos</option>\n ${r.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <select id="f-verb" style=${e&&s("dispatches")} value=${n.verb} onChange=${o=>N({verb:$e(o)})}>\n <option value="">all verbs</option>\n ${Dr.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <label id="chain-control" style=${e&&s("dispatches")}><input id="f-chain" type="checkbox" checked=${!!n.chain} onChange=${o=>N({chain:o.currentTarget.checked})} /> group by chain</label>\n <select id="f-kind" style=${e&&s("notices")} value=${n.noticeKind} onChange=${o=>N({noticeKind:$e(o)})}>\n <option value="">all notice kinds</option>\n ${i.map(o=>a`<option key=${o}>${o}</option>`)}\n </select>\n <input id="f-q" type="search" placeholder="search id \xB7 note \xB7 brief" value=${n.q} onInput=${o=>N({q:$e(o)})} />\n </div>`}function vs({d:e,inp:t,tab:n,st:s,stale:r}){return[a`<div class="headerline"><h1>\u{1F99E}\u2728 spyglass<span id="stale" style=${e||r?r?"display:inline":"display:none":void 0}> \xB7 STALE FEED</span></h1><span id="settings-slot"><button id="gearbtn" title="settings" aria-label="settings" onClick=${()=>M("settings","browser")}>\u2699</button></span></div>`,a`<div class="chips"><span id="chips" style="display:contents">${e&&t&&Ar(e,t.chips)}</span><span class="chip dim" id="clock">${e&&new Date(e.now).toLocaleTimeString("en-GB")}</span></div>`,a`<nav class="tabs" id="tabs" aria-label="Spyglass views">${ce.map(i=>a`<a href=${"#"+i} data-tab=${i} class=${e?i===n?"on":"":void 0}>${Cr[i]}</a>`)}</nav>`,Ir(e,n,s)]}var _s=({d:e})=>e&&["\\u{1F99E}\\u2728 lobstah v"+e.version+" \\xB7 ",a`<a href=${e.repoUrl} target="_blank">${e.repoUrl.replace("https://github.com/","")}</a>`];function $s(e,t){if(!t.lobs)return[];let n={"pr:draft":"draft","pr:review":"review","pr:checks":"checks","pr:conflict":"conflicts","pr:ready":"ready",landed:"landed",watch:"watch",report:"report"},s=t.hidden??{},r=e.filter(o=>!o.acked&&!(o.key!==void 0&&s[o.key]===o.stateHash)).map(o=>{let c=n[o.kind??""]??"",u=o.key!==void 0?{hideKey:o.key,hideHash:o.stateHash??""}:{};return o.kind==="decision"&&o.key?{key:"decision:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:(o.kind??"question")==="question"&&o.key?{key:"question:"+o.key,text:o.note||o.verb,label:c,hash:rt(o.key),...u}:o.kind==="report"&&o.key?{key:"report:"+o.key,text:o.note||o.verb,label:c,href:D(o.key),...u}:typeof o.kind=="string"&&o.kind.startsWith("pr:")&&o.prUrl?{key:o.kind+":"+(o.key??o.prUrl),text:o.note||o.kind,href:o.prUrl,label:c,...u}:{key:(o.kind??"question")+":"+(o.key??o.lane+":"+o.id),text:o.note||o.verb,label:c,...o.kind==="watch"?{}:{open:{type:"dispatch",key:o.lane+":"+o.id}},...u}});!r.length&&t.preview&&(r=[{key:"preview",text:"attention questions crawl in here",...t.previewHelm?{open:{type:"helm",key:t.previewHelm}}:{}}]);let i=r.length>4?r.length-4:0;return r=r.slice(0,4),i&&(r[3]={...r[3],text:"\\u2026and "+i+" more \\u2014 see attention",label:""}),r}var bs=!0;function Mr(e,t,n){let s="animation-duration:"+((innerWidth+180)/(100+t*12)).toFixed(1)+"s;animation-delay:-"+t*9%14+"s",r=()=>{e.hideKey&&An(e.hideKey,e.hideHash??"")},i=[a`<div class="bub">${e.label&&[a`<span class="badge dim">${e.label}</span>`," "]}<span>${e.text.length>48?e.text.slice(0,47)+"\\u2026":e.text}</span></div>`,n===!1?a`<span class="fallback">\u{1F99E}</span>`:a`<div class="sprite"></div>`,bs&&a`<img\n class="star"\n src="/star.png"\n alt=""\n onError=${c=>{bs=!1,c.currentTarget.remove()}}\n />`],o=e.open;return e.hash?a`<a key=${e.key} class="lob" style=${s} title="open the decision" href=${e.hash} onClick=${r}>${i}</a>`:e.href?a`<a key=${e.key} class="lob" style=${s} title=${e.label==="report"?"open the report":"open the PR"} href=${e.href} target="_blank" rel="noopener" onClick=${r}>${i}</a>`:a`<div\n key=${e.key}\n class="lob"\n style=${s}\n title="click to open"\n onClick=${()=>{r(),o&&M(o.type,o.key)}}\n >\n ${i}\n </div>`}function ks({state:e}){let t=e.snapshot,n=$s(t?.attention||[],{lobs:e.prefs.lobs,hidden:e.lobHidden,preview:e.preview,previewHelm:t&&t.helms.length?t.helms[0].grounds:void 0}),s=n.map(r=>r.key).join("|")+(e.spriteOk===null?"?":e.spriteOk?"s":"e");return Vn(()=>n.map((r,i)=>Mr(r,i,e.spriteOk)),[s])}function Ke({box:e}){if(!e)return null;let t=n=>{n.target===n.currentTarget&&ee()};return a`<div id="lightbox" class="lightbox" role="dialog" aria-label=${e.name} onClick=${t}>\n <button type="button" class="lbclose" title="close" onClick=${ee}>\xD7</button>\n <img class="lbimg" src=${e.src} alt=${e.name} />\n <a class="lboriginal" href=${e.src} target="_blank" rel="noopener">open original \xB7 ${e.name}</a>\n </div>`}var be=a`<span class="x" onClick=${te}>\xD7</span>`,ys=(e,t,n)=>a`<span class="seg">${t.map(([s,r])=>a`<button class=${s===n?"on":""} onClick=${()=>e(s)}>${r}</button>`)}</span>`;function Er(e,t){let n=ys(Cn,[["table","table"],["cards","cards"]],t.view),s=ys(Dn,[["on","on"],["off","off"]],t.lobs?"on":"off"),r=e.attentionError?e.attentionError:(e.attentionKinds||[]).join(" \\xB7 ");return[be,a`<h3>\u2699 settings <span class="dim" style="font-weight:normal">\xB7 this browser only</span></h3>`,a`<div class="settings"><div class="row"><span class="lbl">view<span class="hint">cards or table (notices stay a table)</span></span>${n}</div><div class="row"><span class="lbl">lobs<span class="hint">crawling lobsters in this page</span></span>${s}</div><div class="row"><span class="lbl">attention<span class="hint">kinds shown \u2014 attentionKinds in config.toml (read-only here)</span></span><span class="dim" style="font-size:11px;text-align:right;max-width:260px">${r}</span></div></div>`]}function Lr(e,t){let n=oe(t.numbers,new Map(t.numbers.map((r,i)=>[r,t.titles[i]])),e.number),s=t.nextMergeable?[" \\xB7 ",a`<span class="ok">next mergeable</span>`]:t.blockedBy?" \\xB7 blocked by #"+t.blockedBy:t.nextNumber?" \\xB7 next is #"+t.nextNumber:"";return[n," \\xB7 ",t.position," of ",t.size," \\xB7 floor ",t.floor,s]}var Nr=e=>t=>{t.preventDefault(),M("dispatch",e)};function Hr(e,t){let n=un(e,t),s=n.pr,r=n.stack,i=n.watch,o=je(s),c=n.chain.length?n.chain.flatMap((l,f)=>[f?" \\u2192 ":"",l.culled?a`<span class="dim">${l.id.slice(0,8)} (culled)</span>`:[a`<a href="#" onClick=${Nr(l.modalKey)}>${l.id.slice(0,8)}</a>`," ",a`<span class="dim">${l.verb}</span>`]]):a`<span class="dim">none</span>`,u=i?[a`<div>${i.key} \xB7 owner ${i.owner||"?"} \xB7 ${i.lastCheckedAt?["checked ",v(i.lastCheckedAt)," ago"]:"never checked"}</div>`,i.lastError&&a`<div class="bad">${i.lastError}</div>`,a`<div class="dim" style="font-size:11px;margin-top:4px">cursor</div>`,C(i.cursor)]:a`<div class="dim">no watch \u2014 <code>lobstah watch add ${s.url}</code> registers one</div>`;return[be,a`<h3>${He(s)} ${s.title||""} <span class=${"badge "+L(s.badge)}>${s.badge.text}</span>${s.draft&&s.badge.text!=="draft"&&[" ",a`<span class="badge pr-draft">draft</span>`]}</h3>`,a`<div class="sub">${s.repo} \xB7 ${s.state} \xB7 observed ${v(s.observedAt)} ago${s.gate&&" \\xB7 gate "+s.gate}</div>`,a`<div class="sec">checks</div><div>${me(s)}</div><div class="sec">review</div><div>${o||a`<span class="dim">no review yet</span>`}</div><div class="sec">merge</div><div>${he(s)}</div>${s.repairWait&&a`<div class="sec">repair waits</div><div>${s.repairWait.heldBy}: ${s.repairWait.reason}${s.repairWait.until?" \\xB7 until "+s.repairWait.until:""}</div>`}<div class="sec">refs</div><div>${s.headRefName||"?"} \u2192 ${s.baseRefName||"?"}</div><div class="sec">stack</div><div>${r?Lr(s,r):a`<span class="dim">not stacked</span>`}</div><div class="sec">dispatch chain</div><div>${c}</div><div class="sec">watch</div>`,u]}var ws=(e,t)=>(e==="codex"?"codex resume ":"claude --resume ")+t;function Ur(e){let t=Date.now()-Date.parse(e.heartbeatAt)>18e5;return[be,a`<h3>\u26F5 ${e.man}</h3><div class="sub">helm of <b>${e.grounds}</b> (${(e.repos||[]).join(", ")})</div><div class="sub">${e.harness??"?"} \xB7 ${e.cwd??"?"}${e.host&&" \\xB7 "+e.host}</div><div class="sub">session ${e.sessionId??""} \xB7 signed on ${v(e.signedOnAt)} ago \xB7 heartbeat <span class=${t?"warn":"ok"}>${v(e.heartbeatAt)} ago</span></div>`,e.sessionId&&[a`<div class="sec">open this session</div>`,C(ws(e.harness,e.sessionId))],e.transcript&&[a`<div class="sec">transcript</div>`,C(e.transcript)]]}function jr(e){return[a`<div class="sec">report \xB7 ${e.title}</div>`,a`<div class="sub">${Ct(e)} \xB7 <a href=${D(e.key)} target="_blank" rel="noopener">open the report \u2197</a></div>`]}function Br(e,t){let n=e.claimedBy&&e.claimedBy.startsWith("wt:")?[a`<div class="sec">worked by trap</div>`,a`<div class="claimedby">claimed by ${Ne(e.claimedBy.slice(3))}</div>`,C(j(e.claimedBy)),a`<div class="dim" style="font-size:11px">an opted-in interactive session mans this seat \u2014 attach would resume someone\'s live thread. Message it instead: lobstah send ${j(e.claimedBy)} "\u2026"</div>`]:[a`<div class="sec">open this session</div>`,C("lobstah attach "+e.id)];return[be,a`<h3>${e.id.slice(0,8)} <span class=${"badge v-"+e.verb}>${e.verb}</span></h3>`,a`<div class="sub">${e.repo} \xB7 ${e.lane} ${e.bucket} \xB7 ${v(e.verbAt)}${e.for&&[" \\xB7 ",fe(e)]} ${re(e)}</div>`,n,e.worktree&&[a`<div class="sec">worktree${e.worktreeOf?" (reused from "+e.worktreeOf.slice(0,8)+")":""}</div>`,C(e.worktree),e.worktreeKept&&a`<div class="dim" style="font-size:11px">kept: ${e.worktreeKept}</div>`],e.transcript&&[a`<div class="sec">transcript</div>`,C(e.transcript)],t&&jr(t),Qn(e)]}function Or(e){let t=ge(e),n=e.requested?[e.repo??""," \\xB7 ",e.harness??""," \\xB7 requested ",v(e.requested.at)," ago from the glass; the helm reserves it and starts its session."]:e.starting?e.starting.failedAt?[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 start failed: ",e.starting.reason??"",". Its addressed work stays queued; a session can still redeem the ticket, or `lobstah stow --wt ",e.name??e.trapId,"` withdraws it."]:[e.repo??""," \\xB7 reserved ",v(e.starting.reservedAt)," ago \\xB7 starting: waiting for a session to redeem the ticket (due by ",e.starting.deadline,")"]:e.live?[e.repo??"addressed bait only"," \\xB7 session ",e.sessionId??""," \\xB7 signed on ",v(e.signedOnAt)," ago \\xB7 ",t.listen," \\xB7 heartbeat ",t.hb]:"signed off \\u2014 registration gone; the lifecycle, messages, and catches are the surviving record. Re-soaking the same worktree restores this address.",s=e.notices.length?e.notices.map(o=>a`<div key=${o.seq} class="loglines">${v(o.at)} ago \xB7 <b>${o.kind}</b> \u2014 ${o.text}</div>`):a`<div class="empty">none recorded</div>`,r=e.messages.length?e.messages.map(o=>a`<div key=${o.file} class=${"msg"+(o.from==="helm"?" from-helm":"")}><div class="hdr">from ${o.from} \xB7 ${o.at&&[v(o.at)," ago"]} \xB7 ${o.state==="pending"?a`<span class="warn">pending</span>`:a`<span class="ok">delivered</span>`}</div>${o.text}${o.attachments?.length?se(o.attachments,c=>sn(e.trapId,c)):""}</div>`):a`<div class="empty">none</div>`,i=e.catches.length?e.catches.map(o=>a`<div key=${o.lane+":"+o.id} class="catch"><div class="hdr"><b>${o.id.slice(0,8)}</b><span class=${"badge v-"+o.verb}>${o.verb}</span><span class="dim">${v(o.verbAt)}</span>${re(o)}</div><div class="loglines">${_t(o)}</div></div>`):a`<div class="empty">none yet</div>`;return[be,a`<h3>\u{1FAA4} ${e.label??`wt:${e.trapId}`} <span class="badge">${e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??"signed off"}</span></h3>`,e.worktree&&a`<div class="sub">${e.worktree}</div>`,a`<div class="sub">${n}</div>`,a`<div class="sec">window</div>`,z(e),J(e),e.live&&e.sessionId&&[a`<div class="sec">open this session</div>`,C(ws(e.harness,e.sessionId)),a`<div class="dim" style="font-size:11px">as registered at sign-on \u2014 a hookless enlistment may hold a made-up id</div>`],a`<div class="sec">lifecycle (${e.notices.length})</div>`,s,a`<div class="sec">messages (${e.messages.length})</div>`,r,a`<div class="sec">catches (${e.catches.length})</div>`,i]}function Gs({snapshot:e,modal:t,prefs:n}){if(!e||!t)return null;let s=Y(e,t);if(!s)return null;if(t.type==="settings")return Er(s,n);if(t.type==="pr")return Hr(e,t.key);if(t.type==="helm")return Ur(s);if(t.type==="dispatch"){let r=an(e,s);return Br(s,r)}return Or(s)}var qr=e=>a`<tr key=${e.seq}><td class="dim">${v(e.at)}</td><td>${e.kind}</td><td class="grow">${e.text}</td><td class="dim">${e.repo??""}</td></tr>`,Ts=({inp:e})=>R(["at","kind","text","repo"],e.list.map(qr),"no notices");function Ss(e){let t=new Map;for(let n of e.prs){let s=t.get(n.stackId)||[];s.push(n),t.set(n.stackId,s)}return e.stacks.map(n=>({s:n,prs:t.get(n.id)||[]})).filter(n=>n.prs.length)}var xs=(e,t)=>oe(e.numbers,new Map(t.map(n=>[n.number,n.title]))),Rt=e=>e.state+(e.draft?" \\xB7 draft":"")+(e.state==="MERGED"&&e.mergedAt?" \\xB7 "+e.mergedAt:"");function Cs(e){let t=s=>{let r=String(s??"");return r.length>24?r.slice(0,23)+"\\u2026":r},n=e.watches.map(s=>a`<tr key=${s.key}><td>${s.key}</td><td>${s.owner}</td><td title=${s.cursor}>${t(s.cursor)}</td><td>${s.lastCheckedAt&&v(s.lastCheckedAt)}</td><td>${s.lastError||""}</td></tr>`);return[a`<h2>other watches</h2>`,R(["key","owner","cursor","last check","error"],n,"no other watches")]}function Fr(e){let t=Ss(e).flatMap(({s:n,prs:s})=>[a`<tr key=${"stack:"+n.id}><th colspan="8">${xs(n,s)} \xB7 floor ${n.floor}${n.open?" \\xB7 open":" \\xB7 history"}</th></tr>`,...s.map(r=>a`<tr key=${r.key} class="rowhead" onClick=${w("pr",r.key)}><td>${He(r)}</td><td class="grow">${r.title||""}</td><td>${Rt(r)}</td><td>${me(r)}</td><td>${je(r)}</td><td>${he(r)}</td><td>${bt(r.watch)}</td><td>${r.gate||""}</td></tr>`)]);return[R(["PR","title","state","checks","review","merge","watch","gate"],t,"no PR evidence"),Cs(e)]}function Wr(e){let t=Ss(e),n=r=>a`<div key=${r.key} class="card" onClick=${w("pr",r.key)}><div class="top">${Ue(r)}<span class=${"badge "+L(r.badge)+U(r.badge.text)} title=${H(r.badge.text)}>${r.badge.text}</span></div><div class="meta" title=${`${r.repo} \\xB7 ${Rt(r)}`}>${r.repo} \xB7 ${Rt(r)} \xB7 ${he(r)}</div><div class="foot"><span>${me(r)}</span><span>${bt(r.watch)}</span>${r.gate&&a`<span>gate ${r.gate}</span>`}</div></div>`;return[t.length?t.map(({s:r,prs:i})=>a`<${Z} key=${"stack:"+r.id}><h2>${xs(r,i)} \xB7 floor ${r.floor}${r.open?"":" \\xB7 history"}</h2><div class="cards">${i.map(n)}</div><//>`):a`<div class="empty">no PR evidence</div>`,Cs(e)]}var Ds=({inp:e})=>e.view==="cards"?Wr(e):Fr(e);var Pt=e=>e.requested?"requested":e.starting?e.starting.failedAt?"start failed":"starting":e.harness??(e.live?"":"signed off"),As=e=>e.live||e.starting?"":" dim";function Vr(e){let t=ge(e);return a`<tr key=${e.trapId} class=${"rowhead"+As(e)} onClick=${w("trap",e.trapId)}><td><b>${e.label??`wt:${e.trapId}`}</b></td><td>${e.repo??"\\u2014"}</td><td class="grow">${e.worktree??""}</td><td>${e.harness??""}</td><td>${(e.sessionId??"").slice(0,8)}</td><td>${ve(e)}${J(e)}</td><td>${t.listen}</td><td>${t.hb}</td><td>${yt(e)}</td><td>${e.catches.length}</td><td>${z(e)}</td></tr>`}function Kr(e){let t=ge(e),n=yt(e),s=`${e.repo??(e.live?"addressed bait only":"history")}${e.sessionId?" \\xB7 session "+e.sessionId.slice(0,8):""}`;return a`<div key=${e.trapId} class=${"card"+As(e)} onClick=${w("trap",e.trapId)}><div class="top"><b>\u{1FAA4} ${e.label??`wt:${e.trapId}`}</b><span class=${"badge"+U(Pt(e))} title=${H(Pt(e))}>${Pt(e)}</span></div><div class="meta" title=${s}>${s}</div>${e.worktree&&a`<div class="note" title=${e.worktree}>${e.worktree}</div>`}<div class="note" title=${Oe(e)}>${ve(e)}</div>${J(e)}<div class="foot"><span>${t.listen}${e.live&&[" \\xB7 heartbeat ",t.hb]} \xB7 ${e.catches.length} catch${e.catches.length===1?"":"es"}${n&&[" \\xB7 ",n]}</span><span class="footact">${z(e)}</span></div></div>`}function Rs({inp:e}){let t=e.list.map(s=>s.x),n=a`<div class="tabhead"><${Be} /></div>`;return e.view==="cards"?[n,t.length?a`<div class="cards">${t.map(Kr)}</div>`:a`<div class="empty">no traps soaking</div>`]:[n,R(["address","repo","worktree","harness","session","now","listening","heartbeat","mail","catches","window"],t.map(Vr),"no traps soaking")]}function Ps({state:e}){let t=e.snapshot,n=e.route,s=t&&pn(t,{st:e.prefs,modal:e.modal},Date.now()),r=o=>!s||o!==n?null:o==="deck"?a`<${hs} inp=${s.deck} drafts=${e.drafts} focus=${e.focusDecision} extensions=${t?.answerLimits?.extensions??[]} />`:o==="dispatches"?a`<${gs} inp=${s.dispatches} />`:o==="traps"?a`<${Rs} inp=${s.traps} />`:o==="prs"?a`<${Ds} inp=${s.prs} />`:o==="reports"?a`<${ns} inp=${s.reports} />`:a`<${Ts} inp=${s.notices} />`,i=o=>{o.target===o.currentTarget&&te()};return[a`<${vs} d=${t} inp=${s} tab=${n} st=${e.prefs} stale=${e.stale} />`,ce.map(o=>a`<main key=${o} id=${"page-"+o} class=${t&&o===n?"tabpage on":"tabpage"}><div id=${o}>${r(o)}</div></main>`),a`<footer id="foot"><${_s} d=${t} /></footer>`,a`<div id="lobs"><${ks} state=${e} /></div>`,a`<${Ke} box=${e.lightbox} />`,a`<div id="overlay" class=${e.modal&&t?"open":t?"":void 0} onClick=${i}><div class="modal" id="modalbox"><${Gs} snapshot=${t} modal=${e.modal} prefs=${e.prefs} /></div></div>`]}function zr({view:e}){if(e.state==="loading")return a`<main class="reportview"><div class="dim">loading\u2026</div></main>`;if(e.state==="error")return a`<main class="reportview"><div class="bad">${e.error}</div></main>`;let t=e.report;return a`<main class="reportview">\n <h1>${t.title}</h1>\n <${us} r=${t} text=${e.text!==void 0?{text:e.text}:{error:e.error}} />\n </main>`}function Is({view:e,box:t}){return[a`<${zr} view=${e} />`,a`<${Ke} box=${t} />`]}async function Ms(e){let t=encodeURIComponent(e),n;try{let s=await fetch(`/report/${t}/meta`);if(!s.ok)return{state:"error",error:`report not found (${s.status})`};n=JSON.parse(await s.text())}catch{return{state:"error",error:"report could not be read"}}try{let s=await fetch(dn(e));return s.ok?{state:"ready",report:n,text:await s.text()}:{state:"ready",report:n,error:`report not found (${s.status})`}}catch{return{state:"ready",report:n,error:"report could not be read"}}}var It=!1,ke=null;async function Mt(){if(!It){It=!0;try{let e=await fetch("/data");if(!e.ok)throw new Error(`/data answered ${e.status}`);ut(await e.json())}catch{Gn()}finally{It=!1}}}var Es=()=>{ke||(ke=setInterval(Mt,2e3))},Jr=()=>{ke&&clearInterval(ke),ke=null};function Ls(){document.addEventListener("visibilitychange",()=>{document.hidden?Jr():(Mt(),Es())}),Mt(),document.hidden||Es()}var Et=()=>nn(location.hash),Ns=e=>window.addEventListener("hashchange",()=>e(Et()));function Us(){let e=ln(location.hash);return e===null?!1:(location.replace(D(e)),!0)}var js=()=>({snapshot:void 0,route:Et(),prefs:mn(),modal:null,stale:!1,lobHidden:gn(),spriteOk:null,focusResults:{},preview:new URLSearchParams(location.search).has("lob"),drafts:{},focusDecision:ot(location.hash),lightbox:null}),Hs=cn(location.pathname);Hs!==null?Xr(Hs):Us()||Qr();function Xr(e){dt(js());let t={state:"loading"},n=()=>nt(Q(Is,{view:t,box:_().lightbox}),document.body);document.title="report \\xB7 lobstah glass",pt(n),n(),Ms(e).then(s=>{t=s,s.state==="ready"&&(document.title=`${s.report.title} \\xB7 lobstah glass`),n()}),document.addEventListener("keydown",s=>{s.key==="Escape"&&_().lightbox&&ee()})}function Qr(){dt(js());let e,t=!1,n=()=>{let o=_(),c=o.route!==e||!t&&!!o.snapshot;nt(Q(Ps,{state:o}),document.body),e=o.route,t||(t=!!o.snapshot),c&&window.scrollTo(0,0),o.focusDecision||(s=null),r(o.focusDecision)},s=null;function r(o){if(!o||o===s)return;let c=[...document.querySelectorAll("[data-decision]")].find(u=>u.getAttribute("data-decision")===o);c&&(s=o,c.scrollIntoView?.({block:"center"}))}pt(n),n();let i=new Image;i.onload=()=>S({spriteOk:!0}),i.onerror=()=>S({spriteOk:!1}),i.src="/lob-sprite.png",Ns(o=>{if(Us())return;let c=ot(location.hash);c!==_().focusDecision&&(s=null),S({route:o,focusDecision:c})}),document.addEventListener("keydown",o=>{o.key==="Escape"&&(_().lightbox?ee():te())}),Ls()}})();</script>\n </body>\n</html>\n';
13505
13653
 
13506
13654
  // apps/cli/src/glass-snapshot-thread.ts
13507
13655
  init_dist2();
@@ -13548,8 +13696,8 @@ function startSnapshotThread() {
13548
13696
  const pending = inFlight.get(local);
13549
13697
  if (pending) return pending;
13550
13698
  const id = ++next;
13551
- const promise = new Promise((resolve13, reject) => {
13552
- waiting.set(id, { resolve: resolve13, reject });
13699
+ const promise = new Promise((resolve14, reject) => {
13700
+ waiting.set(id, { resolve: resolve14, reject });
13553
13701
  worker.postMessage({ id, local });
13554
13702
  }).finally(() => inFlight.delete(local));
13555
13703
  inFlight.set(local, promise);
@@ -14588,9 +14736,11 @@ function observePr(ref, view, opts = {}) {
14588
14736
  const pr = prEvidence(ref, view, now2.toISOString());
14589
14737
  const id = opts.dispatchId;
14590
14738
  const lane = id ? laneOf4(id) : void 0;
14591
- const 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;
14592
14741
  const { before, after } = upsertPr(pr, lane ? id : void 0);
14593
- 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 });
14594
14744
  const was = before?.state ?? legacyBefore?.state;
14595
14745
  const terminal3 = pr.state === "MERGED" || pr.state === "CLOSED";
14596
14746
  if (terminal3) cancelQueuedRepairs(ref.key);
@@ -14682,26 +14832,81 @@ function cutTitle(title, max = 60) {
14682
14832
  // apps/cli/src/pr-waits.ts
14683
14833
  init_dist2();
14684
14834
  var PR_WAITS = /* @__PURE__ */ new Set(["pr", "review"]);
14685
- function waitedPr(id, lane, entry) {
14835
+ function waitedPrs(id, lane, entry) {
14686
14836
  const link = entry.link ? parsePrRef(entry.link) : void 0;
14687
- 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;
14688
14840
  }
14689
- function ownPr(id, lane) {
14841
+ function ownPrs(id, lane) {
14690
14842
  const evidence = readEvidence(id, lane);
14691
- const url = evidence.prUrl ?? evidence.pr?.url ?? chainPr(id, lane)?.url;
14692
- 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.`;
14693
14854
  }
14694
- function registerWaitWatch(id, lane, entry) {
14855
+ function registerWaitWatches(id, lane, entry) {
14695
14856
  try {
14696
- if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return void 0;
14697
- const own = ownPr(id, lane);
14857
+ if (entry.verb !== "paused" || !entry.waitingOn || !PR_WAITS.has(entry.waitingOn)) return [];
14858
+ const own = ownPrs(id, lane);
14698
14859
  const link = entry.link ? parsePrRef(entry.link) : void 0;
14699
- if (!own || link && link.key !== own.key) return void 0;
14700
- 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);
14701
14862
  } catch {
14702
- return void 0;
14863
+ return [];
14703
14864
  }
14704
14865
  }
14866
+ function observeWaitedPrs(opts = {}) {
14867
+ const now2 = opts.now ?? Date.now();
14868
+ const everyMs = (opts.everySecs ?? 45) * 1e3;
14869
+ const view = opts.view ?? ghPrView;
14870
+ const observed = [];
14871
+ let daemonWatched;
14872
+ try {
14873
+ daemonWatched = new Set(
14874
+ listWatches().filter((w) => w.key.startsWith("pr:") && w.owner.startsWith("dispatch:") && !w.done && laneOf(w.owner.slice("dispatch:".length))).map((w) => w.key)
14875
+ );
14876
+ } catch {
14877
+ return observed;
14878
+ }
14879
+ const done = /* @__PURE__ */ new Set();
14880
+ for (const lane of ["work", "chore"]) {
14881
+ let ids;
14882
+ try {
14883
+ ids = activeIds(lane);
14884
+ } catch {
14885
+ continue;
14886
+ }
14887
+ for (const id of ids) {
14888
+ try {
14889
+ const last = readStatusLog(id, lane).at(-1);
14890
+ if (last?.verb !== "paused" || !last.waitingOn || !PR_WAITS.has(last.waitingOn)) continue;
14891
+ const own = new Set(ownPrs(id, lane).map((r) => r.key));
14892
+ for (const ref of waitedPrs(id, lane, last)) {
14893
+ if (done.has(ref.key) || daemonWatched.has(ref.key)) continue;
14894
+ done.add(ref.key);
14895
+ const record = readPr(ref.key);
14896
+ if (record && (record.state === "MERGED" || record.state === "CLOSED")) continue;
14897
+ if (record && now2 - Date.parse(record.observedAt) < everyMs) continue;
14898
+ try {
14899
+ observePr(ref, view(ref), { ...own.has(ref.key) ? { dispatchId: id } : {}, now: new Date(now2) });
14900
+ observed.push(ref.key);
14901
+ } catch {
14902
+ }
14903
+ }
14904
+ } catch {
14905
+ }
14906
+ }
14907
+ }
14908
+ return observed;
14909
+ }
14705
14910
  function finishResolvedWaits(log = () => {
14706
14911
  }) {
14707
14912
  const out = [];
@@ -14715,27 +14920,114 @@ function finishResolvedWaits(log = () => {
14715
14920
  for (const id of ids) {
14716
14921
  const last = readStatusLog(id, lane).at(-1);
14717
14922
  if (last?.verb !== "paused" || !last.waitingOn || !PR_WAITS.has(last.waitingOn)) continue;
14718
- const ref = waitedPr(id, lane, last);
14719
- const record = ref ? readPr(ref.key) : void 0;
14720
- if (!ref || !record || record.state !== "MERGED" && record.state !== "CLOSED") continue;
14721
- const merged = record.state === "MERGED";
14722
- const verb = merged ? "done" : "failed";
14723
- appendStatus(id, lane, verb, merged ? `the PR merged: ${ref.url}` : `the PR closed without merge: ${ref.url}`);
14724
- if (!readEvidence(id, lane).prUrl) mergeEvidence(id, lane, { prUrl: ref.url });
14725
- out.push({ id, lane, pr: ref.key, verb });
14726
- 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}`);
14727
14936
  }
14728
14937
  }
14729
14938
  return out;
14730
14939
  }
14731
14940
 
14941
+ // apps/cli/src/pr-stack.ts
14942
+ init_dist2();
14943
+ import { spawnSync as spawnSync11 } from "node:child_process";
14944
+ import * as fs65 from "node:fs";
14945
+ import * as path53 from "node:path";
14946
+ var defaultRun = (cmd, args, cwd) => {
14947
+ const res = spawnSync11(cmd, args, { cwd, encoding: "utf8", timeout: cmd === "gh" ? 1e4 : 3e3, windowsHide: true });
14948
+ return { status: res.error ? null : res.status, stdout: res.stdout ?? "" };
14949
+ };
14950
+ var MAX_CHAIN_BRANCHES = 12;
14951
+ function ghStackPrs(cwd, keys, run4) {
14952
+ for (const which of ["--git-dir", "--git-common-dir"]) {
14953
+ const res = run4("git", ["rev-parse", which], cwd);
14954
+ if (res.status !== 0 || !res.stdout.trim()) continue;
14955
+ let state;
14956
+ try {
14957
+ state = JSON.parse(fs65.readFileSync(path53.join(path53.resolve(cwd, res.stdout.trim()), "gh-stack"), "utf8"));
14958
+ } catch {
14959
+ continue;
14960
+ }
14961
+ for (const stack of state.stacks ?? []) {
14962
+ const refs = (stack.branches ?? []).map((b) => b.pullRequest?.url ? parsePrRef(b.pullRequest.url) : void 0).filter((r) => !!r);
14963
+ if (refs.some((r) => keys.has(r.key))) return refs;
14964
+ }
14965
+ }
14966
+ return [];
14967
+ }
14968
+ function baseChainPrs(reported, branches, cwd, run4) {
14969
+ if (branches.length < 2 || reported.length === 0) return [];
14970
+ const repo = `${reported[0].owner}/${reported[0].repo}`;
14971
+ const byHead = /* @__PURE__ */ new Map();
14972
+ for (const branch of branches.slice(0, MAX_CHAIN_BRANCHES)) {
14973
+ const res = run4("gh", ["pr", "view", branch, "--repo", repo, "--json", "url,headRefName,baseRefName"], cwd);
14974
+ if (res.status !== 0) continue;
14975
+ try {
14976
+ const view = JSON.parse(res.stdout);
14977
+ const ref = view.url ? parsePrRef(view.url) : void 0;
14978
+ if (ref && view.headRefName === branch && view.baseRefName) byHead.set(branch, { ref, head: branch, base: view.baseRefName });
14979
+ } catch {
14980
+ }
14981
+ }
14982
+ const keys = new Set(reported.map((r) => r.key));
14983
+ const seen = /* @__PURE__ */ new Set();
14984
+ const todo = [...byHead.values()].filter((n) => keys.has(n.ref.key));
14985
+ const out = [];
14986
+ while (todo.length > 0) {
14987
+ const n = todo.pop();
14988
+ if (seen.has(n.head)) continue;
14989
+ seen.add(n.head);
14990
+ out.push(n);
14991
+ const below = byHead.get(n.base);
14992
+ if (below) todo.push(below);
14993
+ for (const above of byHead.values()) if (above.base === n.head) todo.push(above);
14994
+ }
14995
+ const heads = new Set(out.map((n) => n.head));
14996
+ const ordered = [];
14997
+ let next = out.find((n) => !heads.has(n.base));
14998
+ while (next && !ordered.includes(next)) {
14999
+ ordered.push(next);
15000
+ const head = next.head;
15001
+ next = out.find((n) => n.base === head);
15002
+ }
15003
+ for (const n of out) if (!ordered.includes(n)) ordered.push(n);
15004
+ return ordered.map((n) => n.ref);
15005
+ }
15006
+ function stackPrUrls(reported, opts) {
15007
+ const run4 = opts.run ?? defaultRun;
15008
+ const refs = reported.map((u) => parsePrRef(u)).filter((r) => !!r);
15009
+ const out = /* @__PURE__ */ new Map();
15010
+ const add = (r) => {
15011
+ if (!out.has(r.key)) out.set(r.key, r.url);
15012
+ };
15013
+ refs.forEach(add);
15014
+ if (refs.length === 0) return [];
15015
+ try {
15016
+ const keys = new Set(refs.map((r) => r.key));
15017
+ ghStackPrs(opts.cwd, keys, run4).forEach(add);
15018
+ baseChainPrs(refs, opts.branches ?? [], opts.cwd, run4).forEach(add);
15019
+ } catch {
15020
+ }
15021
+ return [...out.values()];
15022
+ }
15023
+
14732
15024
  // apps/cli/src/soak-worktree.ts
14733
15025
  init_dist2();
14734
15026
  init_dist3();
14735
- import * as fs65 from "node:fs";
14736
- import { spawnSync as spawnSync11 } from "node:child_process";
15027
+ import * as fs66 from "node:fs";
15028
+ import { spawnSync as spawnSync12 } from "node:child_process";
14737
15029
  function branchExists(repo, branch) {
14738
- 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;
14739
15031
  }
14740
15032
  async function createSoakWorktree(opts) {
14741
15033
  const need = opts.minFreeGB * GB;
@@ -14752,7 +15044,7 @@ async function createSoakWorktree(opts) {
14752
15044
  );
14753
15045
  }
14754
15046
  }
14755
- const taken = (id2) => branchExists(opts.repo, `lobstah/soak-${id2}`) || fs65.existsSync(worktreePath(`soak-${id2}`));
15047
+ const taken = (id2) => branchExists(opts.repo, `lobstah/soak-${id2}`) || fs66.existsSync(worktreePath(`soak-${id2}`));
14756
15048
  let trapId = opts.trapId ?? newTrapId();
14757
15049
  if (opts.trapId !== void 0 && taken(trapId)) {
14758
15050
  throw new Error(`could not create a worktree for repo ${opts.repoKey}: soak-${trapId} or its branch already exists`);
@@ -14839,14 +15131,14 @@ async function setTerminalTitle(win, title, options = {}) {
14839
15131
 
14840
15132
  // apps/cli/src/beat.ts
14841
15133
  init_dist2();
14842
- import * as fs66 from "node:fs";
14843
- import * as path53 from "node:path";
15134
+ import * as fs67 from "node:fs";
15135
+ import * as path54 from "node:path";
14844
15136
 
14845
15137
  // apps/cli/src/beat-pr.ts
14846
15138
  init_dist2();
14847
- import { spawnSync as spawnSync12 } from "node:child_process";
14848
- var defaultRun = (cmd, args, cwd) => {
14849
- 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 });
14850
15142
  return { status: res.error ? null : res.status, stdout: res.stdout ?? "" };
14851
15143
  };
14852
15144
  var PR_PROBE_INTERVAL_MS = 6e4;
@@ -14854,7 +15146,7 @@ function probeTrapPr(reg, opts = {}) {
14854
15146
  if (!hasOpenCatch(reg)) return { recorded: false, reason: "no-catch" };
14855
15147
  const id = reg.claimed;
14856
15148
  const now2 = opts.now ?? Date.now();
14857
- const run4 = opts.run ?? defaultRun;
15149
+ const run4 = opts.run ?? defaultRun2;
14858
15150
  const last = readTrapPrProbe(reg.trapId);
14859
15151
  const sameDispatch = last?.dispatch === id;
14860
15152
  if (sameDispatch && now2 - (Date.parse(last.checkedAt) || 0) < PR_PROBE_INTERVAL_MS) return { recorded: false, reason: "throttled" };
@@ -14871,6 +15163,12 @@ function probeTrapPr(reg, opts = {}) {
14871
15163
  const upstream = git10(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
14872
15164
  const tracked = upstream.status === 0 ? upstream.stdout.trim() : "";
14873
15165
  if (!tracked || tracked.endsWith(`/${trunk}`)) return { recorded: false, reason: "no-upstream" };
15166
+ const claimedAt = Date.parse(readSessionClaim(id, "work")?.at ?? "");
15167
+ if (Number.isFinite(claimedAt)) {
15168
+ const committed = git10(["log", "-1", "--format=%ct", "HEAD"]);
15169
+ const at = committed.status === 0 ? Number(committed.stdout.trim()) * 1e3 : NaN;
15170
+ if (!(at >= claimedAt)) return { recorded: false, reason: "before-claim" };
15171
+ }
14874
15172
  const view = run4("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], reg.worktree);
14875
15173
  const prUrl = view.status === 0 ? view.stdout.trim() : "";
14876
15174
  if (!parsePrRef(prUrl)) return { recorded: false, reason: "no-pr" };
@@ -14884,10 +15182,10 @@ function probeTrapPr(reg, opts = {}) {
14884
15182
  // apps/cli/src/beat.ts
14885
15183
  function logError(err) {
14886
15184
  try {
14887
- const dir = path53.join(lobstahHome(), "logs");
14888
- fs66.mkdirSync(dir, { recursive: true });
15185
+ const dir = path54.join(lobstahHome(), "logs");
15186
+ fs67.mkdirSync(dir, { recursive: true });
14889
15187
  const msg = err instanceof Error ? err.message : String(err);
14890
- 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)}
14891
15189
  `);
14892
15190
  } catch {
14893
15191
  }
@@ -14984,7 +15282,7 @@ var COMMANDS = {
14984
15282
  cancel: { flags: { "--session": { value: "<id>" } }, positionals: "<uuid>" },
14985
15283
  report: {
14986
15284
  flags: {
14987
- "--pr": { value: "<url>" },
15285
+ "--pr": { value: "<url>", repeatable: true },
14988
15286
  "--no-watch": {},
14989
15287
  "--waiting-on": { value: "review|pr|deploy|person|external" },
14990
15288
  "--link": { value: "<url>" },
@@ -15335,15 +15633,15 @@ function detectHarness(opts) {
15335
15633
 
15336
15634
  // apps/cli/src/watchers.ts
15337
15635
  init_dist2();
15338
- import * as fs67 from "node:fs";
15339
- import * as path54 from "node:path";
15636
+ import * as fs68 from "node:fs";
15637
+ import * as path55 from "node:path";
15340
15638
  var STALE_MS = 5e3;
15341
15639
  var HEARTBEAT_MS = 1500;
15342
- var watcherDir = () => path54.join(lobstahHome(), "watchers");
15343
- 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`);
15344
15642
  function liveRegistration(sessionId, now2 = Date.now()) {
15345
15643
  try {
15346
- const w = JSON.parse(fs67.readFileSync(watcherFile(sessionId), "utf8"));
15644
+ const w = JSON.parse(fs68.readFileSync(watcherFile(sessionId), "utf8"));
15347
15645
  const beat = Date.parse(w.heartbeatAt);
15348
15646
  if (w.sessionId !== sessionId || !Number.isInteger(w.pid) || w.pid <= 0 || !Number.isFinite(beat) || now2 - beat > STALE_MS) return void 0;
15349
15647
  return w;
@@ -15360,17 +15658,17 @@ async function awaitWatcher(sessionId, kind, graceMs, trapId, pollMs = 100) {
15360
15658
  for (; ; ) {
15361
15659
  const w = liveWatcher(sessionId, kind, trapId);
15362
15660
  if (w || Date.now() >= deadline) return w;
15363
- await new Promise((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()))));
15364
15662
  }
15365
15663
  }
15366
15664
  function armWatcher(sessionId, kind, trapId) {
15367
- fs67.mkdirSync(watcherDir(), { recursive: true });
15665
+ fs68.mkdirSync(watcherDir(), { recursive: true });
15368
15666
  const file = watcherFile(sessionId);
15369
15667
  const own = () => ({ sessionId, kind, pid: process.pid, heartbeatAt: (/* @__PURE__ */ new Date()).toISOString(), ...trapId ? { trapId } : {} });
15370
15668
  let claimed = false;
15371
15669
  for (let attempt = 0; attempt < 3; attempt++) {
15372
15670
  try {
15373
- fs67.writeFileSync(file, `${JSON.stringify(own())}
15671
+ fs68.writeFileSync(file, `${JSON.stringify(own())}
15374
15672
  `, { flag: "wx" });
15375
15673
  claimed = true;
15376
15674
  break;
@@ -15379,7 +15677,7 @@ function armWatcher(sessionId, kind, trapId) {
15379
15677
  const live = liveRegistration(sessionId);
15380
15678
  if (live) throw new Error(`watcher already armed for session ${sessionId} (pid ${live.pid})`);
15381
15679
  try {
15382
- fs67.rmSync(file);
15680
+ fs68.rmSync(file);
15383
15681
  } catch {
15384
15682
  }
15385
15683
  }
@@ -15387,7 +15685,7 @@ function armWatcher(sessionId, kind, trapId) {
15387
15685
  if (!claimed) throw new Error(`could not arm watcher for session ${sessionId}`);
15388
15686
  const ownsFile = () => {
15389
15687
  try {
15390
- return JSON.parse(fs67.readFileSync(file, "utf8")).pid === process.pid;
15688
+ return JSON.parse(fs68.readFileSync(file, "utf8")).pid === process.pid;
15391
15689
  } catch {
15392
15690
  return false;
15393
15691
  }
@@ -15396,12 +15694,12 @@ function armWatcher(sessionId, kind, trapId) {
15396
15694
  if (!ownsFile()) return;
15397
15695
  const tmp = `${file}.tmp-${process.pid}`;
15398
15696
  try {
15399
- fs67.writeFileSync(tmp, `${JSON.stringify(own())}
15697
+ fs68.writeFileSync(tmp, `${JSON.stringify(own())}
15400
15698
  `);
15401
- fs67.renameSync(tmp, file);
15699
+ fs68.renameSync(tmp, file);
15402
15700
  } catch {
15403
15701
  try {
15404
- fs67.rmSync(tmp, { force: true });
15702
+ fs68.rmSync(tmp, { force: true });
15405
15703
  } catch {
15406
15704
  }
15407
15705
  }
@@ -15413,7 +15711,7 @@ function armWatcher(sessionId, kind, trapId) {
15413
15711
  if (stopped) return;
15414
15712
  stopped = true;
15415
15713
  clearInterval(timer);
15416
- if (ownsFile()) fs67.rmSync(file, { force: true });
15714
+ if (ownsFile()) fs68.rmSync(file, { force: true });
15417
15715
  process.off("exit", stop);
15418
15716
  process.off("SIGINT", interrupt);
15419
15717
  process.off("SIGTERM", terminate2);
@@ -15508,7 +15806,9 @@ work (humans and agents):
15508
15806
  hold ends when that dispatch ends
15509
15807
  watch release <key>|--all end a hold: a watch held by the per-cycle
15510
15808
  fork cap ([watch].maxForksPerCycle), by
15511
- watch hold, or by a cancelled repair
15809
+ watch hold, or by a cancelled repair;
15810
+ resume repairs stopped by
15811
+ [watch].maxRepairsWithoutProgress
15512
15812
 
15513
15813
  host processes:
15514
15814
  daemon [--interval <ms>] the supervisor: claims, worktrees, liveness,
@@ -15591,12 +15891,15 @@ lobstah man (orchestrator sessions \u2014 bare \`lobstah man\` prints the manual
15591
15891
  man wait (lobstah man).
15592
15892
 
15593
15893
  workers (dispatched agents; injected into every brief):
15594
- report <uuid> <verb> [--pr <url>] [--no-watch] [--report <file.md> [--attach <file> ...]] [--session <id>] [--] [note]
15894
+ report <uuid> <verb> [--pr <url> ...] [--no-watch] [--report <file.md> [--attach <file> ...]] [--session <id>] [--] [note]
15595
15895
  the validated status write path
15596
15896
  (${VERBS.join(" | ")}). --pr on any
15597
15897
  verb but failed records the PR and
15598
15898
  registers its pr: watch for this chain;
15599
- --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
15600
15903
  --report files a markdown page as the
15601
15904
  dispatch's report, images --attach'ed.
15602
15905
 
@@ -15792,7 +16095,7 @@ function emitWatchAttention(attns, sessionId) {
15792
16095
  function findLane(id) {
15793
16096
  for (const lane of ["work", "chore"]) {
15794
16097
  const d = laneDirs(lane);
15795
- if (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`))) {
15796
16099
  return lane;
15797
16100
  }
15798
16101
  }
@@ -15801,7 +16104,7 @@ function findLane(id) {
15801
16104
  function anythingInFlight() {
15802
16105
  const dispatches = ["work", "chore"].some((lane) => {
15803
16106
  const dirs = laneDirs(lane);
15804
- return [dirs.queue, dirs.active].some((dir) => fs72.readdirSync(dir).some((file) => !file.startsWith(".")));
16107
+ return [dirs.queue, dirs.active].some((dir) => fs73.readdirSync(dir).some((file) => !file.startsWith(".")));
15805
16108
  });
15806
16109
  return dispatches || listWatches().some((watch2) => watch2.owner === "man");
15807
16110
  }
@@ -15818,7 +16121,7 @@ function dispatchChain(target) {
15818
16121
  for (const lane of ["work", "chore"]) {
15819
16122
  const dirs = laneDirs(lane);
15820
16123
  for (const bucket of ["queue", "active", "done"]) {
15821
- for (const entry of fs72.readdirSync(dirs[bucket])) {
16124
+ for (const entry of fs73.readdirSync(dirs[bucket])) {
15822
16125
  if (entry.startsWith(".")) continue;
15823
16126
  const id = bucket === "queue" ? entry.replace(/\.json$/, "") : entry;
15824
16127
  const descriptor = storedDescriptor(id, lane);
@@ -15840,7 +16143,7 @@ function dispatchChain(target) {
15840
16143
  }
15841
16144
  function liveChainMember(member) {
15842
16145
  if (member.bucket !== "active") return false;
15843
- const dir = path59.join(laneDirs(member.lane).active, member.id);
16146
+ const dir = path60.join(laneDirs(member.lane).active, member.id);
15844
16147
  const claim = readSessionClaim(member.id, member.lane);
15845
16148
  if (claim?.by.startsWith("wt:")) {
15846
16149
  const reg = readTrap(claim.by.slice("wt:".length));
@@ -15848,7 +16151,7 @@ function liveChainMember(member) {
15848
16151
  }
15849
16152
  if (isParked(member.id, member.lane)) return true;
15850
16153
  try {
15851
- const runner = JSON.parse(fs72.readFileSync(path59.join(dir, "runner.json"), "utf8"));
16154
+ const runner = JSON.parse(fs73.readFileSync(path60.join(dir, "runner.json"), "utf8"));
15852
16155
  return pidAlive2(runner.pid, runner.processStartTime);
15853
16156
  } catch {
15854
16157
  return false;
@@ -15857,7 +16160,7 @@ function liveChainMember(member) {
15857
16160
  function rowsFor(lane, bucket) {
15858
16161
  const dir = laneDirs(lane)[bucket];
15859
16162
  const staleSecs = wedgeSecs2();
15860
- const entries = 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);
15861
16164
  const sliced = bucket === "done" ? entries.slice(0, 10) : entries;
15862
16165
  return sliced.map(({ f, m }) => {
15863
16166
  const id = f.replace(/\.json$/, "");
@@ -15882,7 +16185,7 @@ function rowsFor(lane, bucket) {
15882
16185
  function dispatchTrap(id, lane, names) {
15883
16186
  const claimedBy = (bucket) => {
15884
16187
  try {
15885
- return JSON.parse(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;
15886
16189
  } catch {
15887
16190
  return void 0;
15888
16191
  }
@@ -16075,7 +16378,7 @@ ${usageFor("trap")}`);
16075
16378
  const d = {
16076
16379
  id: opt("--id") ?? randomUUID6(),
16077
16380
  repo,
16078
- brief: briefText ?? fs72.readFileSync(briefFile, "utf8"),
16381
+ brief: briefText ?? fs73.readFileSync(briefFile, "utf8"),
16079
16382
  harness: opt("--harness"),
16080
16383
  model: opt("--model"),
16081
16384
  effort: opt("--effort"),
@@ -16132,7 +16435,7 @@ ${usageFor("trap")}`);
16132
16435
  const since = queuedAt(id, lane);
16133
16436
  const claimedAt = claim?.at;
16134
16437
  const state = displayState({ log, lastEventAt: lastEventAt(id, lane), queued: since !== void 0, claimedAt });
16135
- const live = fs72.existsSync(path59.join(laneDirs(lane).active, id));
16438
+ const live = fs73.existsSync(path60.join(laneDirs(lane).active, id));
16136
16439
  const activity = live ? activityView(readActivity(id, lane), wedgeSecs2()) : void 0;
16137
16440
  const waitingNow = live ? waitingView(log.at(-1)) : void 0;
16138
16441
  console.log(
@@ -16172,8 +16475,8 @@ ${usageFor("trap")}`);
16172
16475
  if (!id) throw new Error("logs requires a dispatch id");
16173
16476
  const lane = findLane(id);
16174
16477
  const file = eventsPath(id, lane);
16175
- if (fs72.existsSync(file)) {
16176
- const raw = fs72.readFileSync(file, "utf8");
16478
+ if (fs73.existsSync(file)) {
16479
+ const raw = fs73.readFileSync(file, "utf8");
16177
16480
  const lines = raw.split("\n").filter((l) => l.length > 0);
16178
16481
  const LIMIT = 50;
16179
16482
  if (!has("--full") && lines.length > LIMIT) {
@@ -16184,15 +16487,15 @@ ${usageFor("trap")}`);
16184
16487
  }
16185
16488
  }
16186
16489
  if (has("--follow")) {
16187
- let size = fs72.existsSync(file) ? fs72.statSync(file).size : 0;
16490
+ let size = fs73.existsSync(file) ? fs73.statSync(file).size : 0;
16188
16491
  setInterval(() => {
16189
- if (!fs72.existsSync(file)) return;
16190
- const now2 = fs72.statSync(file).size;
16492
+ if (!fs73.existsSync(file)) return;
16493
+ const now2 = fs73.statSync(file).size;
16191
16494
  if (now2 > size) {
16192
- const fd = fs72.openSync(file, "r");
16495
+ const fd = fs73.openSync(file, "r");
16193
16496
  const buf = Buffer.alloc(now2 - size);
16194
- fs72.readSync(fd, buf, 0, buf.length, size);
16195
- fs72.closeSync(fd);
16497
+ fs73.readSync(fd, buf, 0, buf.length, size);
16498
+ fs73.closeSync(fd);
16196
16499
  process.stdout.write(buf.toString("utf8"));
16197
16500
  size = now2;
16198
16501
  }
@@ -16268,10 +16571,10 @@ ${[text, block].filter(Boolean).join("\n\n")}`,
16268
16571
  }
16269
16572
  let address;
16270
16573
  let headless = false;
16271
- const claimFile = 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");
16272
16575
  let claimedBy;
16273
16576
  try {
16274
- claimedBy = JSON.parse(fs72.readFileSync(claimFile, "utf8")).by;
16577
+ claimedBy = JSON.parse(fs73.readFileSync(claimFile, "utf8")).by;
16275
16578
  } catch {
16276
16579
  claimedBy = readEvidence(origin.id, origin.lane).deliveredTo;
16277
16580
  }
@@ -16302,7 +16605,8 @@ ${text}`, from);
16302
16605
  if (!id || !verb) throw new Error(`report requires an id and a verb (${VERBS.join("|")})`);
16303
16606
  const lane = findLane(id);
16304
16607
  const note = rest.join(" ") || void 0;
16305
- const prUrl = opt("--pr");
16608
+ const reportedPrs = values("--pr");
16609
+ const prUrl = reportedPrs[0];
16306
16610
  const noWatch = has("--no-watch");
16307
16611
  const waiting = { waitingOn: opt("--waiting-on"), link: opt("--link"), until: opt("--until") };
16308
16612
  const saysWaiting = waiting.waitingOn !== void 0 || waiting.link !== void 0 || waiting.until !== void 0;
@@ -16354,24 +16658,35 @@ ${usageFor("report")}`);
16354
16658
  if (verb === "done") {
16355
16659
  const claim = readSessionClaim(id, lane);
16356
16660
  let ownCheckout = false;
16357
- if (claim?.by.startsWith("wt:") && claim.worktree && fs72.existsSync(claim.worktree)) {
16358
- const rel = path59.relative(canon(claim.worktree), canon(process.cwd()));
16359
- 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);
16360
16664
  const who = callerSession(opt("--session"))?.id;
16361
16665
  const manning = readTrap(claim.by.slice("wt:".length))?.sessionId;
16362
16666
  ownCheckout = inside || who !== void 0 && (who === claim.sessionId || who === manning);
16363
16667
  }
16364
16668
  if (ownCheckout) {
16365
- const git10 = (args2) => spawnSync13("git", args2, { cwd: claim.worktree, encoding: "utf8", timeout: 1e4 });
16669
+ const git10 = (args2) => spawnSync14("git", args2, { cwd: claim.worktree, encoding: "utf8", timeout: 1e4 });
16366
16670
  const head = git10(["rev-parse", "HEAD"]);
16367
16671
  const branch = git10(["branch", "--show-current"]);
16368
16672
  if (head.status === 0 && branch.status === 0)
16369
16673
  mergeEvidence(id, lane, { commits: [head.stdout.trim()], branch: branch.stdout.trim() });
16370
16674
  }
16371
16675
  }
16372
- 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
+ }
16373
16687
  const gatesNamed = recordReportedGates(id, lane, values("--human-gate"), prUrl);
16374
- 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);
16375
16690
  console.log(
16376
16691
  toonKV({
16377
16692
  id,
@@ -16382,7 +16697,9 @@ ${usageFor("report")}`);
16382
16697
  ...entry.link ? { link: entry.link } : {},
16383
16698
  ...entry.until ? { until: entry.until } : {},
16384
16699
  ...prUrl ? { prUrl } : {},
16385
- ...prWatch ? { watch: prWatch.key } : {},
16700
+ ...reportPrs.length > 1 ? { prs: reportPrs.join(", ") } : {},
16701
+ ...prWatches.length ? { watch: prWatches.map((w) => w.key).join(", ") } : {},
16702
+ ...warning ? { warning } : {},
16386
16703
  ...gatesNamed.length ? { humanGates: gatesNamed.join(", ") } : {},
16387
16704
  ...filed ? { report: reportMarkdownPath(filed.key), reportKey: filed.key, reportTitle: filed.title } : {}
16388
16705
  })
@@ -16429,14 +16746,14 @@ ${usageFor("report")}`);
16429
16746
  );
16430
16747
  }
16431
16748
  const worktree = dispatchWorktree(id, lane).path;
16432
- const cwd = fs72.existsSync(worktree) ? worktree : process.cwd();
16749
+ const cwd = fs73.existsSync(worktree) ? worktree : process.cwd();
16433
16750
  const invocation = harness === "codex" ? codexInvocation(["resume", sessionId]) : { file: "claude", argv: ["--resume", sessionId] };
16434
16751
  if (!invocation) throw new Error("no codex CLI found \u2014 neither on PATH nor vendored by @openai/codex-sdk");
16435
16752
  if (has("--print")) {
16436
16753
  console.log(toonKV({ id, harness, sessionId, cwd, command: `${invocation.file} ${invocation.argv.join(" ")}` }));
16437
16754
  break;
16438
16755
  }
16439
- const res = spawnSync13(invocation.file, invocation.argv, { cwd, stdio: "inherit" });
16756
+ const res = spawnSync14(invocation.file, invocation.argv, { cwd, stdio: "inherit" });
16440
16757
  if (res.error) throw new Error(`could not launch ${invocation.file}: ${res.error.message}`);
16441
16758
  break;
16442
16759
  }
@@ -16447,10 +16764,10 @@ ${usageFor("report")}`);
16447
16764
  gateHelm(callerSession(opt("--session")));
16448
16765
  }
16449
16766
  const lane = findLane(id);
16450
- const activeDir = path59.join(laneDirs(lane).active, id);
16451
- if (!fs72.existsSync(activeDir)) throw new Error(`${id} is not active \u2014 swap only applies to in-flight dispatches`);
16452
- const descFile = path59.join(activeDir, "descriptor.json");
16453
- 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"));
16454
16771
  const fromHarness = resolveSessionHarness(id, loadConfig(), lane).harness ?? descriptor.harness ?? "default";
16455
16772
  for (const key of ["harness", "model", "effort"]) {
16456
16773
  const v = opt(`--${key}`);
@@ -16458,14 +16775,14 @@ ${usageFor("report")}`);
16458
16775
  }
16459
16776
  if (opt("--harness")) descriptor.harnessExplicit = true;
16460
16777
  if (opt("--model")) descriptor.modelExplicit = true;
16461
- fs72.writeFileSync(descFile, JSON.stringify(descriptor, null, 2));
16778
+ fs73.writeFileSync(descFile, JSON.stringify(descriptor, null, 2));
16462
16779
  const worktree = dispatchWorktree(id, lane).path;
16463
- fs72.writeFileSync(path59.join(activeDir, "handoff"), handoffNote(fromHarness, worktreeProgress(worktree)));
16464
- const runnerFile = path59.join(activeDir, "runner.json");
16465
- if (fs72.existsSync(runnerFile)) {
16466
- const runner = JSON.parse(fs72.readFileSync(runnerFile, "utf8"));
16780
+ fs73.writeFileSync(path60.join(activeDir, "handoff"), handoffNote(fromHarness, worktreeProgress(worktree)));
16781
+ const runnerFile = path60.join(activeDir, "runner.json");
16782
+ if (fs73.existsSync(runnerFile)) {
16783
+ const runner = JSON.parse(fs73.readFileSync(runnerFile, "utf8"));
16467
16784
  if (pidAlive2(runner.pid, runner.processStartTime)) killGroup(runner.pid, "SIGKILL");
16468
- fs72.unlinkSync(runnerFile);
16785
+ fs73.unlinkSync(runnerFile);
16469
16786
  }
16470
16787
  appendStatus(id, lane, "working", `swapped to ${descriptor.harness ?? "default"} \u2014 awaiting respawn`);
16471
16788
  console.log(toonKV({ id, harness: descriptor.harness ?? "default", model: descriptor.model, swapped: true }));
@@ -16489,6 +16806,12 @@ ${usageFor("report")}`);
16489
16806
  }),
16490
16807
  branch: ev.branch,
16491
16808
  prUrl: ev.prUrl,
16809
+ ...dispatchPrUrls(ev).length > 1 ? {
16810
+ prs: dispatchPrUrls(ev).map((u) => {
16811
+ const record = readPr(parsePrRef(u).key);
16812
+ return record ? `${prBadge(record).text} ${u}` : u;
16813
+ }).join("; ")
16814
+ } : {},
16492
16815
  sessionId: ev.sessionId,
16493
16816
  ...worktreeView(id, lane),
16494
16817
  ...dispatchTrap(id, lane, names),
@@ -16520,7 +16843,7 @@ ${usageFor("report")}`);
16520
16843
  console.log(`commits[${ev.commits.length}]:`);
16521
16844
  for (const c of ev.commits) console.log(` ${c}`);
16522
16845
  }
16523
- if (fs72.existsSync(path59.join(laneDirs(lane).done, id))) {
16846
+ if (fs73.existsSync(path60.join(laneDirs(lane).done, id))) {
16524
16847
  console.log(toonHelp([`lobstah send ${id} "<instruction>" (wakes it as a follow-up in its worktree)`]));
16525
16848
  }
16526
16849
  const attachments = storedDescriptor(id, lane)?.attachments ?? [];
@@ -16808,7 +17131,7 @@ ${errs.map((e) => `- ${e}`).join("\n")}`);
16808
17131
  const keys = holdCancelledRepair(id);
16809
17132
  return keys.length > 0 ? { repairHeld: `${keys.join(", ")} (lobstah watch release <key> frees it)` } : {};
16810
17133
  };
16811
- if (fs72.existsSync(path59.join(laneDirs(lane).active, id))) {
17134
+ if (fs73.existsSync(path60.join(laneDirs(lane).active, id))) {
16812
17135
  requestCancel(id, lane);
16813
17136
  console.log(toonKV({ id, cancel: "requested", note: "the claimant (daemon or trap) winds it down at its next check", ...repairHeld() }));
16814
17137
  break;
@@ -16817,7 +17140,7 @@ ${errs.map((e) => `- ${e}`).join("\n")}`);
16817
17140
  console.log(toonKV({ id, cancel: "finalized", note: "cancelled before claim \u2014 recorded as failed in done/", ...repairHeld() }));
16818
17141
  break;
16819
17142
  }
16820
- if (fs72.existsSync(path59.join(laneDirs(lane).active, id))) {
17143
+ if (fs73.existsSync(path60.join(laneDirs(lane).active, id))) {
16821
17144
  requestCancel(id, lane);
16822
17145
  console.log(toonKV({ id, cancel: "requested", note: "claimed while cancelling \u2014 the claimant winds it down", ...repairHeld() }));
16823
17146
  break;
@@ -16941,20 +17264,20 @@ ${usageFor("man:wait")}`);
16941
17264
  }
16942
17265
  }
16943
17266
  case "man:init": {
16944
- const file = has("--global") ? 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");
16945
17268
  let existing;
16946
17269
  try {
16947
- existing = JSON.parse(fs72.readFileSync(file, "utf8"));
17270
+ existing = JSON.parse(fs73.readFileSync(file, "utf8"));
16948
17271
  } catch {
16949
17272
  existing = void 0;
16950
17273
  }
16951
17274
  const { settings, changed } = mergeHaulHook(existing);
16952
17275
  if (changed) {
16953
- fs72.mkdirSync(path59.dirname(file), { recursive: true });
16954
- 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)}
16955
17278
  `);
16956
17279
  }
16957
- if (has("--marker")) fs72.writeFileSync(".lobstah-man", "");
17280
+ if (has("--marker")) fs73.writeFileSync(".lobstah-man", "");
16958
17281
  console.log(
16959
17282
  toonKV({
16960
17283
  hook: "lobstah man haul",
@@ -17000,7 +17323,7 @@ ${usageFor("man:wait")}`);
17000
17323
  if (helm) releaseHeldQuestions(helm);
17001
17324
  const cfgHaul = loadConfig();
17002
17325
  if (!helm && liveHelms(cfgHaul.helm.ttlSecs * 1e3).length > 0) break;
17003
- if (!helm && process.env.LOBSTAH_MAN !== "1" && !fs72.existsSync(".lobstah-man")) break;
17326
+ if (!helm && process.env.LOBSTAH_MAN !== "1" && !fs73.existsSync(".lobstah-man")) break;
17004
17327
  const dueDigest = () => helm ? dueHelmDigest(helm, cfgHaul.helm.reportSecs) : void 0;
17005
17328
  const blockDigest = (d) => {
17006
17329
  advanceCursor(helm.grounds, d.now);
@@ -17217,7 +17540,7 @@ ${behind}` : "");
17217
17540
  `this session already mans trap wt:${mine.trapId} (repo ${mine.repo ?? "none"}) at ${mine.worktree}. Sign it off with \`lobstah stow\` before you soak for repo ${repoFlag}.`
17218
17541
  );
17219
17542
  }
17220
- if (!fs72.existsSync(mine.worktree)) {
17543
+ if (!fs73.existsSync(mine.worktree)) {
17221
17544
  throw new Error(
17222
17545
  `this session's trap wt:${mine.trapId} is anchored at ${mine.worktree}, which no longer exists. Run \`lobstah stow\`, then soak again.`
17223
17546
  );
@@ -17293,8 +17616,8 @@ ${usageFor("soak")}`
17293
17616
  if (reservation) dropReservation(reservation.trapId);
17294
17617
  const titled = reg.name ? await setTerminalTitle(reg.window, reg.name) : void 0;
17295
17618
  const inside = (() => {
17296
- const rel = path59.relative(reg.worktree, canon(process.cwd()));
17297
- 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);
17298
17621
  })();
17299
17622
  const sessionFlag = inside ? "" : ` --session ${sessionId}`;
17300
17623
  console.log(
@@ -17383,7 +17706,7 @@ ${usageFor("soak")}`
17383
17706
  const anchor = wtDir !== void 0 ? readTrapAnchor(wtDir) : void 0;
17384
17707
  const created = reg?.createdWorktree === true || anchor?.createdBy === "soak";
17385
17708
  let worktreeOut = {};
17386
- if (wtDir !== void 0 && fs72.existsSync(wtDir)) {
17709
+ if (wtDir !== void 0 && fs73.existsSync(wtDir)) {
17387
17710
  const keepReason = has("--keep") ? "--keep" : sessionEnd ? "the session ended; the SessionEnd hook keeps the worktree" : !created ? "soak did not create this worktree; stow leaves it in place" : void 0;
17388
17711
  if (keepReason) {
17389
17712
  worktreeOut = { worktree: "kept", path: wtDir, reason: keepReason };
@@ -17459,7 +17782,7 @@ ${usageFor("pick")}`);
17459
17782
  restarted: res.command,
17460
17783
  ...kind === "daemon" ? { headless: usage.headless, trapCatches: `${usage.traps} unaffected` } : {},
17461
17784
  ...active > 0 ? { interrupted: `${active} headless dispatch(es) (--force)` } : {},
17462
- ...hb ? { running: `v${hb.version ?? "?"}`, ...hb.pid !== void 0 ? { pid: hb.pid } : {} } : { heartbeat: `none within ${RESTART_WAIT_MS / 1e3}s \u2014 see ${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")}` }
17463
17786
  })
17464
17787
  );
17465
17788
  if (!hb) process.exitCode = 1;
@@ -17470,6 +17793,7 @@ ${usageFor("pick")}`);
17470
17793
  culler: cliCuller,
17471
17794
  prWatches: (now2, log) => {
17472
17795
  observeDispatchPrWatches(pollSecs(), now2);
17796
+ observeWaitedPrs({ everySecs: pollSecs(), now: now2 });
17473
17797
  finishResolvedWaits(log);
17474
17798
  deliverPrRepairs(log, loadConfig().watch.maxForksPerCycle);
17475
17799
  stampRepairerBeat(now2);
@@ -17570,8 +17894,8 @@ ${usageFor("pet")}`);
17570
17894
  break;
17571
17895
  }
17572
17896
  const server = serveGlass(port);
17573
- await new Promise((resolve13, reject) => {
17574
- server.once("listening", () => resolve13());
17897
+ await new Promise((resolve14, reject) => {
17898
+ server.once("listening", () => resolve14());
17575
17899
  server.once("error", reject);
17576
17900
  });
17577
17901
  console.log(
@@ -17607,7 +17931,7 @@ ${usageFor("pet")}`);
17607
17931
  path: r.path,
17608
17932
  trunk: r.trunk,
17609
17933
  pickup: r.pickup ?? false,
17610
- exists: fs72.existsSync(r.path)
17934
+ exists: fs73.existsSync(r.path)
17611
17935
  }));
17612
17936
  console.log(toonTable("repos", rows, ["key", "path", "trunk", "pickup", "exists"]));
17613
17937
  break;
@@ -17698,7 +18022,8 @@ ${usageFor("pet")}`);
17698
18022
  const key = pos[1];
17699
18023
  if (!key && !has("--all")) throw new Error("watch release requires a key or --all");
17700
18024
  const released = releaseHeldWatches(has("--all") ? void 0 : key);
17701
- 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(", ") } : {} }));
17702
18027
  break;
17703
18028
  }
17704
18029
  if (sub === "rm") {
@@ -17720,7 +18045,7 @@ ${usageFor("pet")}`);
17720
18045
  break;
17721
18046
  }
17722
18047
  case "init": {
17723
- if (!fs72.existsSync(configPath())) {
18048
+ if (!fs73.existsSync(configPath())) {
17724
18049
  const exampleRepo = has("--scan") ? "" : `[repos.example]
17725
18050
  path = "~/src/example"
17726
18051
  trunk = "main"
@@ -17729,7 +18054,7 @@ trunk = "main"
17729
18054
  # pickup = true # opt into [pickup.github] multi-repo tracker pickup
17730
18055
 
17731
18056
  `;
17732
- fs72.writeFileSync(
18057
+ fs73.writeFileSync(
17733
18058
  configPath(),
17734
18059
  `# lobstah workspace definitions \u2014 the descriptor's repo key resolves here.
17735
18060
  # Top-level keys must come before any [section].
@@ -17767,12 +18092,12 @@ wallClockSecs = 3600
17767
18092
  let reference = "https://github.com/aequitas-labs/lobstah/blob/main/docs/configuration.md";
17768
18093
  try {
17769
18094
  const local = fileURLToPath6(new URL("../docs/configuration.md", import.meta.url));
17770
- if (fs72.existsSync(local)) reference = local;
18095
+ if (fs73.existsSync(local)) reference = local;
17771
18096
  } catch {
17772
18097
  }
17773
18098
  console.log(
17774
18099
  toonKV({
17775
- home: path59.dirname(configPath()),
18100
+ home: path60.dirname(configPath()),
17776
18101
  config: configPath(),
17777
18102
  ...added.length > 0 ? { added: added.join(", ") } : {},
17778
18103
  ...unmarked.length > 0 ? { note: `none marked pickable \u2014 set pickup = true per [repos.*] (or rerun with --pickup)` } : {},