@dev.fast/whiteboard 0.3.3 → 0.3.4-preview.20261019.125
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-xOdmMnDH.js → account-alias-ClRtWF_-.js} +8 -6
- package/dist/{agent-cli-DT7MdaqL.js → agent-cli-C6AM9dml.js} +15 -10
- package/dist/agent-cli-DKpWo-4F.js +2 -0
- package/dist/{agent-client-BRBzv3AV.js → agent-client-G4KxB8zB.js} +13 -3
- package/dist/{authoring-tools-DGSEag8y.js → authoring-tools-Dfo0uOSj.js} +34 -27
- package/dist/{background-server-WKzI_a98.js → background-server-BF_lLFxY.js} +5 -5
- package/dist/{background-server-C_REWFkG.js → background-server-DJfYWRoN.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-BBSDi_dX.js → cli-CqOiiTW_.js} +1 -1
- package/dist/{cli-runner-DshTBske.js → cli-runner-D8CsXff4.js} +26 -26
- package/dist/cli.js +2 -2
- package/dist/{client-BN_FDxFr.js → client-lKVF_Elo.js} +253 -47
- package/dist/{fuzzy-match-bFX6wm8j.js → fuzzy-match-DH9lZivo.js} +42 -14
- package/dist/{headless-host-CS4Gv0c5.js → headless-host-C4bhVPZx.js} +513 -140
- package/dist/{headless-host-CzvYpKOX.js → headless-host-CeNnXxiL.js} +1 -1
- package/dist/lens-selection-CyJapcsl.js +214 -0
- package/dist/{local-data-DtQTIzHX.js → local-data-BdqqCYUD.js} +2434 -328
- package/dist/{mcp-DpKnnmHH.js → mcp-B2mvDEhC.js} +5 -9
- package/dist/{package-paths-jMg6JxDP.js → package-paths-Cm1tR8c4.js} +1 -1
- package/dist/{profile-DaqmODhc.js → profile-DcigfSms.js} +20 -8
- package/dist/profile-DthRnZ7x.js +2 -0
- package/dist/{remote-attach-DCngg8cl.js → remote-attach-DGwVRmY5.js} +4 -4
- package/dist/{remote-extensions-BCAbQZTv.js → remote-extensions-CferWwM9.js} +3 -3
- package/dist/{remote-extensions-Cf3L-ZGG.js → remote-extensions-D8W2IgKH.js} +1 -1
- package/dist/{remote-language-server-UWQgpoh-.js → remote-language-server-By-rEosC.js} +5 -5
- package/dist/{remote-uninstall-CcDOcGyi.js → remote-uninstall-DgHnDAFH.js} +6 -6
- package/dist/{request-origin-Zcn_CjX4.js → request-origin-DDmfnytX.js} +38 -10
- package/dist/{review-app-launcher-tqSQPlbu.js → review-app-launcher-CG3TFZEx.js} +2 -2
- package/dist/{review-home-paths-Lc02WFuX.js → review-home-paths-D1JVitqN.js} +19 -17
- package/dist/{review-telemetry-BHWJsnoD.js → review-telemetry-Myzm7o6-.js} +5 -5
- package/dist/runtime-Bj2tD33R.js +27 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-77-qkEhs.js → s3-DtJFWC0G.js} +1 -1
- package/dist/{s3-config-DDWWMh5x.js → s3-config-BTO12tCC.js} +1 -1
- package/dist/{s3-config-0xuCh-F_.js → s3-config-CSy3fx8H.js} +1 -1
- package/dist/server/desktop-host.js +29 -76
- package/dist/{server-discovery-BVqrE1_-.js → server-discovery-Bwa_6Tsj.js} +3 -3
- package/dist/sharing/index.d.ts +486 -70
- package/dist/sharing/index.js +1 -1
- package/dist/{src-DsM3jO8G.js → src-BiWNbcF_.js} +89 -12
- package/dist/{src-BMPuxWv4.js → src-WPaQn75m.js} +208 -73
- package/dist/{stored-document-migration-CChxtFAy.js → stored-document-migration-dFbewRTG.js} +270 -269
- package/dist/{thread-state-5pYr6_zM.js → thread-state-Do5LX10I.js} +7 -2
- package/dist/{tool-failure-BFt2xj9i.js → tool-failure-C9J3r7KD.js} +10 -3
- package/instructions/authoring.md +3 -2
- package/package.json +4 -4
- package/src/agent-connect.ts +8 -2
- package/src/ask/agents.ts +51 -9
- package/src/ask/checkout-files.ts +2 -5
- package/src/ask/thread-state.ts +4 -0
- package/src/ask/thread.ts +16 -0
- package/src/ask/threads.ts +24 -3
- package/src/cli-runner.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/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 +137 -51
- package/src/review-api/authoring-tools.ts +45 -30
- 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 +560 -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 +281 -34
- package/src/review-api/foreign-references.ts +328 -0
- package/src/review-api/github-provider.ts +51 -13
- package/src/review-api/http.ts +702 -123
- 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 +3 -11
- 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 +357 -0
- package/src/review-api/store-schema.ts +38 -29
- package/src/review-api/store.ts +2144 -480
- package/src/review-api/workspaces.ts +52 -13
- 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 -31
- package/src/server/headless-host.ts +0 -2
- package/src/server/hono-http.ts +2 -1
- package/src/server/legacy-tutorial-cleanup.ts +2 -3
- 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 +128 -20
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +110 -19
- package/src/ui-telemetry-events.ts +25 -9
- package/dist/agent-cli-DeCrUC2s.js +0 -2
- package/dist/profile-Cz7L781D.js +0 -2
- package/dist/runtime-CKiWhGrf.js +0 -52
- package/instructions/scratchpad.md +0 -23
- package/src/review-preferences.ts +0 -88
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { f as gitAt } from "./src-
|
|
1
|
+
import { f as gitAt } from "./src-WPaQn75m.js";
|
|
2
2
|
import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
3
|
-
import "./src-
|
|
4
|
-
import {
|
|
3
|
+
import "./src-BiWNbcF_.js";
|
|
4
|
+
import { f as sourceAnchors } from "./lens-selection-CyJapcsl.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-dFbewRTG.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,10 +253,7 @@ 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
|
}
|
|
@@ -241,9 +261,21 @@ 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);
|
|
242
262
|
return new ReviewInputError("Could not fetch the GitHub repository. Check your connection and Git credentials for this repository, then retry.", 409);
|
|
243
263
|
}
|
|
244
|
-
/** Recipients need complete history; publication only checks that the commits exist.
|
|
245
|
-
|
|
246
|
-
|
|
264
|
+
/** Recipients need complete history; publication only checks that the commits exist.
|
|
265
|
+
* `commits` are other commits the board's references read at; a plan has
|
|
266
|
+
* only those. */
|
|
267
|
+
async function fetchPinnedRepository(root, url, pins, publishing = false, commits = []) {
|
|
268
|
+
const named = [...pins ? [{
|
|
269
|
+
commit: pins.base,
|
|
270
|
+
ref: "base"
|
|
271
|
+
}, {
|
|
272
|
+
commit: pins.head,
|
|
273
|
+
ref: "head"
|
|
274
|
+
}] : [], ...commits.map((commit) => ({
|
|
275
|
+
commit,
|
|
276
|
+
ref: `commits/${commit}`
|
|
277
|
+
}))];
|
|
278
|
+
if (!named.every(({ commit }) => COMMIT_ID.test(commit))) throw new ReviewInputError("Sharing requires immutable base and head commits.");
|
|
247
279
|
await mkdir(root, {
|
|
248
280
|
recursive: true,
|
|
249
281
|
mode: 448
|
|
@@ -264,7 +296,7 @@ async function fetchPinnedRepository(root, url, pins, publishing = false) {
|
|
|
264
296
|
"remote",
|
|
265
297
|
"add",
|
|
266
298
|
"origin",
|
|
267
|
-
url
|
|
299
|
+
{ remote: url }
|
|
268
300
|
]);
|
|
269
301
|
}
|
|
270
302
|
const refs = publishing ? "refs/heads/review" : "refs/review";
|
|
@@ -274,26 +306,129 @@ async function fetchPinnedRepository(root, url, pins, publishing = false) {
|
|
|
274
306
|
"--no-tags",
|
|
275
307
|
"--force",
|
|
276
308
|
...publishing ? ["--depth=1", "--filter=tree:0"] : [],
|
|
277
|
-
publishing ? url : "origin",
|
|
278
|
-
|
|
279
|
-
|
|
309
|
+
{ remote: publishing ? url : "origin" },
|
|
310
|
+
...named.map(({ commit, ref }) => ({ refspec: {
|
|
311
|
+
from: commit,
|
|
312
|
+
to: `${refs}/${ref}`
|
|
313
|
+
} }))
|
|
280
314
|
]);
|
|
281
315
|
} catch (error) {
|
|
282
316
|
throw fetchError(error instanceof Error && "stderr" in error ? String(error.stderr) : "", publishing);
|
|
283
317
|
}
|
|
284
|
-
if (!publishing) for (const commit of
|
|
318
|
+
if (!publishing) for (const { commit } of named) await sharedGit(root, [
|
|
285
319
|
"cat-file",
|
|
286
320
|
"-e",
|
|
287
|
-
`${commit}^{commit}`
|
|
321
|
+
{ rev: `${commit}^{commit}` }
|
|
288
322
|
]);
|
|
289
323
|
}
|
|
290
|
-
|
|
324
|
+
/** Fetches the commits a shared board's links read at into a checkout of
|
|
325
|
+
* its repository, from its origin unless it has them, and keeps each under
|
|
326
|
+
* `refs/review/shared/<share>/<commit>`, so `git gc` can't prune one the
|
|
327
|
+
* board still quotes. */
|
|
328
|
+
async function fetchSharedCommits(root, commits, share) {
|
|
329
|
+
assertCommitIds(commits);
|
|
330
|
+
const ref = (commit) => `refs/review/shared/${share}/${commit}`;
|
|
331
|
+
const missing = [];
|
|
332
|
+
for (const commit of commits) try {
|
|
333
|
+
await sharedGit(root, [
|
|
334
|
+
"cat-file",
|
|
335
|
+
"-e",
|
|
336
|
+
{ rev: `${commit}^{commit}` }
|
|
337
|
+
]);
|
|
338
|
+
await sharedGit(root, [
|
|
339
|
+
"update-ref",
|
|
340
|
+
{ ref: ref(commit) },
|
|
341
|
+
{ commit }
|
|
342
|
+
]);
|
|
343
|
+
} catch {
|
|
344
|
+
missing.push(commit);
|
|
345
|
+
}
|
|
346
|
+
if (!missing.length) return;
|
|
347
|
+
try {
|
|
348
|
+
await sharedGit(root, [
|
|
349
|
+
"fetch",
|
|
350
|
+
"--no-tags",
|
|
351
|
+
"origin",
|
|
352
|
+
...missing.map((commit) => ({ refspec: {
|
|
353
|
+
from: commit,
|
|
354
|
+
to: ref(commit),
|
|
355
|
+
force: true
|
|
356
|
+
} }))
|
|
357
|
+
]);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
throw fetchError(error instanceof Error && "stderr" in error ? String(error.stderr) : "", false);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/** Drops the refs `fetchSharedCommits` kept for a share, once the share is
|
|
363
|
+
* gone, so `git gc` can prune what only it quoted. */
|
|
364
|
+
async function forgetSharedCommits(root, share) {
|
|
365
|
+
const { stdout } = await sharedGit(root, [
|
|
366
|
+
"for-each-ref",
|
|
367
|
+
"--format=%(refname)",
|
|
368
|
+
{ ref: `refs/review/shared/${share}/` }
|
|
369
|
+
]);
|
|
370
|
+
for (const ref of stdout.split("\n").filter(Boolean)) await sharedGit(root, [
|
|
371
|
+
"update-ref",
|
|
372
|
+
"-d",
|
|
373
|
+
{ ref }
|
|
374
|
+
]);
|
|
375
|
+
}
|
|
376
|
+
/** A managed clone of a repository's default branch, for a shared board
|
|
377
|
+
* with no pins whose repository the receiver has no checkout of. */
|
|
378
|
+
async function cloneSharedRepository(root, url) {
|
|
379
|
+
await mkdir(root, {
|
|
380
|
+
recursive: true,
|
|
381
|
+
mode: 448
|
|
382
|
+
});
|
|
383
|
+
await sharedGit(root, ["init", "--quiet"]);
|
|
384
|
+
await sharedGit(root, [
|
|
385
|
+
"config",
|
|
386
|
+
"core.hooksPath",
|
|
387
|
+
"/dev/null"
|
|
388
|
+
]);
|
|
389
|
+
await sharedGit(root, [
|
|
390
|
+
"config",
|
|
391
|
+
"core.fsmonitor",
|
|
392
|
+
"false"
|
|
393
|
+
]);
|
|
394
|
+
await sharedGit(root, [
|
|
395
|
+
"remote",
|
|
396
|
+
"add",
|
|
397
|
+
"origin",
|
|
398
|
+
{ remote: url }
|
|
399
|
+
]);
|
|
400
|
+
try {
|
|
401
|
+
await sharedGit(root, [
|
|
402
|
+
"fetch",
|
|
403
|
+
"--no-tags",
|
|
404
|
+
"origin",
|
|
405
|
+
"HEAD"
|
|
406
|
+
]);
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw fetchError(error instanceof Error && "stderr" in error ? String(error.stderr) : "", false);
|
|
409
|
+
}
|
|
410
|
+
await sharedGit(root, [
|
|
411
|
+
"checkout",
|
|
412
|
+
"--detach",
|
|
413
|
+
"FETCH_HEAD"
|
|
414
|
+
]);
|
|
415
|
+
}
|
|
416
|
+
async function repositoryReady(root, pins, commits = []) {
|
|
417
|
+
if (![
|
|
418
|
+
pins.base,
|
|
419
|
+
pins.head,
|
|
420
|
+
...commits
|
|
421
|
+
].every((commit) => COMMIT_ID.test(commit))) return false;
|
|
291
422
|
try {
|
|
292
423
|
if ((await sharedGit(root, ["rev-parse", "HEAD"])).stdout.trim() !== pins.head) return false;
|
|
293
|
-
for (const commit of [
|
|
424
|
+
for (const commit of [
|
|
425
|
+
pins.base,
|
|
426
|
+
pins.head,
|
|
427
|
+
...commits
|
|
428
|
+
]) await sharedGit(root, [
|
|
294
429
|
"cat-file",
|
|
295
430
|
"-e",
|
|
296
|
-
`${commit}^{commit}`
|
|
431
|
+
{ rev: `${commit}^{commit}` }
|
|
297
432
|
]);
|
|
298
433
|
return true;
|
|
299
434
|
} catch {
|
|
@@ -313,11 +448,11 @@ async function readShareRepository(root) {
|
|
|
313
448
|
}
|
|
314
449
|
return { cloneUrl };
|
|
315
450
|
}
|
|
316
|
-
async function verifyShareRepository(root, pins, repository) {
|
|
451
|
+
async function verifyShareRepository(root, pins, repository, commits = []) {
|
|
317
452
|
const { cloneUrl } = repository ?? await readShareRepository(root);
|
|
318
453
|
const temporary = await mkdtemp(path.join(tmpdir(), "review-share-check-"));
|
|
319
454
|
try {
|
|
320
|
-
await fetchPinnedRepository(temporary, cloneUrl, pins, true);
|
|
455
|
+
await fetchPinnedRepository(temporary, cloneUrl, pins, true, commits);
|
|
321
456
|
} finally {
|
|
322
457
|
await rm(temporary, {
|
|
323
458
|
recursive: true,
|
|
@@ -331,10 +466,11 @@ async function verifyShareRepository(root, pins, repository) {
|
|
|
331
466
|
const sharedSnapshotSchema = z.strictObject({
|
|
332
467
|
reviewId: z.string().min(1),
|
|
333
468
|
version: z.number().int().nonnegative(),
|
|
334
|
-
|
|
469
|
+
/** Empty for a board never named; it reads "Untitled". */
|
|
470
|
+
title: z.string(),
|
|
335
471
|
pins: pinsSchema.extend({
|
|
336
|
-
base: z.string().regex(
|
|
337
|
-
head: z.string().regex(
|
|
472
|
+
base: z.string().regex(COMMIT_ID),
|
|
473
|
+
head: z.string().regex(COMMIT_ID)
|
|
338
474
|
}).optional(),
|
|
339
475
|
document: documentSchema,
|
|
340
476
|
lenses: z.array(lensSchema).optional(),
|
|
@@ -354,7 +490,7 @@ const counts = z.strictObject({
|
|
|
354
490
|
deletions: count
|
|
355
491
|
});
|
|
356
492
|
const mapSchema = z.strictObject({
|
|
357
|
-
commit: z.string(),
|
|
493
|
+
commit: z.string().regex(COMMIT_ID),
|
|
358
494
|
side: z.enum(["base", "head"]),
|
|
359
495
|
elements: z.array(normalizedSoftwareElementSchema),
|
|
360
496
|
relationships: z.array(normalizedSoftwareRelationshipSchema),
|
|
@@ -387,7 +523,8 @@ function validateShareBundle(bundle) {
|
|
|
387
523
|
if (snapshot.reviewId !== manifest.reviewId || snapshot.version !== manifest.version || snapshot.title !== manifest.title) throw new Error("Shared snapshot does not match its manifest.");
|
|
388
524
|
checkReferences(snapshot.document);
|
|
389
525
|
validateShareSources(snapshot);
|
|
390
|
-
|
|
526
|
+
assertCommitIds(referenceCommits(snapshot));
|
|
527
|
+
if (snapshot.pins && !manifest.repository) throw new Error("Shared source pins require a repository.");
|
|
391
528
|
const presentation = sharePresentationSchema.parse(json(manifest.presentation));
|
|
392
529
|
if (!snapshot.pins && Object.keys(presentation.maps).length) throw new Error("Shared maps require source pins.");
|
|
393
530
|
const traces = /* @__PURE__ */ new Map();
|
|
@@ -435,6 +572,7 @@ function sharedReviewId(origin, shareId) {
|
|
|
435
572
|
var SharedReviewStore = class {
|
|
436
573
|
root;
|
|
437
574
|
fetchRepository;
|
|
575
|
+
cloneRepository;
|
|
438
576
|
loaded = /* @__PURE__ */ new Map();
|
|
439
577
|
validated = /* @__PURE__ */ new Map();
|
|
440
578
|
attention = /* @__PURE__ */ new Map();
|
|
@@ -456,9 +594,10 @@ var SharedReviewStore = class {
|
|
|
456
594
|
repositories = /* @__PURE__ */ new Map();
|
|
457
595
|
jobs = /* @__PURE__ */ new Map();
|
|
458
596
|
states = /* @__PURE__ */ new Map();
|
|
459
|
-
constructor(root, fetchRepository = fetchPinnedRepository) {
|
|
597
|
+
constructor(root, fetchRepository = fetchPinnedRepository, cloneRepository = cloneSharedRepository) {
|
|
460
598
|
this.root = root;
|
|
461
599
|
this.fetchRepository = fetchRepository;
|
|
600
|
+
this.cloneRepository = cloneRepository;
|
|
462
601
|
this.purgeTimer = setInterval(() => {
|
|
463
602
|
this.purgeExpired().catch((error) => console.error("Could not purge expired shared whiteboards:", error));
|
|
464
603
|
}, 6e4);
|
|
@@ -518,14 +657,15 @@ var SharedReviewStore = class {
|
|
|
518
657
|
const saved = this.validated.get(id);
|
|
519
658
|
if (!saved) throw new ReviewInputError("Shared review is not available.", 404);
|
|
520
659
|
if (!saved.snapshot.pins) {
|
|
521
|
-
this.
|
|
660
|
+
await this.prepareUnpinned(id, saved.manifest.repository?.cloneUrl);
|
|
522
661
|
return;
|
|
523
662
|
}
|
|
524
663
|
if (!local) throw new ReviewInputError("Repository service is unavailable.", 409);
|
|
525
664
|
const root = this.repositoryRoot(id);
|
|
665
|
+
const commits = referenceCommits(saved.snapshot);
|
|
526
666
|
try {
|
|
527
667
|
this.setStatus(id, "fetching");
|
|
528
|
-
if (!await repositoryReady(root, saved.snapshot.pins)) {
|
|
668
|
+
if (!await repositoryReady(root, saved.snapshot.pins, commits)) {
|
|
529
669
|
const prior = this.repositories.get(id);
|
|
530
670
|
await local.data.workspaces.remove(id);
|
|
531
671
|
if (prior) await local.data.forgetRepository(prior);
|
|
@@ -533,11 +673,11 @@ var SharedReviewStore = class {
|
|
|
533
673
|
recursive: true,
|
|
534
674
|
force: true
|
|
535
675
|
});
|
|
536
|
-
await this.fetchRepository(root, saved.manifest.repository.cloneUrl, saved.snapshot.pins);
|
|
676
|
+
await this.fetchRepository(root, saved.manifest.repository.cloneUrl, saved.snapshot.pins, false, commits);
|
|
537
677
|
await sharedGit(root, [
|
|
538
678
|
"checkout",
|
|
539
679
|
"--detach",
|
|
540
|
-
saved.snapshot.pins.head
|
|
680
|
+
{ commit: saved.snapshot.pins.head }
|
|
541
681
|
]);
|
|
542
682
|
}
|
|
543
683
|
const repository = await local.data.register(root);
|
|
@@ -552,12 +692,7 @@ var SharedReviewStore = class {
|
|
|
552
692
|
};
|
|
553
693
|
this.setStatus(id, "validating");
|
|
554
694
|
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);
|
|
695
|
+
await this.validateReferences(saved, repository.id, pins);
|
|
561
696
|
await local.data.workspaces.open(id, pins);
|
|
562
697
|
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
698
|
await writeFile(path.join(this.root, id, "repository.json"), JSON.stringify({
|
|
@@ -571,9 +706,71 @@ var SharedReviewStore = class {
|
|
|
571
706
|
throw new ReviewInputError(message, 409);
|
|
572
707
|
}
|
|
573
708
|
}
|
|
709
|
+
/** Reads each of a shared board's references at its own commits, in the
|
|
710
|
+
* checkout of its repository. */
|
|
711
|
+
async validateReferences(saved, repositoryId, pins) {
|
|
712
|
+
const references = /* @__PURE__ */ new Map();
|
|
713
|
+
const add = (at, range) => {
|
|
714
|
+
const key = JSON.stringify([at.base, at.head]);
|
|
715
|
+
const group = references.get(key) ?? {
|
|
716
|
+
pins: at,
|
|
717
|
+
ranges: []
|
|
718
|
+
};
|
|
719
|
+
group.ranges.push(range);
|
|
720
|
+
references.set(key, group);
|
|
721
|
+
};
|
|
722
|
+
for (const { source } of sourceReferences(saved.snapshot.document, { repositoryId })) add({
|
|
723
|
+
...anchorPins(source, pins),
|
|
724
|
+
repositoryId
|
|
725
|
+
}, source);
|
|
726
|
+
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, {
|
|
727
|
+
...range,
|
|
728
|
+
side: map.side
|
|
729
|
+
});
|
|
730
|
+
for (const group of references.values()) await this.local.data.validateSources(group.pins, group.ranges);
|
|
731
|
+
}
|
|
732
|
+
/** Files an unpinned board under a local checkout of its repository,
|
|
733
|
+
* else a managed clone. */
|
|
734
|
+
async prepareUnpinned(id, cloneUrl) {
|
|
735
|
+
const local = this.local;
|
|
736
|
+
const saved = this.validated.get(id);
|
|
737
|
+
if (!cloneUrl || !local) {
|
|
738
|
+
this.setStatus(id, "ready");
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const root = this.repositoryRoot(id);
|
|
742
|
+
try {
|
|
743
|
+
this.setStatus(id, "fetching");
|
|
744
|
+
const managed = path.join(await realpath(this.root), ".repositories");
|
|
745
|
+
let repositoryId = local.store.checkoutsOf(cloneUrl).find((checkout) => !local.store.repositoryPath(checkout).startsWith(managed));
|
|
746
|
+
if (!repositoryId) {
|
|
747
|
+
if (!existsSync(path.join(root, ".git"))) {
|
|
748
|
+
await rm(root, {
|
|
749
|
+
recursive: true,
|
|
750
|
+
force: true
|
|
751
|
+
});
|
|
752
|
+
await this.cloneRepository(root, cloneUrl);
|
|
753
|
+
}
|
|
754
|
+
repositoryId = (await local.data.register(root)).id;
|
|
755
|
+
}
|
|
756
|
+
await fetchSharedCommits(local.store.repositoryPath(repositoryId), referenceCommits(saved.snapshot, repositoryId), id);
|
|
757
|
+
this.setStatus(id, "validating");
|
|
758
|
+
await this.validateReferences(saved, repositoryId);
|
|
759
|
+
this.repositories.set(id, repositoryId);
|
|
760
|
+
await writeFile(path.join(this.root, id, "repository.json"), JSON.stringify({
|
|
761
|
+
repositoryId,
|
|
762
|
+
ready: true
|
|
763
|
+
}), { mode: 384 });
|
|
764
|
+
this.setStatus(id, "ready");
|
|
765
|
+
} catch (error) {
|
|
766
|
+
const message = error instanceof ReviewInputError ? error.message : "Could not prepare the shared repository. Check Git credentials and retry.";
|
|
767
|
+
this.setStatus(id, "error", message);
|
|
768
|
+
throw new ReviewInputError(message, 409);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
574
771
|
async assertReady(id) {
|
|
575
772
|
const saved = this.validated.get(id);
|
|
576
|
-
if (!saved || saved.snapshot.pins && !await repositoryReady(this.repositoryRoot(id), saved.snapshot.pins)) {
|
|
773
|
+
if (!saved || saved.snapshot.pins && !await repositoryReady(this.repositoryRoot(id), saved.snapshot.pins, referenceCommits(saved.snapshot))) {
|
|
577
774
|
this.setStatus(id, "error", "The managed checkout is missing. Reopen the share link to fetch it again.");
|
|
578
775
|
throw new ReviewInputError(this.status(id).error, 409);
|
|
579
776
|
}
|
|
@@ -612,13 +809,15 @@ var SharedReviewStore = class {
|
|
|
612
809
|
repositoryId: z.string(),
|
|
613
810
|
ready: z.boolean()
|
|
614
811
|
}).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)) {
|
|
812
|
+
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
813
|
this.repositories.set(id, metadata.repositoryId);
|
|
617
814
|
prepared = metadata.ready;
|
|
618
815
|
}
|
|
619
816
|
} catch {}
|
|
620
|
-
if (!validated.snapshot.pins)
|
|
621
|
-
|
|
817
|
+
if (!validated.snapshot.pins) {
|
|
818
|
+
if (this.repositories.has(id) || !validated.manifest.repository) this.setStatus(id, "ready");
|
|
819
|
+
else this.prepare(id).catch(() => {});
|
|
820
|
+
} else if (prepared && this.local && await repositoryReady(this.repositoryRoot(id), validated.snapshot.pins, referenceCommits(validated.snapshot))) {
|
|
622
821
|
const repository = await this.local.data.register(this.repositoryRoot(id));
|
|
623
822
|
this.repositories.set(id, repository.id);
|
|
624
823
|
this.setStatus(id, "ready");
|
|
@@ -669,6 +868,9 @@ var SharedReviewStore = class {
|
|
|
669
868
|
kind: "commits",
|
|
670
869
|
...validated.snapshot.pins,
|
|
671
870
|
repositoryId
|
|
871
|
+
} : repositoryId ? {
|
|
872
|
+
kind: "project",
|
|
873
|
+
repositoryId
|
|
672
874
|
} : void 0,
|
|
673
875
|
shared: {
|
|
674
876
|
...bundle.attribution,
|
|
@@ -680,10 +882,12 @@ var SharedReviewStore = class {
|
|
|
680
882
|
list() {
|
|
681
883
|
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
884
|
const { document: _document, ...snapshot } = this.get(id).snapshot;
|
|
885
|
+
const project = snapshot.target ? this.local?.store.checkoutProject(snapshot.target.repositoryId) : void 0;
|
|
683
886
|
return {
|
|
684
887
|
...snapshot,
|
|
685
888
|
firstCreatedAt: snapshot.version === 0 ? snapshot.createdAt : void 0,
|
|
686
889
|
repositoryName: "Shared review",
|
|
890
|
+
...project && { project },
|
|
687
891
|
viewedAt: this.attention.get(id)?.viewedAt ?? null,
|
|
688
892
|
trashedAt: this.attention.get(id)?.trashedAt ?? null
|
|
689
893
|
};
|
|
@@ -705,8 +909,10 @@ var SharedReviewStore = class {
|
|
|
705
909
|
await this.local?.data.workspaces.remove(id);
|
|
706
910
|
const repositoryId = this.repositories.get(id);
|
|
707
911
|
if (repositoryId) {
|
|
912
|
+
const checkout = this.local?.store.repositoryPath(repositoryId);
|
|
913
|
+
if (checkout) await forgetSharedCommits(checkout, id).catch((error) => console.error(`Could not drop the refs kept for share ${id}:`, error));
|
|
708
914
|
await this.local?.data.forgetRepository(repositoryId);
|
|
709
|
-
this.local?.store.unregisterRepository(repositoryId);
|
|
915
|
+
await this.local?.store.unregisterRepository(repositoryId);
|
|
710
916
|
}
|
|
711
917
|
await rm(this.repositoryRoot(id), {
|
|
712
918
|
recursive: true,
|
|
@@ -999,4 +1205,4 @@ var ShareClient = class {
|
|
|
999
1205
|
}
|
|
1000
1206
|
};
|
|
1001
1207
|
//#endregion
|
|
1002
|
-
export { sharedReviewId as a, verifyShareRepository as c,
|
|
1208
|
+
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 {
|
|
3
|
-
import { n as askAgentIds } from "./thread-state-
|
|
1
|
+
import "./review-home-paths-D1JVitqN.js";
|
|
2
|
+
import { N as devReviewHome } from "./s3-config-BTO12tCC.js";
|
|
3
|
+
import { n as askAgentIds } from "./thread-state-Do5LX10I.js";
|
|
4
|
+
import { t as ASK_THREAD_ENV } from "./request-origin-DDmfnytX.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";
|
|
@@ -131,6 +132,7 @@ const askAgents = {
|
|
|
131
132
|
claude: {
|
|
132
133
|
name: "Claude Code",
|
|
133
134
|
commands: ["claude"],
|
|
135
|
+
installDirs: [".claude/local", ".local/bin"],
|
|
134
136
|
launch: {
|
|
135
137
|
adapter: "@agentclientprotocol/claude-agent-acp/dist/index.js",
|
|
136
138
|
executableEnv: "CLAUDE_CODE_EXECUTABLE"
|
|
@@ -211,20 +213,43 @@ const askAgents = {
|
|
|
211
213
|
}
|
|
212
214
|
}
|
|
213
215
|
};
|
|
216
|
+
/** Windows can't run the extensionless sh shims npm puts beside `.cmd`s. */
|
|
217
|
+
const WINDOWS_EXTENSIONS = [".exe", ".cmd"];
|
|
214
218
|
/** Desktop inherits the login shell's PATH, so this sees what a terminal sees.
|
|
215
219
|
* Package-manager bin directories are skipped: the adapters' own dependencies
|
|
216
220
|
* put `codex` there, and that is not the user's install. */
|
|
217
|
-
async function findExecutable(command, env = process.env) {
|
|
221
|
+
async function findExecutable(command, env = process.env, platform = process.platform) {
|
|
222
|
+
const names = platform === "win32" ? WINDOWS_EXTENSIONS.map((extension) => command + extension) : [command];
|
|
218
223
|
for (const directory of (env.PATH ?? "").split(path.delimiter)) {
|
|
219
224
|
if (!directory || directory.split(path.sep).includes("node_modules")) continue;
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
for (const name of names) {
|
|
226
|
+
const candidate = path.join(directory, name);
|
|
227
|
+
try {
|
|
228
|
+
if (!(await stat(candidate)).isFile()) continue;
|
|
229
|
+
await access(candidate, constants.X_OK);
|
|
230
|
+
return candidate;
|
|
231
|
+
} catch {}
|
|
232
|
+
}
|
|
226
233
|
}
|
|
227
234
|
}
|
|
235
|
+
/** Windows runs a `.cmd` only through cmd.exe. */
|
|
236
|
+
function executableCommand(executable, args) {
|
|
237
|
+
if (process.platform !== "win32" || !/\.cmd$/i.test(executable)) return {
|
|
238
|
+
file: executable,
|
|
239
|
+
args,
|
|
240
|
+
windowsVerbatimArguments: false
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
file: "cmd.exe",
|
|
244
|
+
args: [
|
|
245
|
+
"/d",
|
|
246
|
+
"/s",
|
|
247
|
+
"/c",
|
|
248
|
+
`"${[executable, ...args].map((arg) => `"${arg}"`).join(" ")}"`
|
|
249
|
+
],
|
|
250
|
+
windowsVerbatimArguments: true
|
|
251
|
+
};
|
|
252
|
+
}
|
|
228
253
|
/** The user's install of an agent's CLI: on PATH, else where its installer
|
|
229
254
|
* puts it. */
|
|
230
255
|
async function findAgent(spec, env = process.env) {
|
|
@@ -253,7 +278,7 @@ async function detectAskAgents(env = process.env) {
|
|
|
253
278
|
}
|
|
254
279
|
/** Runs an adapter with this server's runtime, Desktop's being Electron, or
|
|
255
280
|
* the user's CLI itself. */
|
|
256
|
-
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 } = {}) => {
|
|
257
282
|
const spec = askAgents[agent];
|
|
258
283
|
const executable = await findAgent(spec, base);
|
|
259
284
|
if (!executable) throw new Error(`${spec.name} is not installed.`);
|
|
@@ -261,6 +286,7 @@ const launchAskAgent = async (agent, cwd, { permissions = "read-only", mcpServer
|
|
|
261
286
|
...base,
|
|
262
287
|
...askAgentSettings(agent, permissions).env
|
|
263
288
|
};
|
|
289
|
+
if (threadId) env[ASK_THREAD_ENV] = threadId;
|
|
264
290
|
delete env.CLAUDECODE;
|
|
265
291
|
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
266
292
|
const { launch } = spec;
|
|
@@ -272,14 +298,16 @@ const launchAskAgent = async (agent, cwd, { permissions = "read-only", mcpServer
|
|
|
272
298
|
command = process.execPath;
|
|
273
299
|
args = [createRequire(import.meta.url).resolve(launch.adapter)];
|
|
274
300
|
} else args = launch.args;
|
|
275
|
-
const
|
|
301
|
+
const start = executableCommand(command, args);
|
|
302
|
+
const child = spawn(start.file, start.args, {
|
|
276
303
|
cwd,
|
|
277
304
|
env,
|
|
278
305
|
stdio: [
|
|
279
306
|
"pipe",
|
|
280
307
|
"pipe",
|
|
281
308
|
"pipe"
|
|
282
|
-
]
|
|
309
|
+
],
|
|
310
|
+
windowsVerbatimArguments: start.windowsVerbatimArguments
|
|
283
311
|
});
|
|
284
312
|
let stderr = "";
|
|
285
313
|
child.stderr.setEncoding("utf8");
|
|
@@ -332,4 +360,4 @@ function fuzzyRank(query, items, labelsOf) {
|
|
|
332
360
|
})).filter((hit) => hit.score >= SCORE_THRESHOLD).toArray().sort((left, right) => right.score - left.score).map((hit) => hit.item);
|
|
333
361
|
}
|
|
334
362
|
//#endregion
|
|
335
|
-
export { defaultAskPermissions as a,
|
|
363
|
+
export { defaultAskPermissions as a, findExecutable as c, askAgents as i, launchAskAgent as l, askAgentSettings as n, detectAskAgents as o, askAgentTakesMcp as r, executableCommand as s, fuzzyRank as t };
|