@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,703 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { type Server, createServer } from "node:http";
|
|
5
|
+
import type { AddressInfo } from "node:net";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
|
|
9
|
+
import { jsonString } from "@dev.fast/json";
|
|
10
|
+
import {
|
|
11
|
+
type JsonObject,
|
|
12
|
+
type JsonValue,
|
|
13
|
+
REVIEW_DESKTOP_DISCOVERY_VERSION,
|
|
14
|
+
type ReviewCliInstallApplyResponse,
|
|
15
|
+
type ReviewDesktopDiscovery,
|
|
16
|
+
type ReviewTutorialOpenResponse,
|
|
17
|
+
isJsonObject,
|
|
18
|
+
isObjectValue,
|
|
19
|
+
parseReviewCliInstallApplyRequest,
|
|
20
|
+
reviewDiffrSummarizerInputSchema,
|
|
21
|
+
} from "@dev.fast/review-protocol";
|
|
22
|
+
import { shellQuote, writePrivateJsonAtomic } from "@dev.fast/trace-core";
|
|
23
|
+
import type { AskTools } from "@review/ask/threads.js";
|
|
24
|
+
import {
|
|
25
|
+
applyCliInstall,
|
|
26
|
+
declineCliInstall,
|
|
27
|
+
finishCliInstallUpdate,
|
|
28
|
+
removeCliInstall,
|
|
29
|
+
removeLegacyReviewSkills,
|
|
30
|
+
resetCliInstall,
|
|
31
|
+
resolveCliInstallStatus,
|
|
32
|
+
skipCliInstall,
|
|
33
|
+
} from "@review/cli-install";
|
|
34
|
+
import {
|
|
35
|
+
REVIEW_INSTANCE_ENV,
|
|
36
|
+
type ReviewInstanceIdentity,
|
|
37
|
+
} from "@review/desktop-discovery";
|
|
38
|
+
import { readReviewPackageVersion } from "@review/package-paths";
|
|
39
|
+
import type { LocalReviewData } from "@review/review-api/local-data.js";
|
|
40
|
+
import type { ReviewStore } from "@review/review-api/store.js";
|
|
41
|
+
import {
|
|
42
|
+
devReviewHome,
|
|
43
|
+
reviewInstanceDiscoveryPath,
|
|
44
|
+
reviewLegacyDiscoveryPath,
|
|
45
|
+
} from "@review/review-home-paths";
|
|
46
|
+
import {
|
|
47
|
+
readScratchpadEnabled,
|
|
48
|
+
writeScratchpadEnabled,
|
|
49
|
+
} from "@review/review-preferences";
|
|
50
|
+
import {
|
|
51
|
+
ReviewTelemetry,
|
|
52
|
+
type ReviewTelemetryContext,
|
|
53
|
+
} from "@review/review-telemetry";
|
|
54
|
+
import type { SharedReviewStore } from "@review/sharing/import.js";
|
|
55
|
+
import { z } from "zod";
|
|
56
|
+
|
|
57
|
+
import { aliasInstallationToAccount } from "./account-alias";
|
|
58
|
+
import { CrashReportRequestSchema, reportCrashDump } from "./crash-report";
|
|
59
|
+
import {
|
|
60
|
+
readDiffrConfig,
|
|
61
|
+
saveDiffrSummarizer,
|
|
62
|
+
setDiffrConfigValue,
|
|
63
|
+
testDiffrSummarizer,
|
|
64
|
+
} from "./diffr-config";
|
|
65
|
+
import { installFullDiffr } from "./diffr-languages.js";
|
|
66
|
+
import {
|
|
67
|
+
GlobalReviewDesktopVerbRelay,
|
|
68
|
+
type ReviewDesktopVerbRelay,
|
|
69
|
+
} from "./global-verb-relay";
|
|
70
|
+
import { createNodeRequestListener, readBoundedRequestJson } from "./hono-http";
|
|
71
|
+
import { ReviewServerError } from "./http-json";
|
|
72
|
+
import { createJsonReviewReporting } from "./json-review-reporting";
|
|
73
|
+
import { reviewLifecycleTelemetry } from "./review-lifecycle-telemetry";
|
|
74
|
+
import { ReviewOpenWatchdog } from "./review-open-watchdog";
|
|
75
|
+
import { createWhiteboardCore, serverJson } from "./review-server-core";
|
|
76
|
+
import { invalidateStructuralComparisons } from "./structural-comparisons.js";
|
|
77
|
+
import { createTutorialService } from "./tutorial-service";
|
|
78
|
+
import { captureSanitizedUiTelemetry } from "./ui-telemetry";
|
|
79
|
+
|
|
80
|
+
const TUTORIAL_LIFECYCLE_LOCK_KEY = "tutorial-lifecycle";
|
|
81
|
+
|
|
82
|
+
const UUID_PATTERN =
|
|
83
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
84
|
+
|
|
85
|
+
export interface GlobalReviewServerInput {
|
|
86
|
+
/** The desktop host owns this shared database and closes it after the server. */
|
|
87
|
+
reviewStore: ReviewStore;
|
|
88
|
+
sharedReviews?: SharedReviewStore;
|
|
89
|
+
reviewData: LocalReviewData;
|
|
90
|
+
appPid: number;
|
|
91
|
+
packageRoot: string;
|
|
92
|
+
toolingRoot: string;
|
|
93
|
+
cliRuntimePath?: string;
|
|
94
|
+
port: number;
|
|
95
|
+
token?: string;
|
|
96
|
+
instanceId?: string;
|
|
97
|
+
identity?: ReviewInstanceIdentity;
|
|
98
|
+
discoveryPath?: string;
|
|
99
|
+
telemetry?: ReviewTelemetry;
|
|
100
|
+
relay?: ReviewDesktopVerbRelay;
|
|
101
|
+
/** Electron's Review crash dump directory; `/crash-reports` reads only inside it. */
|
|
102
|
+
crashDumpsDir?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface GlobalReviewServer {
|
|
106
|
+
readonly discovery: ReviewDesktopDiscovery;
|
|
107
|
+
readonly url: string;
|
|
108
|
+
listen(): Promise<void>;
|
|
109
|
+
close(reason?: "app-exit"): Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createGlobalReviewServer(
|
|
113
|
+
input: GlobalReviewServerInput,
|
|
114
|
+
): GlobalReviewServer {
|
|
115
|
+
const instanceId = input.instanceId ?? crypto.randomUUID();
|
|
116
|
+
const token = input.token ?? crypto.randomBytes(32).toString("base64url");
|
|
117
|
+
// Port 0 asks the OS to choose, so nothing may assume the requested port is
|
|
118
|
+
// the bound one until listen() has resolved.
|
|
119
|
+
let boundPort = input.port;
|
|
120
|
+
const urlForBoundPort = () => `http://127.0.0.1:${boundPort}`;
|
|
121
|
+
const identity = input.identity ?? { key: "stable", channel: "stable" };
|
|
122
|
+
|
|
123
|
+
// Stable also writes the pre-instance server.json, so a CLI or shim that
|
|
124
|
+
// predates instances still finds it.
|
|
125
|
+
const discoveryPaths = input.discoveryPath
|
|
126
|
+
? [input.discoveryPath]
|
|
127
|
+
: identity.key === "stable"
|
|
128
|
+
? [reviewInstanceDiscoveryPath("stable"), reviewLegacyDiscoveryPath()]
|
|
129
|
+
: [reviewInstanceDiscoveryPath(identity.key)];
|
|
130
|
+
|
|
131
|
+
const telemetry = input.telemetry ?? ReviewTelemetry.fromEnv();
|
|
132
|
+
|
|
133
|
+
// Each window attaches its own stream; one at a time keeps a verb from
|
|
134
|
+
// opening in every window, and the next takes over when it goes.
|
|
135
|
+
const relay =
|
|
136
|
+
input.relay ?? new GlobalReviewDesktopVerbRelay({ maxClients: 1 });
|
|
137
|
+
|
|
138
|
+
// This Desktop's own CLI, pinned to this instance so another running
|
|
139
|
+
// Whiteboard never answers it.
|
|
140
|
+
const askCliEnv = () => [
|
|
141
|
+
{ name: REVIEW_INSTANCE_ENV, value: identity.key },
|
|
142
|
+
...(process.versions.electron
|
|
143
|
+
? [{ name: "ELECTRON_RUN_AS_NODE", value: "1" }]
|
|
144
|
+
: []),
|
|
145
|
+
...(process.env.DEV_REVIEW_HOME
|
|
146
|
+
? [{ name: "DEV_REVIEW_HOME", value: process.env.DEV_REVIEW_HOME }]
|
|
147
|
+
: []),
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// Ask sessions get its MCP server, `whiteboard mcp`; an agent whose model
|
|
151
|
+
// would not get it uses `whiteboard api` from its shell instead.
|
|
152
|
+
const askTools: AskTools = {
|
|
153
|
+
mcpServers: () =>
|
|
154
|
+
discovery.cliPath
|
|
155
|
+
? [
|
|
156
|
+
{
|
|
157
|
+
name: "whiteboard",
|
|
158
|
+
command: process.execPath,
|
|
159
|
+
args: [discovery.cliPath, "mcp"],
|
|
160
|
+
env: askCliEnv(),
|
|
161
|
+
},
|
|
162
|
+
]
|
|
163
|
+
: [],
|
|
164
|
+
cli: () =>
|
|
165
|
+
discovery.cliPath &&
|
|
166
|
+
[
|
|
167
|
+
...askCliEnv().map(({ name, value }) => `${name}=${shellQuote(value)}`),
|
|
168
|
+
shellQuote(process.execPath),
|
|
169
|
+
shellQuote(discovery.cliPath),
|
|
170
|
+
].join(" "),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const reviewStore = input.reviewStore;
|
|
174
|
+
|
|
175
|
+
const reviewLocks = new Map<string, Promise<void>>();
|
|
176
|
+
|
|
177
|
+
// A reload or a second window starts the workbench again, but the launch
|
|
178
|
+
// this server belongs to became ready once.
|
|
179
|
+
let appReadyReported = false;
|
|
180
|
+
|
|
181
|
+
const openWatchdog = new ReviewOpenWatchdog({
|
|
182
|
+
onTimeout: (context, elapsedMs) =>
|
|
183
|
+
void telemetry.captureEvent(
|
|
184
|
+
"review_open_timeout",
|
|
185
|
+
{ elapsed_ms: elapsedMs },
|
|
186
|
+
context,
|
|
187
|
+
),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const tutorial = createTutorialService({
|
|
191
|
+
packageRoot: input.packageRoot,
|
|
192
|
+
store: reviewStore,
|
|
193
|
+
data: input.reviewData,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
let closing = false;
|
|
197
|
+
const cliPath = path.join(input.packageRoot, "dist", "cli.js");
|
|
198
|
+
|
|
199
|
+
// The scratchpad preference, read once at listen and kept current by the
|
|
200
|
+
// settings endpoint below: this server is the only writer while it runs.
|
|
201
|
+
let scratchpadEnabled = false;
|
|
202
|
+
|
|
203
|
+
const discovery: ReviewDesktopDiscovery = {
|
|
204
|
+
version: REVIEW_DESKTOP_DISCOVERY_VERSION,
|
|
205
|
+
instanceId,
|
|
206
|
+
url: urlForBoundPort(),
|
|
207
|
+
appPid: input.appPid,
|
|
208
|
+
serverPid: process.pid,
|
|
209
|
+
token,
|
|
210
|
+
startedAt: Date.now(),
|
|
211
|
+
...identity,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// A source-run dev server has no built CLI to advertise.
|
|
215
|
+
if (existsSync(cliPath)) {
|
|
216
|
+
discovery.cliPath = cliPath;
|
|
217
|
+
discovery.cliVersion = readReviewPackageVersion(
|
|
218
|
+
pathToFileURL(cliPath).href,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (input.cliRuntimePath && existsSync(input.cliRuntimePath)) {
|
|
222
|
+
discovery.cliRuntimePath = input.cliRuntimePath;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const core = createWhiteboardCore({
|
|
227
|
+
profile: {
|
|
228
|
+
store: reviewStore,
|
|
229
|
+
data: input.reviewData,
|
|
230
|
+
shared: input.sharedReviews,
|
|
231
|
+
},
|
|
232
|
+
relay,
|
|
233
|
+
token,
|
|
234
|
+
instanceId,
|
|
235
|
+
scratchpad: () => scratchpadEnabled,
|
|
236
|
+
status: () => {
|
|
237
|
+
const { key, channel, checkout, appVersion, cliVersion, instanceId } =
|
|
238
|
+
discovery;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
key: key ?? null,
|
|
242
|
+
channel: channel ?? null,
|
|
243
|
+
checkout: checkout ?? null,
|
|
244
|
+
appVersion: appVersion ?? null,
|
|
245
|
+
cliVersion: cliVersion ?? null,
|
|
246
|
+
instanceId,
|
|
247
|
+
url: urlForBoundPort(),
|
|
248
|
+
home: devReviewHome(),
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
hooks: reviewLifecycleTelemetry(
|
|
252
|
+
telemetry,
|
|
253
|
+
(reviewId) => reviewStore.summary(reviewId)?.firstCreatedAt,
|
|
254
|
+
() => aliasInstallationToAccount(telemetry),
|
|
255
|
+
),
|
|
256
|
+
ask: { tools: askTools },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const { app } = core;
|
|
260
|
+
|
|
261
|
+
app.route(
|
|
262
|
+
"/reviews-api",
|
|
263
|
+
createJsonReviewReporting(input.reviewStore, telemetry, {
|
|
264
|
+
shared: input.sharedReviews,
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
app.route("/reviews-api", core.api);
|
|
269
|
+
app.get("/preferences/scratchpad", () =>
|
|
270
|
+
serverJson(200, { enabled: scratchpadEnabled }),
|
|
271
|
+
);
|
|
272
|
+
app.put("/preferences/scratchpad", async (context) => {
|
|
273
|
+
const request = z
|
|
274
|
+
.object({ enabled: z.boolean() })
|
|
275
|
+
.safeParse(await readBoundedRequestJson(context.req.raw));
|
|
276
|
+
|
|
277
|
+
if (!request.success)
|
|
278
|
+
throw new ReviewServerError("enabled must be a boolean.", 400);
|
|
279
|
+
|
|
280
|
+
scratchpadEnabled = await writeScratchpadEnabled(request.data.enabled);
|
|
281
|
+
|
|
282
|
+
// Home watches the catalog; the pad appears or goes without a store write.
|
|
283
|
+
if (scratchpadEnabled) await reviewStore.ensureScratchpad();
|
|
284
|
+
reviewStore.invalidateCatalog();
|
|
285
|
+
|
|
286
|
+
return serverJson(200, { enabled: scratchpadEnabled });
|
|
287
|
+
});
|
|
288
|
+
app.post("/app/focus", async () => {
|
|
289
|
+
const result = await relay.dispatch({
|
|
290
|
+
name: "focusWindow",
|
|
291
|
+
args: {},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return serverJson(result.ok ? 200 : 409, result);
|
|
295
|
+
});
|
|
296
|
+
app.post("/telemetry/event", async (context) => {
|
|
297
|
+
try {
|
|
298
|
+
const body = await readBoundedRequestJson(context.req.raw, undefined, {});
|
|
299
|
+
const payload: JsonObject = isJsonObject(body) ? body : {};
|
|
300
|
+
let flushBeforeOptOut = false;
|
|
301
|
+
|
|
302
|
+
if (payload.name === "app_ready") {
|
|
303
|
+
if (appReadyReported) return serverJson(200, { ok: true });
|
|
304
|
+
|
|
305
|
+
appReadyReported = true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// The workbench has no reader on the stored review; the server does, so
|
|
309
|
+
// `session_started`'s source_kind is filled in here rather than trusted
|
|
310
|
+
// from the client.
|
|
311
|
+
const eventProperties: JsonObject = isJsonObject(payload.properties)
|
|
312
|
+
? { ...payload.properties }
|
|
313
|
+
: {};
|
|
314
|
+
|
|
315
|
+
if (payload.name === "session_started") {
|
|
316
|
+
// Both are server-derived only: a client can never assert its own
|
|
317
|
+
// source_kind or agent_kind, so a review the store cannot resolve
|
|
318
|
+
// must not fall back to whatever the client sent.
|
|
319
|
+
delete eventProperties.source_kind;
|
|
320
|
+
delete eventProperties.agent_kind;
|
|
321
|
+
|
|
322
|
+
const rawContext = isJsonObject(payload.context) ? payload.context : {};
|
|
323
|
+
|
|
324
|
+
const sourceKind = sessionStartedSourceKind(
|
|
325
|
+
reviewStore,
|
|
326
|
+
jsonString(rawContext.reviewUuid),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (sourceKind) eventProperties.source_kind = sourceKind;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
await captureSanitizedUiTelemetry(
|
|
333
|
+
telemetry,
|
|
334
|
+
context.req.raw,
|
|
335
|
+
payload.name,
|
|
336
|
+
eventProperties,
|
|
337
|
+
(event, eventContext) => {
|
|
338
|
+
flushBeforeOptOut =
|
|
339
|
+
event.event === "review_setting_changed" &&
|
|
340
|
+
event.properties.setting === "telemetry_enabled" &&
|
|
341
|
+
event.properties.enabled === false;
|
|
342
|
+
watchSessionOpen(openWatchdog, event.event, eventContext);
|
|
343
|
+
},
|
|
344
|
+
payload.error,
|
|
345
|
+
payload.context,
|
|
346
|
+
payload.occurredAt,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
if (flushBeforeOptOut) await telemetry.flush(500);
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(error);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return serverJson(200, { ok: true });
|
|
355
|
+
});
|
|
356
|
+
app.post("/crash-reports", async (context) => {
|
|
357
|
+
const body = CrashReportRequestSchema.safeParse(
|
|
358
|
+
await readBoundedRequestJson(context.req.raw),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
if (!body.success)
|
|
362
|
+
throw new ReviewServerError(
|
|
363
|
+
"dump_path, crashed_at and covered are required.",
|
|
364
|
+
400,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const result = await reportCrashDump(
|
|
368
|
+
telemetry,
|
|
369
|
+
body.data,
|
|
370
|
+
input.crashDumpsDir,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
return serverJson(result.status, result.body);
|
|
374
|
+
});
|
|
375
|
+
app.get("/tutorial/status", async () =>
|
|
376
|
+
serverJson(200, await tutorial.status()),
|
|
377
|
+
);
|
|
378
|
+
app.post("/tutorial/prepare", async () => {
|
|
379
|
+
const prepared = await withReviewLock(
|
|
380
|
+
TUTORIAL_LIFECYCLE_LOCK_KEY,
|
|
381
|
+
prepareTutorialLocked,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return serverJson(200, {
|
|
385
|
+
ok: true,
|
|
386
|
+
reviewUuid: prepared.reviewId,
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
app.post("/tutorial/open", async () => {
|
|
390
|
+
return serverJson(
|
|
391
|
+
200,
|
|
392
|
+
await withReviewLock(TUTORIAL_LIFECYCLE_LOCK_KEY, openTutorialLocked),
|
|
393
|
+
);
|
|
394
|
+
});
|
|
395
|
+
app.delete("/tutorial", async () => {
|
|
396
|
+
await withReviewLock(TUTORIAL_LIFECYCLE_LOCK_KEY, deleteTutorialLocked);
|
|
397
|
+
|
|
398
|
+
return serverJson(200, { ok: true });
|
|
399
|
+
});
|
|
400
|
+
app.post("/diffr-languages/install", async () => {
|
|
401
|
+
try {
|
|
402
|
+
await installFullDiffr();
|
|
403
|
+
invalidateStructuralComparisons();
|
|
404
|
+
|
|
405
|
+
return serverJson(200, { installed: true });
|
|
406
|
+
} catch (error) {
|
|
407
|
+
return serverJson(503, {
|
|
408
|
+
error: error instanceof Error ? error.message : String(error),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
app.get("/diffr-config", async () =>
|
|
414
|
+
serverJson(200, await readDiffrConfig()),
|
|
415
|
+
);
|
|
416
|
+
app.put("/diffr-config", async (context) => {
|
|
417
|
+
const body = await readBoundedRequestJson(context.req.raw);
|
|
418
|
+
|
|
419
|
+
const key = isJsonObject(body) ? jsonString(body.key) : undefined;
|
|
420
|
+
|
|
421
|
+
const value = isJsonObject(body) ? body.value : undefined;
|
|
422
|
+
|
|
423
|
+
if (key === undefined || value === undefined) {
|
|
424
|
+
throw new ReviewServerError("key and value are required.", 400);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return serverJson(200, await setDiffrConfigValue(key, value));
|
|
428
|
+
});
|
|
429
|
+
app.put("/diffr-config/summarizer", async (context) => {
|
|
430
|
+
const input = reviewDiffrSummarizerInputSchema.safeParse(
|
|
431
|
+
await readBoundedRequestJson(context.req.raw),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
if (!input.success)
|
|
435
|
+
throw new ReviewServerError("Invalid summary settings.", 400);
|
|
436
|
+
|
|
437
|
+
return serverJson(200, await saveDiffrSummarizer(input.data));
|
|
438
|
+
});
|
|
439
|
+
app.post("/diffr-config/summarizer/test", async (context) => {
|
|
440
|
+
const input = reviewDiffrSummarizerInputSchema.safeParse(
|
|
441
|
+
await readBoundedRequestJson(context.req.raw),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
if (!input.success)
|
|
445
|
+
throw new ReviewServerError("Invalid summary settings.", 400);
|
|
446
|
+
|
|
447
|
+
return serverJson(200, {
|
|
448
|
+
summary: await testDiffrSummarizer(
|
|
449
|
+
input.data,
|
|
450
|
+
undefined,
|
|
451
|
+
context.req.raw.signal,
|
|
452
|
+
),
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
app.get("/install/status", async () =>
|
|
456
|
+
serverJson(
|
|
457
|
+
200,
|
|
458
|
+
await resolveCliInstallStatus({ packageRoot: input.packageRoot }),
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
app.post("/install/apply", async (context) => {
|
|
462
|
+
const request = parseReviewCliInstallApplyRequest(
|
|
463
|
+
await readBoundedRequestJson(context.req.raw),
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const applyInput: Parameters<typeof applyCliInstall>[0] = {
|
|
467
|
+
packageRoot: input.packageRoot,
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
if (request.shim !== undefined) applyInput.shim = request.shim;
|
|
471
|
+
|
|
472
|
+
if (request.autoUpdate) applyInput.autoUpdate = true;
|
|
473
|
+
|
|
474
|
+
if (request.trace !== undefined) applyInput.trace = request.trace;
|
|
475
|
+
|
|
476
|
+
if (discovery.cliPath) applyInput.cliPath = discovery.cliPath;
|
|
477
|
+
|
|
478
|
+
if (discovery.cliRuntimePath) {
|
|
479
|
+
applyInput.cliRuntimePath = discovery.cliRuntimePath;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const result = await applyCliInstall(applyInput);
|
|
483
|
+
|
|
484
|
+
const body: ReviewCliInstallApplyResponse = {
|
|
485
|
+
ok: result.code === 0,
|
|
486
|
+
output: result.output,
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
if (result.shimPath) body.shimPath = result.shimPath;
|
|
490
|
+
|
|
491
|
+
return serverJson(result.code === 0 ? 200 : 500, body);
|
|
492
|
+
});
|
|
493
|
+
app.post("/install/remove", async (context) => {
|
|
494
|
+
const request = parseReviewCliInstallApplyRequest(
|
|
495
|
+
await readBoundedRequestJson(context.req.raw),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
const removeInput: Parameters<typeof removeCliInstall>[0] = {};
|
|
499
|
+
|
|
500
|
+
if (request.shim) removeInput.shim = true;
|
|
501
|
+
|
|
502
|
+
if (request.trace) removeInput.trace = true;
|
|
503
|
+
const result = await removeCliInstall(removeInput);
|
|
504
|
+
|
|
505
|
+
return serverJson(200, { ok: true, output: result.output });
|
|
506
|
+
});
|
|
507
|
+
app.post("/install/legacy-skills/remove", async () => {
|
|
508
|
+
const { removed } = await removeLegacyReviewSkills();
|
|
509
|
+
|
|
510
|
+
return serverJson(200, { ok: true, removed });
|
|
511
|
+
});
|
|
512
|
+
app.post("/install/finish-update", async () => {
|
|
513
|
+
await finishCliInstallUpdate();
|
|
514
|
+
|
|
515
|
+
return serverJson(200, { ok: true });
|
|
516
|
+
});
|
|
517
|
+
app.post("/install/decline", async () => {
|
|
518
|
+
await declineCliInstall();
|
|
519
|
+
|
|
520
|
+
return serverJson(200, { ok: true });
|
|
521
|
+
});
|
|
522
|
+
app.post("/install/skip", async () => {
|
|
523
|
+
await skipCliInstall();
|
|
524
|
+
|
|
525
|
+
return serverJson(200, { ok: true });
|
|
526
|
+
});
|
|
527
|
+
app.post("/install/reset", async () => {
|
|
528
|
+
await resetCliInstall();
|
|
529
|
+
|
|
530
|
+
return serverJson(200, { ok: true });
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const httpServer = createServer(createNodeRequestListener(app));
|
|
534
|
+
|
|
535
|
+
async function prepareTutorialLocked() {
|
|
536
|
+
return tutorial.prepare();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function openTutorialLocked(): Promise<ReviewTutorialOpenResponse> {
|
|
540
|
+
const snapshot = await prepareTutorialLocked();
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
kind: "api",
|
|
544
|
+
reviewUuid: snapshot.reviewId,
|
|
545
|
+
title: snapshot.title,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function deleteTutorialLocked(): Promise<void> {
|
|
550
|
+
await tutorial.cleanup();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async function withReviewLock<T>(
|
|
554
|
+
reviewUuid: string,
|
|
555
|
+
operation: () => Promise<T>,
|
|
556
|
+
): Promise<T> {
|
|
557
|
+
const previous = reviewLocks.get(reviewUuid) ?? Promise.resolve();
|
|
558
|
+
let release: () => void = () => {};
|
|
559
|
+
|
|
560
|
+
const current = new Promise<void>((resolve) => {
|
|
561
|
+
release = resolve;
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const chain = previous.then(() => current);
|
|
565
|
+
reviewLocks.set(reviewUuid, chain);
|
|
566
|
+
await previous;
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
return await operation();
|
|
570
|
+
} finally {
|
|
571
|
+
release();
|
|
572
|
+
|
|
573
|
+
if (reviewLocks.get(reviewUuid) === chain) reviewLocks.delete(reviewUuid);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
discovery,
|
|
579
|
+
get url() {
|
|
580
|
+
return urlForBoundPort();
|
|
581
|
+
},
|
|
582
|
+
listen: async () => {
|
|
583
|
+
scratchpadEnabled = await readScratchpadEnabled();
|
|
584
|
+
boundPort = await listen(httpServer, input.port);
|
|
585
|
+
discovery.url = urlForBoundPort();
|
|
586
|
+
|
|
587
|
+
for (const discoveryPath of discoveryPaths)
|
|
588
|
+
await writePrivateJsonAtomic(discoveryPath, discovery);
|
|
589
|
+
},
|
|
590
|
+
close: async () => {
|
|
591
|
+
if (closing) return;
|
|
592
|
+
closing = true;
|
|
593
|
+
core.close();
|
|
594
|
+
|
|
595
|
+
for (const discoveryPath of discoveryPaths)
|
|
596
|
+
await removeMatchingDiscovery(discoveryPath, discovery);
|
|
597
|
+
relay.close();
|
|
598
|
+
openWatchdog.dispose();
|
|
599
|
+
|
|
600
|
+
await closeHttpServer(httpServer);
|
|
601
|
+
await telemetry.shutdown(1_500);
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function watchSessionOpen(
|
|
607
|
+
watchdog: ReviewOpenWatchdog,
|
|
608
|
+
event: string,
|
|
609
|
+
context: ReviewTelemetryContext | undefined,
|
|
610
|
+
): void {
|
|
611
|
+
const reviewUuid = context?.reviewUuid;
|
|
612
|
+
const presentationSessionId = context?.presentationSessionId;
|
|
613
|
+
|
|
614
|
+
if (!reviewUuid || !presentationSessionId) return;
|
|
615
|
+
|
|
616
|
+
if (event === "review_session_started")
|
|
617
|
+
watchdog.started({ reviewUuid, presentationSessionId });
|
|
618
|
+
else if (
|
|
619
|
+
event === "review_review_presented" ||
|
|
620
|
+
event === "review_session_ended"
|
|
621
|
+
)
|
|
622
|
+
watchdog.presented(presentationSessionId);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* `session_started`'s `source_kind`: the opened review's target kind, or
|
|
627
|
+
* `scratchpad` for the one scratchpad. Undefined when the review is gone (a
|
|
628
|
+
* shared review this store never had, or one deleted between open and the
|
|
629
|
+
* event arriving) or was left without a target.
|
|
630
|
+
*/
|
|
631
|
+
export function sessionStartedSourceKind(
|
|
632
|
+
reviewStore: ReviewStore,
|
|
633
|
+
reviewUuid: string | undefined,
|
|
634
|
+
): string | undefined {
|
|
635
|
+
if (!reviewUuid) return undefined;
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
const snapshot = reviewStore.read(reviewUuid);
|
|
639
|
+
|
|
640
|
+
return snapshot.kind === "scratchpad"
|
|
641
|
+
? snapshot.kind
|
|
642
|
+
: snapshot.target?.kind;
|
|
643
|
+
} catch {
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function listen(server: Server, port: number): Promise<number> {
|
|
649
|
+
return new Promise((resolve, reject) => {
|
|
650
|
+
server.once("error", reject);
|
|
651
|
+
server.listen(port, "127.0.0.1", () => {
|
|
652
|
+
server.off("error", reject);
|
|
653
|
+
const address = server.address();
|
|
654
|
+
|
|
655
|
+
if (!isTcpAddress(address)) {
|
|
656
|
+
reject(new Error("The Whiteboard server did not bind a TCP port."));
|
|
657
|
+
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
resolve(address.port);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function closeHttpServer(server: Server): Promise<void> {
|
|
667
|
+
return new Promise((resolve, reject) => {
|
|
668
|
+
if (!server.listening) {
|
|
669
|
+
resolve();
|
|
670
|
+
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function removeMatchingDiscovery(
|
|
679
|
+
filePath: string,
|
|
680
|
+
discovery: ReviewDesktopDiscovery,
|
|
681
|
+
): Promise<void> {
|
|
682
|
+
try {
|
|
683
|
+
const current: JsonValue = JSON.parse(await readFile(filePath, "utf8"));
|
|
684
|
+
|
|
685
|
+
if (
|
|
686
|
+
isJsonObject(current) &&
|
|
687
|
+
current.instanceId === discovery.instanceId &&
|
|
688
|
+
current.appPid === discovery.appPid
|
|
689
|
+
) {
|
|
690
|
+
await rm(filePath, { force: true });
|
|
691
|
+
}
|
|
692
|
+
} catch (error) {
|
|
693
|
+
// SAFETY: fs/promises rejects with a Node ErrnoException carrying `code`.
|
|
694
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/** `server.address()` is a string for pipe and socket listeners. */
|
|
699
|
+
function isTcpAddress(
|
|
700
|
+
address: string | AddressInfo | null,
|
|
701
|
+
): address is AddressInfo {
|
|
702
|
+
return isObjectValue(address);
|
|
703
|
+
}
|