@dev.fast/whiteboard 0.2.4 → 0.2.5-preview.20261013.110
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-_1_7vROx.js → account-alias-D0hF0sKk.js} +2 -3
- package/dist/{agent-cli-BrFqhnzM.js → agent-cli-BBPxwUOd.js} +6 -6
- package/dist/agent-cli-YSuhE5X8.js +2 -0
- package/dist/{agent-client-B_b1GtUc.js → agent-client-P3J4y8Zn.js} +3 -3
- package/dist/{authoring-tools-DvIO906l.js → authoring-tools-D8WXsm_g.js} +11 -10
- package/dist/build-info.json +1 -1
- package/dist/{cli-gRGxkwc0.js → cli-C0ziP6hg.js} +1 -1
- package/dist/{cli-runner-Cjl8h376.js → cli-runner-C1gY-HAI.js} +16 -17
- package/dist/cli.js +1 -1
- package/dist/{client-BFClfQLO.js → client-DzcSt6pV.js} +63 -13
- package/dist/{desktop-discovery-C-Qz54wO.js → desktop-discovery-Ba3dD5Cn.js} +1 -1
- package/dist/{headless-host-DcWaxx9P.js → headless-host-BHD_Vg5F.js} +4 -4
- package/dist/{local-data-Bm84Xdck.js → local-data-BlERtVm2.js} +281 -75
- package/dist/{mcp-BDy9Aybd.js → mcp-t7VN9R3k.js} +4 -4
- package/dist/{package-paths-B6-zxvIO.js → package-paths-B9PDoAl6.js} +1 -1
- package/dist/{process-error-telemetry-Dzh5a3s_.js → process-error-telemetry-C0swByfp.js} +158 -57
- package/dist/{profile-RDEioF8b.js → profile-BsYDBr7t.js} +2 -2
- package/dist/profile-j97aRTS7.js +2 -0
- package/dist/{review-agent-traces-DNJFYzc8.js → review-home-paths-Y9mYWkCI.js} +2600 -62
- package/dist/{review-telemetry-DBy3eVCo.js → review-telemetry-CmM6tCbj.js} +2 -2
- package/dist/server/desktop-host.js +35 -46
- package/dist/{server-discovery-Dbzn3w6Z.js → server-discovery-ZVi8Ay8J.js} +1 -1
- package/dist/sharing/index.d.ts +37 -49
- package/dist/sharing/index.js +1 -1
- package/dist/{src-CmdiBL20.js → src-DiHP11l3.js} +11 -1
- package/dist/{stored-document-migration-BxZWQQjv.js → stored-document-migration-u4L_1VZa.js} +15 -36
- package/dist/{tool-failure-DJmTieh5.js → tool-failure-CJHBCHMh.js} +2 -2
- package/instructions/scratchpad.md +1 -1
- package/package.json +4 -3
- package/src/ask/agents.ts +6 -0
- package/src/ask/thread-state.ts +17 -1
- package/src/ask/thread.ts +116 -22
- package/src/ask/threads.ts +8 -2
- package/src/error-telemetry.ts +13 -9
- package/src/fixtures/blocks/tutorial.json +3 -40
- package/src/review-api/README.md +10 -7
- package/src/review-api/ask-history.ts +37 -0
- package/src/review-api/authoring-tools.ts +8 -5
- package/src/review-api/blocks/database_lens.ts +5 -0
- package/src/review-api/blocks/tutorial.ts +2 -18
- package/src/review-api/comparison-coverage.ts +16 -9
- package/src/review-api/component-reference.ts +1 -1
- package/src/review-api/document.ts +24 -10
- package/src/review-api/http.ts +267 -189
- package/src/review-api/local-data.ts +126 -37
- package/src/review-api/store-schema.ts +25 -1
- package/src/review-api/store.ts +165 -33
- package/src/review-api/workspaces.ts +14 -14
- package/src/review-api/worktree-events.ts +46 -0
- package/src/review-app.ts +1 -1
- package/src/review-info.ts +1 -1
- package/src/review-stack.ts +2 -2
- package/src/server/review-server-core.ts +25 -1
- package/src/server/tutorial-service.ts +22 -46
- package/src/sharing/export.ts +1 -0
- package/src/sharing/import.ts +111 -26
- package/tutorial/document.json +27 -86
- package/tutorial/lenses.json +10 -0
- package/tutorial/runtime-manifest.json +2 -3
- package/tutorial/sample-service/src/database/drizzle-orm.d.ts +16 -0
- package/tutorial/sample-service/src/database/schema.ts +9 -10
- package/tutorial/sample-service/src/inventory/forklift-mood.ts +4 -0
- package/tutorial/sample-service/src/inventory/inventory-service.ts +18 -1
- package/tutorial/sample-service/src/inventory/warehouse-lore.ts +11 -0
- package/tutorial/sample-service/src/orders/order-service.ts +3 -2
- package/tutorial/sample-service/src/orders/order.ts +27 -5
- package/tutorial/sample-service/src/payments/fraud-vibes.ts +4 -0
- package/tutorial/sample-service/src/payments/payment-gateway.ts +19 -2
- package/tutorial/sample-service/src/payments/raccoon-ledger.ts +9 -0
- package/dist/agent-cli-BR2j-2n5.js +0 -2
- package/dist/input-error-OLgB_h31.js +0 -11
- package/dist/profile-BQGaX8aX.js +0 -2
- package/dist/review-home-paths-6zZH-1c9.js +0 -2560
- package/dist/tutorial-trace-DrBvkaeL.js +0 -43
- package/src/tutorial-conversation.ts +0 -18
- package/tutorial/software-map.json +0 -139
- package/tutorial/trace.json +0 -20
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-
|
|
4
|
-
import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-
|
|
1
|
+
import { C as ReviewInputError } from "./src-DiHP11l3.js";
|
|
2
|
+
import { a as toolResultText, s as ReviewApiError } from "./agent-client-P3J4y8Zn.js";
|
|
3
|
+
import { n as REVIEW_STATUS_TOOL, t as authoringTools } from "./authoring-tools-D8WXsm_g.js";
|
|
4
|
+
import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-CJHBCHMh.js";
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as jsonString, o as jsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
2
|
-
import "./review-home-paths-
|
|
2
|
+
import "./review-home-paths-Y9mYWkCI.js";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import { C as ReviewInputError } from "./src-DiHP11l3.js";
|
|
1
2
|
import { c as jsonString, m as isStringValue, n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
2
|
-
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-
|
|
3
|
-
import { _ as invalidateStructuralComparisons, a as inspectSnapshot, c as askAgentIds, d as askQuestionSchema, f as AgentSelectionSchema, i as commandSchema, l as askChoiceKinds, p as selectionMarkdown, r as SCRATCHPAD_ID, s as applyAskChange, u as askPicksSchema, v as diffrExecutable, y as diffrMissingError } from "./local-data-
|
|
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-DzcSt6pV.js";
|
|
4
|
+
import { _ as invalidateStructuralComparisons, a as inspectSnapshot, c as askAgentIds, d as askQuestionSchema, f as AgentSelectionSchema, i as commandSchema, l as askChoiceKinds, p as selectionMarkdown, r as SCRATCHPAD_ID, s as applyAskChange, u as askPicksSchema, v as diffrExecutable, y as diffrMissingError } from "./local-data-BlERtVm2.js";
|
|
4
5
|
import { d as REVIEW_CLIENT_REMOTE, i as parseReviewVerbRequest, r as parseReviewDesktopVerbResult, u as REVIEW_CLIENT_HEADER, v as reviewDiffrSummarizerInputSchema, y as decodeStructuralDiffEvent } from "./src-WCbabvdS.js";
|
|
5
|
-
import {
|
|
6
|
-
import { $ as unionIntervals, G as selectionKey, H as comparisonKey, I as fileLineRangeSchema, J as coverageSources, N as lensSelections, Q as subtractIntervals, T as selectionReferences, U as formatAnchor, V as anchorSelection, W as resolveDiffSelection, Y as emptyCoverage, Z as scopedCoverage, h as elements, k as UNCATEGORIZED_LENS_ID, u as anchorPins } from "./stored-document-migration-
|
|
7
|
-
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
6
|
+
import { B as TraceStorageDeniedError, D as listReviewTraceSessions, I as isTraceStorageConfigured, O as loadReviewAgentTrace, R as resolveTraceStorage, T as traceMachineEnabled, at as openUrlInBrowser, it as clearStoreAuth, ot as readStoreAuth, rt as DEFAULT_STORE_ORIGIN, st as runStoreLogin, z as selectTraceStorage } from "./review-home-paths-Y9mYWkCI.js";
|
|
7
|
+
import { $ as unionIntervals, G as selectionKey, H as comparisonKey, I as fileLineRangeSchema, J as coverageSources, N as lensSelections, Q as subtractIntervals, T as selectionReferences, U as formatAnchor, V as anchorSelection, W as resolveDiffSelection, Y as emptyCoverage, Z as scopedCoverage, h as elements, k as UNCATEGORIZED_LENS_ID, u as anchorPins } from "./stored-document-migration-u4L_1VZa.js";
|
|
8
8
|
import { A as devReviewHome, h as TraceConfigurationError, i as isS3MockMode } from "./s3-config-CAxhO9u_.js";
|
|
9
9
|
import { t as errorMessage } from "./error-message-OtiDonty.js";
|
|
10
|
-
import {
|
|
11
|
-
import { i as readReviewPackageVersion, n as readBuildCommit } from "./package-paths-B6-zxvIO.js";
|
|
10
|
+
import { i as readReviewPackageVersion, n as readBuildCommit } from "./package-paths-B9PDoAl6.js";
|
|
12
11
|
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-DA4K1qWY.js";
|
|
13
|
-
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-
|
|
12
|
+
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-D8WXsm_g.js";
|
|
14
13
|
import { t as fuzzyRank } from "./fuzzy-match-BoAmcyak.js";
|
|
15
14
|
import { createRequire } from "node:module";
|
|
16
15
|
import crypto$1, { createHash, randomUUID } from "node:crypto";
|
|
@@ -571,11 +570,9 @@ function deriveErrorTelemetryProperties(cause) {
|
|
|
571
570
|
if (name !== void 0 && name.length > 0 && name.length <= MAX_ERROR_NAME_LENGTH && ERROR_NAME_PATTERN.test(name)) derived.error_name = name;
|
|
572
571
|
const message = jsonString(cause.message);
|
|
573
572
|
if (message !== void 0 && message.length > 0) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (isReportableCleanedMessage(cleaned)) derived.message = cleaned;
|
|
578
|
-
}
|
|
573
|
+
const cleaned = cleanTelemetryText(message, NO_DELETED_DIRECTORIES);
|
|
574
|
+
derived.message_hash = hashErrorMessage(cleaned);
|
|
575
|
+
if (!SCHEMA_ERROR_NAMES.has(derived.error_name ?? "") && isReportableCleanedMessage(cleaned)) derived.message = cleaned;
|
|
579
576
|
}
|
|
580
577
|
const frames = packBundleFrames(cause.stack);
|
|
581
578
|
if (frames) derived.frames = frames;
|
|
@@ -901,6 +898,7 @@ const askAgents = {
|
|
|
901
898
|
executableEnv: "CLAUDE_CODE_EXECUTABLE"
|
|
902
899
|
},
|
|
903
900
|
signIn: "claude auth login",
|
|
901
|
+
steers: true,
|
|
904
902
|
readOnlyMode: "default",
|
|
905
903
|
sessionMeta: { claudeCode: { options: {
|
|
906
904
|
disallowedTools: [
|
|
@@ -1221,6 +1219,12 @@ const REFUSED_KINDS = /* @__PURE__ */ new Set([
|
|
|
1221
1219
|
]);
|
|
1222
1220
|
const piGreetingSchema = z.object({ piAcp: z.object({ startupInfo: z.string() }) });
|
|
1223
1221
|
const cursorTodosSchema = z.object({ todos: z.unknown() }).loose();
|
|
1222
|
+
/** The steering extension Claude's adapter takes: a follow-up joins the
|
|
1223
|
+
* turn under way rather than waiting for it to end. */
|
|
1224
|
+
const STEER_METHOD = "_session/steering";
|
|
1225
|
+
/** How an agent says, in its `initialize` response, that it takes steering. */
|
|
1226
|
+
const steeringSchema = z.object({ steering: z.object({ supported: z.literal(true) }) });
|
|
1227
|
+
const steerOutcomeSchema = z.object({ outcome: z.string() });
|
|
1224
1228
|
/** MCP servers the reviewer's own agent may bring whose tools only read,
|
|
1225
1229
|
* so their calls run without asking, like the agent reading files. */
|
|
1226
1230
|
const READ_ONLY_MCP_SERVERS = /* @__PURE__ */ new Set(["fff"]);
|
|
@@ -1282,6 +1286,10 @@ var AskThread = class {
|
|
|
1282
1286
|
/** What Pi greets a new session with, its version and skills, which is no
|
|
1283
1287
|
* part of the answer. */
|
|
1284
1288
|
greeting;
|
|
1289
|
+
/** The agent takes follow-ups into the turn under way. */
|
|
1290
|
+
steerable = false;
|
|
1291
|
+
/** Follow-ups waiting for the turn under way to end, oldest first. */
|
|
1292
|
+
queue = [];
|
|
1285
1293
|
constructor(launch, start, mcpServers = () => [], limits = askThreadLimits) {
|
|
1286
1294
|
this.launch = launch;
|
|
1287
1295
|
this.start = start;
|
|
@@ -1352,15 +1360,22 @@ var AskThread = class {
|
|
|
1352
1360
|
/** Why a question cannot be asked now, if it cannot. */
|
|
1353
1361
|
askRefusal() {
|
|
1354
1362
|
switch (this.state.status) {
|
|
1355
|
-
case "idle":
|
|
1363
|
+
case "idle":
|
|
1364
|
+
case "running":
|
|
1365
|
+
case "waiting": return;
|
|
1356
1366
|
case "failed": return `${this.state.agentName} stopped. Try again first.`;
|
|
1357
|
-
default: return
|
|
1367
|
+
default: return `${this.state.agentName} is still starting.`;
|
|
1358
1368
|
}
|
|
1359
1369
|
}
|
|
1360
|
-
/** Asks a follow-up, starting the agent again if it has stopped since.
|
|
1370
|
+
/** Asks a follow-up, starting the agent again if it has stopped since.
|
|
1371
|
+
* While the agent answers, it joins that turn or waits for it to end. */
|
|
1361
1372
|
async ask(question) {
|
|
1362
1373
|
const refusal = this.askRefusal();
|
|
1363
1374
|
if (refusal) throw new Error(refusal);
|
|
1375
|
+
if (this.state.status !== "idle") {
|
|
1376
|
+
await this.steer(question);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1364
1379
|
this.asking = {
|
|
1365
1380
|
id: this.addUser(question),
|
|
1366
1381
|
question
|
|
@@ -1402,6 +1417,49 @@ var AskThread = class {
|
|
|
1402
1417
|
});
|
|
1403
1418
|
});
|
|
1404
1419
|
}
|
|
1420
|
+
/** Gives the turn under way a follow-up when the agent takes one, else
|
|
1421
|
+
* keeps it for when the turn ends. */
|
|
1422
|
+
async steer(question) {
|
|
1423
|
+
const { connection, sessionId } = this;
|
|
1424
|
+
if (this.steerable && connection && sessionId) {
|
|
1425
|
+
const id = this.addUser(question);
|
|
1426
|
+
if (await connection.agent.request(STEER_METHOD, {
|
|
1427
|
+
sessionId,
|
|
1428
|
+
prompt: [{
|
|
1429
|
+
type: "text",
|
|
1430
|
+
text: question.text
|
|
1431
|
+
}, ...this.attachments(question)],
|
|
1432
|
+
_meta: { steering: { idleBehavior: "promptRequired" } }
|
|
1433
|
+
}).then((response) => steerOutcomeSchema.safeParse(response).data?.outcome).catch(() => void 0) === "injected") return;
|
|
1434
|
+
this.emit({
|
|
1435
|
+
type: "remove",
|
|
1436
|
+
ids: [id]
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
this.queue.push({
|
|
1440
|
+
id: randomUUID(),
|
|
1441
|
+
question
|
|
1442
|
+
});
|
|
1443
|
+
this.showQueue();
|
|
1444
|
+
this.askQueued();
|
|
1445
|
+
}
|
|
1446
|
+
/** Asks the oldest waiting follow-up, once no turn is under way. */
|
|
1447
|
+
askQueued() {
|
|
1448
|
+
if (this.closed || this.state.status !== "idle") return;
|
|
1449
|
+
const next = this.queue.shift();
|
|
1450
|
+
if (!next) return;
|
|
1451
|
+
this.showQueue();
|
|
1452
|
+
this.ask(next.question);
|
|
1453
|
+
}
|
|
1454
|
+
showQueue() {
|
|
1455
|
+
this.emit({
|
|
1456
|
+
type: "set",
|
|
1457
|
+
queued: this.queue.map(({ id, question }) => ({
|
|
1458
|
+
id,
|
|
1459
|
+
text: question.text
|
|
1460
|
+
}))
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1405
1463
|
decide(permissionId, optionId) {
|
|
1406
1464
|
const resolve = this.decisions.get(permissionId);
|
|
1407
1465
|
if (!resolve) return false;
|
|
@@ -1414,6 +1472,10 @@ var AskThread = class {
|
|
|
1414
1472
|
/** Stops the turn, or the start, in progress. A turn the agent does not
|
|
1415
1473
|
* end soon after is ended by stopping the agent. */
|
|
1416
1474
|
async cancel() {
|
|
1475
|
+
if (this.queue.length) {
|
|
1476
|
+
this.queue = [];
|
|
1477
|
+
this.showQueue();
|
|
1478
|
+
}
|
|
1417
1479
|
for (const resolve of this.decisions.values()) resolve({ outcome: { outcome: "cancelled" } });
|
|
1418
1480
|
const { status } = this.state;
|
|
1419
1481
|
if (status === "starting") {
|
|
@@ -1428,16 +1490,19 @@ var AskThread = class {
|
|
|
1428
1490
|
}, this.limits.stopGraceMs);
|
|
1429
1491
|
if (sessionId) await connection.agent.notify(methods.agent.session.cancel, { sessionId }).catch(() => {});
|
|
1430
1492
|
}
|
|
1431
|
-
close() {
|
|
1493
|
+
close(save = true) {
|
|
1432
1494
|
if (this.closed) return;
|
|
1433
1495
|
this.closed = true;
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1496
|
+
try {
|
|
1497
|
+
if (save && this.sessionId && this.state.status !== "starting" && this.state.status !== "failed") this.start.onSave?.(settled(this.state.entries));
|
|
1498
|
+
} finally {
|
|
1499
|
+
for (const resolve of this.decisions.values()) resolve({ outcome: { outcome: "cancelled" } });
|
|
1500
|
+
if (this.connection && this.sessionId) this.connection.agent.notify(methods.agent.session.cancel, { sessionId: this.sessionId }).catch(() => {});
|
|
1501
|
+
this.disconnect();
|
|
1502
|
+
this.listeners.clear();
|
|
1503
|
+
for (const closed of this.closers) closed();
|
|
1504
|
+
this.closers.clear();
|
|
1505
|
+
}
|
|
1441
1506
|
}
|
|
1442
1507
|
/** Runs when the thread closes, at once if it already has. */
|
|
1443
1508
|
onClose(closed) {
|
|
@@ -1546,6 +1611,7 @@ var AskThread = class {
|
|
|
1546
1611
|
version: "1"
|
|
1547
1612
|
}
|
|
1548
1613
|
});
|
|
1614
|
+
this.steerable = askAgents[this.start.agent].steers === true && steeringSchema.safeParse(initialized._meta).success;
|
|
1549
1615
|
this.emit({
|
|
1550
1616
|
type: "set",
|
|
1551
1617
|
accepts: { image: initialized.agentCapabilities?.promptCapabilities?.image === true }
|
|
@@ -1700,6 +1766,7 @@ var AskThread = class {
|
|
|
1700
1766
|
mcpServers: this.mcpServers,
|
|
1701
1767
|
_meta: this.sessionMeta()
|
|
1702
1768
|
});
|
|
1769
|
+
if (this.closed) throw new Error("This conversation has ended.");
|
|
1703
1770
|
this.sessionId = response.sessionId;
|
|
1704
1771
|
start.onSession?.(response.sessionId);
|
|
1705
1772
|
this.greeting = piGreetingSchema.safeParse(response._meta).data?.piAcp.startupInfo;
|
|
@@ -1763,6 +1830,7 @@ var AskThread = class {
|
|
|
1763
1830
|
} finally {
|
|
1764
1831
|
clearTimeout(this.stopTimer);
|
|
1765
1832
|
}
|
|
1833
|
+
if (this.closed) return;
|
|
1766
1834
|
if (withSelection) this.needsContext = false;
|
|
1767
1835
|
if (withPermission) this.permitted = false;
|
|
1768
1836
|
this.asking = void 0;
|
|
@@ -1786,6 +1854,7 @@ var AskThread = class {
|
|
|
1786
1854
|
});
|
|
1787
1855
|
this.start.onTurn?.();
|
|
1788
1856
|
this.start.onSave?.(this.state.entries);
|
|
1857
|
+
this.askQueued();
|
|
1789
1858
|
}
|
|
1790
1859
|
async requestPermission(request, signal) {
|
|
1791
1860
|
const id = request.toolCall.toolCallId;
|
|
@@ -2265,10 +2334,14 @@ var AskThreads = class {
|
|
|
2265
2334
|
}
|
|
2266
2335
|
return offer;
|
|
2267
2336
|
}
|
|
2268
|
-
close(id) {
|
|
2269
|
-
this.threads.get(id)
|
|
2337
|
+
close(id, save = true) {
|
|
2338
|
+
const thread = this.threads.get(id);
|
|
2270
2339
|
this.threads.delete(id);
|
|
2271
2340
|
this.unwatchedSince.delete(id);
|
|
2341
|
+
thread?.close(save);
|
|
2342
|
+
}
|
|
2343
|
+
closeDeletedReviews(isDeleted) {
|
|
2344
|
+
for (const [id, thread] of this.threads) if (isDeleted(thread.reviewId)) this.close(id, false);
|
|
2272
2345
|
}
|
|
2273
2346
|
closeAll() {
|
|
2274
2347
|
for (const id of [...this.threads.keys()]) this.close(id);
|
|
@@ -2478,7 +2551,7 @@ async function resolveReviewStackLayers(subject, reviews, runGitHubApi = default
|
|
|
2478
2551
|
const matches = reviews.filter((candidate) => {
|
|
2479
2552
|
return (candidate.origin?.pullRequestUrl?.replace(/\/pull\/\d+.*$/, "") ?? candidate.pins?.repositoryId) === repoKey && candidate.origin?.pullRequestNumber === pr.number;
|
|
2480
2553
|
});
|
|
2481
|
-
const review = matches.find((candidate) => candidate.reviewId === subject.reviewId) ?? matches.findLast((candidate) => !candidate.
|
|
2554
|
+
const review = matches.find((candidate) => candidate.reviewId === subject.reviewId) ?? matches.findLast((candidate) => !candidate.trashedAt) ?? matches.at(-1);
|
|
2482
2555
|
return {
|
|
2483
2556
|
branch: pr.head.ref,
|
|
2484
2557
|
pullRequestNumber: pr.number,
|
|
@@ -3228,6 +3301,16 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3228
3301
|
});
|
|
3229
3302
|
const sharedData = shared ? new SharedReviewData(shared) : void 0;
|
|
3230
3303
|
const isShared = (id) => id.startsWith("shared-");
|
|
3304
|
+
const assertWritable = (id) => {
|
|
3305
|
+
if (isShared(id)) shared?.assertWritable(id);
|
|
3306
|
+
else store.assertWritable(id);
|
|
3307
|
+
};
|
|
3308
|
+
const reviewAccess = (access) => async (context, next) => {
|
|
3309
|
+
const id = context.req.param("id");
|
|
3310
|
+
if (access !== "read") assertWritable(id);
|
|
3311
|
+
if (access === "edit" && isShared(id)) throw new ReviewInputError("Shared reviews are read-only.", 409);
|
|
3312
|
+
await next();
|
|
3313
|
+
};
|
|
3231
3314
|
const sharedCommandSchema = z.object({ operation: z.object({ reviewId: z.string().optional() }) });
|
|
3232
3315
|
const ensureScratchpad = async (id) => {
|
|
3233
3316
|
if (open && scratchpadEnabled() && (!id || id === SCRATCHPAD_ID)) await store.ensureScratchpad();
|
|
@@ -3242,7 +3325,6 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3242
3325
|
if (!id || !isShared(id)) return next();
|
|
3243
3326
|
const query = readQuerySchemas.get.parse({ version: context.req.query("version") });
|
|
3244
3327
|
readReview(id, query.version);
|
|
3245
|
-
if (context.req.method !== "GET" && !/\/(open|source|copy-context|environment)$/.test(context.req.path) && !/\/workspaces\/[^/]+\/retry$/.test(context.req.path)) throw new ReviewInputError("Shared reviews are read-only.", 409);
|
|
3246
3328
|
return next();
|
|
3247
3329
|
};
|
|
3248
3330
|
app.use("/:id", sharedGuard);
|
|
@@ -3292,7 +3374,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3292
3374
|
}
|
|
3293
3375
|
return context.json(await reviewProgress(store, data, snapshot, context.req.raw.signal, query.mode));
|
|
3294
3376
|
});
|
|
3295
|
-
app.post("/:id/progress", async (context) => {
|
|
3377
|
+
app.post("/:id/progress", reviewAccess("edit"), async (context) => {
|
|
3296
3378
|
if (!data) throw new ReviewInputError("Source data is unavailable.", 409);
|
|
3297
3379
|
const input = progressUpdateSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
3298
3380
|
const id = context.req.param("id");
|
|
@@ -3342,7 +3424,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3342
3424
|
"begin",
|
|
3343
3425
|
"update",
|
|
3344
3426
|
"end"
|
|
3345
|
-
]) app.post(`/:id/activity/${action}`, async (context) => {
|
|
3427
|
+
]) app.post(`/:id/activity/${action}`, reviewAccess("edit"), async (context) => {
|
|
3346
3428
|
const input = await readBoundedRequestJson(context.req.raw);
|
|
3347
3429
|
const id = context.req.param("id");
|
|
3348
3430
|
store.assertExists(id);
|
|
@@ -3375,7 +3457,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3375
3457
|
});
|
|
3376
3458
|
return marked;
|
|
3377
3459
|
};
|
|
3378
|
-
return watch
|
|
3460
|
+
return watch(() => subscriptions.map(({ reviewId, mode }, index) => {
|
|
3379
3461
|
if (!dirty.delete(index)) return null;
|
|
3380
3462
|
try {
|
|
3381
3463
|
return { value: reviewId === null ? catalog(mode) : {
|
|
@@ -3415,7 +3497,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3415
3497
|
}, () => {});
|
|
3416
3498
|
}
|
|
3417
3499
|
await ensureScratchpad();
|
|
3418
|
-
return watch
|
|
3500
|
+
return watch(() => catalog(coverageModeSchema.parse(context.req.query("mode"))), (notify) => {
|
|
3419
3501
|
const local = store.subscribeCatalog(notify);
|
|
3420
3502
|
const activity = store.activity.subscribeWorking(notify);
|
|
3421
3503
|
const imported = shared?.subscribe(notify);
|
|
@@ -3463,7 +3545,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3463
3545
|
};
|
|
3464
3546
|
}
|
|
3465
3547
|
};
|
|
3466
|
-
app.post("/:id/open", async (context) => {
|
|
3548
|
+
app.post("/:id/open", reviewAccess("active"), async (context) => {
|
|
3467
3549
|
const id = context.req.param("id");
|
|
3468
3550
|
if (isShared(id)) await shared?.assertReady(id);
|
|
3469
3551
|
return context.json({
|
|
@@ -3474,7 +3556,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3474
3556
|
app.get("/:id/watch", (context) => {
|
|
3475
3557
|
const id = context.req.param("id");
|
|
3476
3558
|
let document;
|
|
3477
|
-
return watch
|
|
3559
|
+
return watch(() => ({
|
|
3478
3560
|
...document ??= readReview(id),
|
|
3479
3561
|
activity: isShared(id) ? {
|
|
3480
3562
|
workingCount: 0,
|
|
@@ -3523,7 +3605,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3523
3605
|
}, result.status);
|
|
3524
3606
|
return context.json(result);
|
|
3525
3607
|
});
|
|
3526
|
-
app.post("/:id/navigator", async (context) => {
|
|
3608
|
+
app.post("/:id/navigator", reviewAccess("edit"), async (context) => {
|
|
3527
3609
|
if (remoteCaller(context)) throw new ReviewInputError("Source windows are not available for a review on another machine.", 409);
|
|
3528
3610
|
const input = readQuerySchemas.file.extend({
|
|
3529
3611
|
side: z.enum(["base", "head"]).default("head"),
|
|
@@ -3580,7 +3662,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3580
3662
|
...environment.issue && { issue: "The checkout for language features is not available on the remote machine." }
|
|
3581
3663
|
} : environment);
|
|
3582
3664
|
});
|
|
3583
|
-
app.post("/:id/environment", async (context) => {
|
|
3665
|
+
app.post("/:id/environment", reviewAccess("active"), async (context) => {
|
|
3584
3666
|
const input = z.strictObject({ retry: z.boolean().optional() }).parse(await readBoundedRequestJson(context.req.raw));
|
|
3585
3667
|
return context.json({ issues: await data.environmentIssues(readReview(context.req.param("id")), input.retry) });
|
|
3586
3668
|
});
|
|
@@ -3593,7 +3675,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3593
3675
|
readReview(context.req.param("id"));
|
|
3594
3676
|
return context.json(data.workspaces.list(context.req.param("id")));
|
|
3595
3677
|
});
|
|
3596
|
-
app.post("/:id/workspaces/:workspaceId/retry", async (context) => {
|
|
3678
|
+
app.post("/:id/workspaces/:workspaceId/retry", reviewAccess("active"), async (context) => {
|
|
3597
3679
|
return context.json(await data.workspaces.retry(context.req.param("id"), context.req.param("workspaceId")));
|
|
3598
3680
|
});
|
|
3599
3681
|
app.get("/:id/file", async (context) => {
|
|
@@ -3660,7 +3742,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3660
3742
|
return context.json(await data.commits((await data.resolveSource(readReview(context.req.param("id"), input.version))).pins));
|
|
3661
3743
|
});
|
|
3662
3744
|
}
|
|
3663
|
-
app.post("/:id/copy-context", async (context) => {
|
|
3745
|
+
app.post("/:id/copy-context", reviewAccess("read"), async (context) => {
|
|
3664
3746
|
const query = readQuerySchemas.get.pick({ version: true }).extend({ mode: coverageModeSchema }).parse(context.req.query());
|
|
3665
3747
|
const selection = AgentSelectionSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
3666
3748
|
return context.json({ text: await selectionContext(context.req.param("id"), selection, query.version) });
|
|
@@ -3753,7 +3835,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3753
3835
|
const files = fuzzyRank(query, await mentionableFiles(cwd), (file) => [file, file.slice(file.lastIndexOf("/") + 1)]);
|
|
3754
3836
|
return context.json({ paths: files.slice(0, MENTION_LIMIT) });
|
|
3755
3837
|
});
|
|
3756
|
-
app.post("/:id/ask", async (context) => {
|
|
3838
|
+
app.post("/:id/ask", reviewAccess("edit"), async (context) => {
|
|
3757
3839
|
const reviewId = context.req.param("id");
|
|
3758
3840
|
const { version } = readQuerySchemas.get.pick({ version: true }).parse(context.req.query());
|
|
3759
3841
|
const input = askStartSchema.parse(await readBoundedRequestJson(context.req.raw, ASK_REQUEST_MAX_BYTES));
|
|
@@ -3762,6 +3844,8 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3762
3844
|
const target = input.selection.target;
|
|
3763
3845
|
const id = crypto.randomUUID();
|
|
3764
3846
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3847
|
+
const initialContext = await askContext(input.agent, snapshot, checkout, input.selection, version);
|
|
3848
|
+
assertWritable(reviewId);
|
|
3765
3849
|
const thread = ask.threads.open({
|
|
3766
3850
|
id,
|
|
3767
3851
|
reviewId,
|
|
@@ -3793,7 +3877,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3793
3877
|
title: input.selection.title,
|
|
3794
3878
|
quote: target.kind === "text" ? target.quote : void 0
|
|
3795
3879
|
},
|
|
3796
|
-
context:
|
|
3880
|
+
context: initialContext,
|
|
3797
3881
|
question: input.question
|
|
3798
3882
|
});
|
|
3799
3883
|
return context.json({ threadId: thread.id });
|
|
@@ -3803,7 +3887,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3803
3887
|
readReview(reviewId);
|
|
3804
3888
|
return context.json({ threads: store.askHistory.list(reviewId) });
|
|
3805
3889
|
});
|
|
3806
|
-
app.post("/:id/ask/watch", async (context) => {
|
|
3890
|
+
app.post("/:id/ask/watch", reviewAccess("read"), async (context) => {
|
|
3807
3891
|
const reviewId = context.req.param("id");
|
|
3808
3892
|
readReview(reviewId);
|
|
3809
3893
|
const { threads } = askWatchSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
@@ -3833,7 +3917,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3833
3917
|
entries: record.entries ?? []
|
|
3834
3918
|
});
|
|
3835
3919
|
});
|
|
3836
|
-
app.post("/:id/ask/:threadId/open", async (context) => {
|
|
3920
|
+
app.post("/:id/ask/:threadId/open", reviewAccess("edit"), async (context) => {
|
|
3837
3921
|
const reviewId = context.req.param("id");
|
|
3838
3922
|
const threadId = context.req.param("threadId");
|
|
3839
3923
|
if (ask.threads.get(threadId)?.reviewId === reviewId) return context.json({ threadId });
|
|
@@ -3843,6 +3927,8 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3843
3927
|
const snapshot = readReview(reviewId, record.version);
|
|
3844
3928
|
const checkout = await data.agentCheckout(snapshot);
|
|
3845
3929
|
const target = record.selection.target;
|
|
3930
|
+
const initialContext = await askContext(record.agent, snapshot, checkout, record.selection, record.version);
|
|
3931
|
+
assertWritable(reviewId);
|
|
3846
3932
|
ask.threads.open({
|
|
3847
3933
|
id: record.id,
|
|
3848
3934
|
reviewId,
|
|
@@ -3857,7 +3943,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3857
3943
|
sessionId: record.sessionId,
|
|
3858
3944
|
entries: record.entries
|
|
3859
3945
|
},
|
|
3860
|
-
context:
|
|
3946
|
+
context: initialContext,
|
|
3861
3947
|
onSession: (sessionId) => store.askHistory.updateSession(record.id, sessionId),
|
|
3862
3948
|
onTurn: () => store.askHistory.touch(record.id),
|
|
3863
3949
|
onSave: (entries) => store.askHistory.saveEntries(record.id, entries),
|
|
@@ -3869,7 +3955,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3869
3955
|
});
|
|
3870
3956
|
return context.json({ threadId: record.id });
|
|
3871
3957
|
});
|
|
3872
|
-
app.post("/:id/ask/:threadId/prompt", async (context) => {
|
|
3958
|
+
app.post("/:id/ask/:threadId/prompt", reviewAccess("edit"), async (context) => {
|
|
3873
3959
|
const thread = readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3874
3960
|
const { question } = askFollowUpSchema.parse(await readBoundedRequestJson(context.req.raw, ASK_REQUEST_MAX_BYTES));
|
|
3875
3961
|
const refusal = thread.askRefusal();
|
|
@@ -3877,13 +3963,13 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3877
3963
|
thread.ask(question);
|
|
3878
3964
|
return context.json({ ok: true });
|
|
3879
3965
|
});
|
|
3880
|
-
app.post("/:id/ask/:threadId/permission", async (context) => {
|
|
3966
|
+
app.post("/:id/ask/:threadId/permission", reviewAccess("edit"), async (context) => {
|
|
3881
3967
|
const thread = readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3882
3968
|
const decision = askDecisionSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
3883
3969
|
if (!thread.decide(decision.permissionId, decision.optionId)) throw new ReviewInputError("This request was already answered.", 409);
|
|
3884
3970
|
return context.json({ ok: true });
|
|
3885
3971
|
});
|
|
3886
|
-
app.post("/:id/ask/:threadId/files", async (context) => {
|
|
3972
|
+
app.post("/:id/ask/:threadId/files", reviewAccess("edit"), async (context) => {
|
|
3887
3973
|
const thread = readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3888
3974
|
const { paths } = askFilesSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
3889
3975
|
const { cwd, entries } = thread.read();
|
|
@@ -3894,7 +3980,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3894
3980
|
file
|
|
3895
3981
|
})) });
|
|
3896
3982
|
});
|
|
3897
|
-
app.post("/:id/ask/:threadId/choice", async (context) => {
|
|
3983
|
+
app.post("/:id/ask/:threadId/choice", reviewAccess("edit"), async (context) => {
|
|
3898
3984
|
const thread = readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3899
3985
|
const { kind, value } = askChoiceSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
3900
3986
|
if (thread.read().status !== "idle") throw new ReviewInputError("Settings can change once the agent finishes answering.", 409);
|
|
@@ -3905,7 +3991,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3905
3991
|
}
|
|
3906
3992
|
return context.json({ ok: true });
|
|
3907
3993
|
});
|
|
3908
|
-
app.post("/:id/ask/:threadId/permissions", async (context) => {
|
|
3994
|
+
app.post("/:id/ask/:threadId/permissions", reviewAccess("edit"), async (context) => {
|
|
3909
3995
|
const thread = readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3910
3996
|
const { bypass } = askPermitSchema.parse(await readBoundedRequestJson(context.req.raw));
|
|
3911
3997
|
if (thread.read().status !== "idle") throw new ReviewInputError("Settings can change once the agent finishes answering.", 409);
|
|
@@ -3916,22 +4002,22 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
3916
4002
|
}
|
|
3917
4003
|
return context.json({ ok: true });
|
|
3918
4004
|
});
|
|
3919
|
-
app.post("/:id/ask/:threadId/retry", (context) => {
|
|
4005
|
+
app.post("/:id/ask/:threadId/retry", reviewAccess("edit"), (context) => {
|
|
3920
4006
|
const thread = readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3921
4007
|
if (thread.read().status !== "failed") throw new ReviewInputError("Only a conversation that failed can try again.", 409);
|
|
3922
4008
|
thread.retry();
|
|
3923
4009
|
return context.json({ ok: true });
|
|
3924
4010
|
});
|
|
3925
|
-
app.post("/:id/ask/:threadId/cancel", async (context) => {
|
|
4011
|
+
app.post("/:id/ask/:threadId/cancel", reviewAccess("read"), async (context) => {
|
|
3926
4012
|
await readThread(context.req.param("id"), context.req.param("threadId")).cancel();
|
|
3927
4013
|
return context.json({ ok: true });
|
|
3928
4014
|
});
|
|
3929
|
-
app.post("/:id/ask/:threadId/close", (context) => {
|
|
4015
|
+
app.post("/:id/ask/:threadId/close", reviewAccess("read"), (context) => {
|
|
3930
4016
|
readThread(context.req.param("id"), context.req.param("threadId"));
|
|
3931
|
-
ask.threads.close(context.req.param("threadId"));
|
|
4017
|
+
ask.threads.close(context.req.param("threadId"), !readReview(context.req.param("id")).trashedAt);
|
|
3932
4018
|
return context.json({ ok: true });
|
|
3933
4019
|
});
|
|
3934
|
-
app.delete("/:id/ask/:threadId", (context) => {
|
|
4020
|
+
app.delete("/:id/ask/:threadId", reviewAccess("edit"), (context) => {
|
|
3935
4021
|
const reviewId = context.req.param("id");
|
|
3936
4022
|
const threadId = context.req.param("threadId");
|
|
3937
4023
|
if (store.askHistory.get(threadId)?.reviewId !== reviewId) throw new ReviewInputError("This conversation was not found.", 404);
|
|
@@ -4003,6 +4089,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4003
4089
|
if (command.success && command.data.operation.reviewId?.startsWith("shared-")) {
|
|
4004
4090
|
const parsed = commandSchema.parse(input);
|
|
4005
4091
|
if (shared && parsed.operation.type === "delete") {
|
|
4092
|
+
if (!shared.get(parsed.operation.reviewId).snapshot.trashedAt) throw new ReviewInputError("Move this whiteboard to Trash before deleting it.", 409);
|
|
4006
4093
|
await shared.removeLocal(parsed.operation.reviewId);
|
|
4007
4094
|
return context.json({
|
|
4008
4095
|
reviewId: parsed.operation.reviewId,
|
|
@@ -4010,8 +4097,11 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4010
4097
|
deleted: true
|
|
4011
4098
|
});
|
|
4012
4099
|
}
|
|
4013
|
-
if (shared && parsed.operation.type === "attention") {
|
|
4014
|
-
await shared.
|
|
4100
|
+
if (shared && (parsed.operation.type === "attention" || parsed.operation.type === "lifecycle")) {
|
|
4101
|
+
if (parsed.operation.type === "lifecycle") await shared.setLifecycle(parsed.operation.reviewId, parsed.operation.state);
|
|
4102
|
+
else await shared.setAttention(parsed.operation.reviewId);
|
|
4103
|
+
const reviewId = parsed.operation.reviewId;
|
|
4104
|
+
if (shared.get(reviewId).snapshot.trashedAt) ask?.threads.closeDeletedReviews((id) => id === reviewId);
|
|
4015
4105
|
return context.json({
|
|
4016
4106
|
reviewId: parsed.operation.reviewId,
|
|
4017
4107
|
version: shared.get(parsed.operation.reviewId).snapshot.version,
|
|
@@ -4021,6 +4111,7 @@ function createReviewApi(store, data, open, shared, capabilities = () => ({
|
|
|
4021
4111
|
throw new ReviewInputError("Shared reviews are read-only.", 409);
|
|
4022
4112
|
}
|
|
4023
4113
|
const result = await store.execute(input);
|
|
4114
|
+
if (result.deleted || result.lifecycle && store.read(result.reviewId).trashedAt) ask?.threads.closeDeletedReviews((id) => id === result.reviewId);
|
|
4024
4115
|
if (input.operation.type === "edit" && data && !result.deleted) {
|
|
4025
4116
|
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);
|
|
4026
4117
|
return context.json({
|
|
@@ -4105,7 +4196,7 @@ async function locateRepositories(body, register) {
|
|
|
4105
4196
|
};
|
|
4106
4197
|
}
|
|
4107
4198
|
/** Send committed state, coalescing updates when the reader falls behind. */
|
|
4108
|
-
function watch
|
|
4199
|
+
function watch(read, subscribe, probe = read) {
|
|
4109
4200
|
probe();
|
|
4110
4201
|
let stop = () => {};
|
|
4111
4202
|
let dirty = true;
|
|
@@ -4209,6 +4300,11 @@ function createWhiteboardCore(input) {
|
|
|
4209
4300
|
});
|
|
4210
4301
|
const callbacks = relayReviewCallbacks(input.relay, input.softwareMapEnabled);
|
|
4211
4302
|
const askThreads = input.ask && new AskThreads(launchAskAgent, input.ask.tools);
|
|
4303
|
+
const closeDeletedThreads = () => askThreads?.closeDeletedReviews((id) => id.startsWith("shared-") ? !shared?.has(id) || Boolean(shared.get(id).snapshot.trashedAt) : !store.has(id) || Boolean(store.read(id).trashedAt));
|
|
4304
|
+
const stopLocal = askThreads && store.subscribe((result) => {
|
|
4305
|
+
if (result.deleted || result.lifecycle) closeDeletedThreads();
|
|
4306
|
+
});
|
|
4307
|
+
const stopShared = askThreads && shared?.subscribe(closeDeletedThreads);
|
|
4212
4308
|
const api = createReviewApi(store, data, callbacks.open, shared, callbacks.capabilities, input.scratchpad, () => traceMachineEnabled(), input.status, input.hooks, askThreads && {
|
|
4213
4309
|
threads: askThreads,
|
|
4214
4310
|
agents: () => detectAskAgents()
|
|
@@ -4217,7 +4313,12 @@ function createWhiteboardCore(input) {
|
|
|
4217
4313
|
return {
|
|
4218
4314
|
app,
|
|
4219
4315
|
api,
|
|
4220
|
-
close: () =>
|
|
4316
|
+
close: () => {
|
|
4317
|
+
stopLocal?.();
|
|
4318
|
+
stopShared?.();
|
|
4319
|
+
closeDeletedThreads();
|
|
4320
|
+
askThreads?.closeAll();
|
|
4321
|
+
}
|
|
4221
4322
|
};
|
|
4222
4323
|
}
|
|
4223
4324
|
/** The Desktop callbacks `createReviewApi` takes, answered over the relay. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { o as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-
|
|
1
|
+
import { o as initializeReviewStoreSchema, t as openLocalReviewStore } from "./local-data-BlERtVm2.js";
|
|
2
|
+
import "./review-home-paths-Y9mYWkCI.js";
|
|
2
3
|
import { O as withFileLock } from "./s3-config-CAxhO9u_.js";
|
|
3
|
-
import "./review-home-paths-6zZH-1c9.js";
|
|
4
4
|
import { access, mkdir } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { DatabaseSync } from "node:sqlite";
|