@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,811 @@
|
|
|
1
|
+
import { a as jsonNumber, c as jsonString, i as jsonBoolean, n as isJsonObject } from "./src-X9phtB2j.js";
|
|
2
|
+
import { f as REVIEW_DIFF_LAYOUTS, p as REVIEW_THEME_CHOICES } from "./src-DnwdaQ2r.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/telemetry-clean-text.ts
|
|
5
|
+
const USER_DATA_REGEXES = [
|
|
6
|
+
{
|
|
7
|
+
label: "URL",
|
|
8
|
+
regex: /[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^\s]*/
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: "Google API Key",
|
|
12
|
+
regex: /AIza[A-Za-z0-9_\\\-]{35}/
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: "JWT",
|
|
16
|
+
regex: /eyJ[0eXAiOiJKV1Qi|hbGci|a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+/
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "Slack Token",
|
|
20
|
+
regex: /xox[pbar]\-[A-Za-z0-9]/
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
label: "GitHub Token",
|
|
24
|
+
regex: /(gh[psuro]_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59})/
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: "Generic Secret",
|
|
28
|
+
regex: /(key|token|sig|secret|signature|password|passwd|pwd|android:value)[^a-zA-Z0-9]/i
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
label: "CLI Credentials",
|
|
32
|
+
regex: /((login|psexec|(certutil|psexec)\.exe).{1,50}(\s-u(ser(name)?)?\s+.{3,100})?\s-(admin|user|vm|root)?p(ass(word)?)?\s+["']?[^$\-\/\s]|(^|[\s\r\n\\])net(\.exe)?.{1,5}(user\s+|share\s+\/user:| user -? secrets ? set) \s + [^ $\s \/])/
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: "Microsoft Entra ID",
|
|
36
|
+
regex: /eyJ(?:0eXAiOiJKV1Qi|hbGci|[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.)/
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
label: "Email",
|
|
40
|
+
regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
const REDACTED_PATH_MARKER = "<REDACTED: user-file-path>";
|
|
44
|
+
/**
|
|
45
|
+
* The path shape the cleaner replaces: one or more `segment/` runs, with an
|
|
46
|
+
* optional drive letter, UNC, or `file://` root. Upstream keeps this inline in
|
|
47
|
+
* `anonymizeFilePaths`; it is named here so the allowlist can assert that no
|
|
48
|
+
* path shape survived cleaning.
|
|
49
|
+
*/
|
|
50
|
+
const FILE_PATH_PATTERN = /(file:\/\/)?([a-zA-Z]:(\\\\|\\|\/)|(\\\\|\\|\/))?([\w\-\._@]+(\\\\|\\|\/))+[\w\-\._@]*/;
|
|
51
|
+
/**
|
|
52
|
+
* True when the value still holds something path-shaped. Deliberately the same
|
|
53
|
+
* definition the cleaner uses: the point of re-testing is not a second opinion
|
|
54
|
+
* on what a path looks like, it is to catch the cleaner failing to apply its own
|
|
55
|
+
* rule — wrong patterns, wrong order, or an exception swallowed on the way.
|
|
56
|
+
*/
|
|
57
|
+
function containsFilePath(value) {
|
|
58
|
+
return FILE_PATH_PATTERN.test(value);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* True when the value still holds something the cleaner should have replaced.
|
|
62
|
+
* The event allowlist calls this as its own second check, so it must not depend
|
|
63
|
+
* on the cleaner having run.
|
|
64
|
+
*/
|
|
65
|
+
function hasPossibleUserInfo(value) {
|
|
66
|
+
return USER_DATA_REGEXES.some((entry) => entry.regex.test(value));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Cleans a given stack of possible paths
|
|
70
|
+
* @param stack The stack to sanitize
|
|
71
|
+
* @param cleanupPatterns Cleanup patterns to remove from the stack
|
|
72
|
+
* @returns The cleaned stack
|
|
73
|
+
*/
|
|
74
|
+
function anonymizeFilePaths(stack, cleanupPatterns) {
|
|
75
|
+
if (!stack || !stack.includes("/") && !stack.includes("\\")) return stack;
|
|
76
|
+
let updatedStack = stack;
|
|
77
|
+
const cleanUpIndexes = [];
|
|
78
|
+
for (const regexp of cleanupPatterns) while (true) {
|
|
79
|
+
const result = regexp.exec(stack);
|
|
80
|
+
if (!result) break;
|
|
81
|
+
cleanUpIndexes.push([result.index, regexp.lastIndex]);
|
|
82
|
+
}
|
|
83
|
+
const nodeModulesRegex = /(?:^|[\\\/])((node_modules|node_modules\.asar)[\\\/].*)$/;
|
|
84
|
+
const fileRegex = /(file:\/\/)?([a-zA-Z]:(\\\\|\\|\/)|(\\\\|\\|\/))?([\w\-\._@]+(\\\\|\\|\/))+[\w\-\._@]*/g;
|
|
85
|
+
let lastIndex = 0;
|
|
86
|
+
updatedStack = "";
|
|
87
|
+
while (true) {
|
|
88
|
+
const result = fileRegex.exec(stack);
|
|
89
|
+
if (!result) break;
|
|
90
|
+
if (!cleanUpIndexes.some(([start, end]) => result.index < end && start < fileRegex.lastIndex)) {
|
|
91
|
+
const nodeModulesMatch = nodeModulesRegex.exec(result[0]);
|
|
92
|
+
if (nodeModulesMatch) updatedStack += stack.substring(lastIndex, result.index) + `${REDACTED_PATH_MARKER}/` + nodeModulesMatch[1];
|
|
93
|
+
else updatedStack += stack.substring(lastIndex, result.index) + REDACTED_PATH_MARKER;
|
|
94
|
+
lastIndex = fileRegex.lastIndex;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (lastIndex < stack.length) updatedStack += stack.substring(lastIndex);
|
|
98
|
+
return updatedStack;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Redacts a value if it contains commonly leaked PII.
|
|
102
|
+
* @param value The value returned (as-is) when no PII is detected
|
|
103
|
+
* @param probe The string actually matched against the PII heuristics. Defaults
|
|
104
|
+
* to `value`; callers may pass a value that includes a trailing delimiter (e.g. a
|
|
105
|
+
* newline) so that heuristics relying on a non-alphanumeric boundary match the
|
|
106
|
+
* same way they would against the original whole string.
|
|
107
|
+
* @returns A `<REDACTED: ...>` marker if the probe matched, otherwise `value`
|
|
108
|
+
*/
|
|
109
|
+
function redactIfPossibleUserInfo(value, probe = value) {
|
|
110
|
+
for (const secretRegex of USER_DATA_REGEXES) if (secretRegex.regex.test(probe)) return `<REDACTED: ${secretRegex.label}>`;
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Attempts to remove commonly leaked PII.
|
|
115
|
+
*
|
|
116
|
+
* When a match is found the check is applied per line so that a single suspicious
|
|
117
|
+
* frame (e.g. a stack frame containing a function name such as `getStorageKey`
|
|
118
|
+
* which matches the broad `Generic Secret` heuristic) only redacts that line —
|
|
119
|
+
* replacing it with a `<REDACTED: ...>` marker — instead of wiping the entire
|
|
120
|
+
* multi-line value such as a whole callstack.
|
|
121
|
+
* @param property The property whose offending lines will be replaced with a redaction marker if they contain user data
|
|
122
|
+
* @returns The new value for the property
|
|
123
|
+
*/
|
|
124
|
+
function removePropertiesWithPossibleUserInfo(property) {
|
|
125
|
+
if (!property) return property;
|
|
126
|
+
if (!hasPossibleUserInfo(property)) return property;
|
|
127
|
+
if (!property.includes("\n")) return redactIfPossibleUserInfo(property);
|
|
128
|
+
const lines = property.split("\n");
|
|
129
|
+
for (let i = 0; i < lines.length; i++) {
|
|
130
|
+
const probe = i < lines.length - 1 ? lines[i] + "\n" : lines[i];
|
|
131
|
+
lines[i] = redactIfPossibleUserInfo(lines[i], probe);
|
|
132
|
+
}
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The whole pipeline for one free-text value, in VS Code's order: replace
|
|
137
|
+
* path-shaped runs, delete the known local directories outright, then replace
|
|
138
|
+
* any line holding a secret or address shape.
|
|
139
|
+
*/
|
|
140
|
+
function cleanTelemetryText(value, cleanupPatterns) {
|
|
141
|
+
let updated = value.replaceAll("%20", " ");
|
|
142
|
+
updated = anonymizeFilePaths(updated, cleanupPatterns);
|
|
143
|
+
for (const regexp of cleanupPatterns) updated = updated.replace(regexp, "");
|
|
144
|
+
return removePropertiesWithPossibleUserInfo(updated);
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/ui-telemetry-events.ts
|
|
148
|
+
const PEEK_VIA = [
|
|
149
|
+
"prose_link",
|
|
150
|
+
"diagram",
|
|
151
|
+
"map",
|
|
152
|
+
"db_lens",
|
|
153
|
+
"call_stack_frame"
|
|
154
|
+
];
|
|
155
|
+
const PEEK_ROOT_KIND = ["range"];
|
|
156
|
+
const LSP_FEATURE = [
|
|
157
|
+
"hover",
|
|
158
|
+
"goto_definition",
|
|
159
|
+
"peek_definition",
|
|
160
|
+
"goto_type_definition",
|
|
161
|
+
"goto_implementation",
|
|
162
|
+
"references",
|
|
163
|
+
"rename",
|
|
164
|
+
"format",
|
|
165
|
+
"code_action",
|
|
166
|
+
"symbol_search"
|
|
167
|
+
];
|
|
168
|
+
const LSP_VIA = ["command", "mouse"];
|
|
169
|
+
const LSP_EDITOR_KIND = [
|
|
170
|
+
"files_tab",
|
|
171
|
+
"inline_peek",
|
|
172
|
+
"diff"
|
|
173
|
+
];
|
|
174
|
+
const LSP_LANGUAGE = [
|
|
175
|
+
"typescript",
|
|
176
|
+
"javascript",
|
|
177
|
+
"python",
|
|
178
|
+
"go",
|
|
179
|
+
"rust",
|
|
180
|
+
"swift",
|
|
181
|
+
"csharp",
|
|
182
|
+
"json",
|
|
183
|
+
"css",
|
|
184
|
+
"html",
|
|
185
|
+
"markdown",
|
|
186
|
+
"yaml",
|
|
187
|
+
"toml",
|
|
188
|
+
"shell",
|
|
189
|
+
"sql",
|
|
190
|
+
"other"
|
|
191
|
+
];
|
|
192
|
+
const LS_GROUP = [
|
|
193
|
+
"python",
|
|
194
|
+
"go",
|
|
195
|
+
"rust",
|
|
196
|
+
"swift",
|
|
197
|
+
"csharp"
|
|
198
|
+
];
|
|
199
|
+
const EXTENSION_ID = [
|
|
200
|
+
"vscodevim.vim",
|
|
201
|
+
"tuttieee.emacs-mcx",
|
|
202
|
+
"ms-vscode.sublime-keybindings",
|
|
203
|
+
"ms-python.python",
|
|
204
|
+
"astral-sh.ty",
|
|
205
|
+
"charliermarsh.ruff",
|
|
206
|
+
"golang.go",
|
|
207
|
+
"rust-lang.rust-analyzer",
|
|
208
|
+
"swiftlang.swift-vscode",
|
|
209
|
+
"llvm-vs-code-extensions.lldb-dap",
|
|
210
|
+
"muhammad-sammy.csharp",
|
|
211
|
+
"ms-dotnettools.vscode-dotnet-runtime"
|
|
212
|
+
];
|
|
213
|
+
const EXTENSION_TRIGGER = [
|
|
214
|
+
"user",
|
|
215
|
+
"auto_upgrade",
|
|
216
|
+
"startup_seed",
|
|
217
|
+
"keymap",
|
|
218
|
+
"rollback"
|
|
219
|
+
];
|
|
220
|
+
const EXTENSION_INSTALL_PHASE = ["download", "install"];
|
|
221
|
+
const SETTING_NAME = [
|
|
222
|
+
"telemetry_enabled",
|
|
223
|
+
"keymap",
|
|
224
|
+
"software_map_enabled",
|
|
225
|
+
"scratchpad_enabled",
|
|
226
|
+
"diffr_config",
|
|
227
|
+
"structural_diff",
|
|
228
|
+
"theme",
|
|
229
|
+
"ctrl_tab",
|
|
230
|
+
"ready_notification",
|
|
231
|
+
"document_width"
|
|
232
|
+
];
|
|
233
|
+
const REVIEW_OPENED_VIA = [
|
|
234
|
+
"home",
|
|
235
|
+
"cli",
|
|
236
|
+
"other"
|
|
237
|
+
];
|
|
238
|
+
/** Where a review's source comes from: its target's kind, or the one scratchpad. */
|
|
239
|
+
const SESSION_SOURCE_KIND = [
|
|
240
|
+
"worktree",
|
|
241
|
+
"commits",
|
|
242
|
+
"scratchpad"
|
|
243
|
+
];
|
|
244
|
+
/**
|
|
245
|
+
* The authoring agent a review session is attributed to. This is the one
|
|
246
|
+
* definition: `review-telemetry.ts`'s `ReviewSessionAgent` type imports and
|
|
247
|
+
* derives from it instead of redeclaring the list.
|
|
248
|
+
*/
|
|
249
|
+
const SESSION_AGENT_KIND = [
|
|
250
|
+
"codex",
|
|
251
|
+
"claude",
|
|
252
|
+
"cursor",
|
|
253
|
+
"opencode",
|
|
254
|
+
"pi",
|
|
255
|
+
"omp",
|
|
256
|
+
"other"
|
|
257
|
+
];
|
|
258
|
+
/**
|
|
259
|
+
* How a review session ends. `abnormal` is reported on the next launch for a
|
|
260
|
+
* session the previous process never closed. This is the one definition:
|
|
261
|
+
* `review-telemetry.ts`'s `ReviewSessionOutcome` type imports and derives from
|
|
262
|
+
* it instead of redeclaring the list.
|
|
263
|
+
*/
|
|
264
|
+
const SESSION_OUTCOME = [
|
|
265
|
+
"closed",
|
|
266
|
+
"dismissed",
|
|
267
|
+
"deleted",
|
|
268
|
+
"app_quit",
|
|
269
|
+
"abnormal"
|
|
270
|
+
];
|
|
271
|
+
const REVIEW_DISMISSED_VIA = ["review_topbar", "home"];
|
|
272
|
+
const REVIEW_RESTORED_VIA = ["home", "open"];
|
|
273
|
+
const MAP_LEVEL = [
|
|
274
|
+
"system",
|
|
275
|
+
"container",
|
|
276
|
+
"component",
|
|
277
|
+
"code"
|
|
278
|
+
];
|
|
279
|
+
const SOURCE_TREE_OPENED_VIA = ["topbar", "home"];
|
|
280
|
+
/** Tab names the dwell beacon reports. Shared with its request parser. */
|
|
281
|
+
const REVIEW_TELEMETRY_TABS = [
|
|
282
|
+
"review",
|
|
283
|
+
"commits",
|
|
284
|
+
"map",
|
|
285
|
+
"files",
|
|
286
|
+
"trace"
|
|
287
|
+
];
|
|
288
|
+
const COMMIT_DIFF_VIA = [
|
|
289
|
+
"row",
|
|
290
|
+
"file",
|
|
291
|
+
"footer"
|
|
292
|
+
];
|
|
293
|
+
const CLIENT_ERROR_SOURCE = [
|
|
294
|
+
"window",
|
|
295
|
+
"worker",
|
|
296
|
+
"fetch",
|
|
297
|
+
"render",
|
|
298
|
+
"document",
|
|
299
|
+
"renderer_unexpected",
|
|
300
|
+
"main_unexpected",
|
|
301
|
+
"bootstrap",
|
|
302
|
+
"settings",
|
|
303
|
+
"server_unexpected"
|
|
304
|
+
];
|
|
305
|
+
/** Which process died. "unknown" is a minidump the live listeners did not see. */
|
|
306
|
+
const CRASH_PROCESS = [
|
|
307
|
+
"renderer",
|
|
308
|
+
"gpu",
|
|
309
|
+
"utility",
|
|
310
|
+
"server",
|
|
311
|
+
"unknown"
|
|
312
|
+
];
|
|
313
|
+
const CRASH_SOURCE = ["live", "minidump"];
|
|
314
|
+
/** Where a packaged macOS build runs from; Squirrel cannot update `volume` or `translocated`. */
|
|
315
|
+
const INSTALL_LOCATION = [
|
|
316
|
+
"applications",
|
|
317
|
+
"user_applications",
|
|
318
|
+
"volume",
|
|
319
|
+
"translocated",
|
|
320
|
+
"other"
|
|
321
|
+
];
|
|
322
|
+
const DIFF_OPENED_VIA = [
|
|
323
|
+
"topbar",
|
|
324
|
+
"lens",
|
|
325
|
+
"locate"
|
|
326
|
+
];
|
|
327
|
+
/** Which diff opened: a commit's, or a file diff drawn plain or structural. */
|
|
328
|
+
const DIFF_OPENED_KIND = [
|
|
329
|
+
"commit",
|
|
330
|
+
"file",
|
|
331
|
+
"structural"
|
|
332
|
+
];
|
|
333
|
+
const DISCORD_VIA = [
|
|
334
|
+
"topbar",
|
|
335
|
+
"dialog",
|
|
336
|
+
"docs"
|
|
337
|
+
];
|
|
338
|
+
const REVIEW_DELETED_VIA = ["home"];
|
|
339
|
+
const ERROR_PROCESS = [
|
|
340
|
+
"main",
|
|
341
|
+
"renderer",
|
|
342
|
+
"canvas",
|
|
343
|
+
"server"
|
|
344
|
+
];
|
|
345
|
+
const UPDATE_FAILURE_PHASE = [
|
|
346
|
+
"check",
|
|
347
|
+
"download",
|
|
348
|
+
"install"
|
|
349
|
+
];
|
|
350
|
+
const UPDATE_MESSAGE_SOURCE = [
|
|
351
|
+
"electron",
|
|
352
|
+
"request",
|
|
353
|
+
"shipit",
|
|
354
|
+
"fallback"
|
|
355
|
+
];
|
|
356
|
+
/**
|
|
357
|
+
* Directories that exist only in the shipped Review bundle. A stack frame is
|
|
358
|
+
* reportable when — and only when — it has already been rewritten to start at
|
|
359
|
+
* one of these, which is what strips the user's home directory off the front.
|
|
360
|
+
* See error-telemetry.ts; this table re-checks the result independently.
|
|
361
|
+
*/
|
|
362
|
+
const BUNDLE_FRAME_ROOTS = [
|
|
363
|
+
"vs/review/",
|
|
364
|
+
"vs/workbench/",
|
|
365
|
+
"vs/platform/",
|
|
366
|
+
"vs/base/",
|
|
367
|
+
"vs/code/",
|
|
368
|
+
"vs/editor/",
|
|
369
|
+
"assets/",
|
|
370
|
+
"review-runtime/"
|
|
371
|
+
];
|
|
372
|
+
/**
|
|
373
|
+
* The Electron entry files, which sit beside those directories rather than
|
|
374
|
+
* inside one. A crash before the app can start lands here and nowhere else, so
|
|
375
|
+
* without this list the report that matters most carries no location at all.
|
|
376
|
+
* The list is closed for the same reason the directories are.
|
|
377
|
+
*/
|
|
378
|
+
const BUNDLE_FRAME_ENTRY_FILES = [
|
|
379
|
+
"main.js",
|
|
380
|
+
"cli.js",
|
|
381
|
+
"bootstrap-cli.js",
|
|
382
|
+
"bootstrap-esm.js",
|
|
383
|
+
"bootstrap-fork.js",
|
|
384
|
+
"bootstrap-meta.js",
|
|
385
|
+
"bootstrap-node.js"
|
|
386
|
+
];
|
|
387
|
+
/**
|
|
388
|
+
* One normalized stack frame: a bundle-relative path, then `:line:col`. The
|
|
389
|
+
* character class is deliberately narrow — anything outside it could carry user
|
|
390
|
+
* content — and there is no function name, because the file already identifies
|
|
391
|
+
* the frame and a minified name adds surface for nothing.
|
|
392
|
+
*/
|
|
393
|
+
const escapeForPattern = (value) => value.replaceAll(".", "\\.").replaceAll("/", "\\/");
|
|
394
|
+
const BUNDLE_FRAME_PATTERN = new RegExp("^(?:" + [`(?:${BUNDLE_FRAME_ROOTS.map(escapeForPattern).join("|")})[A-Za-z0-9_./-]*`, `(?:${BUNDLE_FRAME_ENTRY_FILES.map(escapeForPattern).join("|")})`].join("|") + "):\\d{1,9}:\\d{1,9}$");
|
|
395
|
+
const MAX_BUNDLE_FRAMES = 10;
|
|
396
|
+
const MAX_BUNDLE_FRAMES_LENGTH = 1024;
|
|
397
|
+
const HASH_HEX_PATTERN = /^[0-9a-f]{16}$/;
|
|
398
|
+
const MAX_CLEANED_MESSAGE_LENGTH = 300;
|
|
399
|
+
/** A marker the cleaner writes, e.g. `<REDACTED: user-file-path>`. */
|
|
400
|
+
const REDACTION_MARKER_PATTERN = /<REDACTED: [A-Za-z][A-Za-z0-9 -]*>/g;
|
|
401
|
+
/**
|
|
402
|
+
* Header the canvas app attaches to review API requests so server-side events
|
|
403
|
+
* can share the UI session id. The CORS preflight allowlist in
|
|
404
|
+
* server/hono-http.ts must permit every header named here — the canvas runs on
|
|
405
|
+
* a vscode-file:// origin, so a header missing from that list fails the
|
|
406
|
+
* preflight and takes the whole request down with it.
|
|
407
|
+
*/
|
|
408
|
+
const REVIEW_APP_SESSION_ID_HEADER = "x-review-app-session-id";
|
|
409
|
+
const UI_TELEMETRY_EVENTS = {
|
|
410
|
+
app_opened: {
|
|
411
|
+
event: "review_app_opened",
|
|
412
|
+
properties: {}
|
|
413
|
+
},
|
|
414
|
+
peek_opened: {
|
|
415
|
+
event: "review_peek_opened",
|
|
416
|
+
properties: { via: PEEK_VIA }
|
|
417
|
+
},
|
|
418
|
+
tour_started: {
|
|
419
|
+
event: "review_tour_started",
|
|
420
|
+
properties: { steps: "number" }
|
|
421
|
+
},
|
|
422
|
+
tour_completed: {
|
|
423
|
+
event: "review_tour_completed",
|
|
424
|
+
properties: { steps: "number" }
|
|
425
|
+
},
|
|
426
|
+
map_expanded: {
|
|
427
|
+
event: "review_map_expanded",
|
|
428
|
+
properties: { level: MAP_LEVEL }
|
|
429
|
+
},
|
|
430
|
+
commit_expanded: {
|
|
431
|
+
event: "review_commit_expanded",
|
|
432
|
+
properties: { expanded: "boolean" }
|
|
433
|
+
},
|
|
434
|
+
commit_diff_opened: {
|
|
435
|
+
event: "review_commit_diff_opened",
|
|
436
|
+
properties: { via: COMMIT_DIFF_VIA }
|
|
437
|
+
},
|
|
438
|
+
source_tree_opened: {
|
|
439
|
+
event: "review_source_tree_opened",
|
|
440
|
+
properties: { via: SOURCE_TREE_OPENED_VIA }
|
|
441
|
+
},
|
|
442
|
+
diff_layout_changed: {
|
|
443
|
+
event: "review_diff_layout_changed",
|
|
444
|
+
properties: { layout: REVIEW_DIFF_LAYOUTS }
|
|
445
|
+
},
|
|
446
|
+
review_dismissed: {
|
|
447
|
+
event: "review_review_dismissed",
|
|
448
|
+
properties: { via: REVIEW_DISMISSED_VIA }
|
|
449
|
+
},
|
|
450
|
+
review_restored: {
|
|
451
|
+
event: "review_review_restored",
|
|
452
|
+
properties: { via: REVIEW_RESTORED_VIA }
|
|
453
|
+
},
|
|
454
|
+
client_error: {
|
|
455
|
+
event: "review_client_error",
|
|
456
|
+
properties: {
|
|
457
|
+
error_source: CLIENT_ERROR_SOURCE,
|
|
458
|
+
error_process: ERROR_PROCESS,
|
|
459
|
+
error_name: "enum_free_short",
|
|
460
|
+
component: "enum_free_short",
|
|
461
|
+
message: "cleaned_message",
|
|
462
|
+
message_hash: "hash_hex",
|
|
463
|
+
frames: "bundle_frames"
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
update_started: {
|
|
467
|
+
event: "review_update_started",
|
|
468
|
+
properties: {
|
|
469
|
+
update_attempt_id: "opaque_id",
|
|
470
|
+
target_version: "release_version"
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
update_completed: {
|
|
474
|
+
event: "review_update_completed",
|
|
475
|
+
properties: {
|
|
476
|
+
update_attempt_id: "opaque_id",
|
|
477
|
+
target_version: "release_version",
|
|
478
|
+
duration_ms: "number"
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
update_failed: {
|
|
482
|
+
event: "review_update_failed",
|
|
483
|
+
properties: {
|
|
484
|
+
phase: UPDATE_FAILURE_PHASE,
|
|
485
|
+
message_source: UPDATE_MESSAGE_SOURCE,
|
|
486
|
+
update_attempt_id: "opaque_id",
|
|
487
|
+
target_version: "release_version",
|
|
488
|
+
duration_ms: "number",
|
|
489
|
+
error_name: "enum_free_short",
|
|
490
|
+
message: "cleaned_message",
|
|
491
|
+
message_hash: "hash_hex"
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
lsp_used: {
|
|
495
|
+
event: "review_lsp_used",
|
|
496
|
+
properties: {
|
|
497
|
+
feature: LSP_FEATURE,
|
|
498
|
+
via: LSP_VIA,
|
|
499
|
+
language: LSP_LANGUAGE,
|
|
500
|
+
editor_kind: LSP_EDITOR_KIND
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
ls_activated: {
|
|
504
|
+
event: "review_ls_activated",
|
|
505
|
+
properties: {
|
|
506
|
+
group: LS_GROUP,
|
|
507
|
+
ok: "boolean"
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
extension_installed: {
|
|
511
|
+
event: "review_extension_installed",
|
|
512
|
+
properties: {
|
|
513
|
+
extension_id: EXTENSION_ID,
|
|
514
|
+
trigger: EXTENSION_TRIGGER,
|
|
515
|
+
cached: "boolean",
|
|
516
|
+
duration_ms: "number"
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
extension_install_failed: {
|
|
520
|
+
event: "review_extension_install_failed",
|
|
521
|
+
properties: {
|
|
522
|
+
extension_id: EXTENSION_ID,
|
|
523
|
+
trigger: EXTENSION_TRIGGER,
|
|
524
|
+
phase: EXTENSION_INSTALL_PHASE
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
extension_enabled: {
|
|
528
|
+
event: "review_extension_enabled",
|
|
529
|
+
properties: {
|
|
530
|
+
extension_id: EXTENSION_ID,
|
|
531
|
+
trigger: EXTENSION_TRIGGER
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
extension_disabled: {
|
|
535
|
+
event: "review_extension_disabled",
|
|
536
|
+
properties: {
|
|
537
|
+
extension_id: EXTENSION_ID,
|
|
538
|
+
trigger: EXTENSION_TRIGGER
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
extension_uninstalled: {
|
|
542
|
+
event: "review_extension_uninstalled",
|
|
543
|
+
properties: {
|
|
544
|
+
extension_id: EXTENSION_ID,
|
|
545
|
+
trigger: EXTENSION_TRIGGER
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
tour_step_advanced: {
|
|
549
|
+
event: "review_tour_step_advanced",
|
|
550
|
+
properties: {
|
|
551
|
+
step: "number",
|
|
552
|
+
steps: "number"
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
tour_abandoned: {
|
|
556
|
+
event: "review_tour_abandoned",
|
|
557
|
+
properties: {
|
|
558
|
+
step: "number",
|
|
559
|
+
steps: "number"
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
bug_report_dialog_opened: {
|
|
563
|
+
event: "review_bug_report_dialog_opened",
|
|
564
|
+
properties: {}
|
|
565
|
+
},
|
|
566
|
+
bug_report_cancelled: {
|
|
567
|
+
event: "review_bug_report_cancelled",
|
|
568
|
+
properties: {}
|
|
569
|
+
},
|
|
570
|
+
bug_report_send_failed: {
|
|
571
|
+
event: "review_bug_report_send_failed",
|
|
572
|
+
properties: { error_name: "enum_free_short" }
|
|
573
|
+
},
|
|
574
|
+
setting_changed: {
|
|
575
|
+
event: "review_setting_changed",
|
|
576
|
+
properties: {
|
|
577
|
+
setting: SETTING_NAME,
|
|
578
|
+
enabled: "boolean",
|
|
579
|
+
value: REVIEW_THEME_CHOICES
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
review_opened: {
|
|
583
|
+
event: "review_review_opened",
|
|
584
|
+
properties: { via: REVIEW_OPENED_VIA }
|
|
585
|
+
},
|
|
586
|
+
session_started: {
|
|
587
|
+
event: "review_session_started",
|
|
588
|
+
properties: {
|
|
589
|
+
source_kind: SESSION_SOURCE_KIND,
|
|
590
|
+
agent_kind: SESSION_AGENT_KIND
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
session_ended: {
|
|
594
|
+
event: "review_session_ended",
|
|
595
|
+
properties: {
|
|
596
|
+
outcome: SESSION_OUTCOME,
|
|
597
|
+
duration_ms: "number"
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
review_presented: {
|
|
601
|
+
event: "review_review_presented",
|
|
602
|
+
properties: { load_ms: "number" }
|
|
603
|
+
},
|
|
604
|
+
home_empty_state_viewed: {
|
|
605
|
+
event: "review_home_empty_state_viewed",
|
|
606
|
+
properties: {}
|
|
607
|
+
},
|
|
608
|
+
crash: {
|
|
609
|
+
event: "review_crash",
|
|
610
|
+
properties: {
|
|
611
|
+
process: CRASH_PROCESS,
|
|
612
|
+
reason: "enum_free_short",
|
|
613
|
+
exit_code: "number",
|
|
614
|
+
uptime_ms: "number",
|
|
615
|
+
source: CRASH_SOURCE
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
hang_started: {
|
|
619
|
+
event: "review_hang_started",
|
|
620
|
+
properties: {}
|
|
621
|
+
},
|
|
622
|
+
hang_ended: {
|
|
623
|
+
event: "review_hang_ended",
|
|
624
|
+
properties: { duration_ms: "number" }
|
|
625
|
+
},
|
|
626
|
+
app_ready: {
|
|
627
|
+
event: "review_app_ready",
|
|
628
|
+
properties: {
|
|
629
|
+
duration_ms: "number",
|
|
630
|
+
install_location: INSTALL_LOCATION
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
error_burst: {
|
|
634
|
+
event: "review_error_burst",
|
|
635
|
+
properties: {
|
|
636
|
+
message_hash: "hash_hex",
|
|
637
|
+
suppressed: "number"
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
diff_opened: {
|
|
641
|
+
event: "review_diff_opened",
|
|
642
|
+
properties: {
|
|
643
|
+
kind: DIFF_OPENED_KIND,
|
|
644
|
+
via: DIFF_OPENED_VIA
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
scratchpad_opened: {
|
|
648
|
+
event: "review_scratchpad_opened",
|
|
649
|
+
properties: {}
|
|
650
|
+
},
|
|
651
|
+
discord_clicked: {
|
|
652
|
+
event: "review_discord_clicked",
|
|
653
|
+
properties: { via: DISCORD_VIA }
|
|
654
|
+
},
|
|
655
|
+
discord_dialog_shown: {
|
|
656
|
+
event: "review_discord_dialog_shown",
|
|
657
|
+
properties: {}
|
|
658
|
+
},
|
|
659
|
+
discord_dialog_dismissed: {
|
|
660
|
+
event: "review_discord_dialog_dismissed",
|
|
661
|
+
properties: {}
|
|
662
|
+
},
|
|
663
|
+
peek_resolved: {
|
|
664
|
+
event: "review_peek_resolved",
|
|
665
|
+
properties: { root_kind: PEEK_ROOT_KIND }
|
|
666
|
+
},
|
|
667
|
+
peek_resolve_failed: {
|
|
668
|
+
event: "review_peek_resolve_failed",
|
|
669
|
+
properties: { root_kind: PEEK_ROOT_KIND }
|
|
670
|
+
},
|
|
671
|
+
review_deleted: {
|
|
672
|
+
event: "review_review_deleted",
|
|
673
|
+
properties: { via: REVIEW_DELETED_VIA }
|
|
674
|
+
},
|
|
675
|
+
review_shared: {
|
|
676
|
+
event: "review_review_shared",
|
|
677
|
+
properties: {}
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
const MAX_FREE_STRING_LENGTH = 40;
|
|
681
|
+
const FREE_STRING_PATTERN = /^[A-Za-z0-9_$-]+$/;
|
|
682
|
+
const OPAQUE_ID_PATTERN = /^[A-Za-z0-9_-]{8,64}$/;
|
|
683
|
+
const RELEASE_VERSION_PATTERN = /^\d{1,10}\.\d{1,10}\.\d{1,10}(?:[-+][0-9A-Za-z.-]{1,40})?$/;
|
|
684
|
+
const COMMON_PROPERTIES = { app_session_id: "opaque_id" };
|
|
685
|
+
function isUiTelemetryEventName(name) {
|
|
686
|
+
return Object.hasOwn(UI_TELEMETRY_EVENTS, name);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Validate a raw UI event payload against the allowlist. Returns the PostHog
|
|
690
|
+
* event name plus only the sanctioned properties, or null when the event
|
|
691
|
+
* itself is unknown. Unknown keys and invalid values are silently dropped —
|
|
692
|
+
* never forwarded.
|
|
693
|
+
*/
|
|
694
|
+
function sanitizeUiTelemetryEvent(input) {
|
|
695
|
+
const name = z.string().safeParse(input.name);
|
|
696
|
+
if (!name.success || !isUiTelemetryEventName(name.data)) return null;
|
|
697
|
+
const spec = UI_TELEMETRY_EVENTS[name.data];
|
|
698
|
+
const raw = isJsonObject(input.properties) ? input.properties : {};
|
|
699
|
+
const properties = {};
|
|
700
|
+
const propertySpecs = {
|
|
701
|
+
...COMMON_PROPERTIES,
|
|
702
|
+
...spec.properties
|
|
703
|
+
};
|
|
704
|
+
for (const [key, propSpec] of Object.entries(propertySpecs)) {
|
|
705
|
+
const value = raw[key];
|
|
706
|
+
if (value === void 0 || value === null) continue;
|
|
707
|
+
const text = jsonString(value);
|
|
708
|
+
if (propSpec === "number") {
|
|
709
|
+
const number = jsonNumber(value);
|
|
710
|
+
if (number !== void 0) properties[key] = number;
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (propSpec === "boolean") {
|
|
714
|
+
const boolean = jsonBoolean(value);
|
|
715
|
+
if (boolean !== void 0) properties[key] = boolean;
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (propSpec === "opaque_id") {
|
|
719
|
+
if (isValidReviewAppSessionId(text)) properties[key] = text;
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (propSpec === "release_version") {
|
|
723
|
+
if (text !== void 0 && RELEASE_VERSION_PATTERN.test(text)) properties[key] = text;
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
if (propSpec === "enum_free_short") {
|
|
727
|
+
if (text !== void 0 && text.length <= MAX_FREE_STRING_LENGTH && FREE_STRING_PATTERN.test(text)) properties[key] = text;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (propSpec === "hash_hex") {
|
|
731
|
+
if (text !== void 0 && HASH_HEX_PATTERN.test(text)) properties[key] = text;
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (propSpec === "bundle_frames") {
|
|
735
|
+
const frames = sanitizeBundleFrames(text);
|
|
736
|
+
if (frames) properties[key] = frames;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (propSpec === "cleaned_message") {
|
|
740
|
+
if (text !== void 0 && isReportableCleanedMessage(text)) properties[key] = text;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (text !== void 0 && propSpec.includes(text)) properties[key] = text;
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
event: spec.event,
|
|
747
|
+
properties
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* The independent second check on a cleaned error message.
|
|
752
|
+
*
|
|
753
|
+
* It never repairs the message. Either the cleaner finished the job or the
|
|
754
|
+
* message is dropped and only the digest goes, so a bug in the cleaner cannot
|
|
755
|
+
* by itself put a path on the wire.
|
|
756
|
+
*
|
|
757
|
+
* The path rule is the load-bearing one: the cleaner replaces every path-shaped
|
|
758
|
+
* run with a marker, so a surviving path shape means something got past it.
|
|
759
|
+
*/
|
|
760
|
+
function isReportableCleanedMessage(value) {
|
|
761
|
+
if (value.length === 0 || value.length > MAX_CLEANED_MESSAGE_LENGTH) return false;
|
|
762
|
+
const remainder = value.replaceAll(REDACTION_MARKER_PATTERN, " ");
|
|
763
|
+
return !containsFilePath(remainder) && !hasPossibleUserInfo(remainder);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Keep only the frames that match BUNDLE_FRAME_PATTERN. Frames arrive already
|
|
767
|
+
* normalized; this is the independent second check, so it never repairs a frame
|
|
768
|
+
* — it drops it. Returns undefined when nothing survives.
|
|
769
|
+
*/
|
|
770
|
+
function sanitizeBundleFrames(value) {
|
|
771
|
+
if (value === void 0) return void 0;
|
|
772
|
+
const kept = [];
|
|
773
|
+
let length = 0;
|
|
774
|
+
for (const frame of value.split("|")) {
|
|
775
|
+
if (!BUNDLE_FRAME_PATTERN.test(frame)) continue;
|
|
776
|
+
const next = length + frame.length + (kept.length > 0 ? 1 : 0);
|
|
777
|
+
if (next > MAX_BUNDLE_FRAMES_LENGTH) break;
|
|
778
|
+
kept.push(frame);
|
|
779
|
+
length = next;
|
|
780
|
+
if (kept.length >= MAX_BUNDLE_FRAMES) break;
|
|
781
|
+
}
|
|
782
|
+
return kept.length > 0 ? kept.join("|") : void 0;
|
|
783
|
+
}
|
|
784
|
+
function isValidReviewAppSessionId(value) {
|
|
785
|
+
return value !== void 0 && OPAQUE_ID_PATTERN.test(value);
|
|
786
|
+
}
|
|
787
|
+
//#endregion
|
|
788
|
+
//#region src/review-api/request-origin.ts
|
|
789
|
+
/**
|
|
790
|
+
* Headers the agent CLI sets so the server can say where a review came from.
|
|
791
|
+
* Both are telemetry only and parse to closed enums: anything else a caller
|
|
792
|
+
* sends becomes "other".
|
|
793
|
+
*/
|
|
794
|
+
const REVIEW_VIA_HEADER = "x-review-via";
|
|
795
|
+
const REVIEW_AGENT_HEADER = "x-review-agent";
|
|
796
|
+
const viaSchema = z.enum([
|
|
797
|
+
"api",
|
|
798
|
+
"mcp",
|
|
799
|
+
"other"
|
|
800
|
+
]).catch("other");
|
|
801
|
+
const agentSchema = z.enum(SESSION_AGENT_KIND).optional().catch("other");
|
|
802
|
+
function reviewRequestOrigin(headers) {
|
|
803
|
+
const via = viaSchema.parse(headers.get(REVIEW_VIA_HEADER));
|
|
804
|
+
const agentKind = agentSchema.parse(headers.get("x-review-agent") ?? void 0);
|
|
805
|
+
return agentKind ? {
|
|
806
|
+
via,
|
|
807
|
+
agentKind
|
|
808
|
+
} : { via };
|
|
809
|
+
}
|
|
810
|
+
//#endregion
|
|
811
|
+
export { REVIEW_APP_SESSION_ID_HEADER as a, sanitizeUiTelemetryEvent as c, BUNDLE_FRAME_PATTERN as i, cleanTelemetryText as l, REVIEW_VIA_HEADER as n, REVIEW_TELEMETRY_TABS as o, reviewRequestOrigin as r, isReportableCleanedMessage as s, REVIEW_AGENT_HEADER as t };
|