@dev.fast/whiteboard 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +146 -2
- package/THIRD_PARTY_NOTICES.md +93 -0
- package/dist/account-alias-DOHN1RSH.js +973 -0
- package/dist/agent-cli-ChOq0ZuO.js +117 -0
- package/dist/agent-cli-DzWfZr6G.js +2 -0
- package/dist/agent-client-CPQo7iTI.js +305 -0
- package/dist/authoring-tools-Cr6Kpjsx.js +234 -0
- package/dist/build-info.json +1 -0
- package/dist/cli-hVgHcdsv.js +47 -0
- package/dist/cli-runner-D8y2_luU.js +5095 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -0
- package/dist/client-DFoh50W3.js +950 -0
- package/dist/desktop-discovery-DBO-PM2V.js +164 -0
- package/dist/error-message-OtiDonty.js +6 -0
- package/dist/fs-utils-BMPLt0cr.js +22 -0
- package/dist/fuzzy-match-BoAmcyak.js +30 -0
- package/dist/headless-host-BJxf4dUU.js +94 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/local-data-CxLk75rx.js +4491 -0
- package/dist/mcp-QeF8vkdf.js +140 -0
- package/dist/package-paths-B6-zxvIO.js +47 -0
- package/dist/process-error-telemetry-v9e5h6D7.js +3952 -0
- package/dist/profile-4ry2f3AM.js +76 -0
- package/dist/profile-Bh5FIrmf.js +2 -0
- package/dist/request-origin-D_4QIdoN.js +811 -0
- package/dist/review-agent-traces-DNJFYzc8.js +3866 -0
- package/dist/review-home-paths-6zZH-1c9.js +2560 -0
- package/dist/review-telemetry-DEFnQsQ2.js +1195 -0
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +2 -0
- package/dist/s3-SzLpeCG6.js +345 -0
- package/dist/s3-config-BgxkSoOy.js +2 -0
- package/dist/s3-config-CAxhO9u_.js +676 -0
- package/dist/server/desktop-host.d.ts +4 -0
- package/dist/server/desktop-host.js +1702 -0
- package/dist/server-discovery-Dbzn3w6Z.js +66 -0
- package/dist/sharing/index.d.ts +1977 -0
- package/dist/sharing/index.js +2 -0
- package/dist/src-CGP5ytbV.js +283 -0
- package/dist/src-CmdiBL20.js +3676 -0
- package/dist/src-DnwdaQ2r.js +865 -0
- package/dist/src-X9phtB2j.js +68 -0
- package/dist/stored-document-migration-DHeMpLMb.js +2671 -0
- package/dist/tool-failure-C8zB73HV.js +98 -0
- package/dist/tutorial-trace-DrBvkaeL.js +43 -0
- package/instructions/authoring.md +36 -0
- package/instructions/file-lenses.md +13 -0
- package/instructions/scratchpad.md +23 -0
- package/instructions/trace-archaeology.md +113 -0
- package/onboarding.md +8 -0
- package/package.json +106 -3
- package/src/agent-selection.ts +100 -0
- package/src/agent-session-ref.ts +80 -0
- package/src/ask/agents.ts +351 -0
- package/src/ask/checkout-files.ts +47 -0
- package/src/ask/file-refs.ts +111 -0
- package/src/ask/pi-mcp.ts +154 -0
- package/src/ask/protocol.ts +244 -0
- package/src/ask/thread-state.ts +350 -0
- package/src/ask/thread.ts +1400 -0
- package/src/ask/threads.ts +219 -0
- package/src/ask/watch.ts +79 -0
- package/src/cli-install.ts +1041 -0
- package/src/cli-runner.ts +1461 -0
- package/src/cli-runtime-info.ts +35 -0
- package/src/cli.ts +222 -0
- package/src/connect-prompts.ts +248 -0
- package/src/cursor-deeplink.ts +11 -0
- package/src/desktop-discovery.ts +355 -0
- package/src/diff-selection-migration.ts +63 -0
- package/src/embedded-posthog-key.ts +6 -0
- package/src/error-telemetry.ts +247 -0
- package/src/evidence.ts +14 -0
- package/src/exception-telemetry.ts +126 -0
- package/src/fixtures/blocks/call_stack_diff.json +27 -0
- package/src/fixtures/blocks/callout.json +11 -0
- package/src/fixtures/blocks/code.json +9 -0
- package/src/fixtures/blocks/code_peek.json +8 -0
- package/src/fixtures/blocks/database_lens.json +51 -0
- package/src/fixtures/blocks/divider.json +1 -0
- package/src/fixtures/blocks/fixtures.ts +33 -0
- package/src/fixtures/blocks/flow_diagram.json +36 -0
- package/src/fixtures/blocks/ids.ts +8 -0
- package/src/fixtures/blocks/image.json +9 -0
- package/src/fixtures/blocks/markdown.json +7 -0
- package/src/fixtures/blocks/section.json +11 -0
- package/src/fixtures/blocks/sequence.json +31 -0
- package/src/fixtures/blocks/software_map.json +7 -0
- package/src/fixtures/blocks/trace_quote.json +9 -0
- package/src/fixtures/blocks/tutorial.json +51 -0
- package/src/fs-utils.ts +32 -0
- package/src/fuzzy-match.ts +94 -0
- package/src/install.ts +29 -0
- package/src/legacy-skills.ts +133 -0
- package/src/lens-selection.ts +230 -0
- package/src/markdown-latex-math.ts +230 -0
- package/src/markdown.ts +81 -0
- package/src/package-paths.ts +53 -0
- package/src/posthog-capture-client.ts +610 -0
- package/src/review-api/README.md +295 -0
- package/src/review-api/activity.ts +340 -0
- package/src/review-api/agent-cli.ts +219 -0
- package/src/review-api/agent-client.ts +188 -0
- package/src/review-api/anchor-quotes.ts +88 -0
- package/src/review-api/ask-history.ts +242 -0
- package/src/review-api/authoring-tools.ts +211 -0
- package/src/review-api/blocks/call_stack_diff.ts +75 -0
- package/src/review-api/blocks/callout.ts +24 -0
- package/src/review-api/blocks/code.ts +9 -0
- package/src/review-api/blocks/code_peek.ts +13 -0
- package/src/review-api/blocks/database_lens.ts +150 -0
- package/src/review-api/blocks/definition.ts +40 -0
- package/src/review-api/blocks/divider.ts +6 -0
- package/src/review-api/blocks/flow_diagram.ts +111 -0
- package/src/review-api/blocks/image.ts +10 -0
- package/src/review-api/blocks/index.ts +90 -0
- package/src/review-api/blocks/markdown.ts +17 -0
- package/src/review-api/blocks/section.ts +24 -0
- package/src/review-api/blocks/sequence.ts +56 -0
- package/src/review-api/blocks/software_map.ts +9 -0
- package/src/review-api/blocks/trace_quote.ts +10 -0
- package/src/review-api/blocks/tutorial.ts +39 -0
- package/src/review-api/checkout-fs.ts +14 -0
- package/src/review-api/client.ts +1 -0
- package/src/review-api/comparison-coverage.ts +304 -0
- package/src/review-api/component-reference.ts +30 -0
- package/src/review-api/diff-lenses.ts +175 -0
- package/src/review-api/document-headings.ts +51 -0
- package/src/review-api/document-text.ts +200 -0
- package/src/review-api/document.ts +906 -0
- package/src/review-api/file-lenses.ts +100 -0
- package/src/review-api/http.ts +1877 -0
- package/src/review-api/image-decode.ts +29 -0
- package/src/review-api/input-error.ts +9 -0
- package/src/review-api/instructions.ts +89 -0
- package/src/review-api/lens-alignment.ts +52 -0
- package/src/review-api/local-data.ts +1837 -0
- package/src/review-api/map-input.ts +154 -0
- package/src/review-api/mcp-client-agent.ts +32 -0
- package/src/review-api/mcp.ts +240 -0
- package/src/review-api/origin.ts +43 -0
- package/src/review-api/profile.ts +146 -0
- package/src/review-api/public-tools.ts +102 -0
- package/src/review-api/pull-request.ts +389 -0
- package/src/review-api/read-schemas.ts +85 -0
- package/src/review-api/recovery.ts +2 -0
- package/src/review-api/request-origin.ts +33 -0
- package/src/review-api/review-progress.ts +382 -0
- package/src/review-api/status-tool.ts +10 -0
- package/src/review-api/store-schema.ts +22 -0
- package/src/review-api/store.ts +1647 -0
- package/src/review-api/tool-failure.ts +64 -0
- package/src/review-api/trace-schema.ts +13 -0
- package/src/review-api/traces.ts +119 -0
- package/src/review-api/unsupported-files.integration.ts +91 -0
- package/src/review-api/workspaces.ts +692 -0
- package/src/review-api/worktree-source.ts +170 -0
- package/src/review-api/worktree-structural.integration.ts +320 -0
- package/src/review-app-launcher.ts +432 -0
- package/src/review-app-picker.ts +168 -0
- package/src/review-app.ts +134 -0
- package/src/review-bundled-tools.ts +249 -0
- package/src/review-checkout-paths.ts +37 -0
- package/src/review-diff-files.ts +89 -0
- package/src/review-head-checkout.ts +300 -0
- package/src/review-home-paths.ts +73 -0
- package/src/review-info.ts +59 -0
- package/src/review-instances.ts +113 -0
- package/src/review-logger.ts +183 -0
- package/src/review-preferences.ts +88 -0
- package/src/review-prepare.ts +291 -0
- package/src/review-stack.ts +112 -0
- package/src/review-telemetry.ts +1066 -0
- package/src/runtime.ts +74 -0
- package/src/server/account-alias.ts +30 -0
- package/src/server/bounded-stream.ts +34 -0
- package/src/server/bug-report.ts +301 -0
- package/src/server/client-error-budget.ts +45 -0
- package/src/server/crash-report.ts +273 -0
- package/src/server/desktop-host-shutdown.ts +64 -0
- package/src/server/desktop-host.ts +190 -0
- package/src/server/desktop-server.ts +703 -0
- package/src/server/diffr-config.ts +509 -0
- package/src/server/diffr-languages.ts +97 -0
- package/src/server/global-verb-relay.ts +190 -0
- package/src/server/headless-host.ts +149 -0
- package/src/server/hono-http.ts +162 -0
- package/src/server/http-json.ts +24 -0
- package/src/server/json-review-reporting.ts +174 -0
- package/src/server/process-error-telemetry.ts +174 -0
- package/src/server/review-api-parsers.ts +90 -0
- package/src/server/review-lifecycle-telemetry.ts +73 -0
- package/src/server/review-open-watchdog.ts +46 -0
- package/src/server/review-server-core.ts +290 -0
- package/src/server/structural-comparisons.ts +153 -0
- package/src/server/structural-diff.ts +186 -0
- package/src/server/tutorial-service.ts +241 -0
- package/src/server/ui-telemetry.ts +198 -0
- package/src/server-discovery.ts +95 -0
- package/src/session-markers.ts +132 -0
- package/src/sharing/auth.ts +41 -0
- package/src/sharing/cli.ts +83 -0
- package/src/sharing/client.ts +334 -0
- package/src/sharing/export.ts +195 -0
- package/src/sharing/host.ts +316 -0
- package/src/sharing/import.ts +787 -0
- package/src/sharing/index.ts +16 -0
- package/src/sharing/repository.ts +145 -0
- package/src/sharing/routes.ts +34 -0
- package/src/slug.ts +23 -0
- package/src/software-map-diff-counts.ts +517 -0
- package/src/software-map-model.ts +1147 -0
- package/src/software-map-topology-diff.ts +260 -0
- package/src/source.ts +92 -0
- package/src/startup-trace.ts +232 -0
- package/src/stored-document-migration.ts +151 -0
- package/src/telemetry-clean-text.ts +257 -0
- package/src/telemetry-config.ts +298 -0
- package/src/telemetry-debug-sink.ts +38 -0
- package/src/telemetry.ts +13 -0
- package/src/trace-cli.ts +156 -0
- package/src/trace-storage-cli.ts +509 -0
- package/src/tutorial-conversation.ts +18 -0
- package/src/ui-telemetry-events.ts +765 -0
- package/src/unified-diff.ts +71 -0
- package/src/viewed-coverage.ts +259 -0
- package/src/windows-cli.ts +125 -0
- package/tutorial/document.json +275 -0
- package/tutorial/runtime-manifest.json +12 -0
- package/tutorial/sample-service/package.json +9 -0
- package/tutorial/sample-service/src/api/checkout-api.ts +17 -0
- package/tutorial/sample-service/src/app.ts +26 -0
- package/tutorial/sample-service/src/database/schema.ts +10 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +15 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +24 -0
- package/tutorial/sample-service/src/inventory/inventory-service.ts +11 -0
- package/tutorial/sample-service/src/orders/order-service.ts +34 -0
- package/tutorial/sample-service/src/orders/order.ts +21 -0
- package/tutorial/sample-service/src/orders/orders-repository.ts +24 -0
- package/tutorial/sample-service/src/payments/payment-gateway.ts +12 -0
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +15 -0
- package/tutorial/sample-service/tsconfig.json +12 -0
- package/tutorial/software-map.json +139 -0
- package/tutorial/trace.json +20 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { n as isJsonObject, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
2
|
+
import { a as toolResultText, i as connectReviewInstance, s as ReviewApiError } from "./agent-client-CPQo7iTI.js";
|
|
3
|
+
import { A as devReviewHome } from "./s3-config-CAxhO9u_.js";
|
|
4
|
+
import { T as traceMachineEnabled } from "./review-home-paths-6zZH-1c9.js";
|
|
5
|
+
import { d as selectReviewInstance, t as REVIEW_INSTANCE_ENV } from "./desktop-discovery-DBO-PM2V.js";
|
|
6
|
+
import { n as reviewSessionAgent } from "./review-telemetry-DEFnQsQ2.js";
|
|
7
|
+
import { n as REVIEW_VIA_HEADER, t as REVIEW_AGENT_HEADER } from "./request-origin-D_4QIdoN.js";
|
|
8
|
+
import { i as publicTool, n as RECOVERY, r as callPublicTool, t as toolFailure } from "./tool-failure-C8zB73HV.js";
|
|
9
|
+
//#region src/review-api/agent-cli.ts
|
|
10
|
+
const reviewAgentCliHelp = "whiteboard api tools (one line per tool)\nwhiteboard api tools <tool-name> (its description and input schema)\nwhiteboard api <tool-name> '<json>'\nwhiteboard api <tool-name> - (read JSON from stdin)\nwhiteboard mcp (stdio MCP adapter; Whiteboard Desktop or whiteboard server start must be running)\nSelect headless state with DEV_REVIEW_SERVER_DIR or whiteboard --state-dir <path> api/mcp.\n";
|
|
11
|
+
async function runReviewAgentCli(input) {
|
|
12
|
+
const env = input.env ?? process.env;
|
|
13
|
+
try {
|
|
14
|
+
const [mode, ...rest] = input.argv;
|
|
15
|
+
const [name, json, ...extra] = rest.filter((argument) => argument !== "--json");
|
|
16
|
+
if (rest.includes("--help") || rest.includes("-h") || mode === "api" && !name) {
|
|
17
|
+
input.stdout.write(reviewAgentCliHelp);
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
if (extra.length || mode === "mcp" && name) throw new Error("Unexpected arguments. Use whiteboard api --help.");
|
|
21
|
+
const headers = {
|
|
22
|
+
[REVIEW_VIA_HEADER]: mode === "mcp" ? "mcp" : "api",
|
|
23
|
+
[REVIEW_AGENT_HEADER]: reviewSessionAgent(env)
|
|
24
|
+
};
|
|
25
|
+
if (mode === "mcp") {
|
|
26
|
+
const { serveReviewMcp } = await import("./mcp-QeF8vkdf.js");
|
|
27
|
+
await serveReviewMcp(async (key, agentKind) => {
|
|
28
|
+
const connected = await connectReviewInstance(key ? {
|
|
29
|
+
...env,
|
|
30
|
+
[REVIEW_INSTANCE_ENV]: key
|
|
31
|
+
} : env, agentKind ? {
|
|
32
|
+
...headers,
|
|
33
|
+
[REVIEW_AGENT_HEADER]: agentKind
|
|
34
|
+
} : headers);
|
|
35
|
+
input.onDesktop?.(connected.instance?.appVersion);
|
|
36
|
+
return connected;
|
|
37
|
+
}, input.stdin ?? process.stdin, input.stdout, input.stderr, await traceMachineEnabled({ env: input.env }), env.DEV_REVIEW_SERVER_DIR?.trim() ? void 0 : async (problem) => {
|
|
38
|
+
const selection = await selectReviewInstance({ env });
|
|
39
|
+
return {
|
|
40
|
+
key: selection.key,
|
|
41
|
+
selectedBy: selection.source,
|
|
42
|
+
desktopAvailable: false,
|
|
43
|
+
running: selection.instances.filter((instance) => instance.healthy).map((instance) => instance.key),
|
|
44
|
+
home: devReviewHome(env),
|
|
45
|
+
problem
|
|
46
|
+
};
|
|
47
|
+
}, input.onToolCall, reviewSessionAgent(env));
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
let client;
|
|
51
|
+
let tools;
|
|
52
|
+
try {
|
|
53
|
+
const connected = await connectReviewInstance(input.env, headers);
|
|
54
|
+
input.onDesktop?.(connected.instance?.appVersion);
|
|
55
|
+
client = connected.client;
|
|
56
|
+
tools = (await client.read("/authoring")).map(publicTool);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (name === "session_get_instructions" && !(error instanceof ReviewApiError)) {
|
|
59
|
+
input.stderr.write(RECOVERY + "\n");
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
if (name === "tools") {
|
|
65
|
+
if (json) {
|
|
66
|
+
const tool = tools.find((tool) => tool.name === json);
|
|
67
|
+
if (!tool) throw new Error(`Unknown review tool: ${json}. Use whiteboard api tools.`);
|
|
68
|
+
input.stdout.write(JSON.stringify(tool, null, 2) + "\n");
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
for (const tool of tools) input.stdout.write(`${tool.name}: ${tool.description.split(/(?<=\.)\s/)[0]}\n`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
const tool = tools.find((tool) => tool.name === name);
|
|
75
|
+
if (!tool) throw new Error(`Unknown review tool: ${name}. Use whiteboard api tools.`);
|
|
76
|
+
let source = json ?? "{}";
|
|
77
|
+
if (source === "-") {
|
|
78
|
+
if (!input.stdin) throw new Error("No input stream supplied.");
|
|
79
|
+
source = "";
|
|
80
|
+
input.stdin.setEncoding("utf8");
|
|
81
|
+
for await (const chunk of input.stdin) source += chunk;
|
|
82
|
+
}
|
|
83
|
+
const args = parseJsonText(source);
|
|
84
|
+
if (!isJsonObject(args)) throw new Error("Tool input must be a JSON object.");
|
|
85
|
+
if (name === "session_get" && rest.includes("--json")) args.format = "json";
|
|
86
|
+
const startedAt = Date.now();
|
|
87
|
+
let result;
|
|
88
|
+
try {
|
|
89
|
+
result = await callPublicTool(client, tool, args);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
await input.onToolCall?.({
|
|
92
|
+
tool: tool.name,
|
|
93
|
+
via: "api",
|
|
94
|
+
ok: false,
|
|
95
|
+
durationMs: Date.now() - startedAt,
|
|
96
|
+
...toolFailure(error, "call")
|
|
97
|
+
});
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
await input.onToolCall?.({
|
|
101
|
+
tool: tool.name,
|
|
102
|
+
via: "api",
|
|
103
|
+
ok: true,
|
|
104
|
+
durationMs: Date.now() - startedAt
|
|
105
|
+
});
|
|
106
|
+
const text = toolResultText(tool, result);
|
|
107
|
+
input.stdout.write(text.endsWith("\n") ? text : text + "\n");
|
|
108
|
+
return 0;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
111
|
+
input.onFailure?.(failure);
|
|
112
|
+
input.stderr.write(failure.message + "\n");
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { runReviewAgentCli as n, reviewAgentCliHelp as t };
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { d as isBooleanValue, f as isNumberValue, m as isStringValue, u as parseJsonText } from "./src-X9phtB2j.js";
|
|
2
|
+
import "./src-DnwdaQ2r.js";
|
|
3
|
+
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
4
|
+
import { d as selectReviewInstance, i as healthyReviewInstance, n as ReviewInstanceUnavailableError, u as reviewInstanceUnavailable } from "./desktop-discovery-DBO-PM2V.js";
|
|
5
|
+
import { a as reviewServerIsHealthy, n as readReviewServerDiscovery, o as reviewServerStateDir, s as serverNotReady } from "./server-discovery-Dbzn3w6Z.js";
|
|
6
|
+
//#region ../review-protocol/src/review-api-client.ts
|
|
7
|
+
const defaultRequest = (url, init) => fetch(url, init);
|
|
8
|
+
/** A non-2xx reply; the status tells a caller whether retrying can help. */
|
|
9
|
+
var ReviewApiError = class extends Error {
|
|
10
|
+
status;
|
|
11
|
+
constructor(message, status) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = "ReviewApiError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const liveConnections = /* @__PURE__ */ new WeakMap();
|
|
18
|
+
/** Shared by the canvas and thin agent clients; no filesystem or SQL access. */
|
|
19
|
+
var ReviewApiClient = class {
|
|
20
|
+
connection;
|
|
21
|
+
request;
|
|
22
|
+
constructor(connection, request = defaultRequest) {
|
|
23
|
+
this.connection = connection;
|
|
24
|
+
this.request = request;
|
|
25
|
+
}
|
|
26
|
+
async response(route, init) {
|
|
27
|
+
const headers = new Headers(init?.headers);
|
|
28
|
+
headers.set("x-review-token", this.connection.token);
|
|
29
|
+
if (init?.body) headers.set("content-type", "application/json");
|
|
30
|
+
const response = await this.request(`${this.connection.serverUrl}/reviews-api${route}`, {
|
|
31
|
+
...init,
|
|
32
|
+
headers
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) throw new ReviewApiError((await response.json().catch(() => null))?.error ?? `Review request failed (${response.status}).`, response.status);
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
async read(route, signal) {
|
|
38
|
+
return (await this.response(route, { signal })).json();
|
|
39
|
+
}
|
|
40
|
+
async post(route, input, signal) {
|
|
41
|
+
return (await this.response(route, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
body: JSON.stringify(input),
|
|
44
|
+
signal
|
|
45
|
+
})).json();
|
|
46
|
+
}
|
|
47
|
+
async *watch(reviewId, signal) {
|
|
48
|
+
const reader = (await this.response(Array.isArray(reviewId) ? `/watch?subscriptions=${encodeURIComponent(JSON.stringify(reviewId))}` : reviewId === null ? "/watch" : `/${encodeURIComponent(reviewId)}/watch`, { signal })).body.pipeThrough(new TextDecoderStream()).getReader();
|
|
49
|
+
const cancel = () => {
|
|
50
|
+
reader.cancel().catch(() => {});
|
|
51
|
+
};
|
|
52
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
53
|
+
if (signal.aborted) cancel();
|
|
54
|
+
let pending = "";
|
|
55
|
+
try {
|
|
56
|
+
while (true) {
|
|
57
|
+
const { value, done } = await reader.read();
|
|
58
|
+
if (done) return;
|
|
59
|
+
pending += value;
|
|
60
|
+
let end;
|
|
61
|
+
while ((end = pending.indexOf("\n")) !== -1) {
|
|
62
|
+
yield JSON.parse(pending.slice(0, end));
|
|
63
|
+
pending = pending.slice(end + 1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
signal.removeEventListener("abort", cancel);
|
|
68
|
+
await reader.cancel().catch(() => {});
|
|
69
|
+
reader.releaseLock();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async follow(reviewId, signal, accept, disconnected, mode) {
|
|
73
|
+
if (signal.aborted) return;
|
|
74
|
+
let connections = liveConnections.get(this.request);
|
|
75
|
+
if (!connections) liveConnections.set(this.request, connections = /* @__PURE__ */ new Map());
|
|
76
|
+
const key = JSON.stringify([this.connection.serverUrl, this.connection.token]);
|
|
77
|
+
let live = connections.get(key);
|
|
78
|
+
if (!live) {
|
|
79
|
+
live = new LiveConnection(this, () => connections.delete(key));
|
|
80
|
+
connections.set(key, live);
|
|
81
|
+
}
|
|
82
|
+
const subscription = { reviewId };
|
|
83
|
+
if (mode) subscription.mode = mode;
|
|
84
|
+
return live.add(subscription, signal, (value) => accept(value), disconnected);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const restartDelayMs = 20;
|
|
88
|
+
const retryDelayMs = 1e3;
|
|
89
|
+
function sleep(ms, signal) {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const done = () => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
signal.removeEventListener("abort", done);
|
|
94
|
+
resolve();
|
|
95
|
+
};
|
|
96
|
+
const timer = setTimeout(done, ms);
|
|
97
|
+
signal.addEventListener("abort", done, { once: true });
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function report(listener, cause) {
|
|
101
|
+
if (listener.signal.aborted) return;
|
|
102
|
+
try {
|
|
103
|
+
listener.disconnected(cause);
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
var LiveConnection = class {
|
|
107
|
+
client;
|
|
108
|
+
empty;
|
|
109
|
+
listeners = /* @__PURE__ */ new Set();
|
|
110
|
+
abort = new AbortController();
|
|
111
|
+
active = false;
|
|
112
|
+
scheduled;
|
|
113
|
+
constructor(client, empty) {
|
|
114
|
+
this.client = client;
|
|
115
|
+
this.empty = empty;
|
|
116
|
+
}
|
|
117
|
+
add(subscription, signal, accept, disconnected) {
|
|
118
|
+
const listener = {
|
|
119
|
+
subscription,
|
|
120
|
+
signal,
|
|
121
|
+
accept,
|
|
122
|
+
disconnected
|
|
123
|
+
};
|
|
124
|
+
this.listeners.add(listener);
|
|
125
|
+
this.restart();
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
const stop = () => {
|
|
128
|
+
signal.removeEventListener("abort", stop);
|
|
129
|
+
this.listeners.delete(listener);
|
|
130
|
+
if (this.listeners.size) this.restart();
|
|
131
|
+
else {
|
|
132
|
+
clearTimeout(this.scheduled);
|
|
133
|
+
this.scheduled = void 0;
|
|
134
|
+
this.abort.abort();
|
|
135
|
+
this.active = false;
|
|
136
|
+
this.empty();
|
|
137
|
+
}
|
|
138
|
+
resolve();
|
|
139
|
+
};
|
|
140
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
141
|
+
if (signal.aborted) stop();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** The first tab connects at once; later tabs share one replacement stream. */
|
|
145
|
+
restart() {
|
|
146
|
+
if (this.scheduled) return;
|
|
147
|
+
if (!this.active) {
|
|
148
|
+
this.reconnect();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.scheduled = setTimeout(() => {
|
|
152
|
+
this.scheduled = void 0;
|
|
153
|
+
this.reconnect();
|
|
154
|
+
}, restartDelayMs);
|
|
155
|
+
}
|
|
156
|
+
reconnect() {
|
|
157
|
+
this.abort.abort();
|
|
158
|
+
this.abort = new AbortController();
|
|
159
|
+
const listeners = [...this.listeners];
|
|
160
|
+
this.active = listeners.length > 0;
|
|
161
|
+
if (this.active) this.run(listeners, this.abort.signal);
|
|
162
|
+
}
|
|
163
|
+
async run(listeners, signal) {
|
|
164
|
+
const disconnected = (cause) => listeners.forEach((listener) => report(listener, cause));
|
|
165
|
+
let delay = 1e3;
|
|
166
|
+
while (!signal.aborted) {
|
|
167
|
+
try {
|
|
168
|
+
for await (const results of this.client.watch(listeners.map((item) => item.subscription), signal)) {
|
|
169
|
+
if (signal.aborted) break;
|
|
170
|
+
delay = 1e3;
|
|
171
|
+
listeners.forEach((listener, index) => {
|
|
172
|
+
const result = results[index];
|
|
173
|
+
if (result) this.deliver(listener, result);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (!signal.aborted) disconnected(/* @__PURE__ */ new Error("Connection closed."));
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (!signal.aborted) disconnected(error);
|
|
179
|
+
if (error instanceof ReviewApiError && [
|
|
180
|
+
401,
|
|
181
|
+
403,
|
|
182
|
+
404
|
|
183
|
+
].includes(error.status)) return;
|
|
184
|
+
}
|
|
185
|
+
if (!signal.aborted) await sleep(delay, signal);
|
|
186
|
+
delay = Math.min(delay * 2, 3e4);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Renders never block the stream: a slow tab only delays its own newest state. */
|
|
190
|
+
deliver(listener, result) {
|
|
191
|
+
listener.queued = result;
|
|
192
|
+
if (listener.draining) return;
|
|
193
|
+
listener.draining = true;
|
|
194
|
+
(async () => {
|
|
195
|
+
while (listener.queued && !listener.signal.aborted) {
|
|
196
|
+
const next = listener.queued;
|
|
197
|
+
listener.queued = void 0;
|
|
198
|
+
if (next.error) {
|
|
199
|
+
report(listener, new Error(next.error));
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await listener.accept(next.value);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
report(listener, error);
|
|
206
|
+
await sleep(retryDelayMs, listener.signal);
|
|
207
|
+
listener.queued ??= next;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
listener.draining = false;
|
|
211
|
+
})();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/review-api/agent-client.ts
|
|
216
|
+
const TEXT_TOOLS = /* @__PURE__ */ new Set([
|
|
217
|
+
"review_get",
|
|
218
|
+
"review_get_instructions",
|
|
219
|
+
"session_get",
|
|
220
|
+
"session_get_instructions"
|
|
221
|
+
]);
|
|
222
|
+
async function connectReviewApi(env = process.env, headers = {}) {
|
|
223
|
+
return (await connectReviewInstance(env, headers)).client;
|
|
224
|
+
}
|
|
225
|
+
async function connectReviewInstance(env = process.env, headers = {}) {
|
|
226
|
+
const request = (url, init) => {
|
|
227
|
+
const merged = new Headers(init?.headers);
|
|
228
|
+
for (const [key, value] of Object.entries(headers)) merged.set(key, value);
|
|
229
|
+
return fetch(url, {
|
|
230
|
+
...init,
|
|
231
|
+
headers: merged
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
if (env.DEV_REVIEW_SERVER_DIR?.trim()) {
|
|
235
|
+
const stateDir = reviewServerStateDir(env);
|
|
236
|
+
const server = await readReviewServerDiscovery(stateDir);
|
|
237
|
+
if (!server || !await reviewServerIsHealthy(server)) throw serverNotReady(stateDir);
|
|
238
|
+
return { client: new ReviewApiClient({
|
|
239
|
+
serverUrl: server.url,
|
|
240
|
+
token: server.token
|
|
241
|
+
}, request) };
|
|
242
|
+
}
|
|
243
|
+
const selection = await selectReviewInstance({ env });
|
|
244
|
+
const discovery = healthyReviewInstance(selection);
|
|
245
|
+
if (!discovery) {
|
|
246
|
+
const unavailable = reviewInstanceUnavailable(selection);
|
|
247
|
+
const message = `${unavailable.message} For headless authoring, select a running server with --state-dir or DEV_REVIEW_SERVER_DIR.`;
|
|
248
|
+
if (unavailable instanceof ReviewInstanceUnavailableError) throw new ReviewInstanceUnavailableError(message);
|
|
249
|
+
throw unavailable;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
client: new ReviewApiClient({
|
|
253
|
+
serverUrl: discovery.url,
|
|
254
|
+
token: discovery.token
|
|
255
|
+
}, request),
|
|
256
|
+
instance: {
|
|
257
|
+
key: selection.key,
|
|
258
|
+
appVersion: discovery.appVersion
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/** Only translate the tool envelope. The host owns validation and persistence. */
|
|
263
|
+
async function callAuthoringTool(client, tool, input, signal) {
|
|
264
|
+
if (tool.commandType) try {
|
|
265
|
+
return await client.post(tool.path, { operation: {
|
|
266
|
+
...input,
|
|
267
|
+
type: tool.commandType
|
|
268
|
+
} }, signal);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof ReviewApiError || signal?.aborted) throw error;
|
|
271
|
+
throw new Error(`Whiteboard may or may not have applied ${tool.name} (${error instanceof Error ? error.message : String(error)}). Check with review_get before retrying.`, { cause: error });
|
|
272
|
+
}
|
|
273
|
+
const fields = { ...input };
|
|
274
|
+
const route = tool.path.replace(/:reviewId\b/g, () => {
|
|
275
|
+
const value = fields.reviewId;
|
|
276
|
+
if (!isStringValue(value) || !value) throw new ReviewInputError("reviewId is required.");
|
|
277
|
+
delete fields.reviewId;
|
|
278
|
+
return encodeURIComponent(value);
|
|
279
|
+
});
|
|
280
|
+
if (tool.method === "POST") return client.post(route, fields, signal);
|
|
281
|
+
const query = new URLSearchParams();
|
|
282
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
283
|
+
if (value === null || value === void 0) continue;
|
|
284
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
285
|
+
if (!isStringValue(item) && !isNumberValue(item) && !isBooleanValue(item)) throw new ReviewInputError(`${key} must be a string, number, boolean or list of them.`);
|
|
286
|
+
query.append(key, String(item));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const response = await client.response(route + (query.size ? `?${query}` : ""), { signal });
|
|
290
|
+
return response.headers.get("content-type")?.startsWith("text/plain") ? new ToolText(await response.text()) : parseJsonText(await response.text());
|
|
291
|
+
}
|
|
292
|
+
/** A plain-text reply, shown to the agent as-is instead of as a JSON string. */
|
|
293
|
+
var ToolText = class {
|
|
294
|
+
text;
|
|
295
|
+
constructor(text) {
|
|
296
|
+
this.text = text;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
/** The text an agent sees for a tool result. */
|
|
300
|
+
function toolResultText(tool, result) {
|
|
301
|
+
if (result instanceof ToolText) return result.text;
|
|
302
|
+
return TEXT_TOOLS.has(tool.name) && isStringValue(result) ? result : JSON.stringify(result);
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
export { toolResultText as a, connectReviewInstance as i, callAuthoringTool as n, ReviewApiClient as o, connectReviewApi as r, ReviewApiError as s, ToolText as t };
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { g as activityUpdateSchema, h as activityEndSchema, i as commandSchema, m as activityBeginSchema, n as uploadSchema } from "./local-data-CxLk75rx.js";
|
|
2
|
+
import { x as publishedEditSchema, y as pathTargetSchema } from "./stored-document-migration-DHeMpLMb.js";
|
|
3
|
+
import { t as ReviewInputError } from "./input-error-OLgB_h31.js";
|
|
4
|
+
import { t as findReviewPackageRoot } from "./package-paths-B6-zxvIO.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
//#region src/review-api/read-schemas.ts
|
|
9
|
+
const version = z.union([z.literal("").transform(() => void 0), z.coerce.number().int().nonnegative()]).optional();
|
|
10
|
+
const commit = z.string().min(1).optional();
|
|
11
|
+
const side = z.enum(["base", "head"]);
|
|
12
|
+
/** Read at a reference's own pins instead of the review's target. Flat, so
|
|
13
|
+
* the same shape serves query strings and tool inputs. */
|
|
14
|
+
const anchor = {
|
|
15
|
+
repositoryId: z.string().min(1).optional(),
|
|
16
|
+
head: z.string().min(1).optional(),
|
|
17
|
+
base: z.string().min(1).optional()
|
|
18
|
+
};
|
|
19
|
+
function queryAnchor(input) {
|
|
20
|
+
if (!input.repositoryId && !input.head && !input.base) return void 0;
|
|
21
|
+
if (!input.repositoryId || !input.head) throw new ReviewInputError("Reading at explicit pins needs both repositoryId and head.");
|
|
22
|
+
const pins = {
|
|
23
|
+
repositoryId: input.repositoryId,
|
|
24
|
+
head: input.head
|
|
25
|
+
};
|
|
26
|
+
if (input.base) pins.base = input.base;
|
|
27
|
+
return pins;
|
|
28
|
+
}
|
|
29
|
+
const inspectQuerySchema = z.strictObject({
|
|
30
|
+
version,
|
|
31
|
+
targetId: z.string().min(1).optional(),
|
|
32
|
+
full: z.union([z.literal(true), z.literal("true")]).optional(),
|
|
33
|
+
format: z.enum(["text", "json"]).default("text")
|
|
34
|
+
});
|
|
35
|
+
const readQuerySchemas = {
|
|
36
|
+
get: z.strictObject({
|
|
37
|
+
version,
|
|
38
|
+
targetId: z.string().min(1).optional(),
|
|
39
|
+
full: z.union([z.literal(true), z.literal("true")]).optional()
|
|
40
|
+
}),
|
|
41
|
+
maps: z.strictObject({ version }),
|
|
42
|
+
tree: z.strictObject({
|
|
43
|
+
version,
|
|
44
|
+
commit,
|
|
45
|
+
...anchor,
|
|
46
|
+
side: side.default("head"),
|
|
47
|
+
path: z.string().default("")
|
|
48
|
+
}),
|
|
49
|
+
file: z.strictObject({
|
|
50
|
+
version,
|
|
51
|
+
commit,
|
|
52
|
+
...anchor,
|
|
53
|
+
side,
|
|
54
|
+
file: z.string().min(1)
|
|
55
|
+
}),
|
|
56
|
+
diff: z.strictObject({
|
|
57
|
+
version,
|
|
58
|
+
commit,
|
|
59
|
+
...anchor
|
|
60
|
+
}),
|
|
61
|
+
structuralDiff: z.strictObject({
|
|
62
|
+
version,
|
|
63
|
+
commit,
|
|
64
|
+
...anchor,
|
|
65
|
+
file: z.string().min(1).optional()
|
|
66
|
+
}),
|
|
67
|
+
commits: z.strictObject({ version })
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/review-api/component-reference.ts
|
|
71
|
+
/** Every component's fields, for session_get_instructions: the edit tool's
|
|
72
|
+
* description must stay short enough that clients don't cut it off. */
|
|
73
|
+
const COMPONENT_REFERENCE = [
|
|
74
|
+
"## Components",
|
|
75
|
+
"",
|
|
76
|
+
"session_edit's content is one component, named by its type. Fields, with ? for optional and anchor for a source anchor string:",
|
|
77
|
+
"",
|
|
78
|
+
"- markdown {markdown, pins?}",
|
|
79
|
+
"- code {text, language?, caption?}",
|
|
80
|
+
"- divider {}",
|
|
81
|
+
"- code_peek {source: anchor, caption?, pins?}",
|
|
82
|
+
"- section {title, children: component[], defaultCollapsed?}",
|
|
83
|
+
"- callout {children: component[], title?, tone?: info|warning|danger|success}",
|
|
84
|
+
"- image {assetId from session_upload, alt, caption?}",
|
|
85
|
+
"- trace_quote {traceId, eventId, text}",
|
|
86
|
+
"- software_map {mapVersionId, focusElementId?}",
|
|
87
|
+
"- sequence {title, actors: {key: label}, steps: step[], pins?}",
|
|
88
|
+
"- step {from, to (actor keys), label, style?: call|return|async, and exactly one of source: anchor, explanation, or code: {text, language?}; pins?}",
|
|
89
|
+
"- flow_diagram {title, nodes: flow_node[] (1 to 100), edges: flow_edge[], description?, direction?: right|down, pins?}",
|
|
90
|
+
"- flow_node {key, label, description?, kind?: process|decision|terminal, attachments?: {label, sources: anchor[], pins?}[], link?: {from or to, label?, style?}}",
|
|
91
|
+
"- flow_edge {from, to (node keys), label?, style?: solid|dashed}",
|
|
92
|
+
"- call_stack_diff {title, base: frame[], head: frame[], pins?}; either side may be empty",
|
|
93
|
+
"- frame {source: anchor, label?, key?, parentKey? (an earlier frame on that side), callSite?: anchor, contextSources?: anchor[], via?: {kind: call|queue|callback|rpc, reason}, pins?}",
|
|
94
|
+
"- database_lens {title, actors: {key: label}, stores: {key: store}, useCases: useCase[] (1 or more), pins?}",
|
|
95
|
+
"- store {label, storage: relational|document, collections: {key: {label, fields: {key: field}}}, dataStoreKind?: database|objectStore|bucket|artifactStore|fileStore}",
|
|
96
|
+
"- field {label, dataType, nullable?, primaryKey?, references?: {store, collection, field}, fields?: {key: field}}",
|
|
97
|
+
"- useCase {label, summary?, operations: operation[] (1 or more)}; operation {kind: read|write, actor, store, collection, label, source: anchor, field?, detail?, pins?}, naming declared keys",
|
|
98
|
+
"",
|
|
99
|
+
"Example (replace its path and lines with verified ones): {\"type\":\"sequence\",\"title\":\"Request\",\"actors\":{\"a\":\"Client\",\"b\":\"Server\"},\"steps\":[{\"from\":\"a\",\"to\":\"b\",\"label\":\"Send\",\"source\":\"head/src/app.ts#L10-L20\"}]}"
|
|
100
|
+
].join("\n");
|
|
101
|
+
const instructionsQuerySchema = z.strictObject({ topic: z.enum([
|
|
102
|
+
"authoring",
|
|
103
|
+
"file-lenses",
|
|
104
|
+
"scratchpad",
|
|
105
|
+
"trace-archaeology"
|
|
106
|
+
]).default("authoring") });
|
|
107
|
+
function scratchpadAvailable(context) {
|
|
108
|
+
return context.desktopAvailable && context.scratchpadEnabled;
|
|
109
|
+
}
|
|
110
|
+
const cache = /* @__PURE__ */ new Map();
|
|
111
|
+
function read(root, name) {
|
|
112
|
+
const file = path.join(root, "instructions", `${name}.md`);
|
|
113
|
+
let content = cache.get(file);
|
|
114
|
+
if (!content) {
|
|
115
|
+
content = readFile(file, "utf8").catch((error) => {
|
|
116
|
+
cache.delete(file);
|
|
117
|
+
throw error;
|
|
118
|
+
});
|
|
119
|
+
cache.set(file, content);
|
|
120
|
+
}
|
|
121
|
+
return content;
|
|
122
|
+
}
|
|
123
|
+
async function renderInstructions(topic, context, root = findReviewPackageRoot(import.meta.url)) {
|
|
124
|
+
if (topic === "scratchpad" && !scratchpadAvailable(context)) return "The Whiteboard scratchpad is turned off or Whiteboard Desktop is not running. Answer in chat; the scratchpad can be turned on in Whiteboard Desktop Settings.";
|
|
125
|
+
if (topic === "trace-archaeology" && !context.traceEnabled) return "Trace capture is off on this machine, so no agent traces are available. It can be turned on in Whiteboard Desktop Settings under Experimental Features.";
|
|
126
|
+
if (topic === "scratchpad") return `${await read(root, topic)}\n\n${COMPONENT_REFERENCE}`;
|
|
127
|
+
if (topic !== "authoring") return read(root, topic);
|
|
128
|
+
const more = [...scratchpadAvailable(context) ? ["- Explaining code visually outside a review: `session_get_instructions({topic:\"scratchpad\"})`"] : [], ...context.traceEnabled ? ["- Why code exists / past agent sessions: `session_get_instructions({topic:\"trace-archaeology\"})`"] : []];
|
|
129
|
+
return [
|
|
130
|
+
await read(root, "authoring"),
|
|
131
|
+
COMPONENT_REFERENCE,
|
|
132
|
+
...context.traceEnabled ? ["## Traces\n\nAt the end, run `whiteboard trace list --session <sessionId> --json`, which covers every commit in the review. If it lists no sessions, there are no traces to quote: skip this step. Otherwise read `session_get_instructions({topic:\"trace-archaeology\"})` and rewrite as much as possible of the what/why, design, and requirements sections in terms of literal trace quotes from the user."] : [],
|
|
133
|
+
...more.length ? [`## More guidance\n\n${more.join("\n")}`] : []
|
|
134
|
+
].join("\n\n");
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/review-api/status-tool.ts
|
|
138
|
+
/** Listed by the MCP adapter even while no Desktop is reachable. Its public
|
|
139
|
+
* name is kept: it names the app instance, not a session. */
|
|
140
|
+
const REVIEW_STATUS_TOOL = {
|
|
141
|
+
name: "whiteboard_status",
|
|
142
|
+
description: "Name the Whiteboard instance this session talks to: key (stable, preview or dev-<checkout>), channel, checkout, appVersion, cliVersion, instanceId, url, home and desktopAvailable. Call it before changing anything when the user mentions Preview, a checkout or a dev build.",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {}
|
|
146
|
+
},
|
|
147
|
+
method: "GET",
|
|
148
|
+
path: "/status"
|
|
149
|
+
};
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/review-api/authoring-tools.ts
|
|
152
|
+
/** The host publishes its input schemas, except where agents write less than
|
|
153
|
+
* the host accepts (publishedEditSchema, publishedLensEditSchema) or name a
|
|
154
|
+
* checkout by path (pathTargetSchema); adapters validate nothing. */
|
|
155
|
+
function authoringTools(scratchpadAvailable = false, traceEnabled = false) {
|
|
156
|
+
const id = z.string().min(1);
|
|
157
|
+
const review = { reviewId: id };
|
|
158
|
+
const [image, trace] = uploadSchema.options;
|
|
159
|
+
const uploadInput = z.strictObject({
|
|
160
|
+
...image.shape,
|
|
161
|
+
...trace.shape,
|
|
162
|
+
kind: z.enum(["image", "trace"])
|
|
163
|
+
}).partial().required({
|
|
164
|
+
id: true,
|
|
165
|
+
repositoryId: true,
|
|
166
|
+
kind: true
|
|
167
|
+
});
|
|
168
|
+
const descriptions = {
|
|
169
|
+
create: "Create a review of saved working files, immutable commits or a GitHub PR. Revisions are resolved on acceptance. A worktree target reviews the saved files in its checkout, uncommitted ones included and untracked ones left out (git add -N a new file to include it), against base: the branch to compare against, by default the default branch of the repository. The diff starts at the merge base of base and HEAD, which follows rebases. Omitted commits base means source at head with no diff; supply the parent to review introduced changes. For a GitHub PR, pullRequestUrl alone is enough: target and title become optional, and the host fetches the PR into a registered checkout of its repository and pins the current PR head and GitHub diff base, titled from the PR. When a review for that PR exists, it is returned instead, reporting whether its head moved and whether an agent is working on it; update it in place, move its target with review_set_target, or create a separate review with reuseExisting. kind:\"scratchpad\" names the one scratchpad, which the host creates itself. The result carries review, the review as review_list shows it: its target with resolved commits, origin (its PR), repositoryName and repositoryPath, so no follow-up read is needed before diffing. When Desktop is available the review opens there and the result reports opened, softwareMapEnabled and environmentIssues, as review_open does; set open:false to author in the background without taking over Desktop.",
|
|
170
|
+
set_target: "Move the review to a new target, such as the commits after a rebase or amend, preserving the document and component IDs. Revisions are resolved on acceptance. Returns warnings for retained source ranges in files the new commits changed, to verify, and resources that no longer match; fix them with review_edit. Earlier versions keep their source. Omitted pullRequestUrl keeps the PR within the same repository; changing repositories clears it. Supply a URL to replace it or null to detach.",
|
|
171
|
+
edit: [
|
|
172
|
+
"Edit one document component. content is one component, named by its type; each type's fields are in the Components section of session_get_instructions (authoring or scratchpad).",
|
|
173
|
+
"Anchors are head/path#L10-L20 or base/path#L7, or diff/path#L84-R90 for a range across sides, with repository-relative paths and 1-based inclusive lines. pins {repositoryId, head, base?} say which commits anchors quote: a step, frame, attachment or operation without pins uses its block's, and a block without them the review's.",
|
|
174
|
+
"insert adds content: parentId nests it, afterId places it, and omitted placement appends (on the scratchpad, it lands at the top). update patches a component's own fields; null removes an optional one. A section's children and a diagram's units change through their own IDs; a call stack's frames change by replacing the call stack. move and remove take a targetId; removing a flow_node removes its edges.",
|
|
175
|
+
"The host assigns short durable IDs. The result names the edited component and, for an insert or replace, its first-level children, and quotes the first and last line of each anchor the edit added or changed (the first 20; unquotedAnchors counts the rest): check they are the lines you meant. Accepted edits save at once.",
|
|
176
|
+
"While a reader may be watching, write small and often: one paragraph per edit. Insert a new diagram whole. Change one already drawn a unit at a time (a flow_node, flow_edge or step, with parentId naming the diagram), and link each added flow_node to a node already drawn."
|
|
177
|
+
].join("\n"),
|
|
178
|
+
lens_edit: "Edit one Diff-view lens. Lenses partition the review's change for the Diff view; they sit beside the document (never in it) and version with it. The host assigns durable lens IDs; updates replace only the fields supplied. Write one lens per call while a reader may be watching; each draws in on the Diffs page. Pass your activityId so your courier draws each lens. The result identifies the lens and reports uncategorized: changed lines no lens selects yet, grouped by file. Keep adding lenses until it is empty or what remains is deliberate. review_lens_get reads the current lenses and gaps.",
|
|
179
|
+
rename: "Change the review title.",
|
|
180
|
+
restore: "Restore title, source pins, PR identity and content from a saved version.",
|
|
181
|
+
attention: "Mark a review viewed, dismissed or restored without changing its content.",
|
|
182
|
+
delete: "Permanently delete this review and its history."
|
|
183
|
+
};
|
|
184
|
+
const tool = (name, description, schema, method, path, commandType) => ({
|
|
185
|
+
name: `review_${name}`,
|
|
186
|
+
description,
|
|
187
|
+
inputSchema: {
|
|
188
|
+
...z.toJSONSchema(schema, { io: "input" }),
|
|
189
|
+
type: "object"
|
|
190
|
+
},
|
|
191
|
+
method,
|
|
192
|
+
path,
|
|
193
|
+
commandType
|
|
194
|
+
});
|
|
195
|
+
return [
|
|
196
|
+
REVIEW_STATUS_TOOL,
|
|
197
|
+
tool("capabilities", "Discover whether Desktop is available and optional software-map generation is enabled. Read before authoring.", z.strictObject({}), "GET", "/capabilities"),
|
|
198
|
+
tool("get_instructions", "Read Whiteboard's guidance before creating or editing a review. The default topic gives the authoring workflow; \"file-lenses\" covers Diff-view file lenses." + (traceEnabled ? " Call review_get_instructions({topic:\"trace-archaeology\"}) for why code exists, what an agent was thinking, or whether an agent solved this before." : "") + (scratchpadAvailable ? " When the user asks in conversation to be shown how code works or wants a diagram, without asking for a review, draw it on the scratchpad rather than answering only in chat: call review_get_instructions({topic:\"scratchpad\"}) first. A request for a review or to use Whiteboard means authoring a review with the default topic." : ""), instructionsQuerySchema.partial(), "GET", "/instructions"),
|
|
199
|
+
tool("activity_begin", "Show that you are working on a review: the reader sees your focus and your courier drawing your edits, and the review reads as finished only once every agent has ended. Returns an activityId: pass it to session_edit and session_lens_edit so your courier draws them, and to review_activity_update and review_activity_end. Nothing is locked; other agents can work on the review too. The activity expires after 3 minutes without an edit or update.", activityBeginSchema.extend(review), "POST", "/:reviewId/activity/begin"),
|
|
200
|
+
tool("activity_update", "Change your focus, or keep your activity alive during long reads or pauses; an edit carrying your activityId already keeps it alive. Omitted focus preserves it and null clears it. Fails once the activity has expired: begin a new one.", activityUpdateSchema.extend(review), "POST", "/:reviewId/activity/update"),
|
|
201
|
+
tool("activity_end", "End your activity when your part is finished: readers treat a review with content and no live activity as ready. Ending it creates no document version. Ending twice, or an expired activity, changes nothing.", activityEndSchema.extend(review), "POST", "/:reviewId/activity/end"),
|
|
202
|
+
...commandSchema.shape.operation.options.map((operation) => {
|
|
203
|
+
const type = operation.shape.type.value;
|
|
204
|
+
const { type: _type, repositoryId: _repositoryId, ...fields } = operation.shape;
|
|
205
|
+
return tool(type, descriptions[type], z.strictObject({
|
|
206
|
+
...fields,
|
|
207
|
+
...type === "create" && {
|
|
208
|
+
target: pathTargetSchema.optional(),
|
|
209
|
+
repositoryPath: id.optional().describe("Only with pullRequestUrl and no target: the local checkout to fetch the PR into. Default: the existing review's, else the first registered checkout with a remote for the PR's repository."),
|
|
210
|
+
open: z.boolean().optional()
|
|
211
|
+
},
|
|
212
|
+
...type === "set_target" && { target: pathTargetSchema },
|
|
213
|
+
...type === "edit" && { edit: publishedEditSchema }
|
|
214
|
+
}), "POST", "/commands", type);
|
|
215
|
+
}),
|
|
216
|
+
tool("list", "List saved reviews.", z.strictObject({}), "GET", ""),
|
|
217
|
+
tool("get", "Read a readable, nested text outline with editable IDs. targetId reads one component in full; full:true reads all content. Use format:json for raw node data or snapshots instead of text.", z.strictObject({
|
|
218
|
+
...review,
|
|
219
|
+
...inspectQuerySchema.shape
|
|
220
|
+
}), "GET", "/:reviewId/inspect"),
|
|
221
|
+
tool("lens_get", "Read the review's Diff-view lenses as authored (ids, titles, targets), each lens's resolved fileCount (and unavailable reason, if any), and uncategorized: the changed lines no lens selects yet, by file.", z.strictObject(review), "GET", "/:reviewId/lenses"),
|
|
222
|
+
tool("history", "List saved document versions.", z.strictObject(review), "GET", "/:reviewId/history"),
|
|
223
|
+
tool("open", "Show an existing review immediately and prepare current pinned checkouts in the background. Returns softwareMapEnabled and any already-recorded environmentIssues. Missing optional setup is not an issue; use review_environment to recheck.", z.strictObject(review), "POST", "/:reviewId/open"),
|
|
224
|
+
tool("environment", "Acquire and recheck this review's current base/head language checkouts (not historical or selected commits). Returns acquisition issues, not full LSP health. Missing optional setup and failed setup with a usable checkout stay silent. Set retry:true to rerun failed preparation after an actual language-feature failure; preparation runs in the background.", z.strictObject({
|
|
225
|
+
...review,
|
|
226
|
+
retry: z.boolean().optional()
|
|
227
|
+
}), "POST", "/:reviewId/environment"),
|
|
228
|
+
tool("workspace_cleanup", "Inspect failed cleanup of retired review-owned checkouts. Supply workspaceId to retry removal of that checkout. This does not remove active review checkouts.", z.strictObject({ workspaceId: id.optional() }), "POST", "/workspace-cleanup"),
|
|
229
|
+
tool("register_repository", "Register a local Git or jj repository and return its id, for pins on scratchpad blocks. session_create and session_set_target take the checkout's path instead.", z.strictObject({ path: id }), "POST", "/repositories"),
|
|
230
|
+
tool("upload", "Retain an image or trace for use in a review. kind:\"image\" takes base64; kind:\"trace\" takes trace. Reusing an upload ID requires identical content; rejected uploads are not saved.", uploadInput, "POST", "/resources")
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
export { scratchpadAvailable as a, readQuerySchemas as c, renderInstructions as i, REVIEW_STATUS_TOOL as n, inspectQuerySchema as o, instructionsQuerySchema as r, queryAnchor as s, authoringTools as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"0.2.0","commit":"2c6b8b5b8fe7c3435cf223f6a12a87e507ccf6f1","dirty":true,"builtAt":"2026-10-05T19:58:31.957Z"}
|