@dev.fast/whiteboard 0.3.4-preview.20261019.126 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-alias-B5-HYi1D.js → account-alias-CprZojXd.js} +8 -13
- package/dist/agent-cli-C6SPSII0.js +2 -0
- package/dist/{agent-cli-CohHf6Ua.js → agent-cli-CThN7Q59.js} +10 -15
- package/dist/{agent-client-Dtc-Kn_w.js → agent-client-BOOi7EUR.js} +3 -13
- package/dist/{authoring-tools-B7KU04f7.js → authoring-tools-BNwuMnz6.js} +27 -34
- package/dist/{background-server-C03RKxKS.js → background-server-CkvxA-6O.js} +5 -5
- package/dist/{background-server-CB9C4uoc.js → background-server-DLSDogkI.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-CJaEb7zC.js → cli-R4yfa5DT.js} +1 -1
- package/dist/{cli-runner-DGDIqh3V.js → cli-runner-Cuc9FSzu.js} +26 -26
- package/dist/cli.js +3 -4
- package/dist/{client-lKVF_Elo.js → client-BN_FDxFr.js} +47 -253
- package/dist/{fuzzy-match-BmVJJm9I.js → fuzzy-match-BO9bK37N.js} +4 -6
- package/dist/{headless-host-DRtjf9WA.js → headless-host-CVBOQyRS.js} +1 -1
- package/dist/{headless-host-XhL1X4vX.js → headless-host-Cog6SpS9.js} +140 -513
- package/dist/{local-data-Cpl4ENbJ.js → local-data-D_9tvdsp.js} +323 -2419
- package/dist/{mcp-ovmiUE_x.js → mcp-D1hziANa.js} +9 -5
- package/dist/{package-paths-BWxO-UEL.js → package-paths-C--__jEv.js} +1 -1
- package/dist/profile-CYPmvBoA.js +2 -0
- package/dist/{profile-BadSkCkI.js → profile-DokFw782.js} +8 -20
- package/dist/{remote-attach-Dd-YobM6.js → remote-attach-CEJ-kvDY.js} +4 -4
- package/dist/{remote-extensions-CNXwqK0D.js → remote-extensions-Blcjr9Ze.js} +3 -3
- package/dist/{remote-extensions-VP-kfq0C.js → remote-extensions-GN8Gf_qG.js} +1 -1
- package/dist/{remote-language-server-BXk87KCr.js → remote-language-server-BTKBQ7x4.js} +5 -5
- package/dist/{remote-uninstall-DuOzsvaD.js → remote-uninstall-DW7Gdryh.js} +6 -6
- package/dist/{request-origin-B9qCPiVB.js → request-origin-Zcn_CjX4.js} +12 -45
- package/dist/{review-app-launcher-C7el2Uoy.js → review-app-launcher-CkwLclzR.js} +2 -2
- package/dist/{review-home-paths-D8AoCdd2.js → review-home-paths-j2BRAaNv.js} +17 -19
- package/dist/{review-telemetry-BsoRMC2w.js → review-telemetry-BLNwYnLV.js} +5 -5
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-CsG2EWED.js → s3-DtJFWC0G.js} +1 -1
- package/dist/{s3-config-CjcLyFV5.js → s3-config-BTO12tCC.js} +2 -4
- package/dist/{s3-config-zz4GWh2T.js → s3-config-CSy3fx8H.js} +1 -1
- package/dist/server/desktop-host.js +76 -29
- package/dist/{server-discovery-CKjw3-6h.js → server-discovery-Bey8IqXH.js} +3 -3
- package/dist/sharing/index.d.ts +70 -484
- package/dist/sharing/index.js +1 -1
- package/dist/{src-WPaQn75m.js → src-BMPuxWv4.js} +73 -208
- package/dist/{src-BiWNbcF_.js → src-DsM3jO8G.js} +12 -89
- package/dist/{stored-document-migration-dFbewRTG.js → stored-document-migration-CChxtFAy.js} +269 -270
- package/dist/{thread-state-Do5LX10I.js → thread-state-5pYr6_zM.js} +2 -7
- package/dist/{tool-failure-DvI-gys2.js → tool-failure-C9MpbXJJ.js} +3 -10
- package/instructions/authoring.md +2 -3
- package/instructions/scratchpad.md +23 -0
- package/package.json +4 -4
- package/src/ask/agents.ts +1 -13
- package/src/ask/checkout-files.ts +5 -2
- package/src/ask/thread-state.ts +0 -4
- package/src/ask/thread.ts +0 -16
- package/src/ask/threads.ts +3 -24
- package/src/cli-install.ts +2 -13
- package/src/cli-runner.ts +1 -4
- package/src/cli.ts +1 -4
- package/src/diff-selection-migration.ts +34 -48
- package/src/lens-selection.ts +2 -38
- package/src/review-api/README.md +13 -52
- package/src/review-api/activity.ts +4 -1
- package/src/review-api/agent-cli.ts +1 -9
- package/src/review-api/anchor-quotes.ts +7 -14
- package/src/review-api/ask-history.ts +51 -137
- package/src/review-api/authoring-tools.ts +30 -45
- package/src/review-api/blocks/definition.ts +1 -1
- package/src/review-api/blocks/markdown.ts +2 -2
- package/src/review-api/document-headings.ts +27 -58
- package/src/review-api/document-text.ts +2 -31
- package/src/review-api/document.ts +34 -281
- package/src/review-api/github-provider.ts +13 -51
- package/src/review-api/http.ts +123 -702
- package/src/review-api/instructions.ts +19 -0
- package/src/review-api/local-data.ts +46 -310
- package/src/review-api/mcp.ts +11 -3
- package/src/review-api/onboarding.ts +1 -1
- package/src/review-api/profile.ts +6 -37
- package/src/review-api/public-tools.ts +2 -24
- package/src/review-api/pull-request.ts +49 -82
- package/src/review-api/request-origin.ts +0 -10
- package/src/review-api/review-progress.ts +27 -61
- package/src/review-api/store-schema.ts +29 -38
- package/src/review-api/store.ts +478 -2142
- package/src/review-api/worktree-events.ts +5 -3
- package/src/review-api/worktree-source.ts +7 -3
- package/src/review-api/worktree-structural.integration.ts +0 -72
- package/src/review-preferences.ts +88 -0
- package/src/review-stack.ts +23 -15
- package/src/runtime.ts +30 -4
- package/src/server/desktop-host.ts +1 -6
- package/src/server/desktop-server.ts +31 -1
- package/src/server/headless-host.ts +2 -0
- package/src/server/hono-http.ts +1 -2
- package/src/server/legacy-tutorial-cleanup.ts +3 -2
- package/src/server/review-gateway-list.ts +5 -3
- package/src/server/review-gateway-memory.ts +1 -1
- package/src/server/review-lifecycle-telemetry.ts +2 -2
- package/src/server/review-server-core.ts +5 -1
- package/src/server/structural-comparisons.ts +0 -47
- package/src/sharing/export.ts +7 -36
- package/src/sharing/host.ts +14 -34
- package/src/sharing/import.ts +24 -156
- package/src/sharing/repository.ts +20 -128
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +19 -110
- package/src/ui-telemetry-events.ts +11 -33
- package/dist/agent-cli-_YyMIMRY.js +0 -2
- package/dist/lens-selection-CyJapcsl.js +0 -214
- package/dist/profile-DJvkBJNQ.js +0 -2
- package/dist/runtime-Bj2tD33R.js +0 -27
- package/src/heading-anchors.ts +0 -42
- package/src/review-api/board-order.ts +0 -144
- package/src/review-api/checkout-groups.ts +0 -560
- package/src/review-api/foreign-references.ts +0 -328
- package/src/review-api/link-preview.ts +0 -113
- package/src/review-api/scratchpad-plan-upgrade.ts +0 -250
- package/src/review-api/store-migrations.ts +0 -357
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as git, r as defaultBranch } from "./src-BMPuxWv4.js";
|
|
2
2
|
import { c as jsonString, m as isStringValue, n as isJsonObject, p as isObjectValue, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
3
|
-
import { a as sharedReviewId, c as verifyShareRepository, d as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { B as TraceStorageDeniedError, D as listReviewTraceSessions, I as isTraceStorageConfigured, O as loadReviewAgentTrace, R as resolveTraceStorage, T as traceMachineEnabled, at as
|
|
7
|
-
import {
|
|
8
|
-
import { B as stepSources, F as selectionReferences, G as lensSelections, J as fileLineRangeSchema, V as UNCATEGORIZED_LENS_ID, _ as boardRepositoryId, f as anchorPins, g as boardLinks, x as elements } from "./stored-document-migration-dFbewRTG.js";
|
|
3
|
+
import { a as sharedReviewId, c as verifyShareRepository, d as StreamLimitError, f as readBoundedStream, l as exportShare, m as shareIdSchema, n as ShareClient, p as parseShareLink, r as SharePreflightError, s as readShareRepository, t as ShareAuthError, u as validateShareSources } from "./client-BN_FDxFr.js";
|
|
4
|
+
import { a as inspectSnapshot, d as invalidateStructuralComparisons, f as diffrExecutable, i as commandSchema, p as diffrMissingError, r as SCRATCHPAD_ID } from "./local-data-D_9tvdsp.js";
|
|
5
|
+
import { D as ReviewFeedbackSchema, E as ReviewFeedbackContactSchema, M as reviewDiffrSummarizerInputSchema, N as decodeStructuralDiffEvent, a as parseReviewVerbRequest, g as REVIEW_HOST_HEADER, i as parseReviewDesktopVerbResult, k as ReviewOnboardingRunRequestSchema } from "./src-DsM3jO8G.js";
|
|
6
|
+
import { B as TraceStorageDeniedError, D as listReviewTraceSessions, I as isTraceStorageConfigured, O as loadReviewAgentTrace, R as resolveTraceStorage, T as traceMachineEnabled, at as openUrlInBrowser, et as shellQuote, it as clearStoreAuth, ot as readStoreAuth, rt as DEFAULT_STORE_ORIGIN, st as runStoreLogin, z as selectTraceStorage } from "./review-home-paths-j2BRAaNv.js";
|
|
7
|
+
import { E as selectionReferences, G as comparisonKey, I as lensSelections, J as selectionKey, K as formatAnchor, M as UNCATEGORIZED_LENS_ID, Q as emptyCoverage, W as anchorSelection, Z as coverageSources, et as scopedCoverage, h as elements, j as stepSources, nt as unionIntervals, q as resolveDiffSelection, tt as subtractIntervals, u as anchorPins, z as fileLineRangeSchema } from "./stored-document-migration-CChxtFAy.js";
|
|
9
8
|
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
10
|
-
import { A as processStartIdentity, L as writePrivateJsonAtomic, N as devReviewHome, h as TraceConfigurationError, i as isS3MockMode, j as withFileLock } from "./s3-config-
|
|
9
|
+
import { A as processStartIdentity, L as writePrivateJsonAtomic, N as devReviewHome, h as TraceConfigurationError, i as isS3MockMode, j as withFileLock } from "./s3-config-BTO12tCC.js";
|
|
11
10
|
import { t as errorMessage } from "./error-message-OtiDonty.js";
|
|
12
|
-
import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-
|
|
13
|
-
import { _ as reviewInstanceIdentity, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-
|
|
14
|
-
import { c as askPicksSchema, d as selectionMarkdown, l as askQuestionSchema, n as askAgentIds, r as askChoiceKinds, s as askPermissionsSchema, t as applyAskChange, u as AgentSelectionSchema } from "./thread-state-
|
|
15
|
-
import { t as openReviewProfile } from "./profile-
|
|
16
|
-
import {
|
|
17
|
-
import { a as defaultAskPermissions, i as askAgents, l as launchAskAgent, n as askAgentSettings, o as detectAskAgents, r as askAgentTakesMcp, t as fuzzyRank } from "./fuzzy-match-
|
|
18
|
-
import { a as
|
|
19
|
-
import { a as headlessServerOwner, c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-
|
|
11
|
+
import { i as readReviewPackageVersion, n as readBuildCommit, t as findReviewPackageRoot } from "./package-paths-C--__jEv.js";
|
|
12
|
+
import { _ as reviewInstanceIdentity, n as desktopOwnsHome, o as reviewServerDiscoveryPath, p as desktopUsesHome, r as headlessServerLockPath, t as DESKTOP_TAKEOVER_SIGNAL } from "./server-discovery-Bey8IqXH.js";
|
|
13
|
+
import { c as askPicksSchema, d as selectionMarkdown, l as askQuestionSchema, n as askAgentIds, r as askChoiceKinds, s as askPermissionsSchema, t as applyAskChange, u as AgentSelectionSchema } from "./thread-state-5pYr6_zM.js";
|
|
14
|
+
import { t as openReviewProfile } from "./profile-DokFw782.js";
|
|
15
|
+
import { a as REVIEW_APP_SESSION_ID_HEADER, c as sanitizeUiTelemetryEvent, i as BUNDLE_FRAME_PATTERN, l as cleanTelemetryText, r as reviewRequestOrigin, s as isReportableCleanedMessage } from "./request-origin-Zcn_CjX4.js";
|
|
16
|
+
import { a as defaultAskPermissions, i as askAgents, l as launchAskAgent, n as askAgentSettings, o as detectAskAgents, r as askAgentTakesMcp, t as fuzzyRank } from "./fuzzy-match-BO9bK37N.js";
|
|
17
|
+
import { a as scratchpadAvailable, c as readQuerySchemas, i as renderInstructions, o as inspectQuerySchema, r as instructionsQuerySchema, s as queryAnchor, t as authoringTools } from "./authoring-tools-BNwuMnz6.js";
|
|
18
|
+
import { a as headlessServerOwner, c as stopBackgroundServer, o as recordedBackgroundServer } from "./background-server-CkvxA-6O.js";
|
|
20
19
|
import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
21
20
|
import { z } from "zod";
|
|
22
21
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
@@ -214,7 +213,6 @@ var AskThread = class {
|
|
|
214
213
|
/** The number of changes so far; a snapshot carries the one it includes. */
|
|
215
214
|
seq = 0;
|
|
216
215
|
listeners = /* @__PURE__ */ new Set();
|
|
217
|
-
statusListeners = /* @__PURE__ */ new Set();
|
|
218
216
|
closers = /* @__PURE__ */ new Set();
|
|
219
217
|
decisions = /* @__PURE__ */ new Map();
|
|
220
218
|
process;
|
|
@@ -320,19 +318,11 @@ var AskThread = class {
|
|
|
320
318
|
snapshot: this.state
|
|
321
319
|
};
|
|
322
320
|
}
|
|
323
|
-
get status() {
|
|
324
|
-
return this.state.status;
|
|
325
|
-
}
|
|
326
321
|
/** Every change after the current `seq`, in order. */
|
|
327
322
|
subscribe(listener) {
|
|
328
323
|
this.listeners.add(listener);
|
|
329
324
|
return () => this.listeners.delete(listener);
|
|
330
325
|
}
|
|
331
|
-
/** Calls back when the status changes, without following the thread. */
|
|
332
|
-
onStatusChange(listener) {
|
|
333
|
-
this.statusListeners.add(listener);
|
|
334
|
-
return () => this.statusListeners.delete(listener);
|
|
335
|
-
}
|
|
336
326
|
/** Whether anything follows the thread, as an open panel does. */
|
|
337
327
|
watched() {
|
|
338
328
|
return this.listeners.size > 0;
|
|
@@ -589,8 +579,7 @@ var AskThread = class {
|
|
|
589
579
|
const generation = ++this.generation;
|
|
590
580
|
const launched = await this.launch(this.start.agent, this.start.cwd, {
|
|
591
581
|
permissions: this.state.permissions,
|
|
592
|
-
mcpServers: this.mcpServers
|
|
593
|
-
threadId: this.id
|
|
582
|
+
mcpServers: this.mcpServers
|
|
594
583
|
});
|
|
595
584
|
if (this.closed || this.halted || generation !== this.generation) {
|
|
596
585
|
launched.stop();
|
|
@@ -1188,7 +1177,6 @@ var AskThread = class {
|
|
|
1188
1177
|
change
|
|
1189
1178
|
};
|
|
1190
1179
|
for (const listener of this.listeners) listener(update);
|
|
1191
|
-
if (change.type === "set" && change.status) for (const listener of this.statusListeners) listener();
|
|
1192
1180
|
}
|
|
1193
1181
|
};
|
|
1194
1182
|
//#endregion
|
|
@@ -1273,16 +1261,13 @@ function cliAskTools(cliPath, selector) {
|
|
|
1273
1261
|
}] : []
|
|
1274
1262
|
];
|
|
1275
1263
|
return {
|
|
1276
|
-
mcpServers: (
|
|
1264
|
+
mcpServers: () => {
|
|
1277
1265
|
const cli = cliPath();
|
|
1278
1266
|
return cli ? [{
|
|
1279
1267
|
name: "whiteboard",
|
|
1280
1268
|
command: process.execPath,
|
|
1281
1269
|
args: [cli, "mcp"],
|
|
1282
|
-
env:
|
|
1283
|
-
name: ASK_THREAD_ENV,
|
|
1284
|
-
value: threadId
|
|
1285
|
-
}]
|
|
1270
|
+
env: env()
|
|
1286
1271
|
}] : [];
|
|
1287
1272
|
},
|
|
1288
1273
|
cli: () => {
|
|
@@ -1310,7 +1295,6 @@ var AskThreads = class {
|
|
|
1310
1295
|
* model. */
|
|
1311
1296
|
offers = /* @__PURE__ */ new Map();
|
|
1312
1297
|
mcpServers;
|
|
1313
|
-
statusListeners = /* @__PURE__ */ new Set();
|
|
1314
1298
|
constructor(launch, tools = {}, limits = askThreadLimits, unwatchedIdleMs = UNWATCHED_IDLE_MS) {
|
|
1315
1299
|
this.launch = launch;
|
|
1316
1300
|
this.tools = tools;
|
|
@@ -1331,8 +1315,6 @@ var AskThreads = class {
|
|
|
1331
1315
|
open(start) {
|
|
1332
1316
|
const thread = new AskThread(this.launch, start, this.mcpServers, this.limits);
|
|
1333
1317
|
this.threads.set(thread.id, thread);
|
|
1334
|
-
thread.onStatusChange(() => this.statusChanged());
|
|
1335
|
-
this.statusChanged();
|
|
1336
1318
|
thread.open();
|
|
1337
1319
|
this.sweeper ??= setInterval(() => this.sweep(), SWEEP_MS);
|
|
1338
1320
|
this.sweeper.unref?.();
|
|
@@ -1363,16 +1345,6 @@ var AskThreads = class {
|
|
|
1363
1345
|
if (now - since >= this.unwatchedIdleMs) this.close(id);
|
|
1364
1346
|
}
|
|
1365
1347
|
}
|
|
1366
|
-
/** Calls back when a thread opens, closes or changes its status. */
|
|
1367
|
-
subscribeStatus(listener) {
|
|
1368
|
-
this.statusListeners.add(listener);
|
|
1369
|
-
return () => {
|
|
1370
|
-
this.statusListeners.delete(listener);
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
statusChanged() {
|
|
1374
|
-
for (const listener of this.statusListeners) listener();
|
|
1375
|
-
}
|
|
1376
1348
|
get(id) {
|
|
1377
1349
|
return this.threads.get(id);
|
|
1378
1350
|
}
|
|
@@ -1389,9 +1361,9 @@ var AskThreads = class {
|
|
|
1389
1361
|
}
|
|
1390
1362
|
close(id, save = true) {
|
|
1391
1363
|
const thread = this.threads.get(id);
|
|
1364
|
+
this.threads.delete(id);
|
|
1392
1365
|
this.unwatchedSince.delete(id);
|
|
1393
1366
|
thread?.close(save);
|
|
1394
|
-
if (this.threads.delete(id)) this.statusChanged();
|
|
1395
1367
|
}
|
|
1396
1368
|
closeDeletedReviews(isDeleted) {
|
|
1397
1369
|
for (const [id, thread] of this.threads) if (isDeleted(thread.reviewId)) this.close(id, false);
|
|
@@ -1404,7 +1376,7 @@ var AskThreads = class {
|
|
|
1404
1376
|
};
|
|
1405
1377
|
//#endregion
|
|
1406
1378
|
//#region src/server/diffr-config.ts
|
|
1407
|
-
const execFileAsync = promisify(execFile);
|
|
1379
|
+
const execFileAsync$2 = promisify(execFile);
|
|
1408
1380
|
let writes = Promise.resolve();
|
|
1409
1381
|
let testing = false;
|
|
1410
1382
|
function serialized(operation) {
|
|
@@ -1445,7 +1417,7 @@ function diffrArgv(args) {
|
|
|
1445
1417
|
async function diffr(args, rootPath, options = {}) {
|
|
1446
1418
|
const executable = diffrExecutable();
|
|
1447
1419
|
try {
|
|
1448
|
-
const operation = execFileAsync(executable, diffrArgv(args), {
|
|
1420
|
+
const operation = execFileAsync$2(executable, diffrArgv(args), {
|
|
1449
1421
|
cwd: rootPath,
|
|
1450
1422
|
maxBuffer: 16777216,
|
|
1451
1423
|
signal: options.signal ?? AbortSignal.timeout(3e4),
|
|
@@ -1875,7 +1847,7 @@ function applyCorsHeaders(request, response) {
|
|
|
1875
1847
|
const varyFields = vary?.split(",").map((field) => field.trim().toLowerCase());
|
|
1876
1848
|
if (!varyFields?.includes("*") && !varyFields?.includes("origin")) response.headers.set("vary", vary ? `${vary}, Origin` : "Origin");
|
|
1877
1849
|
}
|
|
1878
|
-
response.headers.set("access-control-allow-headers", `content-type, x-review-token, ${
|
|
1850
|
+
response.headers.set("access-control-allow-headers", `content-type, x-review-token, ${REVIEW_APP_SESSION_ID_HEADER}`);
|
|
1879
1851
|
response.headers.set("access-control-allow-methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
1880
1852
|
response.headers.set("access-control-allow-private-network", "true");
|
|
1881
1853
|
response.headers.set("access-control-expose-headers", REVIEW_HOST_HEADER);
|
|
@@ -2304,15 +2276,11 @@ async function reviewProgress(store, data, snapshot, signal = new AbortControlle
|
|
|
2304
2276
|
return [];
|
|
2305
2277
|
}
|
|
2306
2278
|
if (!file) {
|
|
2307
|
-
const [base, head] = await Promise.all(["base",
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
})));
|
|
2311
|
-
if (base === null && head === null) throw new SelectionUnavailableError("missing_file", "File is unavailable at these pins.");
|
|
2312
|
-
if (base !== null && head !== null && base !== head) throw new SelectionUnavailableError("no_alignment", "Changed context file has no alignment.");
|
|
2313
|
-
rows = textualRows(source.file, "", base?.split("\n").length ?? 0, head?.split("\n").length ?? 0);
|
|
2279
|
+
const [base, head] = await Promise.all([data.file(sourcePins, "base", source.file), data.file(sourcePins, "head", source.file)]);
|
|
2280
|
+
if (base.text !== head.text) throw new Error("Changed context file has no alignment.");
|
|
2281
|
+
rows = Array.from({ length: base.text.split("\n").length }, (_, line) => [line, line]);
|
|
2314
2282
|
}
|
|
2315
|
-
if (!rows) throw new
|
|
2283
|
+
if (!rows) throw new Error("File has no text alignment.");
|
|
2316
2284
|
const result = resolveDiffSelection(source, rows, file ?? { path: source.file });
|
|
2317
2285
|
resolvedSelections[key] = result;
|
|
2318
2286
|
return result;
|
|
@@ -2320,8 +2288,8 @@ async function reviewProgress(store, data, snapshot, signal = new AbortControlle
|
|
|
2320
2288
|
const resolveAvailable = async (source) => {
|
|
2321
2289
|
try {
|
|
2322
2290
|
return await resolve(source);
|
|
2323
|
-
} catch
|
|
2324
|
-
unavailableSelections[selectionKey(source)] =
|
|
2291
|
+
} catch {
|
|
2292
|
+
unavailableSelections[selectionKey(source)] = "Source links need updating at these pins";
|
|
2325
2293
|
return [];
|
|
2326
2294
|
}
|
|
2327
2295
|
};
|
|
@@ -2347,10 +2315,7 @@ async function reviewProgress(store, data, snapshot, signal = new AbortControlle
|
|
|
2347
2315
|
};
|
|
2348
2316
|
}
|
|
2349
2317
|
}));
|
|
2350
|
-
await Promise.all(selectionReferences(snapshot.document, {
|
|
2351
|
-
tolerant: true,
|
|
2352
|
-
repositoryId: boardRepositoryId(snapshot)
|
|
2353
|
-
}).map(async ({ source }) => {
|
|
2318
|
+
await Promise.all(selectionReferences(snapshot.document, { tolerant: true }).map(async ({ source }) => {
|
|
2354
2319
|
try {
|
|
2355
2320
|
await resolveAvailable(source);
|
|
2356
2321
|
} catch {}
|
|
@@ -2366,7 +2331,7 @@ async function reviewProgress(store, data, snapshot, signal = new AbortControlle
|
|
|
2366
2331
|
unavailable: counting || uncategorized.length ? void 0 : "All changed lines are covered by lenses"
|
|
2367
2332
|
});
|
|
2368
2333
|
const progress = {
|
|
2369
|
-
complete: !counting
|
|
2334
|
+
complete: !counting,
|
|
2370
2335
|
files,
|
|
2371
2336
|
referenceFiles,
|
|
2372
2337
|
lenses,
|
|
@@ -2423,11 +2388,12 @@ function lensReport(lenses, progress) {
|
|
|
2423
2388
|
}
|
|
2424
2389
|
//#endregion
|
|
2425
2390
|
//#region src/ask/checkout-files.ts
|
|
2391
|
+
const execFileAsync$1 = promisify(execFile);
|
|
2426
2392
|
/** The checkout's files relative to its root: tracked ones, and new ones git
|
|
2427
2393
|
* does not ignore. None where git cannot list them. */
|
|
2428
2394
|
async function checkoutFiles(root) {
|
|
2429
2395
|
try {
|
|
2430
|
-
const { stdout } = await
|
|
2396
|
+
const { stdout } = await execFileAsync$1("git", [
|
|
2431
2397
|
"-C",
|
|
2432
2398
|
root,
|
|
2433
2399
|
"ls-files",
|
|
@@ -2598,6 +2564,7 @@ function watchAskThreads(threads, backlog = 256) {
|
|
|
2598
2564
|
}
|
|
2599
2565
|
//#endregion
|
|
2600
2566
|
//#region src/review-stack.ts
|
|
2567
|
+
const execFileAsync = promisify(execFile);
|
|
2601
2568
|
const GitHubStacksSchema = z.array(z.object({ pull_requests: z.array(z.object({
|
|
2602
2569
|
number: z.number().int().positive(),
|
|
2603
2570
|
head: z.object({ ref: z.string().min(1) })
|
|
@@ -2645,12 +2612,12 @@ async function gitHubStack(subject, reviews, runGitHubApi) {
|
|
|
2645
2612
|
}
|
|
2646
2613
|
/** The chain of reviews through this one, linked by the base each names. */
|
|
2647
2614
|
async function localStack(subject, reviews, repositoryPath) {
|
|
2648
|
-
const group = reviews.find((review) => review.reviewId === subject.reviewId)?.
|
|
2615
|
+
const group = reviews.find((review) => review.reviewId === subject.reviewId)?.repositoryGroup?.key;
|
|
2649
2616
|
const current = subject.origin?.branch;
|
|
2650
2617
|
if (!group || !current) return [];
|
|
2651
2618
|
const main = subject.origin?.baseBranch ? await defaultBranch(repositoryPath).catch(() => null) : null;
|
|
2652
2619
|
const name = (ref) => ref.replace(/^origin\//, "");
|
|
2653
|
-
const inGroup = reviews.filter((review) => review.
|
|
2620
|
+
const inGroup = reviews.filter((review) => review.repositoryGroup?.key === group);
|
|
2654
2621
|
const latest = (field, branch) => {
|
|
2655
2622
|
const matches = inGroup.filter((review) => review.origin?.[field] === branch);
|
|
2656
2623
|
return matches.findLast((review) => !review.trashedAt) ?? matches.at(-1);
|
|
@@ -2677,8 +2644,8 @@ async function forkPoint(repositoryPath, main, branch) {
|
|
|
2677
2644
|
const ref = `refs/heads/${branch}`;
|
|
2678
2645
|
const fork = await git(repositoryPath, [
|
|
2679
2646
|
"merge-base",
|
|
2680
|
-
|
|
2681
|
-
|
|
2647
|
+
main,
|
|
2648
|
+
ref
|
|
2682
2649
|
], { allowFailure: true });
|
|
2683
2650
|
if (!fork.ok) return null;
|
|
2684
2651
|
const base = fork.stdout.trim();
|
|
@@ -2688,17 +2655,22 @@ async function forkPoint(repositoryPath, main, branch) {
|
|
|
2688
2655
|
"log",
|
|
2689
2656
|
"--reverse",
|
|
2690
2657
|
"--format=%s",
|
|
2691
|
-
|
|
2658
|
+
`${base}..${ref}`
|
|
2692
2659
|
], { allowFailure: true })).stdout.split("\n")[0] || null
|
|
2693
2660
|
};
|
|
2694
2661
|
}
|
|
2695
2662
|
async function defaultRunGitHubApi(host, endpoint) {
|
|
2696
|
-
const { stdout } = await
|
|
2663
|
+
const { stdout } = await execFileAsync("gh", [
|
|
2697
2664
|
"api",
|
|
2698
2665
|
"--hostname",
|
|
2699
|
-
|
|
2700
|
-
|
|
2666
|
+
host,
|
|
2667
|
+
endpoint
|
|
2701
2668
|
], {
|
|
2669
|
+
env: {
|
|
2670
|
+
...process.env,
|
|
2671
|
+
GH_PROMPT_DISABLED: "1"
|
|
2672
|
+
},
|
|
2673
|
+
encoding: "utf8",
|
|
2702
2674
|
timeout: 1e4,
|
|
2703
2675
|
maxBuffer: 2097152
|
|
2704
2676
|
});
|
|
@@ -2736,6 +2708,7 @@ const publishSchema = z.strictObject({
|
|
|
2736
2708
|
/** Mounted behind local host authentication. Account credentials never enter the renderer. */
|
|
2737
2709
|
function mountSharingHost(app, store, data, shared, options = {}) {
|
|
2738
2710
|
const startLogin = options.login ?? runStoreLogin;
|
|
2711
|
+
const openUrl = options.openUrl ?? openUrlInBrowser;
|
|
2739
2712
|
let login = { pending: false };
|
|
2740
2713
|
app.get("/sharing/account", async (context) => {
|
|
2741
2714
|
const account = await readStoreAuth();
|
|
@@ -2758,9 +2731,9 @@ function mountSharingHost(app, store, data, shared, options = {}) {
|
|
|
2758
2731
|
traces: false,
|
|
2759
2732
|
stdout: discard,
|
|
2760
2733
|
stderr: discard,
|
|
2761
|
-
openUrl:
|
|
2762
|
-
|
|
2763
|
-
|
|
2734
|
+
openUrl: async (url) => {
|
|
2735
|
+
login.url = url;
|
|
2736
|
+
await openUrl(url);
|
|
2764
2737
|
}
|
|
2765
2738
|
}).then((code) => {
|
|
2766
2739
|
login = { pending: false };
|
|
@@ -2829,16 +2802,8 @@ function mountSharingPublisher(app, store, data, options = {}) {
|
|
|
2829
2802
|
validateShareSources(snapshot);
|
|
2830
2803
|
const account = await readSharingAuth();
|
|
2831
2804
|
if (!account) throw new ReviewInputError("Set DEV_REVIEW_SHARE_TOKEN for CI, or run whiteboard login before sharing.", 409);
|
|
2832
|
-
const
|
|
2833
|
-
const
|
|
2834
|
-
const readRepository = options.readRepository ?? readShareRepository;
|
|
2835
|
-
const repository = !root ? void 0 : snapshot.pins ? await readRepository(root) : await (async () => {
|
|
2836
|
-
try {
|
|
2837
|
-
return await readRepository(root);
|
|
2838
|
-
} catch {
|
|
2839
|
-
return;
|
|
2840
|
-
}
|
|
2841
|
-
})();
|
|
2805
|
+
const root = snapshot.pins ? store.repositoryPath(snapshot.pins.repositoryId) : void 0;
|
|
2806
|
+
const repository = root ? await (options.readRepository ?? readShareRepository)(root) : void 0;
|
|
2842
2807
|
const bundle = await exportShare({
|
|
2843
2808
|
store,
|
|
2844
2809
|
data,
|
|
@@ -2846,8 +2811,7 @@ function mountSharingPublisher(app, store, data, options = {}) {
|
|
|
2846
2811
|
version: snapshot.version,
|
|
2847
2812
|
repository
|
|
2848
2813
|
});
|
|
2849
|
-
const
|
|
2850
|
-
const verification = root && (snapshot.pins || commits.length) && repository ? verifyRepository(root, snapshot.pins, repository, commits).then(() => ({ ok: true }), (error) => ({
|
|
2814
|
+
const verification = root && snapshot.pins && repository ? verifyRepository(root, snapshot.pins, repository).then(() => ({ ok: true }), (error) => ({
|
|
2851
2815
|
ok: false,
|
|
2852
2816
|
error
|
|
2853
2817
|
})) : Promise.resolve({ ok: true });
|
|
@@ -2923,14 +2887,8 @@ const clip = (line) => line.trim() === "" ? "(blank line)" : line.length > 120 ?
|
|
|
2923
2887
|
*/
|
|
2924
2888
|
async function anchorQuotes(before, after, read) {
|
|
2925
2889
|
const key = (id, source) => `${id}\0${selectionKey(source)}`;
|
|
2926
|
-
const known = new Set(before ? selectionReferences(before.document, {
|
|
2927
|
-
|
|
2928
|
-
repositoryId: boardRepositoryId(before)
|
|
2929
|
-
}).map(({ id, source }) => key(id, source)) : []);
|
|
2930
|
-
const added = selectionReferences(after.document, {
|
|
2931
|
-
tolerant: true,
|
|
2932
|
-
repositoryId: boardRepositoryId(after)
|
|
2933
|
-
}).filter(({ id, source }) => !known.has(key(id, source)));
|
|
2890
|
+
const known = new Set(before ? selectionReferences(before.document, { tolerant: true }).map(({ id, source }) => key(id, source)) : []);
|
|
2891
|
+
const added = selectionReferences(after.document, { tolerant: true }).filter(({ id, source }) => !known.has(key(id, source)));
|
|
2934
2892
|
return {
|
|
2935
2893
|
quotes: (await Promise.all(added.slice(0, QUOTE_LIMIT).map(async ({ source }) => {
|
|
2936
2894
|
try {
|
|
@@ -2955,18 +2913,15 @@ async function anchorQuotes(before, after, read) {
|
|
|
2955
2913
|
//#endregion
|
|
2956
2914
|
//#region src/review-api/document-text.ts
|
|
2957
2915
|
/** A reading view of saved content, not another document format to maintain. */
|
|
2958
|
-
function documentText(snapshot, targetId, full = false
|
|
2916
|
+
function documentText(snapshot, targetId, full = false) {
|
|
2959
2917
|
const target = targetId === void 0 ? void 0 : elements(snapshot.document).find((item) => item.id === targetId);
|
|
2960
2918
|
if (targetId !== void 0 && !target) throw new ReviewInputError("Target not found in this version.", 404);
|
|
2961
2919
|
const detailed = full || target !== void 0;
|
|
2962
|
-
const lines = [
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
if (relations.dependsOn.length) lines.push(`Depends on: ${list(relations.dependsOn)}`);
|
|
2968
|
-
}
|
|
2969
|
-
lines.push("");
|
|
2920
|
+
const lines = [
|
|
2921
|
+
`# ${snapshot.title}`,
|
|
2922
|
+
`Session ${snapshot.reviewId} · version ${snapshot.version}`,
|
|
2923
|
+
""
|
|
2924
|
+
];
|
|
2970
2925
|
const write = (depth, text) => {
|
|
2971
2926
|
for (const line of text.split("\n")) lines.push(" ".repeat(depth) + line);
|
|
2972
2927
|
};
|
|
@@ -3394,7 +3349,7 @@ function registerOnboardingRoutes(app, store, ask, host, run = runCommand) {
|
|
|
3394
3349
|
agent: input.agent,
|
|
3395
3350
|
...host.historyHooks({
|
|
3396
3351
|
id,
|
|
3397
|
-
|
|
3352
|
+
reviewId,
|
|
3398
3353
|
agent: input.agent,
|
|
3399
3354
|
version: created.version,
|
|
3400
3355
|
head,
|
|
@@ -3542,16 +3497,12 @@ const askDecisionSchema = z.strictObject({
|
|
|
3542
3497
|
permissionId: z.string().min(1),
|
|
3543
3498
|
optionId: z.string().min(1)
|
|
3544
3499
|
});
|
|
3545
|
-
|
|
3546
|
-
* grow past the size of a single command. */
|
|
3547
|
-
const PERSON_EDIT_MAX_BYTES = 16777216;
|
|
3548
|
-
/** A request the gateway forwarded from another machine. */
|
|
3549
|
-
const remoteCaller = (context) => context.req.header(REVIEW_CLIENT_HEADER) === REVIEW_CLIENT_REMOTE;
|
|
3500
|
+
const SCRATCHPAD_DISABLED = "The scratchpad is off. Turn it on in Whiteboard Desktop Settings.";
|
|
3550
3501
|
/** Both hosts mount this behind their token authentication. */
|
|
3551
3502
|
function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
3552
3503
|
desktopAvailable: Boolean(open),
|
|
3553
3504
|
softwareMapEnabled: false
|
|
3554
|
-
}), traceEnabled = async () => false, status = () => ({}), hooks = {}, ask) {
|
|
3505
|
+
}), scratchpadEnabled = () => false, traceEnabled = async () => false, status = () => ({}), hooks = {}, ask) {
|
|
3555
3506
|
const app = new Hono();
|
|
3556
3507
|
app.onError((error, context) => {
|
|
3557
3508
|
if (error instanceof HttpJsonError) return context.json({ error: error.message }, error.statusCode);
|
|
@@ -3580,7 +3531,15 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3580
3531
|
await next();
|
|
3581
3532
|
};
|
|
3582
3533
|
const sharedCommandSchema = z.object({ operation: z.object({ reviewId: z.string().optional() }) });
|
|
3534
|
+
const ensureScratchpad = async (id) => {
|
|
3535
|
+
if (open && scratchpadEnabled() && (!id || id === SCRATCHPAD_ID)) await store.ensureScratchpad();
|
|
3536
|
+
};
|
|
3537
|
+
const refuseDisabledScratchpad = (id) => {
|
|
3538
|
+
if (id === SCRATCHPAD_ID && !scratchpadEnabled()) throw new ReviewInputError(SCRATCHPAD_DISABLED, 409);
|
|
3539
|
+
};
|
|
3583
3540
|
const sharedGuard = async (context, next) => {
|
|
3541
|
+
refuseDisabledScratchpad(context.req.param("id"));
|
|
3542
|
+
await ensureScratchpad(context.req.param("id"));
|
|
3584
3543
|
const id = context.req.param("id");
|
|
3585
3544
|
if (!id || !isShared(id)) return next();
|
|
3586
3545
|
const query = readQuerySchemas.get.parse({ version: context.req.query("version") });
|
|
@@ -3599,53 +3558,23 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3599
3558
|
if (!snapshot || version !== void 0 && version !== snapshot.version) throw new ReviewInputError("Shared review version is unavailable.", 404);
|
|
3600
3559
|
return snapshot;
|
|
3601
3560
|
};
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
return state ? {
|
|
3606
|
-
...board,
|
|
3607
|
-
pullRequestState: state
|
|
3608
|
-
} : board;
|
|
3609
|
-
};
|
|
3610
|
-
const catalog = () => [...store.list().map(withPullRequestState), ...shared?.list() ?? []];
|
|
3611
|
-
/** Where a board sits among its project's, and its PR's facts, for the
|
|
3612
|
-
* board's header. */
|
|
3613
|
-
const placeOf = (id) => {
|
|
3614
|
-
if (isShared(id) || !store.has(id)) return { siblings: [] };
|
|
3615
|
-
const { board, parent, siblings, children } = store.place(id);
|
|
3616
|
-
const url = board.origin?.pullRequestUrl;
|
|
3617
|
-
const state = data?.pullRequestState(url);
|
|
3618
|
-
const baseBranch = url && data?.pullRequestBaseBranch(url);
|
|
3619
|
-
const headBranch = url && data?.pullRequestHeadBranch(url);
|
|
3620
|
-
const place = ({ reviewId, title, target }) => ({
|
|
3621
|
-
reviewId,
|
|
3622
|
-
title,
|
|
3623
|
-
icon: boardIconKind(target)
|
|
3624
|
-
});
|
|
3625
|
-
const listed = (child) => {
|
|
3626
|
-
const childState = data?.pullRequestState(child.origin?.pullRequestUrl);
|
|
3627
|
-
return {
|
|
3628
|
-
...place(child),
|
|
3629
|
-
...child.origin?.pullRequestNumber && { pullRequestNumber: child.origin.pullRequestNumber },
|
|
3630
|
-
...childState && { pullRequestState: childState }
|
|
3631
|
-
};
|
|
3632
|
-
};
|
|
3633
|
-
return {
|
|
3634
|
-
...state && { pullRequestState: state },
|
|
3635
|
-
...baseBranch && { baseBranch },
|
|
3636
|
-
...headBranch && { headBranch },
|
|
3637
|
-
board: place(board),
|
|
3638
|
-
...parent && { parent: place(parent) },
|
|
3639
|
-
siblings: siblings.map(listed),
|
|
3640
|
-
...children?.length && { children: children.map(listed) }
|
|
3641
|
-
};
|
|
3561
|
+
const catalog = () => {
|
|
3562
|
+
const local = store.list();
|
|
3563
|
+
return [...scratchpadEnabled() ? local : local.filter((summary) => summary.kind !== "scratchpad"), ...shared?.list() ?? []];
|
|
3642
3564
|
};
|
|
3643
|
-
app.get("/", (context) =>
|
|
3565
|
+
app.get("/", async (context) => {
|
|
3566
|
+
await ensureScratchpad();
|
|
3567
|
+
return context.json(catalog());
|
|
3568
|
+
});
|
|
3644
3569
|
app.get("/feedback-eligibility", (context) => context.json({ eligible: store.feedbackEligible() }));
|
|
3645
|
-
const instructionContext = async () => ({
|
|
3570
|
+
const instructionContext = async () => ({
|
|
3571
|
+
desktopAvailable: Boolean(open),
|
|
3572
|
+
scratchpadEnabled: scratchpadEnabled(),
|
|
3573
|
+
traceEnabled: await traceEnabled()
|
|
3574
|
+
});
|
|
3646
3575
|
app.get("/authoring", async (context) => {
|
|
3647
3576
|
const instructions = await instructionContext();
|
|
3648
|
-
return context.json(authoringTools(instructions.traceEnabled));
|
|
3577
|
+
return context.json(authoringTools(scratchpadAvailable(instructions), instructions.traceEnabled));
|
|
3649
3578
|
});
|
|
3650
3579
|
app.get("/instructions", async (context) => {
|
|
3651
3580
|
const { topic } = instructionsQuerySchema.parse(context.req.query());
|
|
@@ -3699,7 +3628,10 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3699
3628
|
...status(),
|
|
3700
3629
|
desktopAvailable: (await capabilities()).desktopAvailable
|
|
3701
3630
|
}));
|
|
3702
|
-
app.get("/capabilities", async (context) => context.json(
|
|
3631
|
+
app.get("/capabilities", async (context) => context.json({
|
|
3632
|
+
...await capabilities(),
|
|
3633
|
+
scratchpadEnabled: scratchpadEnabled()
|
|
3634
|
+
}));
|
|
3703
3635
|
app.get("/:id/activity", (context) => {
|
|
3704
3636
|
const id = context.req.param("id");
|
|
3705
3637
|
readReview(id);
|
|
@@ -3721,9 +3653,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3721
3653
|
action
|
|
3722
3654
|
} : input));
|
|
3723
3655
|
});
|
|
3724
|
-
app.post("/:id/person-edit", async (context) => context.json(await store.personEdit(context.req.param("id"), await readBoundedRequestJson(context.req.raw, PERSON_EDIT_MAX_BYTES))));
|
|
3725
|
-
app.post("/:id/focus", async (context) => context.json(store.focus(context.req.param("id"), await readBoundedRequestJson(context.req.raw))));
|
|
3726
|
-
app.post("/:id/discard", async (context) => context.json(await store.discard(context.req.param("id"))));
|
|
3727
3656
|
app.get("/watch", (context) => {
|
|
3728
3657
|
let input;
|
|
3729
3658
|
try {
|
|
@@ -3740,16 +3669,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3740
3669
|
let dirtyList = lists;
|
|
3741
3670
|
const dirtyReviews = new Set(reviewIds);
|
|
3742
3671
|
let sentList;
|
|
3743
|
-
const linksRevision = /* @__PURE__ */ new Map();
|
|
3744
|
-
const linksKey = (id) => {
|
|
3745
|
-
if (!data || isShared(id) || !store.has(id)) return "";
|
|
3746
|
-
const { outgoing, backlinks } = store.links(id);
|
|
3747
|
-
return JSON.stringify([...outgoing, ...backlinks].map((link) => link.pullRequestUrl && data.pullRequestState(link.pullRequestUrl, { seen: false })));
|
|
3748
|
-
};
|
|
3749
|
-
const linksKeys = new Map([...reviewIds].map((reviewId) => [reviewId, linksKey(reviewId)]));
|
|
3750
|
-
const placeRevision = /* @__PURE__ */ new Map();
|
|
3751
|
-
const placeKey = (id) => JSON.stringify(placeOf(id));
|
|
3752
|
-
const placeKeys = new Map([...reviewIds].map((reviewId) => [reviewId, placeKey(reviewId)]));
|
|
3753
3672
|
const readReviewLine = (reviewId) => {
|
|
3754
3673
|
try {
|
|
3755
3674
|
return {
|
|
@@ -3758,9 +3677,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3758
3677
|
value: {
|
|
3759
3678
|
...readReview(reviewId),
|
|
3760
3679
|
activity: store.activity.read(reviewId),
|
|
3761
|
-
coverageRevision: data?.coverageRevision ?? 0
|
|
3762
|
-
linksRevision: linksRevision.get(reviewId) ?? 0,
|
|
3763
|
-
placeRevision: placeRevision.get(reviewId) ?? 0
|
|
3680
|
+
coverageRevision: data?.coverageRevision ?? 0
|
|
3764
3681
|
}
|
|
3765
3682
|
};
|
|
3766
3683
|
} catch (error) {
|
|
@@ -3792,18 +3709,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3792
3709
|
dirtyReviews.add(reviewId);
|
|
3793
3710
|
return true;
|
|
3794
3711
|
};
|
|
3795
|
-
/** Bumps the place of each board whose place changed. */
|
|
3796
|
-
const refreshPlaces = () => {
|
|
3797
|
-
let marked = false;
|
|
3798
|
-
for (const [id, key] of placeKeys) {
|
|
3799
|
-
const next = placeKey(id);
|
|
3800
|
-
if (next === key) continue;
|
|
3801
|
-
placeKeys.set(id, next);
|
|
3802
|
-
placeRevision.set(id, (placeRevision.get(id) ?? 0) + 1);
|
|
3803
|
-
marked = markReview(id) || marked;
|
|
3804
|
-
}
|
|
3805
|
-
return marked;
|
|
3806
|
-
};
|
|
3807
3712
|
const markLists = () => {
|
|
3808
3713
|
dirtyList ||= lists;
|
|
3809
3714
|
return lists;
|
|
@@ -3816,12 +3721,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3816
3721
|
notify();
|
|
3817
3722
|
}) ?? (() => {}),
|
|
3818
3723
|
store.subscribe((result) => {
|
|
3819
|
-
|
|
3820
|
-
for (const from of store.linkingBoards(result.reviewId)) {
|
|
3821
|
-
linksRevision.set(from, (linksRevision.get(from) ?? 0) + 1);
|
|
3822
|
-
marked = markReview(from) || marked;
|
|
3823
|
-
}
|
|
3824
|
-
if (marked) notify();
|
|
3724
|
+
if (markReview(result.reviewId)) notify();
|
|
3825
3725
|
}),
|
|
3826
3726
|
store.activity.subscribe((id) => {
|
|
3827
3727
|
if (markReview(id)) notify();
|
|
@@ -3829,24 +3729,11 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3829
3729
|
store.activity.subscribeWorking(() => {
|
|
3830
3730
|
if (markLists()) notify();
|
|
3831
3731
|
}),
|
|
3832
|
-
data?.subscribePullRequestStates(() => {
|
|
3833
|
-
let marked = markLists();
|
|
3834
|
-
marked = refreshPlaces() || marked;
|
|
3835
|
-
for (const reviewId of reviewIds) {
|
|
3836
|
-
const next = linksKey(reviewId);
|
|
3837
|
-
if (next === linksKeys.get(reviewId)) continue;
|
|
3838
|
-
linksKeys.set(reviewId, next);
|
|
3839
|
-
linksRevision.set(reviewId, (linksRevision.get(reviewId) ?? 0) + 1);
|
|
3840
|
-
marked = markReview(reviewId) || marked;
|
|
3841
|
-
}
|
|
3842
|
-
if (marked) notify();
|
|
3843
|
-
}) ?? (() => {}),
|
|
3844
3732
|
shared?.subscribe(() => {
|
|
3845
3733
|
if (markLists()) notify();
|
|
3846
3734
|
}) ?? (() => {}),
|
|
3847
3735
|
store.subscribeCatalog(() => {
|
|
3848
|
-
|
|
3849
|
-
if (markLists() || marked) notify();
|
|
3736
|
+
if (markLists()) notify();
|
|
3850
3737
|
})
|
|
3851
3738
|
];
|
|
3852
3739
|
return () => stops.forEach((stop) => stop());
|
|
@@ -3902,18 +3789,19 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3902
3789
|
storage: z.enum(["s3", "hosted"]).optional(),
|
|
3903
3790
|
trace: z.string().min(1).optional()
|
|
3904
3791
|
});
|
|
3905
|
-
const tracePins =
|
|
3906
|
-
const
|
|
3907
|
-
|
|
3792
|
+
const tracePins = (id, version) => {
|
|
3793
|
+
const { pins } = readReview(id, version);
|
|
3794
|
+
if (!pins) throw new ReviewInputError("This document has no source pins of its own.", 409);
|
|
3795
|
+
return pins;
|
|
3908
3796
|
};
|
|
3909
3797
|
app.get("/:id/agent-traces", async (context) => {
|
|
3910
3798
|
const query = traceQuery.parse(context.req.query());
|
|
3911
|
-
const pins =
|
|
3799
|
+
const pins = tracePins(context.req.param("id"), query.version);
|
|
3912
3800
|
return context.json(await listPinnedTraces(store.repositoryPath(pins.repositoryId), pins, query.storage));
|
|
3913
3801
|
});
|
|
3914
3802
|
app.get("/:id/agent-traces/:sessionId", async (context) => {
|
|
3915
3803
|
const query = traceQuery.parse(context.req.query());
|
|
3916
|
-
const pins =
|
|
3804
|
+
const pins = tracePins(context.req.param("id"), query.version);
|
|
3917
3805
|
const result = await readStoredTrace(store.repositoryPath(pins.repositoryId), context.req.param("sessionId"), query.trace, query.storage);
|
|
3918
3806
|
if (!result.ok) return context.json({
|
|
3919
3807
|
ok: false,
|
|
@@ -3956,16 +3844,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3956
3844
|
const input = z.strictObject({ path: z.string().min(1) }).parse(await readBoundedRequestJson(context.req.raw));
|
|
3957
3845
|
return context.json(await data.register(input.path));
|
|
3958
3846
|
});
|
|
3959
|
-
app.post("/resources", async (context) =>
|
|
3960
|
-
const body = await readBoundedRequestJson(context.req.raw, 8388608);
|
|
3961
|
-
const named = z.looseObject({ repositoryPath: z.string().min(1) }).safeParse(body);
|
|
3962
|
-
if (!named.success) return context.json(await data.upload(body));
|
|
3963
|
-
const { repositoryPath, ...upload } = named.data;
|
|
3964
|
-
return context.json(await data.upload({
|
|
3965
|
-
...upload,
|
|
3966
|
-
repositoryId: (await data.register(repositoryPath)).id
|
|
3967
|
-
}));
|
|
3968
|
-
});
|
|
3847
|
+
app.post("/resources", async (context) => context.json(await data.upload(await readBoundedRequestJson(context.req.raw, 8388608))));
|
|
3969
3848
|
app.get("/:id/resources/:resourceId", async (context) => {
|
|
3970
3849
|
const id = context.req.param("id");
|
|
3971
3850
|
const snapshot = readReview(id);
|
|
@@ -4072,11 +3951,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4072
3951
|
const { pins } = await data.resolveSource(readReview(context.req.param("id"), input.version), input.commit, queryAnchor(input));
|
|
4073
3952
|
return context.json(await data.changes(pins));
|
|
4074
3953
|
});
|
|
4075
|
-
app.get("/:id/commit/:commit", async (context) => {
|
|
4076
|
-
const repositoryId = boardRepositoryId(readReview(context.req.param("id")));
|
|
4077
|
-
if (!repositoryId) throw new ReviewInputError("This board has no repository.", 404);
|
|
4078
|
-
return context.json(await data.commitInfo(repositoryId, context.req.param("commit")));
|
|
4079
|
-
});
|
|
4080
3954
|
app.get("/:id/commits", async (context) => {
|
|
4081
3955
|
const input = readQuerySchemas.commits.parse(context.req.query());
|
|
4082
3956
|
return context.json(await data.commits((await data.resolveSource(readReview(context.req.param("id"), input.version))).pins));
|
|
@@ -4109,12 +3983,20 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4109
3983
|
head: `b/${diff.newPath}`
|
|
4110
3984
|
} : void 0);
|
|
4111
3985
|
return [
|
|
4112
|
-
`Whiteboard: ${
|
|
3986
|
+
`Whiteboard: ${snapshot.title}`,
|
|
4113
3987
|
`Session ID: ${snapshot.reviewId}`,
|
|
4114
3988
|
`Version: ${snapshot.version}`,
|
|
4115
3989
|
...selection.apiSource?.commit ? [`Selected commit: ${selection.apiSource.commit}`] : [],
|
|
4116
|
-
...selection.apiSource?.pins ? [
|
|
4117
|
-
|
|
3990
|
+
...selection.apiSource?.pins ? [
|
|
3991
|
+
`Selected repository ID: ${selection.apiSource.pins.repositoryId}`,
|
|
3992
|
+
...selection.apiSource.pins.base ? [`Selected base: ${selection.apiSource.pins.base}`] : [],
|
|
3993
|
+
`Selected head: ${selection.apiSource.pins.head}`
|
|
3994
|
+
] : [],
|
|
3995
|
+
...snapshot.pins ? [
|
|
3996
|
+
`Repository ID: ${snapshot.pins.repositoryId}`,
|
|
3997
|
+
`Session base: ${snapshot.pins.base}`,
|
|
3998
|
+
`Session head: ${snapshot.pins.head}`
|
|
3999
|
+
] : [],
|
|
4118
4000
|
`Read this version with session_get({"sessionId":"${snapshot.reviewId}","version":${snapshot.version},"full":true}).`,
|
|
4119
4001
|
"",
|
|
4120
4002
|
text,
|
|
@@ -4123,10 +4005,9 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4123
4005
|
].join("\n");
|
|
4124
4006
|
}
|
|
4125
4007
|
if (ask && data) {
|
|
4126
|
-
const projectOf = (reviewId) => isShared(reviewId) || !store.has(reviewId) ? void 0 : store.projectOf(reviewId);
|
|
4127
4008
|
const readThread = (reviewId, threadId) => {
|
|
4128
4009
|
const thread = ask.threads.get(threadId);
|
|
4129
|
-
if (!thread) throw new ReviewInputError("This conversation has ended.", 404);
|
|
4010
|
+
if (!thread || thread.reviewId !== reviewId) throw new ReviewInputError("This conversation has ended.", 404);
|
|
4130
4011
|
return thread;
|
|
4131
4012
|
};
|
|
4132
4013
|
/** Where a thread in a review saves itself: its Ask history, recorded
|
|
@@ -4176,7 +4057,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4176
4057
|
const reach = await ask.threads.reach(agent, threadId);
|
|
4177
4058
|
const { reviewId } = snapshot;
|
|
4178
4059
|
return [
|
|
4179
|
-
`A reviewer is reading "${
|
|
4060
|
+
`A reviewer is reading "${snapshot.title}" in Whiteboard and has a question about ${selection.target.kind === "review" ? "it" : "a selection"}.`,
|
|
4180
4061
|
checkout.live ? "Your working directory is the repository the review describes." : `Your working directory is a checkout of the review's head commit, ${checkout.head}. Answer from this code, not from other branches.`,
|
|
4181
4062
|
"Answer the question for a staff engineer: lead with the answer and keep it short. Explain at the level of components and data flow before functions, and check each claim about the code against code you have read.",
|
|
4182
4063
|
"Name files by their path from the checkout root, with a line where it helps, as in `src/app.ts:42`; the reviewer can open them from your answer.",
|
|
@@ -4227,17 +4108,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4227
4108
|
const permissions = input.permissions ?? defaultAskPermissions(input.agent);
|
|
4228
4109
|
const initialContext = await askContext(id, input.agent, snapshot, checkout, input.selection, version);
|
|
4229
4110
|
assertWritable(reviewId);
|
|
4230
|
-
store.askHistory.markViewed(id, createdAt);
|
|
4231
4111
|
const thread = ask.threads.open({
|
|
4232
4112
|
id,
|
|
4233
4113
|
reviewId,
|
|
4234
4114
|
agent: input.agent,
|
|
4235
4115
|
...historyHooks({
|
|
4236
4116
|
id,
|
|
4237
|
-
|
|
4238
|
-
board: reviewId,
|
|
4239
|
-
block: target.anchor.blockId
|
|
4240
|
-
} : { board: reviewId },
|
|
4117
|
+
reviewId,
|
|
4241
4118
|
agent: input.agent,
|
|
4242
4119
|
version: snapshot.version,
|
|
4243
4120
|
head: checkout.head,
|
|
@@ -4261,95 +4138,23 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4261
4138
|
});
|
|
4262
4139
|
return context.json({ threadId: thread.id });
|
|
4263
4140
|
});
|
|
4264
|
-
/** Every project's threads for the sidebar: who runs them, where they
|
|
4265
|
-
* started and live, and which boards they changed. */
|
|
4266
|
-
const projectThreads = (reviewId) => {
|
|
4267
|
-
const entries = store.askHistory.listAll();
|
|
4268
|
-
const ids = entries.map((entry) => entry.id);
|
|
4269
|
-
const edited = store.boardsChangedByThreads(ids);
|
|
4270
|
-
const viewed = store.askHistory.viewedAt(ids);
|
|
4271
|
-
const boards = /* @__PURE__ */ new Map();
|
|
4272
|
-
const board = (id) => {
|
|
4273
|
-
let found = boards.get(id);
|
|
4274
|
-
if (!found) {
|
|
4275
|
-
found = store.has(id) ? store.boardFace(id) : {
|
|
4276
|
-
reviewId: id,
|
|
4277
|
-
icon: boardIconKind(void 0)
|
|
4278
|
-
};
|
|
4279
|
-
boards.set(id, found);
|
|
4280
|
-
}
|
|
4281
|
-
return found;
|
|
4282
|
-
};
|
|
4283
|
-
const project = projectOf(reviewId);
|
|
4284
|
-
return {
|
|
4285
|
-
board: reviewId,
|
|
4286
|
-
...project && { project },
|
|
4287
|
-
threads: entries.map((entry) => {
|
|
4288
|
-
const anchor = entry.anchor ?? { board: reviewId };
|
|
4289
|
-
const status = ask.threads.get(entry.id)?.status;
|
|
4290
|
-
const viewedAt = viewed.get(entry.id);
|
|
4291
|
-
const unread = status !== "starting" && status !== "running" && status !== "waiting" && viewedAt !== void 0 && entry.updatedAt > viewedAt;
|
|
4292
|
-
return {
|
|
4293
|
-
id: entry.id,
|
|
4294
|
-
...entry.projectId && { projectId: entry.projectId },
|
|
4295
|
-
...store.has(anchor.board) && { place: store.boardPlace(anchor.board) },
|
|
4296
|
-
agent: entry.agent,
|
|
4297
|
-
agentName: askAgents[entry.agent].name,
|
|
4298
|
-
opening: entry.question ?? entry.title,
|
|
4299
|
-
pinned: entry.selection.target.kind === "text" && entry.selection.target.anchor !== void 0,
|
|
4300
|
-
...status && { status },
|
|
4301
|
-
...unread && { unread },
|
|
4302
|
-
updatedAt: entry.updatedAt,
|
|
4303
|
-
anchor: {
|
|
4304
|
-
board: board(anchor.board),
|
|
4305
|
-
...anchor.block && { block: anchor.block }
|
|
4306
|
-
},
|
|
4307
|
-
edited: edited.get(entry.id) ?? []
|
|
4308
|
-
};
|
|
4309
|
-
})
|
|
4310
|
-
};
|
|
4311
|
-
};
|
|
4312
4141
|
app.get("/:id/ask/threads", (context) => {
|
|
4313
4142
|
const reviewId = context.req.param("id");
|
|
4314
4143
|
readReview(reviewId);
|
|
4315
|
-
|
|
4316
|
-
let sent = "";
|
|
4317
|
-
return watch(() => {
|
|
4318
|
-
const line = JSON.stringify(projectThreads(reviewId));
|
|
4319
|
-
if (line === sent) return [];
|
|
4320
|
-
sent = line;
|
|
4321
|
-
return [line];
|
|
4322
|
-
}, (notify) => {
|
|
4323
|
-
const stops = [
|
|
4324
|
-
store.askHistory.subscribe(notify),
|
|
4325
|
-
ask.threads.subscribeStatus(notify),
|
|
4326
|
-
store.subscribe(() => notify())
|
|
4327
|
-
];
|
|
4328
|
-
return () => stops.forEach((stop) => stop());
|
|
4329
|
-
});
|
|
4330
|
-
}
|
|
4331
|
-
return context.json({
|
|
4332
|
-
threads: store.askHistory.list(reviewId, projectOf(reviewId)),
|
|
4333
|
-
board: reviewId
|
|
4334
|
-
});
|
|
4144
|
+
return context.json({ threads: store.askHistory.list(reviewId) });
|
|
4335
4145
|
});
|
|
4336
4146
|
app.post("/:id/ask/watch", reviewAccess("read"), async (context) => {
|
|
4337
4147
|
const reviewId = context.req.param("id");
|
|
4338
4148
|
readReview(reviewId);
|
|
4339
4149
|
const { threads } = askWatchSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
4340
4150
|
return watchAskThreads(new Map(threads.map((threadId) => {
|
|
4341
|
-
|
|
4151
|
+
const thread = ask.threads.get(threadId);
|
|
4152
|
+
return [threadId, thread?.reviewId === reviewId ? thread : void 0];
|
|
4342
4153
|
})));
|
|
4343
4154
|
});
|
|
4344
|
-
app.get("/:id/ask/:threadId/boards", (context) => {
|
|
4345
|
-
const threadId = context.req.param("threadId");
|
|
4346
|
-
const anchor = store.askHistory.get(threadId)?.anchor.board ?? ask.threads.get(threadId)?.reviewId;
|
|
4347
|
-
if (remoteCaller(context) || !anchor) throw new ReviewInputError("This conversation was not found.", 404);
|
|
4348
|
-
return context.json({ boards: store.boardsChangedBy(threadId) });
|
|
4349
|
-
});
|
|
4350
4155
|
app.get("/:id/ask/:threadId", (context) => {
|
|
4351
4156
|
const record = store.askHistory.get(context.req.param("threadId"));
|
|
4352
|
-
if (
|
|
4157
|
+
if (record?.reviewId !== context.req.param("id")) throw new ReviewInputError("This conversation was not found.", 404);
|
|
4353
4158
|
const target = record.selection.target;
|
|
4354
4159
|
return context.json({
|
|
4355
4160
|
id: record.id,
|
|
@@ -4371,19 +4176,18 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4371
4176
|
app.post("/:id/ask/:threadId/open", reviewAccess("edit"), async (context) => {
|
|
4372
4177
|
const reviewId = context.req.param("id");
|
|
4373
4178
|
const threadId = context.req.param("threadId");
|
|
4374
|
-
if (ask.threads.get(threadId)) return context.json({ threadId });
|
|
4179
|
+
if (ask.threads.get(threadId)?.reviewId === reviewId) return context.json({ threadId });
|
|
4375
4180
|
const record = store.askHistory.get(threadId);
|
|
4376
4181
|
const { picks } = askOpenSchema.parse(await readBoundedRequestJson(context.req.raw, void 0, {}));
|
|
4377
|
-
if (
|
|
4378
|
-
const
|
|
4379
|
-
const snapshot = readReview(anchor, record.version);
|
|
4182
|
+
if (record?.reviewId !== reviewId) throw new ReviewInputError("This conversation was not found.", 404);
|
|
4183
|
+
const snapshot = readReview(reviewId, record.version);
|
|
4380
4184
|
const checkout = await data.agentCheckout(snapshot);
|
|
4381
4185
|
const target = record.selection.target;
|
|
4382
4186
|
const initialContext = await askContext(record.id, record.agent, snapshot, checkout, record.selection, record.version);
|
|
4383
4187
|
assertWritable(reviewId);
|
|
4384
4188
|
ask.threads.open({
|
|
4385
4189
|
id: record.id,
|
|
4386
|
-
reviewId
|
|
4190
|
+
reviewId,
|
|
4387
4191
|
agent: record.agent,
|
|
4388
4192
|
cwd: checkout.rootPath,
|
|
4389
4193
|
head: checkout.head,
|
|
@@ -4459,91 +4263,27 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4459
4263
|
await readThread(context.req.param("id"), context.req.param("threadId")).cancel();
|
|
4460
4264
|
return context.json({ ok: true });
|
|
4461
4265
|
});
|
|
4462
|
-
app.post("/:id/ask/:threadId/viewed", (context) => {
|
|
4463
|
-
const threadId = context.req.param("threadId");
|
|
4464
|
-
if (!ask.threads.get(threadId) && !store.askHistory.get(threadId)) throw new ReviewInputError("This conversation was not found.", 404);
|
|
4465
|
-
store.askHistory.markViewed(threadId);
|
|
4466
|
-
return context.json({ ok: true });
|
|
4467
|
-
});
|
|
4468
4266
|
app.post("/:id/ask/:threadId/close", reviewAccess("read"), (context) => {
|
|
4469
4267
|
readThread(context.req.param("id"), context.req.param("threadId"));
|
|
4470
4268
|
ask.threads.close(context.req.param("threadId"), !readReview(context.req.param("id")).trashedAt);
|
|
4471
4269
|
return context.json({ ok: true });
|
|
4472
4270
|
});
|
|
4473
4271
|
app.delete("/:id/ask/:threadId", reviewAccess("edit"), (context) => {
|
|
4474
|
-
context.req.param("id");
|
|
4272
|
+
const reviewId = context.req.param("id");
|
|
4475
4273
|
const threadId = context.req.param("threadId");
|
|
4476
|
-
if (
|
|
4477
|
-
if (ask.threads.get(threadId)) ask.threads.close(threadId);
|
|
4274
|
+
if (store.askHistory.get(threadId)?.reviewId !== reviewId) throw new ReviewInputError("This conversation was not found.", 404);
|
|
4275
|
+
if (ask.threads.get(threadId)?.reviewId === reviewId) ask.threads.close(threadId);
|
|
4478
4276
|
store.askHistory.delete(threadId);
|
|
4479
4277
|
return context.json({ ok: true });
|
|
4480
4278
|
});
|
|
4481
4279
|
}
|
|
4482
|
-
app.get("/:id/place", (context) => {
|
|
4483
|
-
const id = context.req.param("id");
|
|
4484
|
-
if (!isShared(id)) store.assertExists(id);
|
|
4485
|
-
return context.json(placeOf(id));
|
|
4486
|
-
});
|
|
4487
4280
|
app.get("/:id/stack", async (context) => {
|
|
4488
4281
|
const query = readQuerySchemas.get.parse(context.req.query());
|
|
4489
4282
|
const id = context.req.param("id");
|
|
4490
4283
|
const snapshot = readReview(id, query.version);
|
|
4491
4284
|
if (isShared(id) || !snapshot.pins) return context.json({ layers: [] });
|
|
4492
|
-
const
|
|
4493
|
-
|
|
4494
|
-
const trunk = layers.length > 1 && repositoryPath ? await defaultBranch(repositoryPath).catch(() => null) : null;
|
|
4495
|
-
return context.json({
|
|
4496
|
-
layers: layers.map((layer) => {
|
|
4497
|
-
const state = data?.pullRequestState(layer.pullRequestUrl ?? void 0);
|
|
4498
|
-
return state ? {
|
|
4499
|
-
...layer,
|
|
4500
|
-
pullRequestState: state
|
|
4501
|
-
} : layer;
|
|
4502
|
-
}),
|
|
4503
|
-
...trunk && { base: trunk.ref.replace(/^origin\//, "") }
|
|
4504
|
-
});
|
|
4505
|
-
});
|
|
4506
|
-
app.get("/:id/project", (context) => {
|
|
4507
|
-
const id = context.req.param("id");
|
|
4508
|
-
if (isShared(id)) return context.json({
|
|
4509
|
-
project: null,
|
|
4510
|
-
boards: []
|
|
4511
|
-
});
|
|
4512
|
-
const project = store.summary(id)?.project ?? null;
|
|
4513
|
-
return context.json({
|
|
4514
|
-
project,
|
|
4515
|
-
boards: project ? store.list().filter((board) => board.project?.id === project.id && !board.trashedAt).map(withPullRequestState) : []
|
|
4516
|
-
});
|
|
4517
|
-
});
|
|
4518
|
-
app.get("/:id/links", (context) => {
|
|
4519
|
-
const id = context.req.param("id");
|
|
4520
|
-
if (isShared(id)) return context.json({
|
|
4521
|
-
outgoing: boardLinks(id, readReview(id).document).map(({ from, to }) => ({
|
|
4522
|
-
from,
|
|
4523
|
-
to,
|
|
4524
|
-
missing: true,
|
|
4525
|
-
elsewhere: true
|
|
4526
|
-
})),
|
|
4527
|
-
backlinks: []
|
|
4528
|
-
});
|
|
4529
|
-
const { version } = readQuerySchemas.get.pick({ version: true }).parse({ version: context.req.query("version") });
|
|
4530
|
-
const { outgoing, backlinks } = store.links(id, version);
|
|
4531
|
-
return context.json({
|
|
4532
|
-
outgoing: outgoing.map(withPullRequestState),
|
|
4533
|
-
backlinks: backlinks.map(withPullRequestState)
|
|
4534
|
-
});
|
|
4535
|
-
});
|
|
4536
|
-
app.get("/:id/links/peek", (context) => {
|
|
4537
|
-
const id = context.req.param("id");
|
|
4538
|
-
if (isShared(id)) return context.json({});
|
|
4539
|
-
const { version } = readQuerySchemas.get.pick({ version: true }).parse({ version: context.req.query("version") });
|
|
4540
|
-
const board = context.req.query("board");
|
|
4541
|
-
const block = context.req.query("block");
|
|
4542
|
-
if (!board) throw new ReviewInputError("Name the linked board.", 400);
|
|
4543
|
-
return context.json(store.linkPeek(id, {
|
|
4544
|
-
board,
|
|
4545
|
-
...block && { block }
|
|
4546
|
-
}, version));
|
|
4285
|
+
const layers = await resolveReviewStackLayers(snapshot, store.list(), { repositoryPath: store.repositories().find((repository) => repository.id === snapshot.pins.repositoryId)?.path });
|
|
4286
|
+
return context.json({ layers });
|
|
4547
4287
|
});
|
|
4548
4288
|
app.get("/:id/history", (context) => {
|
|
4549
4289
|
const id = context.req.param("id");
|
|
@@ -4552,39 +4292,14 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4552
4292
|
return context.json([{
|
|
4553
4293
|
version: snapshot.version,
|
|
4554
4294
|
title: snapshot.title,
|
|
4555
|
-
createdAt: snapshot.createdAt
|
|
4556
|
-
author: "agent"
|
|
4295
|
+
createdAt: snapshot.createdAt
|
|
4557
4296
|
}]);
|
|
4558
4297
|
});
|
|
4559
|
-
/** A board's relations as session_list reports them: only those it has. */
|
|
4560
|
-
const listedRelations = ({ parentId, children, dependsOn }) => {
|
|
4561
|
-
const listed = {};
|
|
4562
|
-
if (parentId) listed.parentId = parentId;
|
|
4563
|
-
if (children.length) listed.children = children;
|
|
4564
|
-
if (dependsOn.length) listed.dependsOn = dependsOn;
|
|
4565
|
-
return listed;
|
|
4566
|
-
};
|
|
4567
|
-
/** A board's relations, each board named by its title. */
|
|
4568
|
-
const relatedBoards = (id) => {
|
|
4569
|
-
const { parentId, children, dependsOn } = store.relations(id);
|
|
4570
|
-
const named = (board) => ({
|
|
4571
|
-
id: board,
|
|
4572
|
-
title: store.read(board).title
|
|
4573
|
-
});
|
|
4574
|
-
return {
|
|
4575
|
-
...parentId && { parent: named(parentId) },
|
|
4576
|
-
children: children.map(named),
|
|
4577
|
-
dependsOn: dependsOn.map(named)
|
|
4578
|
-
};
|
|
4579
|
-
};
|
|
4580
4298
|
app.get("/:id/inspect", (context) => {
|
|
4581
4299
|
const query = inspectQuerySchema.parse(context.req.query());
|
|
4582
4300
|
const id = context.req.param("id");
|
|
4583
4301
|
const snapshot = readReview(id, query.version);
|
|
4584
|
-
return context.json(query.format === "text" ? documentText(snapshot, query.targetId, Boolean(query.full)
|
|
4585
|
-
...query.full ? snapshot : inspectSnapshot(snapshot),
|
|
4586
|
-
...!isShared(id) && listedRelations(store.relations(id))
|
|
4587
|
-
});
|
|
4302
|
+
return context.json(query.format === "text" ? documentText(snapshot, query.targetId, Boolean(query.full)) : query.targetId !== void 0 ? inspectSnapshot(snapshot, query.targetId) : query.full ? snapshot : inspectSnapshot(snapshot));
|
|
4588
4303
|
});
|
|
4589
4304
|
app.get("/:id", async (context) => {
|
|
4590
4305
|
const query = readQuerySchemas.get.parse(context.req.query());
|
|
@@ -4611,13 +4326,17 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4611
4326
|
};
|
|
4612
4327
|
app.post("/commands", async (context) => {
|
|
4613
4328
|
const { command: body, open: requestedOpen } = takeCreateOpen(await readBoundedRequestJson(context.req.raw));
|
|
4614
|
-
const
|
|
4615
|
-
const request = withBoardRepository(await locateRepositories(await pullRequestTargets(body, register, (url, repositoryId) => {
|
|
4329
|
+
const request = await locateRepositories(body, (path) => {
|
|
4616
4330
|
if (!data) throw new ReviewInputError("Repositories are unavailable.");
|
|
4617
|
-
return data.
|
|
4618
|
-
}
|
|
4331
|
+
return data.register(path);
|
|
4332
|
+
});
|
|
4619
4333
|
const input = commandSchema.parse(request);
|
|
4620
4334
|
const command = sharedCommandSchema.safeParse(input);
|
|
4335
|
+
if (command.success) {
|
|
4336
|
+
refuseDisabledScratchpad(command.data.operation.reviewId);
|
|
4337
|
+
await ensureScratchpad(command.data.operation.reviewId);
|
|
4338
|
+
}
|
|
4339
|
+
if (input.operation.type === "create" && input.operation.kind === "scratchpad") refuseDisabledScratchpad(SCRATCHPAD_ID);
|
|
4621
4340
|
if (command.success && command.data.operation.reviewId?.startsWith("shared-")) {
|
|
4622
4341
|
const parsed = commandSchema.parse(input);
|
|
4623
4342
|
if (shared && parsed.operation.type === "delete") {
|
|
@@ -4642,12 +4361,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4642
4361
|
}
|
|
4643
4362
|
throw new ReviewInputError("Shared reviews are read-only.", 409);
|
|
4644
4363
|
}
|
|
4645
|
-
const
|
|
4646
|
-
const author = !remoteCaller(context) && context.req.header("x-review-person") !== void 0 ? { kind: "person" } : {
|
|
4647
|
-
kind: "agent",
|
|
4648
|
-
...threadId && { threadId }
|
|
4649
|
-
};
|
|
4650
|
-
const result = await store.execute(input, void 0, author);
|
|
4364
|
+
const result = await store.execute(input);
|
|
4651
4365
|
if (result.deleted || result.lifecycle && store.read(result.reviewId).trashedAt) ask?.threads.closeDeletedReviews((id) => id === result.reviewId);
|
|
4652
4366
|
if (input.operation.type === "edit" && data && !result.deleted) {
|
|
4653
4367
|
const { quotes, unquoted } = await anchorQuotes(result.version > 0 ? store.read(result.reviewId, result.version - 1) : void 0, store.read(result.reviewId, result.version), async (pins, side, file) => (await data.file(pins, side, file)).text);
|
|
@@ -4668,6 +4382,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4668
4382
|
if (input.operation.type !== "create") return context.json(result);
|
|
4669
4383
|
if (result.created !== false) hooks.onReviewCreated?.({
|
|
4670
4384
|
reviewId: result.reviewId,
|
|
4385
|
+
kind: input.operation.kind === "scratchpad" ? "scratchpad" : "review",
|
|
4671
4386
|
blocks: store.read(result.reviewId).document.length,
|
|
4672
4387
|
...reviewRequestOrigin(context.req.raw.headers)
|
|
4673
4388
|
});
|
|
@@ -4731,95 +4446,6 @@ async function locateRepositories(body, register) {
|
|
|
4731
4446
|
}
|
|
4732
4447
|
};
|
|
4733
4448
|
}
|
|
4734
|
-
/** Turns an agent's pullRequest target into what the store takes: a create
|
|
4735
|
-
* from pullRequestUrl, or for set_target the PR's resolved commits. */
|
|
4736
|
-
async function pullRequestTargets(body, register, resolve, board) {
|
|
4737
|
-
const target = z.strictObject({
|
|
4738
|
-
kind: z.literal("pullRequest"),
|
|
4739
|
-
repositoryPath: z.string().min(1),
|
|
4740
|
-
pullRequest: z.union([z.string().min(1), z.number().int().positive()])
|
|
4741
|
-
});
|
|
4742
|
-
const located = z.looseObject({ operation: z.discriminatedUnion("type", [z.looseObject({
|
|
4743
|
-
type: z.literal("create"),
|
|
4744
|
-
target
|
|
4745
|
-
}), z.strictObject({
|
|
4746
|
-
type: z.literal("set_target"),
|
|
4747
|
-
reviewId: z.string().min(1),
|
|
4748
|
-
target
|
|
4749
|
-
})]) }).safeParse(body);
|
|
4750
|
-
if (!located.success) return body;
|
|
4751
|
-
const { target: pullRequest, ...operation } = located.data.operation;
|
|
4752
|
-
if (operation.type === "set_target") board(operation.reviewId);
|
|
4753
|
-
const { id: repositoryId } = await register(pullRequest.repositoryPath);
|
|
4754
|
-
const url = pullRequestAt(pullRequest.repositoryPath, pullRequest.pullRequest);
|
|
4755
|
-
if (operation.type === "create") return {
|
|
4756
|
-
...located.data,
|
|
4757
|
-
operation: {
|
|
4758
|
-
...operation,
|
|
4759
|
-
pullRequestUrl: url,
|
|
4760
|
-
repositoryId
|
|
4761
|
-
}
|
|
4762
|
-
};
|
|
4763
|
-
return {
|
|
4764
|
-
...located.data,
|
|
4765
|
-
operation: {
|
|
4766
|
-
...operation,
|
|
4767
|
-
target: (await resolve(url, repositoryId)).target,
|
|
4768
|
-
pullRequestUrl: url
|
|
4769
|
-
}
|
|
4770
|
-
};
|
|
4771
|
-
}
|
|
4772
|
-
/** A PR's URL: as given, or its number on the checkout's GitHub repository. */
|
|
4773
|
-
function pullRequestAt(repositoryPath, pullRequest) {
|
|
4774
|
-
if (isStringValue(pullRequest) && !/^\d+$/.test(pullRequest)) return pullRequest;
|
|
4775
|
-
const slug = resolveRepoContextSync(repositoryPath)?.githubSlug;
|
|
4776
|
-
if (!slug) throw new ReviewInputError("This checkout has no GitHub remote to find the PR number in; pass the PR's URL.");
|
|
4777
|
-
return `https://github.com/${slug}/pull/${pullRequest}`;
|
|
4778
|
-
}
|
|
4779
|
-
/** A pins object as an edit spells it: commits, with no other fields. A
|
|
4780
|
-
* diagram may key its own data `pins`, and that is left alone. */
|
|
4781
|
-
/** Pins as an agent writes them: the board's repository is the host's to
|
|
4782
|
-
* add. */
|
|
4783
|
-
const agentPinsSchema = z.strictObject({
|
|
4784
|
-
head: z.string().min(1),
|
|
4785
|
-
base: z.string().min(1).optional()
|
|
4786
|
-
});
|
|
4787
|
-
/** An object an agent meant as pins: one naming a head, base or repository.
|
|
4788
|
-
* Content may have a field called `pins` that is nothing of the kind. */
|
|
4789
|
-
const meantAsPins = (value) => isJsonObject(value) && [
|
|
4790
|
-
"head",
|
|
4791
|
-
"base",
|
|
4792
|
-
"repositoryId"
|
|
4793
|
-
].some((key) => key in value);
|
|
4794
|
-
/** Sets every pins object in an edit to the board's own repository. */
|
|
4795
|
-
function withBoardRepository(body, board) {
|
|
4796
|
-
const located = z.looseObject({ operation: z.looseObject({
|
|
4797
|
-
type: z.enum(["edit", "lens_edit"]),
|
|
4798
|
-
reviewId: z.string(),
|
|
4799
|
-
edit: z.json()
|
|
4800
|
-
}) }).safeParse(body);
|
|
4801
|
-
if (!located.success) return body;
|
|
4802
|
-
const snapshot = board(located.data.operation.reviewId);
|
|
4803
|
-
const repositoryId = snapshot ? boardRepositoryId(snapshot) : void 0;
|
|
4804
|
-
const edit = JSON.parse(JSON.stringify(located.data.operation.edit), (key, value) => {
|
|
4805
|
-
if (key !== "pins" || !meantAsPins(value)) return value;
|
|
4806
|
-
const { repositoryId: _ignored, ...written } = value;
|
|
4807
|
-
const pins = agentPinsSchema.safeParse(written);
|
|
4808
|
-
if (!pins.success) throw new ReviewInputError(`Write pins as {head, base?}, each a commit: ${pins.error.issues.map((issue) => `pins.${issue.path.join(".")}: ${issue.message}`).join("; ")}.`);
|
|
4809
|
-
if (!repositoryId) throw new ReviewInputError("This whiteboard has no repository for its pins to name commits of.");
|
|
4810
|
-
return {
|
|
4811
|
-
...pins.data,
|
|
4812
|
-
repositoryId
|
|
4813
|
-
};
|
|
4814
|
-
});
|
|
4815
|
-
return {
|
|
4816
|
-
...located.data,
|
|
4817
|
-
operation: {
|
|
4818
|
-
...located.data.operation,
|
|
4819
|
-
edit
|
|
4820
|
-
}
|
|
4821
|
-
};
|
|
4822
|
-
}
|
|
4823
4449
|
function watch(lines, subscribe) {
|
|
4824
4450
|
let stop = () => {};
|
|
4825
4451
|
const encoder = new TextEncoder();
|
|
@@ -4988,7 +4614,7 @@ function createWhiteboardCore(input) {
|
|
|
4988
4614
|
if (result.deleted || result.lifecycle) closeDeletedThreads();
|
|
4989
4615
|
});
|
|
4990
4616
|
const stopShared = askThreads && shared?.subscribe(closeDeletedThreads);
|
|
4991
|
-
const api = createReviewApi(store, data, callbacks.open, shared, callbacks.capabilities, () => traceMachineEnabled(), input.status, input.hooks, askThreads && {
|
|
4617
|
+
const api = createReviewApi(store, data, callbacks.open, shared, callbacks.capabilities, input.scratchpad, () => traceMachineEnabled(), input.status, input.hooks, askThreads && {
|
|
4992
4618
|
threads: askThreads,
|
|
4993
4619
|
agents: async () => detectAskAgents(await ask?.env)
|
|
4994
4620
|
});
|
|
@@ -5277,6 +4903,7 @@ async function serve(input) {
|
|
|
5277
4903
|
token: discovery.token,
|
|
5278
4904
|
instanceId: discovery.instanceId,
|
|
5279
4905
|
softwareMapEnabled: input.softwareMapEnabled,
|
|
4906
|
+
scratchpad: () => false,
|
|
5280
4907
|
status: () => ({
|
|
5281
4908
|
key: "headless",
|
|
5282
4909
|
home: input.stateDir
|