prreviewbuddy 0.80.3 → 0.82.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1274,22 +1274,115 @@ var AGENTS = {
1274
1274
  [GEMINI_AGENT_ID]: geminiAgent,
1275
1275
  [CODEX_AGENT_ID]: codexAgent
1276
1276
  };
1277
+ /**
1278
+ * Adapters this build ships and will not run, each with the sentence a person is shown instead.
1279
+ *
1280
+ * A parked id is still known: reviews it made keep their name and their history, telemetry still
1281
+ * recognises it, and a job queued under it is refused by name rather than treated as an agent this
1282
+ * build has never heard of. What it loses is everything that would start it. `agentById` throws
1283
+ * before an adapter is handed out, so no run site can reach one, and every list a person chooses
1284
+ * from is built from `AGENT_IDS`, which leaves it out.
1285
+ *
1286
+ * Gemini CLI, since 0.81.0. Its read-only boundary is not applied on current releases (the system
1287
+ * settings file that carried it is skipped unless root owns it), and Google has ended Gemini CLI
1288
+ * for individual accounts. `index.test.ts` holds a tripwire against unparking it before the adapter
1289
+ * enforces its boundary again.
1290
+ */
1291
+ var PARKED = { [GEMINI_AGENT_ID]: "Gemini CLI is turned off in this version of PR Review Buddy. Google has ended Gemini CLI access for individual accounts, and its read-only mode can't be guaranteed on current versions." };
1277
1292
  var DEFAULT_AGENT_ID = CLAUDE_AGENT_ID;
1278
1293
  /**
1279
1294
  * Every agent this build can run, for a surface that has to say which ones those are.
1280
1295
  *
1281
1296
  * Derived from the registry rather than written out, so a second adapter appears in the sentence a
1282
- * user reads by being registered, and nobody has to remember a list in another package.
1297
+ * user reads by being registered, and nobody has to remember a list in another package. Parked
1298
+ * adapters are left out, which is how they leave every picker, listing and help sentence at once.
1299
+ */
1300
+ var AGENT_IDS = Object.keys(AGENTS).filter((id) => !(id in PARKED));
1301
+ /**
1302
+ * Whether a string names an agent this build ships, answered by the registry rather than a list.
1303
+ *
1304
+ * True for a parked agent too. This answers "do we know this id", which is what a stored review, a
1305
+ * job record and telemetry need; "can it run" is `AGENT_IDS` and `agentById`.
1283
1306
  */
1284
- var AGENT_IDS = Object.keys(AGENTS);
1285
- /** Whether a string names an agent this build ships, answered by the registry rather than a list. */
1286
1307
  function isAgentId(value) {
1287
1308
  return value !== void 0 && value in AGENTS;
1288
1309
  }
1310
+ /**
1311
+ * Why this agent will not run in this build, or undefined when nothing stops it here.
1312
+ *
1313
+ * For a surface that has to tell a person something more useful than "unknown agent" when they
1314
+ * name one that is parked.
1315
+ */
1316
+ function parkedReason(id) {
1317
+ if (id === void 0 || !Object.hasOwn(PARKED, id)) return void 0;
1318
+ return `${PARKED[id]} Use ${runnableAgents().names} instead.`;
1319
+ }
1320
+ /**
1321
+ * The agents that can run, as a person reads them ("Claude Code or Codex") and as `--agent` takes
1322
+ * them ("`--agent claude` or `--agent codex`"), for a refusal that has to say what to do instead.
1323
+ *
1324
+ * Built from `AGENT_IDS` so no module outside this folder spells an agent out, and so the advice
1325
+ * stays true when an adapter is parked or added.
1326
+ */
1327
+ function runnableAgents() {
1328
+ const ids = [...AGENT_IDS].sort();
1329
+ return {
1330
+ names: orList(ids.map((id) => AGENTS[id].displayName)),
1331
+ flags: orList(ids.map((id) => `\`--agent ${id}\``))
1332
+ };
1333
+ }
1334
+ function orList(items) {
1335
+ if (items.length <= 1) return items[0] ?? "";
1336
+ return `${items.slice(0, -1).join(", ")} or ${items[items.length - 1]}`;
1337
+ }
1338
+ /**
1339
+ * A run was asked of a parked agent. An `AgentUnavailableError`, so every caller that already
1340
+ * answers "you have not got this agent" answers this with the parked sentence instead.
1341
+ */
1342
+ var AgentParkedError = class extends AgentUnavailableError {
1343
+ agentId;
1344
+ constructor(agentId, reason) {
1345
+ super(reason);
1346
+ this.agentId = agentId;
1347
+ }
1348
+ };
1349
+ /**
1350
+ * Who an agent is, without the means to run it. For a parked agent, which `agentById` refuses, when
1351
+ * a surface has to name it: a refusal that says "Gemini CLI" rather than "gemini".
1352
+ */
1353
+ function agentIdentityOf(id) {
1354
+ const { displayName, provider, modelSelectionHelp, setupNotes } = AGENTS[id];
1355
+ return {
1356
+ id,
1357
+ displayName,
1358
+ provider,
1359
+ ...modelSelectionHelp ? { modelSelectionHelp } : {},
1360
+ ...setupNotes ? { setupNotes } : {}
1361
+ };
1362
+ }
1363
+ /**
1364
+ * The environment a review made by this agent is pinned to, captured now.
1365
+ *
1366
+ * Works for a parked agent, because capturing starts nothing: it reads variables. A review made by
1367
+ * one still has a forge to watch and a conversation to re-read, under the account that made it, and
1368
+ * an agent run started from what this returns is refused at `agentFor` like any other.
1369
+ */
1370
+ function captureEnvFor(id, source) {
1371
+ return AGENTS[id].captureEnv(source);
1372
+ }
1289
1373
  function defaultAgent() {
1290
1374
  return agentById(DEFAULT_AGENT_ID);
1291
1375
  }
