@dev.fast/whiteboard 0.3.4 → 0.3.5-preview.20261021.131
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-CprZojXd.js → account-alias-CisiYUZT.js} +13 -8
- package/dist/agent-cli-BglNRDZN.js +2 -0
- package/dist/{agent-cli-CThN7Q59.js → agent-cli-TJ7_5_B3.js} +15 -10
- package/dist/{agent-client-BOOi7EUR.js → agent-client-H53ajvrm.js} +13 -3
- package/dist/{authoring-tools-BNwuMnz6.js → authoring-tools-BCquUsB3.js} +34 -27
- package/dist/{background-server-DLSDogkI.js → background-server-BSYXD9au.js} +1 -1
- package/dist/{background-server-CkvxA-6O.js → background-server-Xom8cA_u.js} +5 -5
- package/dist/build-info.json +1 -1
- package/dist/{cli-R4yfa5DT.js → cli-BOnV4cCh.js} +1 -1
- package/dist/{cli-runner-Cuc9FSzu.js → cli-runner-cRY5NOno.js} +70 -34
- package/dist/cli.js +4 -3
- package/dist/{client-BN_FDxFr.js → client-UH1bEG7Z.js} +260 -49
- package/dist/{fuzzy-match-BO9bK37N.js → fuzzy-match-D_x3wumC.js} +6 -4
- package/dist/{headless-host-CVBOQyRS.js → headless-host-BEe-aFtB.js} +1 -1
- package/dist/{headless-host-Cog6SpS9.js → headless-host-CEb5d-A0.js} +563 -175
- package/dist/lens-selection-BxPk53c8.js +214 -0
- package/dist/{local-data-D_9tvdsp.js → local-data-mbCb1HDH.js} +2448 -347
- package/dist/{mcp-D1hziANa.js → mcp-cBtzXsmN.js} +9 -12
- package/dist/{package-paths-C--__jEv.js → package-paths-BkeQsdaw.js} +1 -1
- package/dist/profile-Dbyidrcr.js +2 -0
- package/dist/{profile-DokFw782.js → profile-duqp5uPa.js} +20 -8
- package/dist/{remote-attach-CEJ-kvDY.js → remote-attach-BeKuMm_x.js} +4 -4
- package/dist/{remote-extensions-GN8Gf_qG.js → remote-extensions-CX2fAec1.js} +1 -1
- package/dist/{remote-extensions-Blcjr9Ze.js → remote-extensions-CYWNTVSf.js} +3 -3
- package/dist/{remote-language-server-BTKBQ7x4.js → remote-language-server-DLWluAeR.js} +5 -5
- package/dist/{remote-uninstall-DW7Gdryh.js → remote-uninstall-Y3S0YmJs.js} +6 -6
- package/dist/{request-origin-Zcn_CjX4.js → request-origin-icXPv6jh.js} +55 -18
- package/dist/{review-app-launcher-CkwLclzR.js → review-app-launcher-DZvG5ZAh.js} +2 -2
- package/dist/{review-home-paths-j2BRAaNv.js → review-home-paths-BwH08ihj.js} +19 -17
- package/dist/{review-telemetry-BLNwYnLV.js → review-telemetry-AZ0imsmJ.js} +6 -6
- package/dist/runtime-CF4JXdb8.js +27 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-DtJFWC0G.js → s3-CsG2EWED.js} +1 -1
- package/dist/{s3-config-BTO12tCC.js → s3-config-CjcLyFV5.js} +4 -2
- package/dist/{s3-config-CSy3fx8H.js → s3-config-zz4GWh2T.js} +1 -1
- package/dist/server/desktop-host.js +28 -104
- package/dist/{server-discovery-Bey8IqXH.js → server-discovery-DeVTdcqK.js} +3 -3
- package/dist/sharing/index.d.ts +484 -74
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DsM3jO8G.js → src-DYZLCUP_.js} +98 -18
- package/dist/{src-BMPuxWv4.js → src-DtfSndqS.js} +269 -227
- package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-B29lve9-.js} +318 -271
- package/dist/{thread-state-5pYr6_zM.js → thread-state-BPpvKFwQ.js} +12 -2
- package/dist/{tool-failure-C9MpbXJJ.js → tool-failure-CQhyLht3.js} +10 -3
- package/instructions/authoring.md +20 -5
- package/package.json +4 -4
- package/src/agent-selection.ts +6 -0
- package/src/ask/agents.ts +13 -1
- package/src/ask/checkout-files.ts +2 -5
- package/src/ask/thread-state.ts +4 -0
- package/src/ask/thread.ts +61 -31
- package/src/ask/threads.ts +24 -3
- package/src/cli-install.ts +13 -2
- package/src/cli-runner.ts +4 -1
- package/src/cli.ts +4 -1
- package/src/diff-selection-migration.ts +48 -34
- package/src/heading-anchors.ts +42 -0
- package/src/lens-selection.ts +38 -2
- package/src/markdown.ts +42 -0
- package/src/posthog-capture-client.ts +1 -1
- package/src/review-api/README.md +52 -13
- package/src/review-api/activity.ts +1 -4
- package/src/review-api/agent-cli.ts +9 -1
- package/src/review-api/anchor-quotes.ts +14 -7
- package/src/review-api/ask-history.ts +118 -51
- package/src/review-api/authoring-tools.ts +45 -30
- package/src/review-api/azure-devops-provider.ts +2 -20
- package/src/review-api/azure-remote.ts +21 -0
- package/src/review-api/blocks/definition.ts +1 -1
- package/src/review-api/blocks/markdown.ts +2 -2
- package/src/review-api/board-order.ts +144 -0
- package/src/review-api/checkout-groups.ts +586 -0
- package/src/review-api/document-headings.ts +58 -27
- package/src/review-api/document-text.ts +31 -2
- package/src/review-api/document.ts +362 -67
- package/src/review-api/foreign-references.ts +328 -0
- package/src/review-api/github-provider.ts +51 -13
- package/src/review-api/http.ts +734 -135
- package/src/review-api/instructions.ts +0 -19
- package/src/review-api/link-preview.ts +113 -0
- package/src/review-api/local-data.ts +310 -46
- package/src/review-api/mcp.ts +7 -13
- package/src/review-api/onboarding.ts +1 -1
- package/src/review-api/profile.ts +37 -6
- package/src/review-api/public-tools.ts +24 -2
- package/src/review-api/pull-request.ts +82 -49
- package/src/review-api/request-origin.ts +10 -0
- package/src/review-api/review-progress.ts +61 -27
- package/src/review-api/scratchpad-plan-upgrade.ts +250 -0
- package/src/review-api/store-migrations.ts +355 -0
- package/src/review-api/store-schema.ts +38 -29
- package/src/review-api/store.ts +2176 -495
- package/src/review-api/worktree-events.ts +3 -5
- package/src/review-api/worktree-source.ts +3 -7
- package/src/review-api/worktree-structural.integration.ts +72 -0
- package/src/review-stack.ts +15 -23
- package/src/runtime.ts +4 -30
- package/src/server/desktop-host.ts +6 -1
- package/src/server/desktop-server.ts +1 -36
- package/src/server/headless-host.ts +0 -2
- package/src/server/hono-http.ts +2 -1
- package/src/server/review-gateway-list.ts +3 -5
- 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 +1 -5
- package/src/server/structural-comparisons.ts +47 -0
- package/src/sharing/export.ts +36 -7
- package/src/sharing/host.ts +34 -14
- package/src/sharing/import.ts +156 -24
- package/src/sharing/repository.ts +143 -24
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +110 -19
- package/src/ui-telemetry-events.ts +44 -18
- package/dist/agent-cli-C6SPSII0.js +0 -2
- package/dist/profile-CYPmvBoA.js +0 -2
- package/dist/runtime-CKiWhGrf.js +0 -52
- package/instructions/scratchpad.md +0 -23
- package/src/review-preferences.ts +0 -88
- package/src/server/legacy-tutorial-cleanup.ts +0 -38
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { f as gitAt } from "./src-
|
|
1
|
+
import { f as gitAt } from "./src-DtfSndqS.js";
|
|
2
2
|
import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
3
|
-
import "./src-
|
|
4
|
-
import {
|
|
3
|
+
import "./src-DYZLCUP_.js";
|
|
4
|
+
import { f as sourceAnchors } from "./lens-selection-BxPk53c8.js";
|
|
5
|
+
import { $ as parseMarkdown, A as resourceReferences, E as pinsSchema, G as lensSelections, I as sourceReferences, O as readsAtBoardPins, R as checkReferences, W as lensSchema, Z as markdownNodes, _ as boardRepositoryId, c as normalizedSoftwareRelationshipSchema, f as anchorPins, k as resourceReference, l as traceSchema, n as migrateStoredDocument, r as migrateStoredLenses, s as normalizedSoftwareElementSchema, t as liftFileLenses, u as textIncludesQuote, x as elements, y as documentSchema } from "./stored-document-migration-B29lve9-.js";
|
|
5
6
|
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
8
|
import { z } from "zod";
|
|
@@ -123,11 +124,25 @@ function digestBytes(bytes) {
|
|
|
123
124
|
return createHash("sha256").update(bytes).digest("hex");
|
|
124
125
|
}
|
|
125
126
|
function validateShareSources(snapshot) {
|
|
126
|
-
if (!snapshot.pins &&
|
|
127
|
+
if (!snapshot.pins && readsAtBoardPins(snapshot)) throw new ReviewInputError("Reviews with code references need source pins before sharing.", 409);
|
|
128
|
+
}
|
|
129
|
+
/** The commits a board's references read at besides its base and head. A
|
|
130
|
+
* recipient fetches them too. A shared board names no repository of its
|
|
131
|
+
* own, so one stands in for its commit links. */
|
|
132
|
+
function referenceCommits(snapshot, repositoryId = boardRepositoryId(snapshot) ?? "shared") {
|
|
133
|
+
const own = /* @__PURE__ */ new Set([snapshot.pins?.base, snapshot.pins?.head]);
|
|
134
|
+
const commits = /* @__PURE__ */ new Set();
|
|
135
|
+
for (const { source } of sourceReferences(snapshot.document, {
|
|
136
|
+
tolerant: true,
|
|
137
|
+
repositoryId
|
|
138
|
+
})) for (const commit of [source.pins?.base, source.pins?.head]) if (commit && !own.has(commit)) commits.add(commit);
|
|
139
|
+
return [...commits].sort();
|
|
127
140
|
}
|
|
128
141
|
/** Freeze one stored version before any asynchronous source reads. */
|
|
129
142
|
async function exportShare(input) {
|
|
130
|
-
const
|
|
143
|
+
const stored = structuredClone(input.store.read(input.reviewId, input.version));
|
|
144
|
+
const repositoryId = boardRepositoryId(stored);
|
|
145
|
+
const { target, staleSources: _staleSources, sourceUnavailable: _sourceUnavailable, lastEdit: _lastEdit, trashedAt: _trashedAt, personEdit: _personEdit, ...snapshot } = stored;
|
|
131
146
|
if (target?.kind === "worktree") throw new ReviewInputError("Pin this review to commits before sharing it.");
|
|
132
147
|
documentSchema.parse(snapshot.document);
|
|
133
148
|
checkReferences(snapshot.document);
|
|
@@ -146,14 +161,14 @@ async function exportShare(input) {
|
|
|
146
161
|
const json = (value) => add(Buffer.from(JSON.stringify(value)));
|
|
147
162
|
const resources = [];
|
|
148
163
|
const maps = {};
|
|
149
|
-
const sources = [...sourceReferences(snapshot.document), ...lensSelections(snapshot.lenses ?? []).flatMap((selection) => sourceAnchors(selection.source).map((source) => ({
|
|
164
|
+
const sources = [...sourceReferences(snapshot.document, { repositoryId }), ...lensSelections(snapshot.lenses ?? []).flatMap((selection) => sourceAnchors(selection.source).map((source) => ({
|
|
150
165
|
...selection,
|
|
151
166
|
source,
|
|
152
167
|
peek: false
|
|
153
168
|
})))];
|
|
154
169
|
const pins = snapshot.pins;
|
|
155
170
|
validateShareSources(snapshot);
|
|
156
|
-
if (
|
|
171
|
+
if ((pins || referenceCommits(stored).length) && !input.repository) throw new ReviewInputError("Shared source pins require a repository.", 409);
|
|
157
172
|
for (const block of elements(snapshot.document)) {
|
|
158
173
|
if (block.type !== "markdown") continue;
|
|
159
174
|
for (const node of markdownNodes(parseMarkdown(block.markdown))) if (node.type === "image") throw new ReviewInputError("Convert Markdown images to managed image blocks before sharing.");
|
|
@@ -215,6 +230,14 @@ async function exportShare(input) {
|
|
|
215
230
|
}
|
|
216
231
|
//#endregion
|
|
217
232
|
//#region src/sharing/repository.ts
|
|
233
|
+
/** A full commit id, SHA-1 or SHA-256. */
|
|
234
|
+
const COMMIT_ID = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
|
|
235
|
+
/** Refuses anything but full commit ids, before any of them reaches git:
|
|
236
|
+
* a shared board's commits come from someone else, and one spelled as an
|
|
237
|
+
* option or a refspec would make git do something else. */
|
|
238
|
+
function assertCommitIds(commits) {
|
|
239
|
+
if (!commits.every((commit) => COMMIT_ID.test(commit))) throw new ReviewInputError("The shared whiteboard names a commit that isn't a full commit id.");
|
|
240
|
+
}
|
|
218
241
|
/** Keep credential helpers but prevent the calling shell from redirecting Git's object store. */
|
|
219
242
|
function sharedGit(cwd, args) {
|
|
220
243
|
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_") || [
|
|
@@ -230,20 +253,30 @@ function sharedGit(cwd, args) {
|
|
|
230
253
|
"core.fsmonitor=false",
|
|
231
254
|
...args
|
|
232
255
|
], {
|
|
233
|
-
env
|
|
234
|
-
...env,
|
|
235
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
236
|
-
},
|
|
256
|
+
env,
|
|
237
257
|
timeout: 3e5
|
|
238
258
|
});
|
|
239
259
|
}
|
|
240
260
|
function fetchError(stderr, publishing) {
|
|
241
261
|
if (/not our ref|couldn't find remote ref|unadvertised object/i.test(stderr)) return new ReviewInputError(publishing ? "Push the reviewed commits to GitHub before sharing." : "The shared commits are no longer available on GitHub. Ask the sender to restore them, then retry.", 409);
|
|
262
|
+
if (/could not read (?:username|password)|authentication failed|permission denied \(publickey|repository not found|returned error: 40[13]|\bSAML\b/i.test(stderr)) return new ReviewInputError("Git couldn't access this GitHub repository. Your Whiteboard sign-in doesn't grant repository access, so set up Git credentials for it, then retry.", 409);
|
|
242
263
|
return new ReviewInputError("Could not fetch the GitHub repository. Check your connection and Git credentials for this repository, then retry.", 409);
|
|
243
264
|
}
|
|
244
|
-
/** Recipients need complete history; publication only checks that the commits exist.
|
|
245
|
-
|
|
246
|
-
|
|
265
|
+
/** Recipients need complete history; publication only checks that the commits exist.
|
|
266
|
+
* `commits` are other commits the board's references read at; a plan has
|
|
267
|
+
* only those. */
|
|
268
|
+
async function fetchPinnedRepository(root, url, pins, publishing = false, commits = []) {
|
|
269
|
+
const named = [...pins ? [{
|
|
270
|
+
commit: pins.base,
|
|
271
|
+
ref: "base"
|
|
272
|
+
}, {
|
|
273
|
+
commit: pins.head,
|
|
274
|
+
ref: "head"
|
|
275
|
+
}] : [], ...commits.map((commit) => ({
|
|
276
|
+
commit,
|
|
277
|
+
ref: `commits/${commit}`
|
|
278
|
+
}))];
|
|
279
|
+
if (!named.every(({ commit }) => COMMIT_ID.test(commit))) throw new ReviewInputError("Sharing requires immutable base and head commits.");
|
|
247
280
|
await mkdir(root, {
|
|
248
281
|
recursive: true,
|
|
249
282
|
mode: 448
|
|
@@ -264,7 +297,7 @@ async function fetchPinnedRepository(root, url, pins, publishing = false) {
|
|
|
264
297
|
"remote",
|
|
265
298
|
"add",
|
|
266
299
|
"origin",
|
|
267
|
-
url
|
|
300
|
+
{ remote: url }
|
|
268
301
|
]);
|
|
269
302
|
}
|
|
270
303
|
const refs = publishing ? "refs/heads/review" : "refs/review";
|
|
@@ -274,26 +307,129 @@ async function fetchPinnedRepository(root, url, pins, publishing = false) {
|
|
|
274
307
|
"--no-tags",
|
|
275
308
|
"--force",
|
|
276
309
|
...publishing ? ["--depth=1", "--filter=tree:0"] : [],
|
|
277
|
-
publishing ? url : "origin",
|
|
278
|
-
|
|
279
|
-
|
|
310
|
+
{ remote: publishing ? url : "origin" },
|
|
311
|
+
...named.map(({ commit, ref }) => ({ refspec: {
|
|
312
|
+
from: commit,
|
|
313
|
+
to: `${refs}/${ref}`
|
|
314
|
+
} }))
|
|
280
315
|
]);
|
|
281
316
|
} catch (error) {
|
|
282
317
|
throw fetchError(error instanceof Error && "stderr" in error ? String(error.stderr) : "", publishing);
|
|
283
318
|
}
|
|
284
|
-
if (!publishing) for (const commit of
|
|
319
|
+
if (!publishing) for (const { commit } of named) await sharedGit(root, [
|
|
285
320
|
"cat-file",
|
|
286
321
|
"-e",
|
|
287
|
-
`${commit}^{commit}`
|
|
322
|
+
{ rev: `${commit}^{commit}` }
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
/** Fetches the commits a shared board's links read at into a checkout of
|
|
326
|
+
* its repository, from its origin unless it has them, and keeps each under
|
|
327
|
+
* `refs/review/shared/<share>/<commit>`, so `git gc` can't prune one the
|
|
328
|
+
* board still quotes. */
|
|
329
|
+
async function fetchSharedCommits(root, commits, share) {
|
|
330
|
+
assertCommitIds(commits);
|
|
331
|
+
const ref = (commit) => `refs/review/shared/${share}/${commit}`;
|
|
332
|
+
const missing = [];
|
|
333
|
+
for (const commit of commits) try {
|
|
334
|
+
await sharedGit(root, [
|
|
335
|
+
"cat-file",
|
|
336
|
+
"-e",
|
|
337
|
+
{ rev: `${commit}^{commit}` }
|
|
338
|
+
]);
|
|
339
|
+
await sharedGit(root, [
|
|
340
|
+
"update-ref",
|
|
341
|
+
{ ref: ref(commit) },
|
|
342
|
+
{ commit }
|
|
343
|
+
]);
|
|
344
|
+
} catch {
|
|
345
|
+
missing.push(commit);
|
|
346
|
+
}
|
|
347
|
+
if (!missing.length) return;
|
|
348
|
+
try {
|
|
349
|
+
await sharedGit(root, [
|
|
350
|
+
"fetch",
|
|
351
|
+
"--no-tags",
|
|
352
|
+
"origin",
|
|
353
|
+
...missing.map((commit) => ({ refspec: {
|
|
354
|
+
from: commit,
|
|
355
|
+
to: ref(commit),
|
|
356
|
+
force: true
|
|
357
|
+
} }))
|
|
358
|
+
]);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
throw fetchError(error instanceof Error && "stderr" in error ? String(error.stderr) : "", false);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Drops the refs `fetchSharedCommits` kept for a share, once the share is
|
|
364
|
+
* gone, so `git gc` can prune what only it quoted. */
|
|
365
|
+
async function forgetSharedCommits(root, share) {
|
|
366
|
+
const { stdout } = await sharedGit(root, [
|
|
367
|
+
"for-each-ref",
|
|
368
|
+
"--format=%(refname)",
|
|
369
|
+
{ ref: `refs/review/shared/${share}/` }
|
|
370
|
+
]);
|
|
371
|
+
for (const ref of stdout.split("\n").filter(Boolean)) await sharedGit(root, [
|
|
372
|
+
"update-ref",
|
|
373
|
+
"-d",
|
|
374
|
+
{ ref }
|
|
375
|
+
]);
|
|
376
|
+
}
|
|
377
|
+
/** A managed clone of a repository's default branch, for a shared board
|
|
378
|
+
* with no pins whose repository the receiver has no checkout of. */
|
|
379
|
+
async function cloneSharedRepository(root, url) {
|
|
380
|
+
await mkdir(root, {
|
|
381
|
+
recursive: true,
|
|
382
|
+
mode: 448
|
|
383
|
+
});
|
|
384
|
+
await sharedGit(root, ["init", "--quiet"]);
|
|
385
|
+
await sharedGit(root, [
|
|
386
|
+
"config",
|
|
387
|
+
"core.hooksPath",
|
|
388
|
+
"/dev/null"
|
|
389
|
+
]);
|
|
390
|
+
await sharedGit(root, [
|
|
391
|
+
"config",
|
|
392
|
+
"core.fsmonitor",
|
|
393
|
+
"false"
|
|
394
|
+
]);
|
|
395
|
+
await sharedGit(root, [
|
|
396
|
+
"remote",
|
|
397
|
+
"add",
|
|
398
|
+
"origin",
|
|
399
|
+
{ remote: url }
|
|
400
|
+
]);
|
|
401
|
+
try {
|
|
402
|
+
await sharedGit(root, [
|
|
403
|
+
"fetch",
|
|
404
|
+
"--no-tags",
|
|
405
|
+
"origin",
|
|
406
|
+
"HEAD"
|
|
407
|
+
]);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
throw fetchError(error instanceof Error && "stderr" in error ? String(error.stderr) : "", false);
|
|
410
|
+
}
|
|
411
|
+
await sharedGit(root, [
|
|
412
|
+
"checkout",
|
|
413
|
+
"--detach",
|
|
414
|
+
"FETCH_HEAD"
|
|
288
415
|
]);
|
|
289
416
|
}
|
|
290
|
-
async function repositoryReady(root, pins) {
|
|
417
|
+
async function repositoryReady(root, pins, commits = []) {
|
|
418
|
+
if (![
|
|
419
|
+
pins.base,
|
|
420
|
+
pins.head,
|
|
421
|
+
...commits
|
|
422
|
+
].every((commit) => COMMIT_ID.test(commit))) return false;
|
|
291
423
|
try {
|
|
292
424
|
if ((await sharedGit(root, ["rev-parse", "HEAD"])).stdout.trim() !== pins.head) return false;
|
|
293
|
-
for (const commit of [
|
|
425
|
+
for (const commit of [
|
|
426
|
+
pins.base,
|
|
427
|
+
pins.head,
|
|
428
|
+
...commits
|
|
429
|
+
]) await sharedGit(root, [
|
|
294
430
|
"cat-file",
|
|
295
431
|
"-e",
|
|
296
|
-
`${commit}^{commit}`
|
|
432
|
+
{ rev: `${commit}^{commit}` }
|
|
297
433
|
]);
|
|
298
434
|
return true;
|
|
299
435
|
} catch {
|
|
@@ -313,28 +449,33 @@ async function readShareRepository(root) {
|
|
|
313
449
|
}
|
|
314
450
|
return { cloneUrl };
|
|
315
451
|
}
|
|
316
|
-
|
|
317
|
-
|
|
452
|
+
/** Fetches through origin itself, so its transport and credentials apply. */
|
|
453
|
+
async function verifyShareRepository(root, pins, commits = []) {
|
|
454
|
+
const origin = (await sharedGit(root, [
|
|
455
|
+
"remote",
|
|
456
|
+
"get-url",
|
|
457
|
+
"origin"
|
|
458
|
+
])).stdout.trim();
|
|
318
459
|
const temporary = await mkdtemp(path.join(tmpdir(), "review-share-check-"));
|
|
319
460
|
try {
|
|
320
|
-
await fetchPinnedRepository(temporary,
|
|
461
|
+
await fetchPinnedRepository(temporary, origin, pins, true, commits);
|
|
321
462
|
} finally {
|
|
322
463
|
await rm(temporary, {
|
|
323
464
|
recursive: true,
|
|
324
465
|
force: true
|
|
325
466
|
});
|
|
326
467
|
}
|
|
327
|
-
return { cloneUrl };
|
|
328
468
|
}
|
|
329
469
|
//#endregion
|
|
330
470
|
//#region src/sharing/import.ts
|
|
331
471
|
const sharedSnapshotSchema = z.strictObject({
|
|
332
472
|
reviewId: z.string().min(1),
|
|
333
473
|
version: z.number().int().nonnegative(),
|
|
334
|
-
|
|
474
|
+
/** Empty for a board never named; it reads "Untitled". */
|
|
475
|
+
title: z.string(),
|
|
335
476
|
pins: pinsSchema.extend({
|
|
336
|
-
base: z.string().regex(
|
|
337
|
-
head: z.string().regex(
|
|
477
|
+
base: z.string().regex(COMMIT_ID),
|
|
478
|
+
head: z.string().regex(COMMIT_ID)
|
|
338
479
|
}).optional(),
|
|
339
480
|
document: documentSchema,
|
|
340
481
|
lenses: z.array(lensSchema).optional(),
|
|
@@ -354,7 +495,7 @@ const counts = z.strictObject({
|
|
|
354
495
|
deletions: count
|
|
355
496
|
});
|
|
356
497
|
const mapSchema = z.strictObject({
|
|
357
|
-
commit: z.string(),
|
|
498
|
+
commit: z.string().regex(COMMIT_ID),
|
|
358
499
|
side: z.enum(["base", "head"]),
|
|
359
500
|
elements: z.array(normalizedSoftwareElementSchema),
|
|
360
501
|
relationships: z.array(normalizedSoftwareRelationshipSchema),
|
|
@@ -387,7 +528,8 @@ function validateShareBundle(bundle) {
|
|
|
387
528
|
if (snapshot.reviewId !== manifest.reviewId || snapshot.version !== manifest.version || snapshot.title !== manifest.title) throw new Error("Shared snapshot does not match its manifest.");
|
|
388
529
|
checkReferences(snapshot.document);
|
|
389
530
|
validateShareSources(snapshot);
|
|
390
|
-
|
|
531
|
+
assertCommitIds(referenceCommits(snapshot));
|
|
532
|
+
if (snapshot.pins && !manifest.repository) throw new Error("Shared source pins require a repository.");
|
|
391
533
|
const presentation = sharePresentationSchema.parse(json(manifest.presentation));
|
|
392
534
|
if (!snapshot.pins && Object.keys(presentation.maps).length) throw new Error("Shared maps require source pins.");
|
|
393
535
|
const traces = /* @__PURE__ */ new Map();
|
|
@@ -435,6 +577,7 @@ function sharedReviewId(origin, shareId) {
|
|
|
435
577
|
var SharedReviewStore = class {
|
|
436
578
|
root;
|
|
437
579
|
fetchRepository;
|
|
580
|
+
cloneRepository;
|
|
438
581
|
loaded = /* @__PURE__ */ new Map();
|
|
439
582
|
validated = /* @__PURE__ */ new Map();
|
|
440
583
|
attention = /* @__PURE__ */ new Map();
|
|
@@ -456,9 +599,10 @@ var SharedReviewStore = class {
|
|
|
456
599
|
repositories = /* @__PURE__ */ new Map();
|
|
457
600
|
jobs = /* @__PURE__ */ new Map();
|
|
458
601
|
states = /* @__PURE__ */ new Map();
|
|
459
|
-
constructor(root, fetchRepository = fetchPinnedRepository) {
|
|
602
|
+
constructor(root, fetchRepository = fetchPinnedRepository, cloneRepository = cloneSharedRepository) {
|
|
460
603
|
this.root = root;
|
|
461
604
|
this.fetchRepository = fetchRepository;
|
|
605
|
+
this.cloneRepository = cloneRepository;
|
|
462
606
|
this.purgeTimer = setInterval(() => {
|
|
463
607
|
this.purgeExpired().catch((error) => console.error("Could not purge expired shared whiteboards:", error));
|
|
464
608
|
}, 6e4);
|
|
@@ -518,14 +662,15 @@ var SharedReviewStore = class {
|
|
|
518
662
|
const saved = this.validated.get(id);
|
|
519
663
|
if (!saved) throw new ReviewInputError("Shared review is not available.", 404);
|
|
520
664
|
if (!saved.snapshot.pins) {
|
|
521
|
-
this.
|
|
665
|
+
await this.prepareUnpinned(id, saved.manifest.repository?.cloneUrl);
|
|
522
666
|
return;
|
|
523
667
|
}
|
|
524
668
|
if (!local) throw new ReviewInputError("Repository service is unavailable.", 409);
|
|
525
669
|
const root = this.repositoryRoot(id);
|
|
670
|
+
const commits = referenceCommits(saved.snapshot);
|
|
526
671
|
try {
|
|
527
672
|
this.setStatus(id, "fetching");
|
|
528
|
-
if (!await repositoryReady(root, saved.snapshot.pins)) {
|
|
673
|
+
if (!await repositoryReady(root, saved.snapshot.pins, commits)) {
|
|
529
674
|
const prior = this.repositories.get(id);
|
|
530
675
|
await local.data.workspaces.remove(id);
|
|
531
676
|
if (prior) await local.data.forgetRepository(prior);
|
|
@@ -533,11 +678,11 @@ var SharedReviewStore = class {
|
|
|
533
678
|
recursive: true,
|
|
534
679
|
force: true
|
|
535
680
|
});
|
|
536
|
-
await this.fetchRepository(root, saved.manifest.repository.cloneUrl, saved.snapshot.pins);
|
|
681
|
+
await this.fetchRepository(root, saved.manifest.repository.cloneUrl, saved.snapshot.pins, false, commits);
|
|
537
682
|
await sharedGit(root, [
|
|
538
683
|
"checkout",
|
|
539
684
|
"--detach",
|
|
540
|
-
saved.snapshot.pins.head
|
|
685
|
+
{ commit: saved.snapshot.pins.head }
|
|
541
686
|
]);
|
|
542
687
|
}
|
|
543
688
|
const repository = await local.data.register(root);
|
|
@@ -552,12 +697,7 @@ var SharedReviewStore = class {
|
|
|
552
697
|
};
|
|
553
698
|
this.setStatus(id, "validating");
|
|
554
699
|
await local.data.validatePins(pins);
|
|
555
|
-
|
|
556
|
-
for (const map of Object.values(saved.presentation.maps)) for (const element of map.elements) for (const range of element.sourceRanges ?? []) references.push({
|
|
557
|
-
...range,
|
|
558
|
-
side: map.side
|
|
559
|
-
});
|
|
560
|
-
await local.data.validateSources(pins, references);
|
|
700
|
+
await this.validateReferences(saved, repository.id, pins);
|
|
561
701
|
await local.data.workspaces.open(id, pins);
|
|
562
702
|
if (local.data.workspaces.list(id).some((workspace) => !workspace.rootPath)) throw new ReviewInputError("Could not prepare the pinned checkout. Retry opening the share.", 409);
|
|
563
703
|
await writeFile(path.join(this.root, id, "repository.json"), JSON.stringify({
|
|
@@ -571,9 +711,71 @@ var SharedReviewStore = class {
|
|
|
571
711
|
throw new ReviewInputError(message, 409);
|
|
572
712
|
}
|
|
573
713
|
}
|
|
714
|
+
/** Reads each of a shared board's references at its own commits, in the
|
|
715
|
+
* checkout of its repository. */
|
|
716
|
+
async validateReferences(saved, repositoryId, pins) {
|
|
717
|
+
const references = /* @__PURE__ */ new Map();
|
|
718
|
+
const add = (at, range) => {
|
|
719
|
+
const key = JSON.stringify([at.base, at.head]);
|
|
720
|
+
const group = references.get(key) ?? {
|
|
721
|
+
pins: at,
|
|
722
|
+
ranges: []
|
|
723
|
+
};
|
|
724
|
+
group.ranges.push(range);
|
|
725
|
+
references.set(key, group);
|
|
726
|
+
};
|
|
727
|
+
for (const { source } of sourceReferences(saved.snapshot.document, { repositoryId })) add({
|
|
728
|
+
...anchorPins(source, pins),
|
|
729
|
+
repositoryId
|
|
730
|
+
}, source);
|
|
731
|
+
if (pins) for (const map of Object.values(saved.presentation.maps)) for (const element of map.elements) for (const range of element.sourceRanges ?? []) add(pins, {
|
|
732
|
+
...range,
|
|
733
|
+
side: map.side
|
|
734
|
+
});
|
|
735
|
+
for (const group of references.values()) await this.local.data.validateSources(group.pins, group.ranges);
|
|
736
|
+
}
|
|
737
|
+
/** Files an unpinned board under a local checkout of its repository,
|
|
738
|
+
* else a managed clone. */
|
|
739
|
+
async prepareUnpinned(id, cloneUrl) {
|
|
740
|
+
const local = this.local;
|
|
741
|
+
const saved = this.validated.get(id);
|
|
742
|
+
if (!cloneUrl || !local) {
|
|
743
|
+
this.setStatus(id, "ready");
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const root = this.repositoryRoot(id);
|
|
747
|
+
try {
|
|
748
|
+
this.setStatus(id, "fetching");
|
|
749
|
+
const managed = path.join(await realpath(this.root), ".repositories");
|
|
750
|
+
let repositoryId = local.store.checkoutsOf(cloneUrl).find((checkout) => !local.store.repositoryPath(checkout).startsWith(managed));
|
|
751
|
+
if (!repositoryId) {
|
|
752
|
+
if (!existsSync(path.join(root, ".git"))) {
|
|
753
|
+
await rm(root, {
|
|
754
|
+
recursive: true,
|
|
755
|
+
force: true
|
|
756
|
+
});
|
|
757
|
+
await this.cloneRepository(root, cloneUrl);
|
|
758
|
+
}
|
|
759
|
+
repositoryId = (await local.data.register(root)).id;
|
|
760
|
+
}
|
|
761
|
+
await fetchSharedCommits(local.store.repositoryPath(repositoryId), referenceCommits(saved.snapshot, repositoryId), id);
|
|
762
|
+
this.setStatus(id, "validating");
|
|
763
|
+
await this.validateReferences(saved, repositoryId);
|
|
764
|
+
this.repositories.set(id, repositoryId);
|
|
765
|
+
await writeFile(path.join(this.root, id, "repository.json"), JSON.stringify({
|
|
766
|
+
repositoryId,
|
|
767
|
+
ready: true
|
|
768
|
+
}), { mode: 384 });
|
|
769
|
+
this.setStatus(id, "ready");
|
|
770
|
+
} catch (error) {
|
|
771
|
+
const message = error instanceof ReviewInputError ? error.message : "Could not prepare the shared repository. Check Git credentials and retry.";
|
|
772
|
+
this.setStatus(id, "error", message);
|
|
773
|
+
throw new ReviewInputError(message, 409);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
574
776
|
async assertReady(id) {
|
|
575
777
|
const saved = this.validated.get(id);
|
|
576
|
-
if (!saved || saved.snapshot.pins && !await repositoryReady(this.repositoryRoot(id), saved.snapshot.pins)) {
|
|
778
|
+
if (!saved || saved.snapshot.pins && !await repositoryReady(this.repositoryRoot(id), saved.snapshot.pins, referenceCommits(saved.snapshot))) {
|
|
577
779
|
this.setStatus(id, "error", "The managed checkout is missing. Reopen the share link to fetch it again.");
|
|
578
780
|
throw new ReviewInputError(this.status(id).error, 409);
|
|
579
781
|
}
|
|
@@ -612,13 +814,15 @@ var SharedReviewStore = class {
|
|
|
612
814
|
repositoryId: z.string(),
|
|
613
815
|
ready: z.boolean()
|
|
614
816
|
}).parse(JSON.parse(await readFile(path.join(this.root, id, "repository.json"), "utf8")));
|
|
615
|
-
if (this.local?.store.repositoryPath(metadata.repositoryId) === path.join(await realpath(this.root), ".repositories", id)) {
|
|
817
|
+
if (this.local?.store.repositoryPath(metadata.repositoryId) === path.join(await realpath(this.root), ".repositories", id) || !validated.snapshot.pins && this.local?.store.repositories().some((repository) => repository.id === metadata.repositoryId)) {
|
|
616
818
|
this.repositories.set(id, metadata.repositoryId);
|
|
617
819
|
prepared = metadata.ready;
|
|
618
820
|
}
|
|
619
821
|
} catch {}
|
|
620
|
-
if (!validated.snapshot.pins)
|
|
621
|
-
|
|
822
|
+
if (!validated.snapshot.pins) {
|
|
823
|
+
if (this.repositories.has(id) || !validated.manifest.repository) this.setStatus(id, "ready");
|
|
824
|
+
else this.prepare(id).catch(() => {});
|
|
825
|
+
} else if (prepared && this.local && await repositoryReady(this.repositoryRoot(id), validated.snapshot.pins, referenceCommits(validated.snapshot))) {
|
|
622
826
|
const repository = await this.local.data.register(this.repositoryRoot(id));
|
|
623
827
|
this.repositories.set(id, repository.id);
|
|
624
828
|
this.setStatus(id, "ready");
|
|
@@ -669,6 +873,9 @@ var SharedReviewStore = class {
|
|
|
669
873
|
kind: "commits",
|
|
670
874
|
...validated.snapshot.pins,
|
|
671
875
|
repositoryId
|
|
876
|
+
} : repositoryId ? {
|
|
877
|
+
kind: "project",
|
|
878
|
+
repositoryId
|
|
672
879
|
} : void 0,
|
|
673
880
|
shared: {
|
|
674
881
|
...bundle.attribution,
|
|
@@ -680,10 +887,12 @@ var SharedReviewStore = class {
|
|
|
680
887
|
list() {
|
|
681
888
|
const reviews = this.loaded.keys().filter((id) => this.status(id).stage === "ready" && (!this.validated.get(id)?.snapshot.pins || existsSync(path.join(this.repositoryRoot(id), ".git")))).map((id) => {
|
|
682
889
|
const { document: _document, ...snapshot } = this.get(id).snapshot;
|
|
890
|
+
const project = snapshot.target ? this.local?.store.checkoutProject(snapshot.target.repositoryId) : void 0;
|
|
683
891
|
return {
|
|
684
892
|
...snapshot,
|
|
685
893
|
firstCreatedAt: snapshot.version === 0 ? snapshot.createdAt : void 0,
|
|
686
894
|
repositoryName: "Shared review",
|
|
895
|
+
...project && { project },
|
|
687
896
|
viewedAt: this.attention.get(id)?.viewedAt ?? null,
|
|
688
897
|
trashedAt: this.attention.get(id)?.trashedAt ?? null
|
|
689
898
|
};
|
|
@@ -705,8 +914,10 @@ var SharedReviewStore = class {
|
|
|
705
914
|
await this.local?.data.workspaces.remove(id);
|
|
706
915
|
const repositoryId = this.repositories.get(id);
|
|
707
916
|
if (repositoryId) {
|
|
917
|
+
const checkout = this.local?.store.repositoryPath(repositoryId);
|
|
918
|
+
if (checkout) await forgetSharedCommits(checkout, id).catch((error) => console.error(`Could not drop the refs kept for share ${id}:`, error));
|
|
708
919
|
await this.local?.data.forgetRepository(repositoryId);
|
|
709
|
-
this.local?.store.unregisterRepository(repositoryId);
|
|
920
|
+
await this.local?.store.unregisterRepository(repositoryId);
|
|
710
921
|
}
|
|
711
922
|
await rm(this.repositoryRoot(id), {
|
|
712
923
|
recursive: true,
|
|
@@ -999,4 +1210,4 @@ var ShareClient = class {
|
|
|
999
1210
|
}
|
|
1000
1211
|
};
|
|
1001
1212
|
//#endregion
|
|
1002
|
-
export { sharedReviewId as a, verifyShareRepository as c,
|
|
1213
|
+
export { sharedReviewId as a, verifyShareRepository as c, validateShareSources as d, StreamLimitError as f, shareIdSchema as h, SharedReviewStore as i, exportShare as l, parseShareLink as m, ShareClient as n, validateShareBundle as o, readBoundedStream as p, SharePreflightError as r, readShareRepository as s, ShareAuthError as t, referenceCommits as u };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import "./review-home-paths-
|
|
2
|
-
import { N as devReviewHome } from "./s3-config-
|
|
3
|
-
import { n as askAgentIds } from "./thread-state-
|
|
1
|
+
import "./review-home-paths-BwH08ihj.js";
|
|
2
|
+
import { N as devReviewHome } from "./s3-config-CjcLyFV5.js";
|
|
3
|
+
import { n as askAgentIds } from "./thread-state-BPpvKFwQ.js";
|
|
4
|
+
import { t as ASK_THREAD_ENV } from "./request-origin-icXPv6jh.js";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { constants } from "node:fs";
|
|
6
7
|
import { access, chmod, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
@@ -277,7 +278,7 @@ async function detectAskAgents(env = process.env) {
|
|
|
277
278
|
}
|
|
278
279
|
/** Runs an adapter with this server's runtime, Desktop's being Electron, or
|
|
279
280
|
* the user's CLI itself. */
|
|
280
|
-
const launchAskAgent = async (agent, cwd, { permissions = "read-only", mcpServers = [], env: base = process.env } = {}) => {
|
|
281
|
+
const launchAskAgent = async (agent, cwd, { permissions = "read-only", mcpServers = [], env: base = process.env, threadId } = {}) => {
|
|
281
282
|
const spec = askAgents[agent];
|
|
282
283
|
const executable = await findAgent(spec, base);
|
|
283
284
|
if (!executable) throw new Error(`${spec.name} is not installed.`);
|
|
@@ -285,6 +286,7 @@ const launchAskAgent = async (agent, cwd, { permissions = "read-only", mcpServer
|
|
|
285
286
|
...base,
|
|
286
287
|
...askAgentSettings(agent, permissions).env
|
|
287
288
|
};
|
|
289
|
+
if (threadId) env[ASK_THREAD_ENV] = threadId;
|
|
288
290
|
delete env.CLAUDECODE;
|
|
289
291
|
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
290
292
|
const { launch } = spec;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as withHeadlessServerLock, n as runHeadlessServer, r as takeOverServerHome, t as HeadlessServerBusyError } from "./headless-host-
|
|
1
|
+
import { i as withHeadlessServerLock, n as runHeadlessServer, r as takeOverServerHome, t as HeadlessServerBusyError } from "./headless-host-CEb5d-A0.js";
|
|
2
2
|
export { HeadlessServerBusyError, runHeadlessServer, takeOverServerHome, withHeadlessServerLock };
|