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.
- package/CHANGELOG.md +52 -0
- package/README.md +3 -4
- package/dist/{delete_review-CYiIfV5A.js → delete_review-BED4mPxB.js} +471 -35
- package/dist/main.js +21 -1
- package/dist/server.js +575 -45
- package/package.json +2 -4
- package/static/chunk-CQNkZ9TG.js +1 -0
- package/static/{chunk-DnOyRP8m.js → chunk-CWRKWlhO.js} +1 -1
- package/static/{chunk-C3E91izb.js → chunk-CxFmUaBj.js} +1 -1
- package/static/help.js +1 -1
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/runs.js +1 -1
- package/static/settings.js +1 -1
- package/static/shell.css +128 -0
- package/static/workspace.js +79 -79
- package/static/chunk-C8d9FvLk.js +0 -1
|
@@ -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(
|
|
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 &&
|
|
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$
|
|
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$
|
|
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
|
-
* **
|
|
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,
|
|
6040
|
-
*
|
|
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) (
|
|
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 [
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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
|
-
|
|
20070
|
+
if (isBusy(outcome)) return {
|
|
19659
20071
|
...emptyReport(),
|
|
19660
20072
|
busy: true
|
|
19661
|
-
}
|
|
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
|
-
|
|
20169
|
+
if (isBusy(outcome)) return {
|
|
19736
20170
|
...emptyReport(),
|
|
19737
20171
|
busy: true
|
|
19738
|
-
}
|
|
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 {
|
|
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 };
|