@dev.fast/whiteboard 0.0.0-stage → 0.2.1-preview.20261005.98
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,219 @@
|
|
|
1
|
+
import { PROTOCOL_VERSION, client, methods } from "@agentclientprotocol/sdk";
|
|
2
|
+
import { errorMessage } from "@dev.fast/trace-core";
|
|
3
|
+
import {
|
|
4
|
+
type AskAgentLauncher,
|
|
5
|
+
askAgentTakesMcp,
|
|
6
|
+
askAgents,
|
|
7
|
+
} from "@review/ask/agents.js";
|
|
8
|
+
import { choicesOf, commandOf } from "@review/ask/protocol.js";
|
|
9
|
+
import type {
|
|
10
|
+
AskAgentId,
|
|
11
|
+
AskCommand,
|
|
12
|
+
AskOffer,
|
|
13
|
+
} from "@review/ask/thread-state.js";
|
|
14
|
+
import {
|
|
15
|
+
type AskMcpServers,
|
|
16
|
+
AskThread,
|
|
17
|
+
type AskThreadLimits,
|
|
18
|
+
type AskThreadStart,
|
|
19
|
+
askThreadLimits,
|
|
20
|
+
} from "@review/ask/thread.js";
|
|
21
|
+
|
|
22
|
+
/** How long an agent may take to say what it offers. */
|
|
23
|
+
const OFFER_TIMEOUT_MS = 30_000;
|
|
24
|
+
|
|
25
|
+
/** How long after its session opens an agent may take to list its
|
|
26
|
+
* commands, which it sends on its own rather than in the response. */
|
|
27
|
+
const COMMANDS_WAIT_MS = 2_000;
|
|
28
|
+
|
|
29
|
+
/** What an agent offers, from a session it starts and leaves without
|
|
30
|
+
* asking anything: with the model given, where it offers that model, since
|
|
31
|
+
* the efforts on offer depend on it. */
|
|
32
|
+
async function offeredBy(
|
|
33
|
+
launch: AskAgentLauncher,
|
|
34
|
+
agent: AskAgentId,
|
|
35
|
+
cwd: string,
|
|
36
|
+
model?: string,
|
|
37
|
+
): Promise<AskOffer> {
|
|
38
|
+
const process = await launch(agent, cwd);
|
|
39
|
+
// Stopping the process ends the connection, which fails its requests.
|
|
40
|
+
const timer = setTimeout(() => process.stop(), OFFER_TIMEOUT_MS);
|
|
41
|
+
let listed: (commands: AskCommand[]) => void = () => {};
|
|
42
|
+
|
|
43
|
+
const commands = new Promise<AskCommand[]>((resolve) => {
|
|
44
|
+
listed = resolve;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const connection = process.connect(
|
|
49
|
+
client({ name: "whiteboard" }).onNotification(
|
|
50
|
+
methods.client.session.update,
|
|
51
|
+
({ params: { update } }) => {
|
|
52
|
+
if (update.sessionUpdate === "available_commands_update")
|
|
53
|
+
listed(update.availableCommands.map(commandOf));
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const initialized = await connection.agent.request(
|
|
59
|
+
methods.agent.initialize,
|
|
60
|
+
{
|
|
61
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
62
|
+
clientCapabilities: { _meta: { parameterizedModelPicker: true } },
|
|
63
|
+
clientInfo: { name: "whiteboard", title: "Whiteboard", version: "1" },
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const session = await connection.agent.request(methods.agent.session.new, {
|
|
68
|
+
cwd,
|
|
69
|
+
mcpServers: [],
|
|
70
|
+
_meta: askAgents[agent].sessionMeta,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const offer: AskOffer = {
|
|
74
|
+
choices: {},
|
|
75
|
+
accepts: {
|
|
76
|
+
image:
|
|
77
|
+
initialized.agentCapabilities?.promptCapabilities?.image === true,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let found = choicesOf(session.configOptions);
|
|
82
|
+
const models = found.get("model");
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
model &&
|
|
86
|
+
models &&
|
|
87
|
+
model !== models.select.current &&
|
|
88
|
+
models.select.options.some((option) => option.value === model)
|
|
89
|
+
)
|
|
90
|
+
found = choicesOf(
|
|
91
|
+
(
|
|
92
|
+
await connection.agent.request(
|
|
93
|
+
methods.agent.session.setConfigOption,
|
|
94
|
+
{
|
|
95
|
+
sessionId: session.sessionId,
|
|
96
|
+
configId: models.configId,
|
|
97
|
+
value: model,
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
).configOptions,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
for (const [kind, { select }] of found) offer.choices[kind] = select;
|
|
104
|
+
|
|
105
|
+
let wait: ReturnType<typeof setTimeout> | undefined;
|
|
106
|
+
|
|
107
|
+
const offered = await Promise.race([
|
|
108
|
+
commands,
|
|
109
|
+
new Promise<undefined>((resolve) => {
|
|
110
|
+
wait = setTimeout(() => resolve(undefined), COMMANDS_WAIT_MS);
|
|
111
|
+
}),
|
|
112
|
+
]).finally(() => clearTimeout(wait));
|
|
113
|
+
|
|
114
|
+
if (offered) offer.commands = offered;
|
|
115
|
+
|
|
116
|
+
return offer;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const diagnostics = process.diagnostics().trim();
|
|
119
|
+
|
|
120
|
+
throw new Error(
|
|
121
|
+
`${askAgents[agent].name} did not say what it offers. ${errorMessage(error)}${diagnostics ? `\n${diagnostics}` : ""}`,
|
|
122
|
+
{ cause: error },
|
|
123
|
+
);
|
|
124
|
+
} finally {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
process.stop();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Whiteboard's own tools, for every Ask session. */
|
|
131
|
+
export interface AskTools {
|
|
132
|
+
/** Its MCP server, for agents whose model gets it. */
|
|
133
|
+
mcpServers?: AskMcpServers;
|
|
134
|
+
/** A shell command that runs Whiteboard's CLI against this server, for
|
|
135
|
+
* the agents that don't. */
|
|
136
|
+
cli?: () => string | undefined;
|
|
137
|
+
/** Whether an agent's model gets the MCP servers it is given. */
|
|
138
|
+
takesMcp?: (agent: AskAgentId) => Promise<boolean>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** How an agent reaches Whiteboard's tools, which its first prompt says. */
|
|
142
|
+
export type AskToolsReach =
|
|
143
|
+
| { kind: "mcp" }
|
|
144
|
+
| { kind: "cli"; command: string }
|
|
145
|
+
| undefined;
|
|
146
|
+
|
|
147
|
+
/** The live Ask threads of one server; they end with it. */
|
|
148
|
+
export class AskThreads {
|
|
149
|
+
private readonly threads = new Map<string, AskThread>();
|
|
150
|
+
/** One question to each agent at a time about what it offers with a
|
|
151
|
+
* model. */
|
|
152
|
+
private readonly offers = new Map<string, Promise<AskOffer>>();
|
|
153
|
+
|
|
154
|
+
private readonly mcpServers: AskMcpServers;
|
|
155
|
+
|
|
156
|
+
constructor(
|
|
157
|
+
private readonly launch: AskAgentLauncher,
|
|
158
|
+
private readonly tools: AskTools = {},
|
|
159
|
+
private readonly limits: AskThreadLimits = askThreadLimits,
|
|
160
|
+
) {
|
|
161
|
+
this.mcpServers = tools.mcpServers ?? (() => []);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** How the agent's sessions reach Whiteboard's tools: the MCP tools they
|
|
165
|
+
* are given where its model gets them, else the CLI from its shell. */
|
|
166
|
+
async reach(agent: AskAgentId): Promise<AskToolsReach> {
|
|
167
|
+
if (
|
|
168
|
+
this.mcpServers().length &&
|
|
169
|
+
(await (this.tools.takesMcp ?? askAgentTakesMcp)(agent))
|
|
170
|
+
)
|
|
171
|
+
return { kind: "mcp" };
|
|
172
|
+
const command = this.tools.cli?.();
|
|
173
|
+
|
|
174
|
+
return command ? { kind: "cli", command } : undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
open(start: AskThreadStart) {
|
|
178
|
+
const thread = new AskThread(
|
|
179
|
+
this.launch,
|
|
180
|
+
start,
|
|
181
|
+
this.mcpServers,
|
|
182
|
+
this.limits,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
this.threads.set(thread.id, thread);
|
|
186
|
+
void thread.open();
|
|
187
|
+
|
|
188
|
+
return thread;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
get(id: string) {
|
|
192
|
+
return this.threads.get(id);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** What the agent offers to choose before anything is asked of it, with
|
|
196
|
+
* its own model or the one given. */
|
|
197
|
+
offered(agent: AskAgentId, cwd: string, model?: string) {
|
|
198
|
+
const key = JSON.stringify([agent, model]);
|
|
199
|
+
let offer = this.offers.get(key);
|
|
200
|
+
|
|
201
|
+
if (!offer) {
|
|
202
|
+
offer = offeredBy(this.launch, agent, cwd, model).finally(() =>
|
|
203
|
+
this.offers.delete(key),
|
|
204
|
+
);
|
|
205
|
+
this.offers.set(key, offer);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return offer;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
close(id: string) {
|
|
212
|
+
this.threads.get(id)?.close();
|
|
213
|
+
this.threads.delete(id);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
closeAll() {
|
|
217
|
+
for (const id of [...this.threads.keys()]) this.close(id);
|
|
218
|
+
}
|
|
219
|
+
}
|
package/src/ask/watch.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AskUpdate } from "@review/ask/thread-state.js";
|
|
2
|
+
|
|
3
|
+
/** What a watch stream needs from a thread. */
|
|
4
|
+
export interface AskWatchable {
|
|
5
|
+
snapshot(): AskUpdate;
|
|
6
|
+
subscribe(listener: (update: AskUpdate) => void): () => void;
|
|
7
|
+
onClose(closed: () => void): () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Changes a slow reader may fall behind by before its backlog is replaced
|
|
11
|
+
* with one snapshot, which bounds the memory a stalled panel holds. */
|
|
12
|
+
export const ASK_WATCH_BACKLOG = 256;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* NDJSON of a thread: a snapshot, then each change in `seq` order. Answer
|
|
16
|
+
* tokens arrive as appends, so a reply costs about what the agent sent.
|
|
17
|
+
*/
|
|
18
|
+
export function watchAskThread(
|
|
19
|
+
thread: AskWatchable,
|
|
20
|
+
backlog = ASK_WATCH_BACKLOG,
|
|
21
|
+
): Response {
|
|
22
|
+
const encoder = new TextEncoder();
|
|
23
|
+
let pending: AskUpdate[] = [thread.snapshot()];
|
|
24
|
+
let stop = () => {};
|
|
25
|
+
|
|
26
|
+
/** The thread closed; the stream ends once `pending` is sent. */
|
|
27
|
+
let ended = false;
|
|
28
|
+
/** The stream ended, so nothing more may be enqueued. */
|
|
29
|
+
let finished = false;
|
|
30
|
+
|
|
31
|
+
const flush = (controller: ReadableStreamDefaultController<Uint8Array>) => {
|
|
32
|
+
while (pending.length && (controller.desiredSize ?? 0) > 0)
|
|
33
|
+
controller.enqueue(
|
|
34
|
+
encoder.encode(`${JSON.stringify(pending.shift())}\n`),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// A closed thread sends nothing more; its watchers hear it end.
|
|
38
|
+
if (ended && !pending.length && !finished) {
|
|
39
|
+
finished = true;
|
|
40
|
+
controller.close();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const body = new ReadableStream<Uint8Array>({
|
|
45
|
+
start(controller) {
|
|
46
|
+
const unsubscribe = thread.subscribe((update) => {
|
|
47
|
+
pending.push(update);
|
|
48
|
+
|
|
49
|
+
// The snapshot already holds every change it would replay.
|
|
50
|
+
if (pending.length > backlog) pending = [thread.snapshot()];
|
|
51
|
+
flush(controller);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const unclose = thread.onClose(() => {
|
|
55
|
+
ended = true;
|
|
56
|
+
unsubscribe();
|
|
57
|
+
flush(controller);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
stop = () => {
|
|
61
|
+
unsubscribe();
|
|
62
|
+
unclose();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
flush(controller);
|
|
66
|
+
},
|
|
67
|
+
pull: flush,
|
|
68
|
+
cancel() {
|
|
69
|
+
stop();
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return new Response(body, {
|
|
74
|
+
headers: {
|
|
75
|
+
"content-type": "application/x-ndjson",
|
|
76
|
+
"cache-control": "no-store",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|