@dev.fast/whiteboard 0.3.4-preview.20261019.126 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-alias-B5-HYi1D.js → account-alias-CprZojXd.js} +8 -13
- package/dist/agent-cli-C6SPSII0.js +2 -0
- package/dist/{agent-cli-CohHf6Ua.js → agent-cli-CThN7Q59.js} +10 -15
- package/dist/{agent-client-Dtc-Kn_w.js → agent-client-BOOi7EUR.js} +3 -13
- package/dist/{authoring-tools-B7KU04f7.js → authoring-tools-BNwuMnz6.js} +27 -34
- package/dist/{background-server-C03RKxKS.js → background-server-CkvxA-6O.js} +5 -5
- package/dist/{background-server-CB9C4uoc.js → background-server-DLSDogkI.js} +1 -1
- package/dist/build-info.json +1 -1
- package/dist/{cli-CJaEb7zC.js → cli-R4yfa5DT.js} +1 -1
- package/dist/{cli-runner-DGDIqh3V.js → cli-runner-Cuc9FSzu.js} +26 -26
- package/dist/cli.js +3 -4
- package/dist/{client-lKVF_Elo.js → client-BN_FDxFr.js} +47 -253
- package/dist/{fuzzy-match-BmVJJm9I.js → fuzzy-match-BO9bK37N.js} +4 -6
- package/dist/{headless-host-DRtjf9WA.js → headless-host-CVBOQyRS.js} +1 -1
- package/dist/{headless-host-XhL1X4vX.js → headless-host-Cog6SpS9.js} +140 -513
- package/dist/{local-data-Cpl4ENbJ.js → local-data-D_9tvdsp.js} +323 -2419
- package/dist/{mcp-ovmiUE_x.js → mcp-D1hziANa.js} +9 -5
- package/dist/{package-paths-BWxO-UEL.js → package-paths-C--__jEv.js} +1 -1
- package/dist/profile-CYPmvBoA.js +2 -0
- package/dist/{profile-BadSkCkI.js → profile-DokFw782.js} +8 -20
- package/dist/{remote-attach-Dd-YobM6.js → remote-attach-CEJ-kvDY.js} +4 -4
- package/dist/{remote-extensions-CNXwqK0D.js → remote-extensions-Blcjr9Ze.js} +3 -3
- package/dist/{remote-extensions-VP-kfq0C.js → remote-extensions-GN8Gf_qG.js} +1 -1
- package/dist/{remote-language-server-BXk87KCr.js → remote-language-server-BTKBQ7x4.js} +5 -5
- package/dist/{remote-uninstall-DuOzsvaD.js → remote-uninstall-DW7Gdryh.js} +6 -6
- package/dist/{request-origin-B9qCPiVB.js → request-origin-Zcn_CjX4.js} +12 -45
- package/dist/{review-app-launcher-C7el2Uoy.js → review-app-launcher-CkwLclzR.js} +2 -2
- package/dist/{review-home-paths-D8AoCdd2.js → review-home-paths-j2BRAaNv.js} +17 -19
- package/dist/{review-telemetry-BsoRMC2w.js → review-telemetry-BLNwYnLV.js} +5 -5
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +1 -1
- package/dist/{s3-CsG2EWED.js → s3-DtJFWC0G.js} +1 -1
- package/dist/{s3-config-CjcLyFV5.js → s3-config-BTO12tCC.js} +2 -4
- package/dist/{s3-config-zz4GWh2T.js → s3-config-CSy3fx8H.js} +1 -1
- package/dist/server/desktop-host.js +76 -29
- package/dist/{server-discovery-CKjw3-6h.js → server-discovery-Bey8IqXH.js} +3 -3
- package/dist/sharing/index.d.ts +70 -484
- package/dist/sharing/index.js +1 -1
- package/dist/{src-WPaQn75m.js → src-BMPuxWv4.js} +73 -208
- package/dist/{src-BiWNbcF_.js → src-DsM3jO8G.js} +12 -89
- package/dist/{stored-document-migration-dFbewRTG.js → stored-document-migration-CChxtFAy.js} +269 -270
- package/dist/{thread-state-Do5LX10I.js → thread-state-5pYr6_zM.js} +2 -7
- package/dist/{tool-failure-DvI-gys2.js → tool-failure-C9MpbXJJ.js} +3 -10
- package/instructions/authoring.md +2 -3
- package/instructions/scratchpad.md +23 -0
- package/package.json +4 -4
- package/src/ask/agents.ts +1 -13
- package/src/ask/checkout-files.ts +5 -2
- package/src/ask/thread-state.ts +0 -4
- package/src/ask/thread.ts +0 -16
- package/src/ask/threads.ts +3 -24
- package/src/cli-install.ts +2 -13
- package/src/cli-runner.ts +1 -4
- package/src/cli.ts +1 -4
- package/src/diff-selection-migration.ts +34 -48
- package/src/lens-selection.ts +2 -38
- package/src/review-api/README.md +13 -52
- package/src/review-api/activity.ts +4 -1
- package/src/review-api/agent-cli.ts +1 -9
- package/src/review-api/anchor-quotes.ts +7 -14
- package/src/review-api/ask-history.ts +51 -137
- package/src/review-api/authoring-tools.ts +30 -45
- package/src/review-api/blocks/definition.ts +1 -1
- package/src/review-api/blocks/markdown.ts +2 -2
- package/src/review-api/document-headings.ts +27 -58
- package/src/review-api/document-text.ts +2 -31
- package/src/review-api/document.ts +34 -281
- package/src/review-api/github-provider.ts +13 -51
- package/src/review-api/http.ts +123 -702
- package/src/review-api/instructions.ts +19 -0
- package/src/review-api/local-data.ts +46 -310
- package/src/review-api/mcp.ts +11 -3
- package/src/review-api/onboarding.ts +1 -1
- package/src/review-api/profile.ts +6 -37
- package/src/review-api/public-tools.ts +2 -24
- package/src/review-api/pull-request.ts +49 -82
- package/src/review-api/request-origin.ts +0 -10
- package/src/review-api/review-progress.ts +27 -61
- package/src/review-api/store-schema.ts +29 -38
- package/src/review-api/store.ts +478 -2142
- package/src/review-api/worktree-events.ts +5 -3
- package/src/review-api/worktree-source.ts +7 -3
- package/src/review-api/worktree-structural.integration.ts +0 -72
- package/src/review-preferences.ts +88 -0
- package/src/review-stack.ts +23 -15
- package/src/runtime.ts +30 -4
- package/src/server/desktop-host.ts +1 -6
- package/src/server/desktop-server.ts +31 -1
- package/src/server/headless-host.ts +2 -0
- package/src/server/hono-http.ts +1 -2
- package/src/server/legacy-tutorial-cleanup.ts +3 -2
- package/src/server/review-gateway-list.ts +5 -3
- package/src/server/review-gateway-memory.ts +1 -1
- package/src/server/review-lifecycle-telemetry.ts +2 -2
- package/src/server/review-server-core.ts +5 -1
- package/src/server/structural-comparisons.ts +0 -47
- package/src/sharing/export.ts +7 -36
- package/src/sharing/host.ts +14 -34
- package/src/sharing/import.ts +24 -156
- package/src/sharing/repository.ts +20 -128
- package/src/startup-trace.ts +3 -3
- package/src/stored-document-migration.ts +19 -110
- package/src/ui-telemetry-events.ts +11 -33
- package/dist/agent-cli-_YyMIMRY.js +0 -2
- package/dist/lens-selection-CyJapcsl.js +0 -214
- package/dist/profile-DJvkBJNQ.js +0 -2
- package/dist/runtime-Bj2tD33R.js +0 -27
- package/src/heading-anchors.ts +0 -42
- package/src/review-api/board-order.ts +0 -144
- package/src/review-api/checkout-groups.ts +0 -560
- package/src/review-api/foreign-references.ts +0 -328
- package/src/review-api/link-preview.ts +0 -113
- package/src/review-api/scratchpad-plan-upgrade.ts +0 -250
- package/src/review-api/store-migrations.ts +0 -357
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as ReviewSelectedDiffSchema, C as ReviewApiSelectionSourceSchema } from "./src-DsM3jO8G.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
/** A semantic selection, independent of comment/thread creation. */
|
|
4
4
|
const AgentSelectionSchema = z.strictObject({
|
|
@@ -374,12 +374,7 @@ const askHistoryEntrySchema = z.object({
|
|
|
374
374
|
/** The commit the agent read. */
|
|
375
375
|
head: z.string(),
|
|
376
376
|
createdAt: z.string(),
|
|
377
|
-
updatedAt: z.string()
|
|
378
|
-
/** The board the conversation started on, and the block if it was about one. */
|
|
379
|
-
anchor: z.object({
|
|
380
|
-
board: z.string(),
|
|
381
|
-
block: z.string().optional()
|
|
382
|
-
}).optional()
|
|
377
|
+
updatedAt: z.string()
|
|
383
378
|
});
|
|
384
379
|
//#endregion
|
|
385
380
|
export { askHistoryEntrySchema as a, askPicksSchema as c, selectionMarkdown as d, askEntrySchema as i, askQuestionSchema as l, askAgentIds as n, askOfferSchema as o, askChoiceKinds as r, askPermissionsSchema as s, applyAskChange as t, AgentSelectionSchema as u };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { m as isStringValue, n as isJsonObject } from "./src-X9phtB2j.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-Dtc-Kn_w.js";
|
|
2
|
+
import { n as callAuthoringTool, s as ReviewApiError, t as ToolText } from "./agent-client-BOOi7EUR.js";
|
|
4
3
|
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
5
4
|
//#region src/review-api/public-tools.ts
|
|
6
5
|
function publicDescription(text) {
|
|
@@ -39,7 +38,7 @@ function publicResult(value) {
|
|
|
39
38
|
if (Array.isArray(value)) return value.map(publicResult);
|
|
40
39
|
if (!isJsonObject(value)) return value;
|
|
41
40
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => {
|
|
42
|
-
return [key === "reviewId" ? "sessionId" : key === "review" ? "session" : key === "reviews" ? "sessions" : key, key === "review" || key === "reviews" ? publicResult(item) :
|
|
41
|
+
return [key === "reviewId" ? "sessionId" : key === "review" ? "session" : key === "reviews" ? "sessions" : key, key === "review" || key === "reviews" ? publicResult(item) : item];
|
|
43
42
|
}));
|
|
44
43
|
}
|
|
45
44
|
async function callPublicTool(client, tool, input, signal) {
|
|
@@ -48,13 +47,7 @@ async function callPublicTool(client, tool, input, signal) {
|
|
|
48
47
|
const fields = { ...rest };
|
|
49
48
|
if (sessionId !== void 0) fields.reviewId = sessionId;
|
|
50
49
|
const result = await callAuthoringTool(client, tool, fields, signal);
|
|
51
|
-
return result instanceof ToolText ? result :
|
|
52
|
-
}
|
|
53
|
-
/** Agents never see repository ids. */
|
|
54
|
-
function withoutRepositoryIds(value) {
|
|
55
|
-
if (Array.isArray(value)) return value.map(withoutRepositoryIds);
|
|
56
|
-
if (!isJsonObject(value)) return value;
|
|
57
|
-
return Object.fromEntries(Object.entries(value).filter(([key]) => key !== "repositoryId").map(([key, item]) => [key, withoutRepositoryIds(item)]));
|
|
50
|
+
return result instanceof ToolText ? result : publicResult(result);
|
|
58
51
|
}
|
|
59
52
|
//#endregion
|
|
60
53
|
//#region src/review-api/recovery.ts
|
|
@@ -3,7 +3,7 @@ you are writing an interactive rfc-style whiteboard, for consumption by a staff
|
|
|
3
3
|
**flow**
|
|
4
4
|
follow these first five steps in order, with no tool calls beyond what they need: loading the tools they use (`session_create`, `session_activity_begin`, `session_edit`, `session_get`, and later `session_activity_update` and `session_activity_end`) and, for a PR, reading its description (`gh pr view`).
|
|
5
5
|
|
|
6
|
-
- create the whiteboard
|
|
6
|
+
- create the whiteboard, pinned to the commits/pr the user describes, naming the checkout by its `repositoryPath`. if they name none, use a `worktree` target with no `base`: it reviews the checkout, uncommitted work included, against the default branch. for uncommitted work only, pass `base: "HEAD"`. untracked files are left out: run `git add -N <file>` on new files you want reviewed
|
|
7
7
|
- trigger a subagent with this exact instruction: "Call `session_get_instructions({topic:\"file-lenses\"})` and follow it for whiteboard <sessionId>, whose change is `<base>..<head>` in `<repositoryPath>`."
|
|
8
8
|
- call `session_activity_begin` and pass its `activityId` on every `session_edit`
|
|
9
9
|
- read the diff with git (or jj) in `repositoryPath`, at the commits `session_create` returned: `git diff <base> <head>`, or for a worktree target `git diff <base>` plus `git status` for untracked files. if it shows no changes, the target is wrong: fix it with `session_set_target` before writing
|
|
@@ -37,5 +37,4 @@ follow these first five steps in order, with no tool calls beyond what they need
|
|
|
37
37
|
- use `session_activity_update` to provide regular status updates on your area of focus.
|
|
38
38
|
- keep whiteboards short and sweet when possible (esp. for small changes.) feel free to omit sections.
|
|
39
39
|
- when something (a phrase in the prose, diagram node, etc.) describes actual code in the codebase, always default to attaching/hyperlink code.
|
|
40
|
-
- Link repository code as `[label](
|
|
41
|
-
- Link another whiteboard inline as `[label](whiteboard:board/<sessionId>)`, or one of its blocks as `[label](whiteboard:board/<sessionId>#<blockId>)`, wherever the prose refers to it. The board and block must exist.
|
|
40
|
+
- Link repository code as `[label](review-source:head/src/file.ts#L10-L24)`; use `base` for old code. Use repository-relative paths and verified line numbers.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Scratchpad
|
|
2
|
+
|
|
3
|
+
The scratchpad is one document in Whiteboard that is not a session for any change. It has no target, base, head or lifecycle. You draw on it the way you would sketch an explanation on a whiteboard: a short paragraph, a sequence or flow diagram, a call tree, a code peek, and the smallest view that makes the point. The user reads it in the app and copies blocks back to you.
|
|
4
|
+
|
|
5
|
+
Its id is fixed: `scratchpad`. While it is turned on in Whiteboard Settings it always exists; never create, rename, trash, delete or share it.
|
|
6
|
+
|
|
7
|
+
## When to draw here
|
|
8
|
+
|
|
9
|
+
- The user asks to be shown how something works, wants a picture of a flow, a call path, a data shape or a layout, or is thinking out loud with you before there is a concrete plan.
|
|
10
|
+
- The explanation spans more than one file or more than one repository, or would be an ASCII diagram in chat.
|
|
11
|
+
|
|
12
|
+
Answer in chat when one sentence or one code line does it. Do not use the scratchpad to review a change; author a session instead (`session_get_instructions({})`).
|
|
13
|
+
|
|
14
|
+
## How
|
|
15
|
+
|
|
16
|
+
1. `session_capabilities({})`. Draw only when `desktopAvailable` and `scratchpadEnabled` are true. Otherwise answer in chat; if the pad is off, say once that it can be turned on in Whiteboard Settings.
|
|
17
|
+
2. Every source reference needs pins. For each repository you will quote, `session_register_repository({path})` for its `repositoryId`, and `git rev-parse HEAD` (or the commit the user is looking at) for a commit id. Put `pins: {repositoryId, head}` on each block that quotes code (`code_peek`, `sequence`, `flow_diagram`, `call_stack_diff`, `database_lens` and `markdown` with `review-source:` links); a step, frame, attachment or operation that quotes other commits than its block takes its own `pins`. Add `base` only when an anchor reads the base side. A reference without pins is rejected, since the scratchpad has none to lend.
|
|
18
|
+
3. Read what you cite at those commits, for example `git show <head>:<path>` in the repository, so line numbers match the pins.
|
|
19
|
+
4. Call `session_activity_begin` and pass the `activityId` it returns on each `session_edit` as you append blocks. Omitted placement puts the block at the top of the pad, and the response's `targetId` names it. A thought that spans several blocks reads top-down only if you insert it bottom-up (last block first) or give each following block `afterId` of the block you just inserted. Draw a diagram whole in one insert; fix one you drew earlier by patching its nodes, edges or steps by ID, and `replace` a block only when the whole picture was wrong. Each block's fields are in the Components section below; read `session_get_instructions({topic:"authoring"})` for choosing components. End the activity when finished.
|
|
20
|
+
5. After your first insert in a session, `session_open({sessionId: "scratchpad"})` once so the pad is showing. Do not call it again for later edits; they appear live.
|
|
21
|
+
6. In chat, say in one line what you drew. Do not repeat the diagram there.
|
|
22
|
+
|
|
23
|
+
Keep it a log, not a document: the newest thought sits at the top, above older ones. Do not build sections, outlines or status markers unless the user asks for structure. When the user asks to clear the pad, `replace` or `remove` its blocks; history keeps what was there.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev.fast/whiteboard",
|
|
3
|
-
"version": "0.3.4
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -75,10 +75,10 @@
|
|
|
75
75
|
"typescript": "6.0.3",
|
|
76
76
|
"vitest": "4.1.11",
|
|
77
77
|
"@dev.fast/json": "0.0.0",
|
|
78
|
-
"@dev.fast/review-protocol": "0.0.0",
|
|
79
78
|
"@dev.fast/review-share-protocol": "0.1.2",
|
|
80
79
|
"@dev.fast/trace-core": "0.0.0",
|
|
81
|
-
"@dev.fast/trace-protocol": "0.5.0"
|
|
80
|
+
"@dev.fast/trace-protocol": "0.5.0",
|
|
81
|
+
"@dev.fast/review-protocol": "0.0.0"
|
|
82
82
|
},
|
|
83
83
|
"engines": {
|
|
84
84
|
"node": ">=24 <25"
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"repo": "devdotfast/review",
|
|
89
89
|
"docs": "packages/review/instructions/authoring.md"
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "f33bd6620742fe65ccc1d5fef416fbe8254dcf79",
|
|
92
92
|
"scripts": {
|
|
93
93
|
"prebuild": "pnpm run build:workspace-deps",
|
|
94
94
|
"build": "node --max-old-space-size=8192 node_modules/tsdown/dist/run.mjs",
|
package/src/ask/agents.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
type AskPermissions,
|
|
20
20
|
askAgentIds,
|
|
21
21
|
} from "@review/ask/thread-state.js";
|
|
22
|
-
import { ASK_THREAD_ENV } from "@review/review-api/request-origin.js";
|
|
23
22
|
|
|
24
23
|
/** How Whiteboard runs an agent that speaks ACP on stdio. */
|
|
25
24
|
type AskAgentLaunch =
|
|
@@ -248,8 +247,6 @@ export type AskAgentLauncher = (
|
|
|
248
247
|
/** The MCP servers its sessions are given. */
|
|
249
248
|
mcpServers?: McpServer[];
|
|
250
249
|
env?: NodeJS.ProcessEnv;
|
|
251
|
-
/** The Ask thread this agent serves. */
|
|
252
|
-
threadId?: string;
|
|
253
250
|
},
|
|
254
251
|
) => Promise<AskAgentProcess>;
|
|
255
252
|
|
|
@@ -360,12 +357,7 @@ const STDERR_LIMIT = 8_000;
|
|
|
360
357
|
export const launchAskAgent: AskAgentLauncher = async (
|
|
361
358
|
agent,
|
|
362
359
|
cwd,
|
|
363
|
-
{
|
|
364
|
-
permissions = "read-only",
|
|
365
|
-
mcpServers = [],
|
|
366
|
-
env: base = process.env,
|
|
367
|
-
threadId,
|
|
368
|
-
} = {},
|
|
360
|
+
{ permissions = "read-only", mcpServers = [], env: base = process.env } = {},
|
|
369
361
|
) => {
|
|
370
362
|
const spec = askAgents[agent];
|
|
371
363
|
const executable = await findAgent(spec, base);
|
|
@@ -377,10 +369,6 @@ export const launchAskAgent: AskAgentLauncher = async (
|
|
|
377
369
|
...askAgentSettings(agent, permissions).env,
|
|
378
370
|
};
|
|
379
371
|
|
|
380
|
-
// Lets `whiteboard api` calls from the agent's shell name the thread.
|
|
381
|
-
// The MCP server gets it in its own env, since MCP clients may not pass theirs on.
|
|
382
|
-
if (threadId) env[ASK_THREAD_ENV] = threadId;
|
|
383
|
-
|
|
384
372
|
// A server started from inside a Claude Code session must not look nested.
|
|
385
373
|
delete env.CLAUDECODE;
|
|
386
374
|
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
2
5
|
|
|
3
6
|
/** The checkout's files relative to its root: tracked ones, and new ones git
|
|
4
7
|
* does not ignore. None where git cannot list them. */
|
|
5
8
|
export async function checkoutFiles(root: string): Promise<string[]> {
|
|
6
9
|
try {
|
|
7
|
-
const { stdout } = await
|
|
10
|
+
const { stdout } = await execFileAsync(
|
|
8
11
|
"git",
|
|
9
12
|
[
|
|
10
13
|
"-C",
|
package/src/ask/thread-state.ts
CHANGED
|
@@ -375,10 +375,6 @@ export const askHistoryEntrySchema = z.object({
|
|
|
375
375
|
head: z.string(),
|
|
376
376
|
createdAt: z.string(),
|
|
377
377
|
updatedAt: z.string(),
|
|
378
|
-
/** The board the conversation started on, and the block if it was about one. */
|
|
379
|
-
anchor: z
|
|
380
|
-
.object({ board: z.string(), block: z.string().optional() })
|
|
381
|
-
.optional(),
|
|
382
378
|
});
|
|
383
379
|
|
|
384
380
|
export type AskHistoryEntry = z.infer<typeof askHistoryEntrySchema>;
|
package/src/ask/thread.ts
CHANGED
|
@@ -176,7 +176,6 @@ export class AskThread {
|
|
|
176
176
|
/** The number of changes so far; a snapshot carries the one it includes. */
|
|
177
177
|
private seq = 0;
|
|
178
178
|
private readonly listeners = new Set<(update: AskUpdate) => void>();
|
|
179
|
-
private readonly statusListeners = new Set<() => void>();
|
|
180
179
|
private readonly closers = new Set<() => void>();
|
|
181
180
|
private readonly decisions = new Map<
|
|
182
181
|
string,
|
|
@@ -289,10 +288,6 @@ export class AskThread {
|
|
|
289
288
|
return { seq: this.seq, snapshot: this.state };
|
|
290
289
|
}
|
|
291
290
|
|
|
292
|
-
get status() {
|
|
293
|
-
return this.state.status;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
291
|
/** Every change after the current `seq`, in order. */
|
|
297
292
|
subscribe(listener: (update: AskUpdate) => void) {
|
|
298
293
|
this.listeners.add(listener);
|
|
@@ -300,13 +295,6 @@ export class AskThread {
|
|
|
300
295
|
return () => this.listeners.delete(listener);
|
|
301
296
|
}
|
|
302
297
|
|
|
303
|
-
/** Calls back when the status changes, without following the thread. */
|
|
304
|
-
onStatusChange(listener: () => void) {
|
|
305
|
-
this.statusListeners.add(listener);
|
|
306
|
-
|
|
307
|
-
return () => this.statusListeners.delete(listener);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
298
|
/** Whether anything follows the thread, as an open panel does. */
|
|
311
299
|
watched() {
|
|
312
300
|
return this.listeners.size > 0;
|
|
@@ -642,7 +630,6 @@ export class AskThread {
|
|
|
642
630
|
const launched = await this.launch(this.start.agent, this.start.cwd, {
|
|
643
631
|
permissions: this.state.permissions,
|
|
644
632
|
mcpServers: this.mcpServers,
|
|
645
|
-
threadId: this.id,
|
|
646
633
|
});
|
|
647
634
|
|
|
648
635
|
// Closed, stopped or given up on while the process started.
|
|
@@ -1546,8 +1533,5 @@ export class AskThread {
|
|
|
1546
1533
|
const update = { seq: this.seq, change };
|
|
1547
1534
|
|
|
1548
1535
|
for (const listener of this.listeners) listener(update);
|
|
1549
|
-
|
|
1550
|
-
if (change.type === "set" && change.status)
|
|
1551
|
-
for (const listener of this.statusListeners) listener();
|
|
1552
1536
|
}
|
|
1553
1537
|
}
|
package/src/ask/threads.ts
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
type AskThreadStart,
|
|
19
19
|
askThreadLimits,
|
|
20
20
|
} from "@review/ask/thread.js";
|
|
21
|
-
import { ASK_THREAD_ENV } from "@review/review-api/request-origin.js";
|
|
22
21
|
|
|
23
22
|
/** How long an agent may take to say what it offers. */
|
|
24
23
|
const OFFER_TIMEOUT_MS = 30_000;
|
|
@@ -164,7 +163,7 @@ export function cliAskTools(
|
|
|
164
163
|
];
|
|
165
164
|
|
|
166
165
|
return {
|
|
167
|
-
mcpServers: (
|
|
166
|
+
mcpServers: () => {
|
|
168
167
|
const cli = cliPath();
|
|
169
168
|
|
|
170
169
|
return cli
|
|
@@ -173,9 +172,7 @@ export function cliAskTools(
|
|
|
173
172
|
name: "whiteboard",
|
|
174
173
|
command: process.execPath,
|
|
175
174
|
args: [cli, "mcp"],
|
|
176
|
-
|
|
177
|
-
// gets the thread it serves in its own.
|
|
178
|
-
env: [...env(), { name: ASK_THREAD_ENV, value: threadId }],
|
|
175
|
+
env: env(),
|
|
179
176
|
},
|
|
180
177
|
]
|
|
181
178
|
: [];
|
|
@@ -221,8 +218,6 @@ export class AskThreads {
|
|
|
221
218
|
|
|
222
219
|
private readonly mcpServers: AskMcpServers;
|
|
223
220
|
|
|
224
|
-
private readonly statusListeners = new Set<() => void>();
|
|
225
|
-
|
|
226
221
|
constructor(
|
|
227
222
|
private readonly launch: AskAgentLauncher,
|
|
228
223
|
private readonly tools: AskTools = {},
|
|
@@ -254,8 +249,6 @@ export class AskThreads {
|
|
|
254
249
|
);
|
|
255
250
|
|
|
256
251
|
this.threads.set(thread.id, thread);
|
|
257
|
-
thread.onStatusChange(() => this.statusChanged());
|
|
258
|
-
this.statusChanged();
|
|
259
252
|
void thread.open();
|
|
260
253
|
this.sweeper ??= setInterval(() => this.sweep(), SWEEP_MS);
|
|
261
254
|
this.sweeper.unref?.();
|
|
@@ -298,19 +291,6 @@ export class AskThreads {
|
|
|
298
291
|
}
|
|
299
292
|
}
|
|
300
293
|
|
|
301
|
-
/** Calls back when a thread opens, closes or changes its status. */
|
|
302
|
-
subscribeStatus(listener: () => void) {
|
|
303
|
-
this.statusListeners.add(listener);
|
|
304
|
-
|
|
305
|
-
return () => {
|
|
306
|
-
this.statusListeners.delete(listener);
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private statusChanged() {
|
|
311
|
-
for (const listener of this.statusListeners) listener();
|
|
312
|
-
}
|
|
313
|
-
|
|
314
294
|
get(id: string) {
|
|
315
295
|
return this.threads.get(id);
|
|
316
296
|
}
|
|
@@ -333,10 +313,9 @@ export class AskThreads {
|
|
|
333
313
|
|
|
334
314
|
close(id: string, save = true) {
|
|
335
315
|
const thread = this.threads.get(id);
|
|
316
|
+
this.threads.delete(id);
|
|
336
317
|
this.unwatchedSince.delete(id);
|
|
337
318
|
thread?.close(save);
|
|
338
|
-
|
|
339
|
-
if (this.threads.delete(id)) this.statusChanged();
|
|
340
319
|
}
|
|
341
320
|
|
|
342
321
|
closeDeletedReviews(isDeleted: (reviewId: string) => boolean) {
|
package/src/cli-install.ts
CHANGED
|
@@ -699,16 +699,11 @@ export async function writePathShim(
|
|
|
699
699
|
cliPath: string,
|
|
700
700
|
runtimePath: string | undefined,
|
|
701
701
|
devHome: string,
|
|
702
|
-
previewHome = devHome,
|
|
703
702
|
): Promise<void> {
|
|
704
703
|
if (process.platform === "win32") {
|
|
705
704
|
await writeFileAtomicAsync(
|
|
706
705
|
shimPath,
|
|
707
|
-
windowsCliShim(
|
|
708
|
-
cliPath,
|
|
709
|
-
runtimePath ?? process.execPath,
|
|
710
|
-
devHome === previewHome ? devHome : undefined,
|
|
711
|
-
),
|
|
706
|
+
windowsCliShim(cliPath, runtimePath ?? process.execPath, devHome),
|
|
712
707
|
{ replaceSymlink: true },
|
|
713
708
|
);
|
|
714
709
|
|
|
@@ -721,8 +716,6 @@ export async function writePathShim(
|
|
|
721
716
|
FALLBACK_CLI=${shSingleQuote(cliPath)}
|
|
722
717
|
FALLBACK_RUNTIME=${shSingleQuote(runtimePath ?? "")}
|
|
723
718
|
DEFAULT_HOME=${shSingleQuote(devHome)}
|
|
724
|
-
channel="\${DEV_FAST_REVIEW_CHANNEL:-\${DEV_REVIEW_INSTANCE:-}}"
|
|
725
|
-
[ "$channel" = preview ] && DEFAULT_HOME=${shSingleQuote(previewHome)}
|
|
726
719
|
export DEV_REVIEW_HOME="\${DEV_REVIEW_HOME:-$DEFAULT_HOME}"
|
|
727
720
|
DESKTOP="$DEV_REVIEW_HOME/review-desktop"
|
|
728
721
|
|
|
@@ -843,11 +836,7 @@ export async function installReviewCommand(input: {
|
|
|
843
836
|
shimPath,
|
|
844
837
|
input.cliPath,
|
|
845
838
|
input.cliRuntimePath,
|
|
846
|
-
devReviewHome(
|
|
847
|
-
devReviewHome(
|
|
848
|
-
{ DEV_REVIEW_HOME: env.DEV_REVIEW_HOME, DEV_REVIEW_INSTANCE: "preview" },
|
|
849
|
-
homeDir,
|
|
850
|
-
),
|
|
839
|
+
devReviewHome(env, homeDir),
|
|
851
840
|
);
|
|
852
841
|
|
|
853
842
|
if (await traceMachineEnabled({ homeDir, env })) {
|
package/src/cli-runner.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { Writable } from "node:stream";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
-
import { boardTitle } from "@dev.fast/review-protocol";
|
|
9
8
|
import {
|
|
10
9
|
REVIEW_CLI_INSTALL_TARGET_LABELS,
|
|
11
10
|
REVIEW_REMOTE_AGENT_IDS,
|
|
@@ -837,9 +836,7 @@ export async function runReviewCli(input: ReviewCliInput): Promise<number> {
|
|
|
837
836
|
: "Whiteboard Desktop is ready in the background. Pass --focus to bring it forward.\n",
|
|
838
837
|
);
|
|
839
838
|
} else {
|
|
840
|
-
input.stdout.write(
|
|
841
|
-
`Whiteboard Desktop is showing "${boardTitle(event.title)}".\n`,
|
|
842
|
-
);
|
|
839
|
+
input.stdout.write(`Whiteboard Desktop is showing "${event.title}".\n`);
|
|
843
840
|
}
|
|
844
841
|
};
|
|
845
842
|
|
package/src/cli.ts
CHANGED
|
@@ -84,12 +84,9 @@ async function maybeDelegateToDesktopCli(
|
|
|
84
84
|
// This bootstrap runs before the Node floor check, so it cannot import
|
|
85
85
|
// devReviewHome() from @dev.fast/trace-core: that module graph needs a modern
|
|
86
86
|
// Node. Keep this copy in step with devReviewHome().
|
|
87
|
-
const channel =
|
|
88
|
-
env.DEV_FAST_REVIEW_CHANNEL?.trim() || env.DEV_REVIEW_INSTANCE?.trim();
|
|
89
|
-
|
|
90
87
|
const devHome = env.DEV_REVIEW_HOME?.trim()
|
|
91
88
|
? path.resolve(env.DEV_REVIEW_HOME.trim())
|
|
92
|
-
: path.join(os.homedir(),
|
|
89
|
+
: path.join(os.homedir(), ".dev");
|
|
93
90
|
|
|
94
91
|
let cliPath: string;
|
|
95
92
|
let runtimePath: string | undefined;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type JsonObject,
|
|
3
2
|
type JsonValue,
|
|
4
3
|
isJsonObject,
|
|
5
4
|
isNumberValue,
|
|
@@ -15,63 +14,50 @@ export function migrateDiffSelections(input: unknown): JsonValue {
|
|
|
15
14
|
return migrateNode(jsonValueSchema.parse(input));
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
/** Keys whose values held retired attachments. */
|
|
19
|
-
export const ATTACHMENT_KEYS = [
|
|
20
|
-
"source",
|
|
21
|
-
"peek",
|
|
22
|
-
"sources",
|
|
23
|
-
"contextSources",
|
|
24
|
-
"callSite",
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
/** A retired attachment as a selection, or undefined for anything else. */
|
|
28
|
-
export function retiredSelection(value: JsonObject): JsonObject | undefined {
|
|
29
|
-
if (!isStringValue(value.file)) return undefined;
|
|
30
|
-
|
|
31
|
-
if (isNumberValue(value.fromLine) && isNumberValue(value.toLine)) {
|
|
32
|
-
const side = value.side ?? value.graph ?? "head";
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
file: value.file,
|
|
36
|
-
start: { side, line: value.fromLine },
|
|
37
|
-
end: { side, line: value.toLine },
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
isJsonObject(value.start) &&
|
|
43
|
-
isJsonObject(value.end) &&
|
|
44
|
-
"baseLine" in value.start
|
|
45
|
-
) {
|
|
46
|
-
const endpoint = (row: JsonObject) =>
|
|
47
|
-
isNumberValue(row.headLine)
|
|
48
|
-
? { side: "head", line: row.headLine }
|
|
49
|
-
: { side: "base", line: row.baseLine ?? null };
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
file: value.file,
|
|
53
|
-
start: endpoint(value.start),
|
|
54
|
-
end: endpoint(value.end),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
17
|
function migrateNode(value: JsonValue, attachment = false): JsonValue {
|
|
62
18
|
if (Array.isArray(value))
|
|
63
19
|
return value.map((child) => migrateNode(child, attachment));
|
|
64
20
|
|
|
65
21
|
if (!isJsonObject(value)) return value;
|
|
66
22
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
23
|
+
if (attachment && isStringValue(value.file)) {
|
|
24
|
+
if (isNumberValue(value.fromLine) && isNumberValue(value.toLine)) {
|
|
25
|
+
const side = value.side ?? value.graph ?? "head";
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
file: value.file,
|
|
29
|
+
start: { side, line: value.fromLine },
|
|
30
|
+
end: { side, line: value.toLine },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
isJsonObject(value.start) &&
|
|
36
|
+
isJsonObject(value.end) &&
|
|
37
|
+
"baseLine" in value.start
|
|
38
|
+
) {
|
|
39
|
+
const endpoint = (row: typeof value.start) =>
|
|
40
|
+
isNumberValue(row.headLine)
|
|
41
|
+
? { side: "head", line: row.headLine }
|
|
42
|
+
: { side: "base", line: row.baseLine };
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
file: value.file,
|
|
46
|
+
start: endpoint(value.start),
|
|
47
|
+
end: endpoint(value.end),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
70
51
|
|
|
71
52
|
return Object.fromEntries(
|
|
72
53
|
Object.entries(value).map(([key, child]) => [
|
|
73
54
|
key,
|
|
74
|
-
migrateNode(
|
|
55
|
+
migrateNode(
|
|
56
|
+
child,
|
|
57
|
+
["source", "peek", "sources", "contextSources", "callSite"].includes(
|
|
58
|
+
key,
|
|
59
|
+
),
|
|
60
|
+
),
|
|
75
61
|
]),
|
|
76
62
|
);
|
|
77
63
|
}
|
package/src/lens-selection.ts
CHANGED
|
@@ -24,23 +24,8 @@ const sameSideAnchor = /^(head|base)\/(.+)#L(\d+)(?:-L(\d+))?$/;
|
|
|
24
24
|
|
|
25
25
|
const crossSideAnchor = /^diff\/(.+)#([LR])(\d+)-([LR])(\d+)$/;
|
|
26
26
|
|
|
27
|
-
const commitLinkAnchor = /^([0-9a-f]{7,40})\/(.+#L\d+(?:-L\d+)?)$/i;
|
|
28
|
-
|
|
29
|
-
/** A source link's commit form, "<sha>/path#L10-L24" (without its
|
|
30
|
-
* `whiteboard:source/` prefix): lines of that commit of the board's own
|
|
31
|
-
* repository, read as their head side. Undefined for any other form. */
|
|
32
|
-
export function commitLinkTarget(
|
|
33
|
-
target: string,
|
|
34
|
-
): { commit: string; anchor: Anchor } | undefined {
|
|
35
|
-
const match = commitLinkAnchor.exec(target);
|
|
36
|
-
|
|
37
|
-
return match
|
|
38
|
-
? { commit: match[1]!.toLowerCase(), anchor: `head/${match[2]!}` }
|
|
39
|
-
: undefined;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
27
|
/** An anchor's selection, as markdown source links write it too (without
|
|
43
|
-
* their `
|
|
28
|
+
* their `review-source:` scheme). Undefined when it isn't one. */
|
|
44
29
|
export function parseAnchor(text: string): DiffSelection | undefined {
|
|
45
30
|
const same = sameSideAnchor.exec(text);
|
|
46
31
|
|
|
@@ -210,26 +195,6 @@ export function sourceAnchor(source: LensSource): FileLineRange {
|
|
|
210
195
|
return sourceAnchors(source)[0]!;
|
|
211
196
|
}
|
|
212
197
|
|
|
213
|
-
/** Why a selection cannot be resolved at its pins, as telemetry reports it. */
|
|
214
|
-
export const SELECTION_UNAVAILABLE_REASONS = [
|
|
215
|
-
"missing_file",
|
|
216
|
-
"no_alignment",
|
|
217
|
-
"outside_lines",
|
|
218
|
-
"other",
|
|
219
|
-
] as const;
|
|
220
|
-
|
|
221
|
-
export type SelectionUnavailableReason =
|
|
222
|
-
(typeof SELECTION_UNAVAILABLE_REASONS)[number];
|
|
223
|
-
|
|
224
|
-
export class SelectionUnavailableError extends Error {
|
|
225
|
-
constructor(
|
|
226
|
-
readonly reason: SelectionUnavailableReason,
|
|
227
|
-
message: string,
|
|
228
|
-
) {
|
|
229
|
-
super(message);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
198
|
/** Resolve once against the renderer's rows. No matching or context expansion. */
|
|
234
199
|
export function resolveDiffSelection(
|
|
235
200
|
source: LensSource,
|
|
@@ -245,8 +210,7 @@ export function resolveDiffSelection(
|
|
|
245
210
|
const end = locate(source.end);
|
|
246
211
|
|
|
247
212
|
if (start < 0 || end < start)
|
|
248
|
-
throw new
|
|
249
|
-
"outside_lines",
|
|
213
|
+
throw new Error(
|
|
250
214
|
"Lens endpoints do not identify an ordered interval in this diff.",
|
|
251
215
|
);
|
|
252
216
|
const selected = rows.slice(start, end + 1);
|