1376
+ /**
1377
+ * The adapter for this id, and the one way any run reaches one.
1378
+ *
1379
+ * A parked id throws here rather than handing out something that refuses later. Several callers
1380
+ * write before they run (a new job, a retired analysis, a KISS run), so a refusal at `run()` would
1381
+ * already have replaced what the reviewer was reading with a failure.
1382
+ */
1292
1383
  function agentById(id) {
1384
+ const reason = parkedReason(id);
1385
+ if (reason) throw new AgentParkedError(id, reason);
1293
1386
  const agent = AGENTS[id];
1294
1387
  if (!agent) throw new AgentUnavailableError(`This review was made with an agent called "${id}", which this version does not know how to run.`);
1295
1388
  return agent;
@@ -1346,7 +1439,7 @@ function captureEnv(source) {
1346
1439
  * against fakes that fail in ways no installed binary would.
1347
1440
  */
1348
1441
  async function detectAgents() {
1349
- return detectAgents$1(Object.values(AGENTS));
1442
+ return detectAgents$1(AGENT_IDS.map((id) => AGENTS[id]));
1350
1443
  }
1351
1444
  //#endregion
1352
1445
  //#region ../../packages/review-harness/src/workspace/store_root.ts
@@ -1724,7 +1817,7 @@ function countDiscussion(context) {
1724
1817
  async function resolveAgent(request) {
1725
1818
  if (request.flag) return named(request.flag, "flag");
1726
1819
  const fromReview = request.review ? agentIdOf(request.review.session) : null;
1727
- if (fromReview && AGENT_IDS.includes(fromReview)) return named(fromReview, "review");
1820
+ if (fromReview && isAgentId(fromReview)) return named(fromReview, "review");
1728
1821
  const preferred = readAgentPreference();
1729
1822
  if (preferred) return named(preferred, "preference");
1730
1823
  const installed = (await detectAgents()).filter((agent) => agent.state === "installed");
@@ -1736,6 +1829,13 @@ async function resolveAgent(request) {
1736
1829
  };
1737
1830
  }
1738
1831
  async function named(id, because) {
1832
+ const parked = parkedReason(id);
1833
+ if (parked && isAgentId(id)) return {
1834
+ kind: "unavailable",
1835
+ agent: agentIdentityOf(id),
1836
+ reason: parked,
1837
+ parked: true
1838
+ };
1739
1839
  if (!AGENT_IDS.includes(id)) return {
1740
1840
  kind: "unknown-id",
1741
1841
  id
@@ -5359,7 +5459,7 @@ var QUEUE_LOCK = "queue";
5359
5459
  * `version` is not branched on today. It exists so that a later shape change is a decision rather
5360
5460
  * than a guess about what an old file meant.
5361
5461
  */
5362
- var VERSION$1 = 1;
5462
+ var VERSION$2 = 1;
5363
5463
  /**
5364
5464
  * What is queued, in order.
5365
5465
  *
@@ -5373,7 +5473,7 @@ function readQueue() {
5373
5473
  const parsed = JSON.parse(readFileSync(QUEUE_PATH, "utf8"));
5374
5474
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
5375
5475
  const ids = parsed.ids;
5376
- return Array.isArray(ids) ? normalise(ids) : [];
5476
+ return Array.isArray(ids) ? normalise$1(ids) : [];
5377
5477
  } catch {
5378
5478
  return [];
5379
5479
  }
@@ -5419,10 +5519,10 @@ function updateQueue(change) {
5419
5519
  * processes never share one, although under the lock they never write at once anyway.
5420
5520
  */
5421
5521
  function writeQueueUnlocked(ids) {
5422
- const next = normalise(ids);
5522
+ const next = normalise$1(ids);
5423
5523
  mkdirSync(STORE_ROOT, { recursive: true });
5424
5524
  const stored = {
5425
- version: VERSION$1,
5525
+ version: VERSION$2,
5426
5526
  ids: next
5427
5527
  };
5428
5528
  const temporary = `${QUEUE_PATH}.${process.pid}.tmp`;
@@ -5487,7 +5587,7 @@ function queueStamp() {
5487
5587
  }
5488
5588
  }
5489
5589
  /** Strings only, first position wins, and never more than the cap. */
5490
- function normalise(ids) {
5590
+ function normalise$1(ids) {
5491
5591
  const seen = [];
5492
5592
  for (const id of ids) {
5493
5593
  if (typeof id !== "string" || id === "" || seen.includes(id)) continue;
@@ -5497,6 +5597,268 @@ function normalise(ids) {
5497
5597
  return seen;
5498
5598
  }
5499
5599
  //#endregion
5600
+ //#region ../../packages/review-harness/src/workspace/collections.ts
5601
+ /**
5602
+ * Collections: the reviewer's own groups of reviews, such as everything in one refactor.
5603
+ *
5604
+ * A collection says what belongs together and nothing else. Whether a member still needs attention
5605
+ * is its lifecycle, which this file never reads or writes: assigning, removing, renaming and
5606
+ * deleting all leave every review's state exactly where it was.
5607
+ *
5608
+ * **Members are lineage keys, not review ids.** A card is a branch, and analysing the branch again
5609
+ * makes a new record with a new id. The queue stores ids and has to forward them (`forwardQueue`);
5610
+ * a lineage key is already what the card stands for, so membership survives updates, re-analysis
5611
+ * and restarts with nothing to keep in step.
5612
+ *
5613
+ * **Its own file**, for the reasons `queue.ts` gives: a record is rewritten whole by whoever loaded
5614
+ * it last and carries its diff inline, and `ui.json` is best-effort. And because a write here never
5615
+ * touches a record, it never moves the record's mtime, which is the clock retention runs on.
5616
+ * Organising a review is not opening or marking it.
5617
+ *
5618
+ * `STORE_ROOT` from `store_root.ts` rather than `store.ts`, for the import cycle `queue.ts` explains:
5619
+ * the prune in `store.ts` reads this file.
5620
+ */
5621
+ var COLLECTIONS_PATH = join(STORE_ROOT, "collections.json");
5622
+ /** The lock's name. Reviews are locked under UUIDs in the same directory, so a word cannot collide. */
5623
+ var COLLECTIONS_LOCK = "collections";
5624
+ var VERSION$1 = 1;
5625
+ var ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
5626
+ /** Whether a value is shaped like a collection's id, which is what a fold key and a route accept. */
5627
+ function isCollectionId(value) {
5628
+ return typeof value === "string" && ID_PATTERN.test(value);
5629
+ }
5630
+ /**
5631
+ * Every collection, in the order they were created.
5632
+ *
5633
+ * Best-effort like `readQueue`: missing, unreadable and corrupt all mean "no collections". An entry
5634
+ * this code did not write is dropped rather than guessed at, and so is a second entry claiming an id
5635
+ * or a name already taken, so the rules `createCollection` keeps hold for a hand-edited file too.
5636
+ */
5637
+ function readCollections() {
5638
+ try {
5639
+ const parsed = JSON.parse(readFileSync(COLLECTIONS_PATH, "utf8"));
5640
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return [];
5641
+ const collections = parsed.collections;
5642
+ return Array.isArray(collections) ? normalise(collections) : [];
5643
+ } catch {
5644
+ return [];
5645
+ }
5646
+ }
5647
+ function normalise(raw) {
5648
+ const kept = [];
5649
+ for (const entry of raw) {
5650
+ if (kept.length >= 100) break;
5651
+ if (!entry || typeof entry !== "object") continue;
5652
+ const { id, name, colour, createdAt, members } = entry;
5653
+ const clean = typeof name === "string" ? cleanName(name) : "";
5654
+ if (!isCollectionId(id) || clean === "" || clean.length > 60) continue;
5655
+ if (typeof createdAt !== "number" || !Number.isFinite(createdAt)) continue;
5656
+ if (kept.some((other) => other.id === id || sameName(other.name, clean))) continue;
5657
+ const keys = Array.isArray(members) ? members.filter((key) => typeof key === "string" && key !== "") : [];
5658
+ kept.push({
5659
+ id,
5660
+ name: clean,
5661
+ ...typeof colour === "string" && colour !== "" ? { colour } : {},
5662
+ createdAt,
5663
+ members: [...new Set(keys)].slice(0, 500)
5664
+ });
5665
+ }
5666
+ return kept;
5667
+ }
5668
+ /** Spaces trimmed and runs of them collapsed, so " Release 0.82 " is "Release 0.82". */
5669
+ function cleanName(name) {
5670
+ return name.trim().replace(/\s+/g, " ");
5671
+ }
5672
+ /** Two names are one collection's name regardless of case: "refactor" is not a second Refactor. */
5673
+ function sameName(a, b) {
5674
+ return a.localeCompare(b, void 0, { sensitivity: "base" }) === 0;
5675
+ }
5676
+ function nameProblem(name, collections, except) {
5677
+ const clean = cleanName(name);
5678
+ if (clean === "") return "empty";
5679
+ if (clean.length > 60) return "too-long";
5680
+ if (collections.some((collection) => collection.id !== except && sameName(collection.name, clean))) return "duplicate";
5681
+ }
5682
+ /** What a refused change says, in the words the page shows. */
5683
+ function nameProblemText(problem) {
5684
+ if (problem === "empty") return "Give the collection a name.";
5685
+ if (problem === "too-long") return `Keep the name under 61 characters.`;
5686
+ return "A collection with that name already exists.";
5687
+ }
5688
+ /**
5689
+ * Change the collections as they are on disk now, under the lock: the only way they are written.
5690
+ * Same contract as `updateQueue`: `change` returns what to store or null for nothing, and null from
5691
+ * here means the write failed.
5692
+ */
5693
+ function updateCollections(change) {
5694
+ try {
5695
+ return withWriteLock(COLLECTIONS_LOCK, () => {
5696
+ const current = readCollections();
5697
+ const next = change(current);
5698
+ return next === null ? current : writeUnlocked(next);
5699
+ });
5700
+ } catch {
5701
+ return null;
5702
+ }
5703
+ }
5704
+ /** To a temporary file and renamed into place, for the reason `writeQueueUnlocked` gives. */
5705
+ function writeUnlocked(collections) {
5706
+ const next = normalise(collections);
5707
+ mkdirSync(STORE_ROOT, { recursive: true });
5708
+ const stored = {
5709
+ version: VERSION$1,
5710
+ collections: next
5711
+ };
5712
+ const temporary = `${COLLECTIONS_PATH}.${process.pid}.tmp`;
5713
+ try {
5714
+ writeFileSync(temporary, `${JSON.stringify(stored, null, 2)}\n`);
5715
+ renameSync(temporary, COLLECTIONS_PATH);
5716
+ } catch (error) {
5717
+ try {
5718
+ unlinkSync(temporary);
5719
+ } catch {}
5720
+ throw error;
5721
+ }
5722
+ return next;
5723
+ }
5724
+ function run$1(change) {
5725
+ const outcome = {};
5726
+ const stored = updateCollections((current) => {
5727
+ const result = change(current);
5728
+ if ("refused" in result) {
5729
+ outcome.refused = result.refused;
5730
+ return null;
5731
+ }
5732
+ outcome.id = result.id;
5733
+ return result.next;
5734
+ });
5735
+ if (outcome.refused !== void 0) return {
5736
+ ok: false,
5737
+ reason: outcome.refused
5738
+ };
5739
+ if (stored === null) return {
5740
+ ok: false,
5741
+ reason: "unsaved"
5742
+ };
5743
+ return {
5744
+ ok: true,
5745
+ collections: stored,
5746
+ ...outcome.id === void 0 ? {} : { id: outcome.id }
5747
+ };
5748
+ }
5749
+ function createCollection(name, now = Date.now()) {
5750
+ return run$1((current) => {
5751
+ const problem = nameProblem(name, current);
5752
+ if (problem) return { refused: problem };
5753
+ if (current.length >= 100) return { refused: "full" };
5754
+ const id = randomUUID();
5755
+ return {
5756
+ next: [...current, {
5757
+ id,
5758
+ name: cleanName(name),
5759
+ createdAt: now,
5760
+ members: []
5761
+ }],
5762
+ id
5763
+ };
5764
+ });
5765
+ }
5766
+ /**
5767
+ * A new collection holding these lineages from the start: one transaction, so it is made with all of
5768
+ * them or not at all. "Create Refactor and add these three" never leaves an empty Refactor behind a
5769
+ * failure, and a retry never meets a duplicate name of its own making.
5770
+ */
5771
+ function createWithMembers(name, keys, now = Date.now()) {
5772
+ const members = [...new Set(keys.filter((key) => key !== ""))];
5773
+ return run$1((current) => {
5774
+ const problem = nameProblem(name, current);
5775
+ if (problem) return { refused: problem };
5776
+ if (current.length >= 100 || members.length > 500) return { refused: "full" };
5777
+ const id = randomUUID();
5778
+ return {
5779
+ next: [...current, {
5780
+ id,
5781
+ name: cleanName(name),
5782
+ createdAt: now,
5783
+ members
5784
+ }],
5785
+ id
5786
+ };
5787
+ });
5788
+ }
5789
+ /** A new name on the same collection: its id, its members and every fold kept against the id stay. */
5790
+ function renameCollection(id, name) {
5791
+ return run$1((current) => {
5792
+ if (!current.some((collection) => collection.id === id)) return { refused: "missing" };
5793
+ const problem = nameProblem(name, current, id);
5794
+ if (problem) return { refused: problem };
5795
+ return {
5796
+ next: current.map((collection) => collection.id === id ? {
5797
+ ...collection,
5798
+ name: cleanName(name)
5799
+ } : collection),
5800
+ id
5801
+ };
5802
+ });
5803
+ }
5804
+ /** The collection and its memberships go. No review is touched: they were never inside it. */
5805
+ function deleteCollection(id) {
5806
+ return run$1((current) => {
5807
+ if (!current.some((collection) => collection.id === id)) return { refused: "missing" };
5808
+ return { next: current.filter((collection) => collection.id !== id) };
5809
+ });
5810
+ }
5811
+ /**
5812
+ * Add the given lineages to one collection, or take them out of it.
5813
+ *
5814
+ * **Only the named collection is touched.** Assigning a selection to Refactor leaves every other
5815
+ * membership those reviews have exactly as it was, which is what makes a bulk assignment additive.
5816
+ * Adding a member already there and removing one that is not are both nothing, not errors: a mixed
5817
+ * selection is the ordinary case.
5818
+ */
5819
+ function setMembership(keys, id, action) {
5820
+ return run$1((current) => {
5821
+ const target = current.find((collection) => collection.id === id);
5822
+ if (!target) return { refused: "missing" };
5823
+ const members = action === "add" ? [...target.members, ...keys.filter((key, at) => key !== "" && !target.members.includes(key) && keys.indexOf(key) === at)] : target.members.filter((key) => !keys.includes(key));
5824
+ if (members.length === target.members.length && members.every((key, at) => key === target.members[at])) return {
5825
+ next: null,
5826
+ id
5827
+ };
5828
+ if (members.length > 500) return { refused: "full" };
5829
+ return {
5830
+ next: current.map((collection) => collection.id === id ? {
5831
+ ...collection,
5832
+ members
5833
+ } : collection),
5834
+ id
5835
+ };
5836
+ });
5837
+ }
5838
+ /**
5839
+ * Take lineages out of every collection, because their reviews are gone. Stores nothing when none
5840
+ * of them was a member, so a deletion of an uncollected review never writes this file.
5841
+ */
5842
+ function dropMembers(keys) {
5843
+ const gone = new Set(keys);
5844
+ return updateCollections((current) => current.some((collection) => collection.members.some((key) => gone.has(key))) ? current.map((collection) => ({
5845
+ ...collection,
5846
+ members: collection.members.filter((key) => !gone.has(key))
5847
+ })) : null);
5848
+ }
5849
+ /** Every lineage in at least one collection. What retention asks: collected is kept. */
5850
+ function collectedKeys(collections = readCollections()) {
5851
+ return new Set(collections.flatMap((collection) => collection.members));
5852
+ }
5853
+ /** A token for the index's revision, as `queueStamp` is: it changes only when a person changes it. */
5854
+ function collectionsStamp() {
5855
+ try {
5856
+ return String(statSync(COLLECTIONS_PATH).mtimeMs);
5857
+ } catch {
5858
+ return "";
5859
+ }
5860
+ }
5861
+ //#endregion
5500
5862
  //#region ../../packages/review-harness/src/workspace/lifecycle.ts
5501
5863
  /**
5502
5864
  * What happens next with a change, as the reviewer says it: still in play, parked until something
@@ -6034,18 +6396,23 @@ function followedRefName(branch, followedRef) {
6034
6396
  * Queue exist to defeat recency: a review saved three weeks ago has to be reachable from the list
6035
6397
  * or the pill that claims to show it is simply broken.
6036
6398
  *
6037
- * **Two windows, and the page shows their sum.** Drawing both from one limit would mean saving
6399
+ * **Three windows, and the page shows their sum.** Drawing them from one limit would mean saving
6038
6400
  * eleven reviews pushed eleven recent ones off the page -- the working set eating the list it was
6039
- * added to. So `limit` bounds recency, `keptLimit` bounds what was kept, and the most cards this
6040
- * can return is the two added together. Neither constant says that on its own, which is why it is
6041
- * said here.
6401
+ * added to. So `limit` bounds recency, `keptLimit` bounds what was kept, `collected.limit` bounds
6402
+ * what is in a collection, and the most cards this can return is the three added together. No
6403
+ * constant says that on its own, which is why it is said here.
6042
6404
  */
6043
- function indexReviewGroups(limit, keptLimit, keptIds, rank, groups = groupByLineage(recentWorkspaces(Number.MAX_SAFE_INTEGER))) {
6044
- return selectIndexGroups(groups, limit, keptLimit, keptIds, rank);
6405
+ function indexReviewGroups(limit, keptLimit, keptIds, rank, groups = groupByLineage(recentWorkspaces(Number.MAX_SAFE_INTEGER)), collected = NO_COLLECTED) {
6406
+ return selectIndexGroups(groups, limit, keptLimit, keptIds, rank, collected);
6045
6407
  }
6408
+ var NO_COLLECTED = {
6409
+ keys: /* @__PURE__ */ new Set(),
6410
+ limit: 0
6411
+ };
6046
6412
  /**
6047
6413
  * What the reviewer explicitly kept, which is what stops the recency window hiding it: the queue,
6048
- * Saved, and anything marked Waiting or Done.
6414
+ * Saved, and anything marked Waiting or Done. Collections have a window of their own
6415
+ * (`CollectedWindow`).
6049
6416
  *
6050
6417
  * Done is the reviewer's archive, and an archive the recency window can empty is not one. Waiting
6051
6418
  * is work they expect back, and a push to it must have a card to come back on.
@@ -6071,12 +6438,19 @@ function indexKeptIds(reviews, queue) {
6071
6438
  * window rather than cut before its activity is ever looked at. Absent, it is when the newest
6072
6439
  * review was made, which is what every other caller has always meant by recent.
6073
6440
  */
6074
- function selectIndexGroups(groups, limit, keptLimit, keptIds, rank = (group) => group.latest.createdAt) {
6441
+ function selectIndexGroups(groups, limit, keptLimit, keptIds, rank = (group) => group.latest.createdAt, collected = NO_COLLECTED) {
6442
+ const inCollection = [];
6075
6443
  const kept = [];
6076
6444
  const rest = [];
6077
- for (const group of groups) (group.history.some((review) => keptIds.has(review.id)) ? kept : rest).push(group);
6445
+ for (const group of groups) if (collected.keys.has(group.key)) inCollection.push(group);
6446
+ else if (group.history.some((review) => keptIds.has(review.id))) kept.push(group);
6447
+ else rest.push(group);
6078
6448
  const byRank = (a, b) => rank(b) - rank(a);
6079
- return [...rest.sort(byRank).slice(0, limit), ...kept.sort(byRank).slice(0, keptLimit)].sort(byRank);
6449
+ return [
6450
+ ...rest.sort(byRank).slice(0, limit),
6451
+ ...kept.sort(byRank).slice(0, keptLimit),
6452
+ ...inCollection.sort(byRank).slice(0, collected.limit)
6453
+ ].sort(byRank);
6080
6454
  }
6081
6455
  /**
6082
6456
  * The grouping itself, with the disk left out.
@@ -6428,11 +6802,12 @@ function touchWorkspace(workspace) {
6428
6802
  function pruneOldWorkspaces() {
6429
6803
  try {
6430
6804
  const queued = new Set(readQueue());
6805
+ const collected = collectedKeys();
6431
6806
  for (const entry of readdirSync(WORKSPACES_DIR)) {
6432
6807
  const path = join(WORKSPACES_DIR, entry);
6433
6808
  const age = Date.now() - statSync(path).mtimeMs;
6434
6809
  if (age <= 2592e6) continue;
6435
- if (age <= 7776e6 && isKept(entry, queued)) continue;
6810
+ if (age <= 7776e6 && isKept(entry, queued, collected)) continue;
6436
6811
  unlinkSync(path);
6437
6812
  }
6438
6813
  } catch {}
@@ -6444,12 +6819,22 @@ function pruneOldWorkspaces() {
6444
6819
  * is the safe direction: the alternative keeps unreadable files for three months on the strength of
6445
6820
  * a question nobody could answer.
6446
6821
  */
6447
- function isKept(entry, queued) {
6822
+ function isKept(entry, queued, collected) {
6448
6823
  const id = workspaceIdFrom(entry);
6449
6824
  if (id === null) return false;
6450
6825
  if (queued.has(id)) return true;
6451
6826
  const workspace = loadWorkspace(id);
6452
- return workspace?.pinned === true || parseLifecycle(workspace?.lifecycle)?.state === "done";
6827
+ if (!workspace) return false;
6828
+ if (workspace.pinned === true || parseLifecycle(workspace.lifecycle)?.state === "done") return true;
6829
+ return collected.size > 0 && isCollected(workspace, collected);
6830
+ }
6831
+ /** A record that cannot be summarised cannot claim a lineage, so it falls to the ordinary window. */
6832
+ function isCollected(workspace, collected) {
6833
+ try {
6834
+ return collected.has(lineageKey(summarise(workspace)));
6835
+ } catch {
6836
+ return false;
6837
+ }
6453
6838
  }
6454
6839
  /** The review an entry belongs to, whether it is the record or the usage sidecar beside it. */
6455
6840
  function workspaceIdFrom(entry) {
@@ -7177,7 +7562,7 @@ function advance(job, phase, progress) {
7177
7562
  ...phase === "done" ? { finishedAt: Date.now() } : {}
7178
7563
  };
7179
7564
  }
7180
- function fail(job, message, kind, detail) {
7565
+ function fail(job, message, kind, detail, parked) {
7181
7566
  return {
7182
7567
  ...job,
7183
7568
  phase: "failed",
@@ -7185,7 +7570,8 @@ function fail(job, message, kind, detail) {
7185
7570
  phase: job.phase,
7186
7571
  message,
7187
7572
  ...detail && detail !== message ? { detail } : {},
7188
- ...kind ? { kind } : {}
7573
+ ...kind ? { kind } : {},
7574
+ ...parked ? { parked } : {}
7189
7575
  },
7190
7576
  updatedAt: Date.now(),
7191
7577
  finishedAt: Date.now()
@@ -7212,11 +7598,11 @@ function fail(job, message, kind, detail) {
7212
7598
  * last thing the agent said survives being failed. Only a driver whose own claim has gone is
7213
7599
  * refused, so a job nobody holds -- a crashed driver's, a resumed run's -- is still failable.
7214
7600
  */
7215
- function recordFailure(job, message, driver, kind, detail) {
7601
+ function recordFailure(job, message, driver, kind, detail, parked) {
7216
7602
  const current = loadJob(job.id) ?? job;
7217
7603
  const claim = liveClaim(job.id);
7218
7604
  if (claim && claim.holder !== driver) return current;
7219
- const failed = fail(current, message, kind, detail);
7605
+ const failed = fail(current, message, kind, detail, parked);
7220
7606
  saveJob(failed);
7221
7607
  return failed;
7222
7608
  }
@@ -12970,7 +13356,7 @@ function agentEnvOf(workspace) {
12970
13356
  const job = workspace.jobId ? loadJob(workspace.jobId) : null;
12971
13357
  if (job?.env) return job.env;
12972
13358
  const id = agentIdOf(workspace.session);
12973
- return (AGENT_IDS.includes(id) ? agentById(id) : defaultAgent()).captureEnv();
13359
+ return isAgentId(id) ? captureEnvFor(id) : defaultAgent().captureEnv();
12974
13360
  }
12975
13361
  //#endregion
12976
13362
  //#region ../../packages/review-harness/src/domain/discussion/discussion_attribution.ts
@@ -16370,6 +16756,8 @@ async function drive(jobId, workspaceId, runners, driver, signal, source) {
16370
16756
  saveJob(job);
16371
16757
  try {
16372
16758
  signal?.throwIfAborted();
16759
+ const parked = parkedReason(job.env.agentId);
16760
+ if (parked) throw new AgentParkedError(job.env.agentId, parked);
16373
16761
  if (needs(from, "preparing")) {
16374
16762
  job = update(job, "preparing", "Preparing an isolated checkout");
16375
16763
  const path = await createWorktree({
@@ -16494,7 +16882,7 @@ async function drive(jobId, workspaceId, runners, driver, signal, source) {
16494
16882
  }
16495
16883
  const kind = error instanceof GuideRejected || error instanceof KissRejected ? "agent_output_invalid" : error instanceof AgentRunError ? error.kind : void 0;
16496
16884
  const raw = message(error);
16497
- job = recordFailure(job, explain(job, raw), driver, kind, raw);
16885
+ job = recordFailure(job, explain(job, raw), driver, kind, raw, error instanceof AgentParkedError ? true : void 0);
16498
16886
  if (job.reviewType === "kiss") failCurrentKissRun(workspaceId, job, job.failure?.message ?? raw, kind, error instanceof KissRejected ? error.answer : void 0);
16499
16887
  finished(job, workspaceId, startedAt, false, now);
16500
16888
  announceJob(job);
@@ -17885,7 +18273,7 @@ async function recordKissRun(input) {
17885
18273
  if (!workspace) throw new Error("No such review.");
17886
18274
  const sha = reviewedCommit(workspace);
17887
18275
  if (!sha) throw new Error("This review does not record which commit it describes, so KISS cannot run on it.");
17888
- const agent = input.agentId ? agentById(input.agentId) : workspace.jobId ? agentById(agentIdOf(workspace.session)) : defaultAgent();
18276
+ const agent = agentById(kissAgentId(workspace, input.agentId));
17889
18277
  const sameAgent = !workspace.jobId || agent.id === agentIdOf(workspace.session);
17890
18278
  const requestedModel = input.requestedModel ?? (sameAgent ? resolveModel({ review: workspace }) : null);
17891
18279
  const started = await withReviewLock(workspace.id, () => {
@@ -17937,6 +18325,16 @@ async function recordKissRun(input) {
17937
18325
  function startKissJob(input, runners = {}) {
17938
18326
  return track(recordKissRun(input).then((job) => runJob(job.workspaceId, runners, "kiss")));
17939
18327
  }
18328
+ /**
18329
+ * Which agent a KISS run of this review runs with: the one named, else the standard review's own,
18330
+ * else the default when there has been no standard review.
18331
+ *
18332
+ * Its own function so the page can refuse a run before answering. The route starts the run and
18333
+ * replies without waiting for it, so a refusal thrown inside the run never reached the page.
18334
+ */
18335
+ function kissAgentId(workspace, agentId) {
18336
+ return agentId ?? (workspace.jobId ? agentIdOf(workspace.session) : DEFAULT_AGENT_ID);
18337
+ }
17940
18338
  //#endregion
17941
18339
  //#region ../../packages/review-harness/src/domain/ai_review/reconciliation.ts
17942
18340
  /**
@@ -18829,6 +19227,17 @@ var RefreshUnavailableError = class extends Error {};
18829
19227
  var FETCH_TIMEOUT_MS = 6e4;
18830
19228
  /** Said wherever an update is asked of a commit review. One sentence, so both surfaces agree. */
18831
19229
  var COMMIT_REVIEW_CANNOT_UPDATE = "This review is of one commit, and a commit cannot move, so there is nothing to bring it up to date with. To analyse the same commit again, reanalyse it instead.";
19230
+ /**
19231
+ * Why this review cannot be updated in this build, when the agent it would continue with is parked.
19232
+ *
19233
+ * Read from `agentEnvOf`, which is what the update's job would be pinned to, so this refuses
19234
+ * exactly the updates that would otherwise be refused at the run.
19235
+ */
19236
+ function parkedUpdateRefusal(workspace) {
19237
+ const reason = parkedReason(agentEnvOf(workspace).agentId);
19238
+ if (!reason) return null;
19239
+ return `${reason} An update continues with the agent that made this review, so this one can't be brought up to date. To carry on with another agent, analyse it again with ${runnableAgents().names}.`;
19240
+ }
18832
19241
  /** The installed agent's own answer, through the `agentFor` this module imports. */
18833
19242
  var installedAgentVersion = (env) => agentVersionOf(agentFor(env), env);
18834
19243
  /** What the notice says after a cancel, and what the terminal would print if it could cancel. */
@@ -18864,6 +19273,8 @@ async function updateReview(workspace, runners = {}) {
18864
19273
  const startedAt = now();
18865
19274
  const reviewedSha = workspace.session.metadata?.commitSha || null;
18866
19275
  if (workspace.changeSet.targetType === "commit") return refuse(reviewedSha, false, COMMIT_REVIEW_CANNOT_UPDATE);
19276
+ const parked = parkedUpdateRefusal(workspace);
19277
+ if (parked) return refuse(reviewedSha, false, parked);
18867
19278
  const held = await claimCheckoutJob(workspace, reviewedSha ?? "", {
18868
19279
  purpose: "update",
18869
19280
  ...runners.source ? { source: runners.source } : {},
@@ -19647,6 +20058,7 @@ function liveJobsFor(reviewId, now = Date.now()) {
19647
20058
  * a fact: no job for this review can be created or claimed without the lock this call is holding.
19648
20059
  */
19649
20060
  async function deleteReview(id) {
20061
+ const key = lineageKeyOf(id);
19650
20062
  const outcome = await withReviewLock(id, async () => {
19651
20063
  const live = liveJobsFor(id);
19652
20064
  if (live.length > 0) return {
@@ -19655,10 +20067,31 @@ async function deleteReview(id) {
19655
20067
  };
19656
20068
  return deleteLocked(id);
19657
20069
  });
19658
- return isBusy(outcome) ? {
20070
+ if (isBusy(outcome)) return {
19659
20071
  ...emptyReport(),
19660
20072
  busy: true
19661
- } : outcome;
20073
+ };
20074
+ forgetIfEmpty(key, outcome);
20075
+ return outcome;
20076
+ }
20077
+ /** The lineage a review belongs to, read before it is deleted, or absent when there is no review. */
20078
+ function lineageKeyOf(id) {
20079
+ const workspace = loadWorkspace(id);
20080
+ if (!workspace) return void 0;
20081
+ try {
20082
+ return lineageKey(summarise(workspace));
20083
+ } catch {
20084
+ return;
20085
+ }
20086
+ }
20087
+ /**
20088
+ * A lineage with no review left leaves every collection, so a branch reviewed again later starts
20089
+ * uncollected rather than reappearing in a group from before it was deleted. A lineage that still
20090
+ * has reviews keeps its memberships: deleting one analysis from a card is not deleting the card.
20091
+ */
20092
+ function forgetIfEmpty(key, report) {
20093
+ if (key === void 0 || report.deleted.length === 0) return;
20094
+ if (!recentWorkspaces(Number.MAX_SAFE_INTEGER).some((review) => lineageKey(review) === key)) dropMembers([key]);
19662
20095
  }
19663
20096
  /**
19664
20097
  * The removal itself, with the lock already held and the check already made.
@@ -19715,6 +20148,7 @@ async function deleteLocked(id) {
19715
20148
  * quietly delete a review made after it was rendered.
19716
20149
  */
19717
20150
  async function deleteLineage(id) {
20151
+ const key = lineageKeyOf(id);
19718
20152
  const ids = lineageIds(id);
19719
20153
  const outcome = await withReviewLocks(ids, async () => {
19720
20154
  const live = ids.flatMap((each) => liveJobsFor(each));
@@ -19732,10 +20166,12 @@ async function deleteLineage(id) {
19732
20166
  }
19733
20167
  return report;
19734
20168
  });
19735
- return isBusy(outcome) ? {
20169
+ if (isBusy(outcome)) return {
19736
20170
  ...emptyReport(),
19737
20171
  busy: true
19738
- } : outcome;
20172
+ };
20173
+ forgetIfEmpty(key, outcome);
20174
+ return outcome;
19739
20175
  }
19740
20176
  /**
19741
20177
  * The ids on one card, so a caller can check every one of them for running work before it commits
@@ -19753,4 +20189,4 @@ function lineageIds(id) {
19753
20189
  return [.../* @__PURE__ */ new Set([id, ...lineage.map((review) => review.id)])];
19754
20190
  }
19755
20191
  //#endregion
19756
- export { resolveWorkspacePort as $, AgentUnavailableError as $i, attemptIdOf as $n, isIssueOutstanding as $r, moveIds as $t, parsePullRequestLink as A, resolveTarget as Ai, modelHelpLines as An, recentWorkspaces as Ar, readArchitecture as At, ensureServer as B, resolveAgent as Bi, requestJobStop as Bn, isLifecycle as Br, writeJsonAtomically as Bt, completeRun as C, followUpFindingId as Ci, hasAuthorContent as Cn, lineagePosition as Cr, RUNTIME_WORDS as Ct, relativeTime as D, describeAuthorship as Di, CODE_COPY_CLASS as Dn, positionInLineage as Dr, checkArchitecture as Dt, stopRun as E, objectWithAnyOf as Ei, CODE_BLOCK_CLASS as En, mutateWorkspace as Er, ARCHITECTURE_DIR as Et, queueForReview as F, AmbiguousRefError as Fi, createWorktree as Fn, workspaceRevision as Fr, ownersOf as Ft, stopChild as G, CONFIG_PATH as Gi, checkReview as Gn, parseLifecycle as Gr, createArchitectureNotification as Gt, reviewsUrl as H, clearAgentPreference as Hi, describeSubject as Hn, lifecycleItemLabel as Hr, branchOf as Ht, settleQueue as I, shortNameOf as Ii, holdWorktree as In, workspaceStamps as Ir, QUOTE_LIMITS as It, writeServerRecord as J, agentById as Ji, fetchHead as Jn, queueAddMany as Jr, eventName as Jt, stopServer as K, STORE_ROOT as Ki, followedBranch as Kn, sectionOf as Kr, createPrState as Kt, readIndexToken as L, shorthandOf as Li, isWorktreeHeld as Ln, LIFECYCLES as Lr, REPOSITORY_SCOPE as Lt, runBatch as M, DetachedHeadError as Mi, isBusy as Mn, reviewedRepositories as Mr, storeArchitecture as Mt, activateQueued as N, isCommitReview as Ni, withReviewLock as Nn, summarise as Nr, storedScopes as Nt, batchIdentity as O, WITHHELD_REASON as Oi, escapeHtml as On, presentedKissRunOf as Or, loadArchitecture as Ot, compactQueue as P, resolveBase as Pi, MANAGED_ROOT as Pn, touchWorkspace as Pr, trackedFiles as Pt, parseWorkspacePort as Q, AgentCancelledError as Qi, allJobs as Qn, doneVerb as Qr, markReadForReview as Qt, bootstrapUrl as R, git as Ri, readMarker as Rn, activityAttributes as Rr, ancestorsOf as Rt, beginRun as S, wasReassessed as Si, authorPreview as Sn, latestKissRun as Sr, recordRuntimeBestEffort as St, reportRun as T, extractJsonText as Ti, renderMarkdown as Tn, matchingWorkspaceIds as Tr, formatDuration as Tt, serverEntryExists as U, readAgentPreference as Ui, findExistingReview as Un, lifecyclePill as Ur, subjectOf as Ut, readServerRecord as V, resolveModel as Vi, describeMovement as Vn, lifecycleChip as Vr, announceJob as Vt, spawnServer as W, writeAgentPreference as Wi, checkCodeFreshness as Wn, offersLifecycle as Wr, categoryOf as Wt, MAX_WORKSPACE_PORT as X, detectAgents as Xi, fillFileUrlTemplate as Xn, queueStamp as Xr, markAllRead as Xt, DEFAULT_WORKSPACE_PORT as Y, agentFor as Yi, forgeResolver as Yn, queueRemove as Yr, listNotifications as Yt, MIN_WORKSPACE_PORT as Z, isAgentId as Zi, RUN_RECORD_RETENTION_MS as Zn, readQueue as Zr, markRead as Zt, recordKissRun as _, gradeScale as _i, isUnchanged as _n, groupByLineage as _r, BUILD_VERSION as _t, wasBlocked as a, processDiscussion as ai, toneOf as an, saveJob as ar, settleReview as at, track as b, reassessIn as bi, chatConversationSection as bn, kissDecisionRefusal as br, waitingHistory as bt, RefreshUnavailableError as c, explanationOf as ci, withUsageRecorded as cn, isTerminalJobPhase as cr, startJob as ct, mergeUpdateInto as d, ANALYSIS_ONLY as di, adoptLegacyBaseline as dn, purposeOf as dr, record as dt, isQuestionOutstanding as ei, pruneNotifications as en, endedAt as er, statedWorkspacePort as et, updateReview as f, analysisRefOf as fi, prepareConversationWithin as fn, clearClaim as fr, stamp as ft, runningJobs as g, findingIn as gi, refreshPrContext as gn, followedRefName as gr, telemetryUploadEnabled as gt, liveUpdateFor as h, findingAssessment as hi, unforeseenFailure as hn, decideKissRecommendation as hr, telemetryUploadConsent as ht, liveJobsFor as i, reviewerDispositions as ii, retireNotification as in, pruneJobResidue as ir, prepareReview as it, unsupportedReason as j, originRemote as ji, ReviewBeingDeletedError as jn, reviewedCommit as jr, repositoryKey as jt, extractPullRequestLinks as k, kissOutcome as ki, renderInlineText as kn, previousKissResult as kr, partFiles as kt, cancelUpdate as l, isExplanationAction as li, existingPaths as ln, phasesFor as lr, append as lt, UpdateAlreadyRunningError as m, currentGradeIn as mi, storedConversation as mn, CLAIM_LEASE_MS as mr, recordTelemetryUploadConsent as mt, deleteReview as n, questionsOutstanding as ni, readStateOf as nn, isTerminal as nr, failureOf as nt, discardJob as o, recommendationsOf as oi, unreadAround as on, PHASES as or, reanalyseReview as ot, askCheckout as p, assessmentLookup as pi, saveRefresh as pn, isClaimed as pr, deliver as pt, workspaceUrl as q, AGENT_IDS as qi, reviewedShaOf as qn, queueAdd as qr, createPrUpdate as qt, lineageIds as r, resolvedByConversation as ri, removeNotification as rn, loadJob as rr, observeReview as rt, FETCH_TIMEOUT_MS as s, explanationActionOf as si, unreadCount as sn, isLiveJob as sr, runJob as st, deleteLineage as t, issuesOutstanding as ti, readNotification as tn, fail as tr, writePortPreference as tt, cancellableHere as u, withExplanation as ui, ASK_REREAD_CEILING_MS as un, progressSteps as ur, readEvents as ut, startKissJob as v, isGradeFor as vi, agentEnvOf as vn, indexKeptIds as vr, PACKAGE_NAME as vt, endRun as w, asString$1 as wi, GITHUB_MARKDOWN as wn, loadWorkspace as wr, describeRuntimes as wt, RUN_KINDS as x, sourcePhrase as xi, UNESTABLISHED_MOTIVATION as xn, kissDecisionsOf as xr, mapRuntimes as xt, isDriving as y, promptGrade as yi, EXPLAIN_SIMPLY_PROMPT as yn, indexReviewGroups as yr, feedbackUrl as yt, clearServerRecordOwnedBy as z, refExists as zi, removeWorktree as zn, activityLabel as zr, isArchitectureId as zt };
20192
+ export { MIN_WORKSPACE_PORT as $, resolveModel as $i, RUN_RECORD_RETENTION_MS as $n, deleteCollection as $r, markRead as $t, batchIdentity as A, isGradeFor as Ai, escapeHtml as An, positionInLineage as Ar, loadArchitecture as At, bootstrapUrl as B, WITHHELD_REASON as Bi, readMarker as Bn, LIFECYCLES as Br, ancestorsOf as Bt, RUN_KINDS as C, ANALYSIS_ONLY as Ci, UNESTABLISHED_MOTIVATION as Cn, kissDecisionsOf as Cr, mapRuntimes as Ct, reportRun as D, findingAssessment as Di, renderMarkdown as Dn, loadWorkspace as Dr, formatDuration as Dt, endRun as E, currentGradeIn as Ei, GITHUB_MARKDOWN as En, lineagePosition as Er, describeRuntimes as Et, activateQueued as F, followUpFindingId as Fi, withReviewLock as Fn, reviewedRepositories as Fr, storedScopes as Ft, serverEntryExists as G, isCommitReview as Gi, findExistingReview as Gn, lifecycleItemLabel as Gr, subjectOf as Gt, ensureServer as H, resolveTarget as Hi, requestJobStop as Hn, activityLabel as Hr, writeJsonAtomically as Ht, compactQueue as I, asString$1 as Ii, MANAGED_ROOT as In, summarise as Ir, trackedFiles as It, stopServer as J, shortNameOf as Ji, followedBranch as Jn, parseLifecycle as Jr, createPrState as Jt, spawnServer as K, resolveBase as Ki, checkCodeFreshness as Kn, lifecyclePill as Kr, categoryOf as Kt, queueForReview as L, extractJsonText as Li, createWorktree as Ln, touchWorkspace as Lr, ownersOf as Lt, parsePullRequestLink as M, reassessIn as Mi, modelHelpLines as Mn, previousKissResult as Mr, readArchitecture as Mt, unsupportedReason as N, sourcePhrase as Ni, ReviewBeingDeletedError as Nn, recentWorkspaces as Nr, repositoryKey as Nt, stopRun as O, findingIn as Oi, CODE_BLOCK_CLASS as On, matchingWorkspaceIds as Or, ARCHITECTURE_DIR as Ot, runBatch as P, wasReassessed as Pi, isBusy as Pn, reviewedCommit as Pr, storeArchitecture as Pt, MAX_WORKSPACE_PORT as Q, resolveAgent as Qi, fillFileUrlTemplate as Qn, createWithMembers as Qr, markAllRead as Qt, settleQueue as R, objectWithAnyOf as Ri, holdWorktree as Rn, workspaceRevision as Rr, QUOTE_LIMITS as Rt, track as S, withExplanation as Si, chatConversationSection as Sn, kissDecisionRefusal as Sr, waitingHistory as St, completeRun as T, assessmentLookup as Ti, hasAuthorContent as Tn, lineageKey as Tr, RUNTIME_WORDS as Tt, readServerRecord as U, originRemote as Ui, describeMovement as Un, isLifecycle as Ur, announceJob as Ut, clearServerRecordOwnedBy as V, kissOutcome as Vi, removeWorktree as Vn, activityAttributes as Vr, isArchitectureId as Vt, reviewsUrl as W, DetachedHeadError as Wi, describeSubject as Wn, lifecycleChip as Wr, branchOf as Wt, writeServerRecord as X, git as Xi, fetchHead as Xn, collectionsStamp as Xr, eventName as Xt, workspaceUrl as Y, shorthandOf as Yi, reviewedShaOf as Yn, sectionOf as Yr, createPrUpdate as Yt, DEFAULT_WORKSPACE_PORT as Z, refExists as Zi, forgeResolver as Zn, createCollection as Zr, listNotifications as Zt, runningJobs as _, processDiscussion as _i, unforeseenFailure as _n, decideKissRecommendation as _r, telemetryUploadConsent as _t, wasBlocked as a, STORE_ROOT as aa, queueAdd as ai, removeNotification as an, loadJob as ar, observeReview as at, startKissJob as b, explanationOf as bi, agentEnvOf as bn, indexKeptIds as br, PACKAGE_NAME as bt, RefreshUnavailableError as c, agentFor as ca, queueStamp as ci, unreadAround as cn, PHASES as cr, reanalyseReview as ct, mergeUpdateInto as d, isAgentId as da, isIssueOutstanding as di, existingPaths as dn, phasesFor as dr, append as dt, agentIdOf as ea, nameProblemText as ei, markReadForReview as en, allJobs as er, parseWorkspacePort as et, parkedUpdateRefusal as f, parkedReason as fa, isQuestionOutstanding as fi, ASK_REREAD_CEILING_MS as fn, progressSteps as fr, readEvents as ft, liveUpdateFor as g, reviewerDispositions as gi, storedConversation as gn, CLAIM_LEASE_MS as gr, recordTelemetryUploadConsent as gt, UpdateAlreadyRunningError as h, AgentUnavailableError as ha, resolvedByConversation as hi, saveRefresh as hn, isClaimed as hr, deliver as ht, liveJobsFor as i, CONFIG_PATH as ia, updateCollections as ii, readStateOf as in, isTerminal as ir, failureOf as it, extractPullRequestLinks as j, promptGrade as ji, renderInlineText as jn, presentedKissRunOf as jr, partFiles as jt, relativeTime as k, gradeScale as ki, CODE_COPY_CLASS as kn, mutateWorkspace as kr, checkArchitecture as kt, cancelUpdate as l, agentIdentityOf as la, readQueue as li, unreadCount as ln, isLiveJob as lr, runJob as lt, askCheckout as m, AgentCancelledError as ma, questionsOutstanding as mi, prepareConversationWithin as mn, clearClaim as mr, stamp as mt, deleteReview as n, readAgentPreference as na, renameCollection as ni, pruneNotifications as nn, endedAt as nr, statedWorkspacePort as nt, discardJob as o, AGENT_IDS as oa, queueAddMany as oi, retireNotification as on, pruneJobResidue as or, prepareReview as ot, updateReview as p, runnableAgents as pa, issuesOutstanding as pi, adoptLegacyBaseline as pn, purposeOf as pr, record as pt, stopChild as q, AmbiguousRefError as qi, checkReview as qn, offersLifecycle as qr, createArchitectureNotification as qt, lineageIds as r, writeAgentPreference as ra, setMembership as ri, readNotification as rn, fail as rr, writePortPreference as rt, FETCH_TIMEOUT_MS as s, agentById as sa, queueRemove as si, toneOf as sn, saveJob as sr, settleReview as st, deleteLineage as t, clearAgentPreference as ta, readCollections as ti, moveIds as tn, attemptIdOf as tr, resolveWorkspacePort as tt, cancellableHere as u, detectAgents as ua, doneVerb as ui, withUsageRecorded as un, isTerminalJobPhase as ur, startJob as ut, kissAgentId as v, recommendationsOf as vi, refreshPrContext as vn, followedRefName as vr, telemetryUploadEnabled as vt, beginRun as w, analysisRefOf as wi, authorPreview as wn, latestKissRun as wr, recordRuntimeBestEffort as wt, isDriving as x, isExplanationAction as xi, EXPLAIN_SIMPLY_PROMPT as xn, indexReviewGroups as xr, feedbackUrl as xt, recordKissRun as y, explanationActionOf as yi, isUnchanged as yn, groupByLineage as yr, BUILD_VERSION as yt, readIndexToken as z, describeAuthorship as zi, isWorktreeHeld as zn, workspaceStamps as zr, REPOSITORY_SCOPE as zt };