pi-agent-browser-native 0.2.48 → 0.2.49
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/CHANGELOG.md +17 -0
- package/README.md +16 -6
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -1,923 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Render diagnostic command families and safe redacted diagnostic data.
|
|
3
|
-
* Responsibilities: Format sessions, profiles, auth/cookies/storage, network diagnostics, console/errors, stream/dashboard/chat, and build network follow-up actions.
|
|
4
|
-
* Scope: Diagnostic/result-state command presentation only; core orchestration stays in presentation.ts.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { isRecord } from "../../parsing.js";
|
|
8
|
-
import { redactSensitiveText, redactSensitiveValue, type CommandInfo } from "../../runtime.js";
|
|
9
|
-
import type { AgentBrowserNextAction, NetworkRouteDiagnostic } from "../contracts.js";
|
|
10
|
-
import { classifyNetworkRequestFailure, isApiLikeNetworkRequest, isNetworkArtifactNoiseRequest, summarizeNetworkFailures } from "../network.js";
|
|
11
|
-
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
12
|
-
import { stringifyUnknown, truncateText } from "../text.js";
|
|
13
|
-
import {
|
|
14
|
-
firstLine,
|
|
15
|
-
formatCount,
|
|
16
|
-
getArrayField,
|
|
17
|
-
getStringField,
|
|
18
|
-
parseJsonPreviewString,
|
|
19
|
-
redactModelFacingText,
|
|
20
|
-
redactModelFacingTextIfSensitive,
|
|
21
|
-
stringifyModelFacing,
|
|
22
|
-
} from "./common.js";
|
|
23
|
-
|
|
24
|
-
const DIAGNOSTIC_REQUEST_PREVIEW_LIMIT = 40;
|
|
25
|
-
|
|
26
|
-
const DIAGNOSTIC_LOG_PREVIEW_LIMIT = 80;
|
|
27
|
-
|
|
28
|
-
const NETWORK_BODY_PREVIEW_MAX_CHARS = 280;
|
|
29
|
-
|
|
30
|
-
const NETWORK_ERROR_PREVIEW_MAX_CHARS = 220;
|
|
31
|
-
|
|
32
|
-
const NETWORK_NEXT_ACTION_LIMIT = 6;
|
|
33
|
-
|
|
34
|
-
const NETWORK_FILTER_MAX_CHARS = 160;
|
|
35
|
-
|
|
36
|
-
const STORAGE_VALUE_PREVIEW_MAX_CHARS = 160;
|
|
37
|
-
|
|
38
|
-
const STORAGE_SECRET_KEY_PATTERN = /(?:access(?:_|-)?token|account|api(?:_|-)?key|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|credential|csrf|email|id(?:_|-)?token|jwt|pass(?:word)?|private(?:_|-)?key|profile|refresh(?:_|-)?token|secret|session|sid|sig(?:nature)?|token|user(?:name)?|x(?:_|-)?api(?:_|-)?key|xsrf)/i;
|
|
39
|
-
|
|
40
|
-
const STORAGE_BENIGN_KEY_PATTERN = /^(?:color(?:scheme)?|debug|dev|experiment|feature(?:flag)?|flag|language|layout|locale|mode|onboarding|sort|tab|theme|timezone|tour|variant|view)$/i;
|
|
41
|
-
|
|
42
|
-
const STORAGE_TOKEN_VALUE_PATTERN = /(?:\bBearer\s+[A-Za-z0-9._~-]+|\bBasic\s+[A-Za-z0-9+/=]+|^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$|(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9_~+/=-]{32,})/;
|
|
43
|
-
|
|
44
|
-
const STORAGE_SECRET_VALUE_WORD_PATTERN = /(?:secret|token|password|passwd|bearer|credential|authorization|cookie|session[-_ ]?id)/i;
|
|
45
|
-
|
|
46
|
-
const STORAGE_EMAIL_VALUE_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
47
|
-
|
|
48
|
-
const STORAGE_IDENTITY_VALUE_PATTERN = /(?:^|[\s:=/_-])(?:account|profile|session|sid|user(?:id|name)?)(?:[\s:=/_-]|$)/i;
|
|
49
|
-
|
|
50
|
-
const NETWORK_FILTER_SENSITIVE_SEGMENT_TERMS = [
|
|
51
|
-
"apikey",
|
|
52
|
-
"api-key",
|
|
53
|
-
"api_key",
|
|
54
|
-
"authentication",
|
|
55
|
-
"authorization",
|
|
56
|
-
"bearer",
|
|
57
|
-
"credential",
|
|
58
|
-
"credentials",
|
|
59
|
-
"jwt",
|
|
60
|
-
"passwd",
|
|
61
|
-
"password",
|
|
62
|
-
"reset",
|
|
63
|
-
"secret",
|
|
64
|
-
"session",
|
|
65
|
-
"token",
|
|
66
|
-
] as const;
|
|
67
|
-
|
|
68
|
-
const SENSITIVE_PRESENTATION_FIELD_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|secret|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|token|x(?:_|-)?api(?:_|-)?key)$/i;
|
|
69
|
-
|
|
70
|
-
const NETWORK_FILTER_OPAQUE_SEGMENT_PATTERN = /^(?:[A-Fa-f0-9]{16,}|(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9_-]{16,})$/;
|
|
71
|
-
|
|
72
|
-
const NETWORK_PREVIEW_FIELD_CANDIDATES = {
|
|
73
|
-
request: ["postData"] as const,
|
|
74
|
-
response: ["responseBody"] as const,
|
|
75
|
-
error: ["error", "failureText", "errorText"] as const,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const AUTH_SHOW_SAFE_FIELDS = ["name", "profile", "url", "username", "createdAt", "updatedAt"] as const;
|
|
79
|
-
|
|
80
|
-
export function getTabSummary(data: Record<string, unknown>): string | undefined {
|
|
81
|
-
const tabs = Array.isArray(data.tabs) ? data.tabs : undefined;
|
|
82
|
-
if (!tabs) return undefined;
|
|
83
|
-
|
|
84
|
-
const lines = tabs.map((tab, index) => {
|
|
85
|
-
if (!isRecord(tab)) return `${index}: <invalid tab>`;
|
|
86
|
-
const marker = tab.active === true ? "*" : "-";
|
|
87
|
-
const title = typeof tab.title === "string" ? tab.title : "(untitled)";
|
|
88
|
-
const url = typeof tab.url === "string" ? tab.url : "(no url)";
|
|
89
|
-
const label = typeof tab.label === "string" && tab.label.trim().length > 0 ? tab.label.trim() : undefined;
|
|
90
|
-
const tabSelector =
|
|
91
|
-
typeof tab.tabId === "string" && tab.tabId.trim().length > 0
|
|
92
|
-
? tab.tabId.trim()
|
|
93
|
-
: label
|
|
94
|
-
? label
|
|
95
|
-
: typeof tab.index === "number"
|
|
96
|
-
? String(tab.index)
|
|
97
|
-
: String(index);
|
|
98
|
-
const labelText = label && label !== tabSelector ? ` label=${redactModelFacingText(label)}` : "";
|
|
99
|
-
return `${marker} [${tabSelector}]${labelText} ${title} — ${url}`;
|
|
100
|
-
});
|
|
101
|
-
return lines.join("\n");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function getStreamSummary(data: Record<string, unknown>): string | undefined {
|
|
105
|
-
if (data.alreadyEnabled === true) {
|
|
106
|
-
const lines = ["Stream already enabled (idempotent no-op)."];
|
|
107
|
-
if (typeof data.port === "number") {
|
|
108
|
-
lines.push(`Port: ${data.port}`);
|
|
109
|
-
lines.push(`WebSocket URL: ${getStreamWebSocketUrl(data.port)}`);
|
|
110
|
-
}
|
|
111
|
-
lines.push("Run stream status for current connection details or stream disable when streaming is no longer needed.");
|
|
112
|
-
return lines.join("\n");
|
|
113
|
-
}
|
|
114
|
-
if (typeof data.enabled !== "boolean" || typeof data.connected !== "boolean") {
|
|
115
|
-
return undefined;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const lines = [
|
|
119
|
-
`Enabled: ${data.enabled}`,
|
|
120
|
-
`Connected: ${data.connected}`,
|
|
121
|
-
`Screencasting: ${data.screencasting === true}`,
|
|
122
|
-
];
|
|
123
|
-
if (typeof data.port === "number") {
|
|
124
|
-
lines.push(`Port: ${data.port}`);
|
|
125
|
-
lines.push(`WebSocket URL: ${getStreamWebSocketUrl(data.port)}`);
|
|
126
|
-
lines.push(`Frame format: JSON messages with base64 JPEG frame data`);
|
|
127
|
-
}
|
|
128
|
-
return lines.join("\n");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function getStreamWebSocketUrl(port: number): string {
|
|
132
|
-
return `ws://127.0.0.1:${port}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function enrichStreamStatusData(commandInfo: CommandInfo, data: unknown): unknown {
|
|
136
|
-
if (commandInfo.command !== "stream" || commandInfo.subcommand !== "status" || !isRecord(data) || typeof data.port !== "number") {
|
|
137
|
-
return data;
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
...data,
|
|
141
|
-
frameFormat: "JSON messages with base64 JPEG frame data",
|
|
142
|
-
wsUrl: getStreamWebSocketUrl(data.port),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function formatDiagnosticSummary(commandInfo: CommandInfo, data: Record<string, unknown>): string | undefined {
|
|
147
|
-
if (commandInfo.command === "session") {
|
|
148
|
-
const sessions = getArrayField(data, "sessions");
|
|
149
|
-
if (sessions) return `Sessions: ${sessions.length}`;
|
|
150
|
-
const session = getStringField(data, "session");
|
|
151
|
-
if (session) return `Session: ${session}`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (commandInfo.command === "profiles") {
|
|
155
|
-
const profiles = getArrayField(data, "profiles");
|
|
156
|
-
if (profiles) return `Chrome profiles: ${profiles.length}`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (commandInfo.command === "auth") {
|
|
160
|
-
const profiles = getArrayField(data, "profiles");
|
|
161
|
-
if (profiles) return `Auth profiles: ${profiles.length}`;
|
|
162
|
-
const name = getStringField(data, "name") ?? getStringField(data, "profile") ?? commandInfo.subcommand;
|
|
163
|
-
if (name && commandInfo.subcommand === "show") return `Auth profile: ${name}`;
|
|
164
|
-
if (name && ["save", "login", "delete"].includes(commandInfo.subcommand ?? "")) return `Auth ${commandInfo.subcommand}: ${name}`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (commandInfo.command === "cookies") {
|
|
168
|
-
const cookies = getArrayField(data, "cookies");
|
|
169
|
-
if (cookies) return `Cookies: ${cookies.length}`;
|
|
170
|
-
const name = getStringField(data, "name");
|
|
171
|
-
if (name) return name;
|
|
172
|
-
if (data.set === true) return "Cookie set";
|
|
173
|
-
if (data.cleared === true || data.clear === true) return "Cookies cleared";
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (commandInfo.command === "storage") {
|
|
177
|
-
const entries = getArrayField(data, "entries") ?? getArrayField(data, "items");
|
|
178
|
-
if (entries) return `Storage entries: ${entries.length}`;
|
|
179
|
-
const key = getStringField(data, "key");
|
|
180
|
-
if (key && (commandInfo.subcommand === "set" || data.set === true || Object.hasOwn(data, "value"))) return `Storage set: ${key}`;
|
|
181
|
-
if (data.cleared === true || data.clear === true) return "Storage cleared";
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (commandInfo.command === "dialog") {
|
|
185
|
-
const open = typeof data.open === "boolean" ? data.open : undefined;
|
|
186
|
-
if (open !== undefined) return open ? "Dialog open" : "No dialog open";
|
|
187
|
-
if (data.accepted === true) return "Dialog accepted";
|
|
188
|
-
if (data.dismissed === true) return "Dialog dismissed";
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (commandInfo.command === "frame") {
|
|
192
|
-
const frame = getStringField(data, "frame") ?? getStringField(data, "name") ?? getStringField(data, "selector") ?? commandInfo.subcommand;
|
|
193
|
-
if (frame) return `Frame: ${frame}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (commandInfo.command === "state") {
|
|
197
|
-
const states = getArrayField(data, "states") ?? getArrayField(data, "files");
|
|
198
|
-
if (states) return `States: ${states.length}`;
|
|
199
|
-
if (commandInfo.subcommand === "load") return undefined;
|
|
200
|
-
const stateName = getStringField(data, "name") ?? getStringField(data, "file") ?? getStringField(data, "path") ?? commandInfo.subcommand;
|
|
201
|
-
if (stateName) return `State ${commandInfo.subcommand ?? "result"}: ${stateName}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (commandInfo.command === "network") {
|
|
205
|
-
if (commandInfo.subcommand === "requests") {
|
|
206
|
-
const requests = getArrayField(data, "requests");
|
|
207
|
-
if (requests) return `Network requests: ${requests.length}`;
|
|
208
|
-
}
|
|
209
|
-
if (commandInfo.subcommand === "route") {
|
|
210
|
-
const routed = getStringField(data, "routed") ?? getStringField(data, "url") ?? getStringField(data, "pattern");
|
|
211
|
-
return routed ? `Network route: ${redactModelFacingTextIfSensitive(routed)}` : "Network route configured";
|
|
212
|
-
}
|
|
213
|
-
if (commandInfo.subcommand === "unroute") {
|
|
214
|
-
const unrouted = getStringField(data, "unrouted") ?? getStringField(data, "url") ?? getStringField(data, "pattern");
|
|
215
|
-
return unrouted ? `Network unroute: ${redactModelFacingTextIfSensitive(unrouted)}` : "Network route removed";
|
|
216
|
-
}
|
|
217
|
-
if (commandInfo.subcommand === "har") {
|
|
218
|
-
const state = getStringField(data, "state") ?? getStringField(data, "status") ?? commandInfo.subcommand;
|
|
219
|
-
return `Network HAR: ${state}`;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (commandInfo.command === "diff") {
|
|
224
|
-
if (commandInfo.subcommand === "snapshot") return "Snapshot diff completed";
|
|
225
|
-
if (commandInfo.subcommand === "url") return "URL diff completed";
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (["trace", "profiler"].includes(commandInfo.command ?? "")) {
|
|
229
|
-
const state = getStringField(data, "state") ?? getStringField(data, "status") ?? commandInfo.subcommand;
|
|
230
|
-
if (state) return `${commandInfo.command === "trace" ? "Trace" : "Profiler"}: ${state}`;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (commandInfo.command === "highlight") return "Element highlighted";
|
|
234
|
-
if (commandInfo.command === "inspect") return "DevTools inspect opened";
|
|
235
|
-
if (commandInfo.command === "clipboard") return `Clipboard ${commandInfo.subcommand ?? "completed"}`;
|
|
236
|
-
|
|
237
|
-
if (commandInfo.command === "stream") {
|
|
238
|
-
if (commandInfo.subcommand === "enable") {
|
|
239
|
-
if (data.alreadyEnabled === true) return "Stream already enabled";
|
|
240
|
-
const port = typeof data.port === "number" ? ` on port ${data.port}` : "";
|
|
241
|
-
return `Stream enabled${port}`;
|
|
242
|
-
}
|
|
243
|
-
if (commandInfo.subcommand === "disable") return "Stream disabled";
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (commandInfo.command === "chat") return "Chat response";
|
|
247
|
-
|
|
248
|
-
if (commandInfo.command === "console") {
|
|
249
|
-
const messages = getArrayField(data, "messages");
|
|
250
|
-
if (messages) return `Console messages: ${messages.length}`;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (commandInfo.command === "errors") {
|
|
254
|
-
const errors = getArrayField(data, "errors");
|
|
255
|
-
if (errors) return `Page errors: ${errors.length}`;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (commandInfo.command === "dashboard") {
|
|
259
|
-
if (typeof data.port === "number") return `Dashboard running on port ${data.port}`;
|
|
260
|
-
if (data.stopped === true) return "Dashboard stopped";
|
|
261
|
-
if (data.stopped === false) {
|
|
262
|
-
const reason = getStringField(data, "reason");
|
|
263
|
-
return reason ? `Dashboard not stopped: ${reason}` : "Dashboard not stopped";
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (commandInfo.command === "doctor") {
|
|
268
|
-
const status = getStringField(data, "status") ?? getStringField(data, "result");
|
|
269
|
-
if (status) return `Doctor: ${status}`;
|
|
270
|
-
const checks = getArrayField(data, "checks") ?? getArrayField(data, "issues") ?? getArrayField(data, "problems");
|
|
271
|
-
if (checks) return `Doctor: ${formatCount(checks.length, "item")}`;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return undefined;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function formatSessionText(data: Record<string, unknown>): string | undefined {
|
|
278
|
-
const sessions = getArrayField(data, "sessions");
|
|
279
|
-
if (sessions) {
|
|
280
|
-
if (sessions.length === 0) return "No active sessions.";
|
|
281
|
-
return sessions
|
|
282
|
-
.map((item, index) => {
|
|
283
|
-
if (!isRecord(item)) return `${index + 1}. ${stringifyModelFacing(item)}`;
|
|
284
|
-
const name = redactModelFacingText(getStringField(item, "name") ?? getStringField(item, "session") ?? getStringField(item, "id") ?? `(session ${index + 1})`);
|
|
285
|
-
const active = item.active === true;
|
|
286
|
-
const url = getStringField(item, "url");
|
|
287
|
-
const title = getStringField(item, "title");
|
|
288
|
-
const label = getStringField(item, "label");
|
|
289
|
-
const tabCount = typeof item.tabCount === "number" ? `${item.tabCount} tab${item.tabCount === 1 ? "" : "s"}` : undefined;
|
|
290
|
-
const metadata = [
|
|
291
|
-
`active=${active ? "true" : "false"}`,
|
|
292
|
-
label ? `label=${redactModelFacingText(label)}` : undefined,
|
|
293
|
-
title ? `title=${redactModelFacingTextIfSensitive(title)}` : undefined,
|
|
294
|
-
url ? `url=${redactModelFacingTextIfSensitive(url)}` : undefined,
|
|
295
|
-
tabCount,
|
|
296
|
-
].filter(Boolean).join("; ");
|
|
297
|
-
return `${index + 1}. name=${name}${active ? " *active*" : ""}; ${metadata}`;
|
|
298
|
-
})
|
|
299
|
-
.join("\n");
|
|
300
|
-
}
|
|
301
|
-
const session = getStringField(data, "session");
|
|
302
|
-
return session ? `Current session: ${redactModelFacingText(session)}` : undefined;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function formatProfilesText(profiles: unknown[], label: string): string {
|
|
306
|
-
if (profiles.length === 0) return `No ${label}.`;
|
|
307
|
-
return profiles
|
|
308
|
-
.map((item, index) => {
|
|
309
|
-
if (!isRecord(item)) return `${index + 1}. ${stringifyModelFacing(item)}`;
|
|
310
|
-
const name = redactModelFacingText(getStringField(item, "name") ?? getStringField(item, "profile") ?? `(unnamed ${index + 1})`);
|
|
311
|
-
const directory = getStringField(item, "directory") ?? getStringField(item, "path");
|
|
312
|
-
return directory ? `${index + 1}. ${name} (${redactModelFacingText(directory)})` : `${index + 1}. ${name}`;
|
|
313
|
-
})
|
|
314
|
-
.join("\n");
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function formatAuthShowText(data: Record<string, unknown>): string | undefined {
|
|
318
|
-
const lines = AUTH_SHOW_SAFE_FIELDS.flatMap((key) => {
|
|
319
|
-
const value = data[key];
|
|
320
|
-
return typeof value === "string" && value.trim().length > 0 ? [`${key}: ${redactModelFacingText(value.trim())}`] : [];
|
|
321
|
-
});
|
|
322
|
-
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function getPreviewCandidate(item: Record<string, unknown>, keys: readonly string[]): unknown {
|
|
326
|
-
for (const key of keys) {
|
|
327
|
-
const value = item[key];
|
|
328
|
-
if (value !== undefined && value !== null && value !== "") return value;
|
|
329
|
-
}
|
|
330
|
-
return undefined;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function formatNetworkPreviewValue(value: unknown, maxChars: number): string | undefined {
|
|
334
|
-
if (value === undefined || value === null) return undefined;
|
|
335
|
-
const previewValue = typeof value === "string" ? parseJsonPreviewString(value) : value;
|
|
336
|
-
const redacted = redactSensitiveValue(previewValue);
|
|
337
|
-
const raw = typeof redacted === "string" ? redacted : stringifyUnknown(redacted);
|
|
338
|
-
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
339
|
-
if (normalized.length === 0) return undefined;
|
|
340
|
-
return truncateText(redactSensitiveText(normalized), maxChars);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function appendNetworkPreview(lines: string[], label: string, value: unknown, maxChars: number): void {
|
|
344
|
-
const preview = formatNetworkPreviewValue(value, maxChars);
|
|
345
|
-
if (!preview) return;
|
|
346
|
-
lines.push(` ${label}: ${preview}`);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function formatNetworkRequestLine(item: Record<string, unknown>, index: number): string[] {
|
|
350
|
-
const method = getStringField(item, "method") ?? "GET";
|
|
351
|
-
const status = typeof item.status === "number" ? String(item.status) : "pending";
|
|
352
|
-
const type = getStringField(item, "resourceType") ?? getStringField(item, "mimeType");
|
|
353
|
-
const url = getStringField(item, "url") ?? "(no url)";
|
|
354
|
-
const requestId = getStringField(item, "requestId") ?? getStringField(item, "id");
|
|
355
|
-
const idText = requestId ? ` [${redactSensitiveText(requestId)}]` : "";
|
|
356
|
-
const failureClassification = classifyNetworkRequestFailure(item);
|
|
357
|
-
const impactText = failureClassification ? ` [${failureClassification.impact}: ${failureClassification.reason}]` : "";
|
|
358
|
-
const lines = [`${index + 1}. ${status} ${method} ${truncateText(redactSensitiveText(url), 180)}${type ? ` (${type})` : ""}${idText}${impactText}`];
|
|
359
|
-
appendNetworkPreview(lines, "Payload", getPreviewCandidate(item, NETWORK_PREVIEW_FIELD_CANDIDATES.request), NETWORK_BODY_PREVIEW_MAX_CHARS);
|
|
360
|
-
appendNetworkPreview(lines, "Response", getPreviewCandidate(item, NETWORK_PREVIEW_FIELD_CANDIDATES.response), NETWORK_BODY_PREVIEW_MAX_CHARS);
|
|
361
|
-
appendNetworkPreview(lines, "Error", getPreviewCandidate(item, NETWORK_PREVIEW_FIELD_CANDIDATES.error), NETWORK_ERROR_PREVIEW_MAX_CHARS);
|
|
362
|
-
return lines;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function formatNetworkRequestsText(data: Record<string, unknown>): string | undefined {
|
|
366
|
-
const requests = getArrayField(data, "requests");
|
|
367
|
-
if (!requests) return undefined;
|
|
368
|
-
if (requests.length === 0) return "No network requests captured. Scope: upstream session aggregate unless the upstream command output says it was cleared or filtered for this page.";
|
|
369
|
-
const shown = ["Scope: upstream session aggregate unless the upstream command output says it was cleared or filtered for this page; do not attribute old requests to the current page without URL/time evidence."];
|
|
370
|
-
const indexedRequests = requests.map((item, index) => ({ index, item }));
|
|
371
|
-
const artifactNoiseRequests = indexedRequests.filter((indexed) => isRecord(indexed.item) && isNetworkArtifactNoiseRequest(indexed.item));
|
|
372
|
-
const previewRequests = indexedRequests.filter((indexed) => !(isRecord(indexed.item) && isNetworkArtifactNoiseRequest(indexed.item)));
|
|
373
|
-
const networkFailureSummary = summarizeNetworkFailures(previewRequests.map((indexed) => indexed.item));
|
|
374
|
-
if (networkFailureSummary.totalCount > 0) {
|
|
375
|
-
shown.push(`Network failure summary: ${networkFailureSummary.actionableCount} actionable, ${networkFailureSummary.benignCount} benign low-impact (${networkFailureSummary.totalCount} total).`);
|
|
376
|
-
}
|
|
377
|
-
const failedRequests: typeof indexedRequests = [];
|
|
378
|
-
const normalRequests: typeof indexedRequests = [];
|
|
379
|
-
for (const indexed of previewRequests) {
|
|
380
|
-
if (isRecord(indexed.item) && classifyNetworkRequestFailure(indexed.item)) failedRequests.push(indexed);
|
|
381
|
-
else normalRequests.push(indexed);
|
|
382
|
-
}
|
|
383
|
-
if (artifactNoiseRequests.length > 0) {
|
|
384
|
-
shown.push(`Diagnostic noise hidden from preview: ${artifactNoiseRequests.length} data:image/artifact request row${artifactNoiseRequests.length === 1 ? "" : "s"}; raw rows remain in details.data.requests.`);
|
|
385
|
-
}
|
|
386
|
-
failedRequests.sort((left, right) => {
|
|
387
|
-
const leftClassification = isRecord(left.item) ? classifyNetworkRequestFailure(left.item) : undefined;
|
|
388
|
-
const rightClassification = isRecord(right.item) ? classifyNetworkRequestFailure(right.item) : undefined;
|
|
389
|
-
const leftRank = leftClassification?.impact === "actionable" ? 0 : 1;
|
|
390
|
-
const rightRank = rightClassification?.impact === "actionable" ? 0 : 1;
|
|
391
|
-
return leftRank - rightRank || left.index - right.index;
|
|
392
|
-
});
|
|
393
|
-
const prioritizedRequests = [...failedRequests, ...normalRequests];
|
|
394
|
-
shown.push(...prioritizedRequests.slice(0, DIAGNOSTIC_REQUEST_PREVIEW_LIMIT).flatMap(({ item, index }) => {
|
|
395
|
-
if (!isRecord(item)) return [`${index + 1}. ${stringifyModelFacing(item)}`];
|
|
396
|
-
return formatNetworkRequestLine(item, index);
|
|
397
|
-
}));
|
|
398
|
-
const omittedPreviewCount = Math.max(0, prioritizedRequests.length - DIAGNOSTIC_REQUEST_PREVIEW_LIMIT);
|
|
399
|
-
if (omittedPreviewCount > 0) {
|
|
400
|
-
shown.push(`... (${omittedPreviewCount} additional non-noise requests omitted from preview; failed requests are shown first when present)`);
|
|
401
|
-
}
|
|
402
|
-
return shown.join("\n");
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function formatNetworkRequestText(data: Record<string, unknown>): string | undefined {
|
|
406
|
-
if (!getStringField(data, "url") && !getStringField(data, "requestId") && !getStringField(data, "id")) {
|
|
407
|
-
return undefined;
|
|
408
|
-
}
|
|
409
|
-
return formatNetworkRequestLine(data, 0).join("\n");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
interface NetworkRequestActionCandidate {
|
|
413
|
-
filter?: string;
|
|
414
|
-
item: Record<string, unknown>;
|
|
415
|
-
kind: "actionable" | "api" | "benign" | "request";
|
|
416
|
-
requestId: string;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
function getSafeNetworkActionValue(value: string | undefined): string | undefined {
|
|
420
|
-
if (!value) return undefined;
|
|
421
|
-
const trimmed = value.trim();
|
|
422
|
-
if (trimmed.length === 0 || redactSensitiveText(trimmed) !== trimmed) return undefined;
|
|
423
|
-
return trimmed;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function getNetworkRequestId(item: Record<string, unknown>): string | undefined {
|
|
427
|
-
return getSafeNetworkActionValue(getStringField(item, "requestId") ?? getStringField(item, "id"));
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function isSensitiveNetworkPathSegment(segment: string): boolean {
|
|
431
|
-
const normalized = segment.toLowerCase();
|
|
432
|
-
return normalized === "auth" || NETWORK_FILTER_SENSITIVE_SEGMENT_TERMS.some((term) => normalized.includes(term));
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function pathFilterMayExposeSensitiveSegment(filter: string): boolean {
|
|
436
|
-
const decoded = (() => {
|
|
437
|
-
try {
|
|
438
|
-
return decodeURIComponent(filter);
|
|
439
|
-
} catch {
|
|
440
|
-
return filter;
|
|
441
|
-
}
|
|
442
|
-
})();
|
|
443
|
-
return decoded.split("/").some((segment) => isSensitiveNetworkPathSegment(segment) || NETWORK_FILTER_OPAQUE_SEGMENT_PATTERN.test(segment));
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function getNetworkRequestPathFilter(item: Record<string, unknown>): string | undefined {
|
|
447
|
-
const url = getStringField(item, "url");
|
|
448
|
-
if (!url) return undefined;
|
|
449
|
-
let filter: string | undefined;
|
|
450
|
-
try {
|
|
451
|
-
filter = new URL(url).pathname;
|
|
452
|
-
} catch {
|
|
453
|
-
filter = url.split(/[?#]/, 1)[0];
|
|
454
|
-
}
|
|
455
|
-
filter = filter?.trim();
|
|
456
|
-
if (!filter || filter === "/" || filter.length > NETWORK_FILTER_MAX_CHARS || pathFilterMayExposeSensitiveSegment(filter)) return undefined;
|
|
457
|
-
return getSafeNetworkActionValue(filter);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function getNetworkRequestActionCandidate(item: Record<string, unknown>): NetworkRequestActionCandidate | undefined {
|
|
461
|
-
if (isNetworkArtifactNoiseRequest(item)) return undefined;
|
|
462
|
-
const requestId = getNetworkRequestId(item);
|
|
463
|
-
if (!requestId) return undefined;
|
|
464
|
-
const classification = classifyNetworkRequestFailure(item);
|
|
465
|
-
const kind: NetworkRequestActionCandidate["kind"] = classification?.impact === "actionable"
|
|
466
|
-
? "actionable"
|
|
467
|
-
: classification?.impact === "benign"
|
|
468
|
-
? "benign"
|
|
469
|
-
: isApiLikeNetworkRequest(item)
|
|
470
|
-
? "api"
|
|
471
|
-
: "request";
|
|
472
|
-
return { filter: getNetworkRequestPathFilter(item), item, kind, requestId };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function chooseNetworkRequestActionCandidate(candidates: NetworkRequestActionCandidate[]): NetworkRequestActionCandidate | undefined {
|
|
476
|
-
return candidates.find((candidate) => candidate.kind === "actionable")
|
|
477
|
-
?? candidates.find((candidate) => candidate.kind === "api")
|
|
478
|
-
?? candidates.find((candidate) => candidate.kind === "benign")
|
|
479
|
-
?? candidates[0];
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function formatNetworkRequestActionDescriptor(candidate: NetworkRequestActionCandidate): string {
|
|
483
|
-
const method = getStringField(candidate.item, "method") ?? "GET";
|
|
484
|
-
const status = typeof candidate.item.status === "number" ? String(candidate.item.status) : "pending";
|
|
485
|
-
const target = candidate.filter ? ` ${candidate.filter}` : "";
|
|
486
|
-
return `${status} ${method}${target} [${candidate.requestId}]`;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
function getNetworkRequestDetailActionId(candidate: NetworkRequestActionCandidate): string {
|
|
490
|
-
if (candidate.kind === "actionable") return "inspect-actionable-network-request";
|
|
491
|
-
if (candidate.kind === "benign") return "inspect-benign-network-request";
|
|
492
|
-
return "inspect-network-request";
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
export function formatNetworkRouteDiagnosticsText(diagnostics: NetworkRouteDiagnostic[] | undefined): string | undefined {
|
|
496
|
-
if (!diagnostics || diagnostics.length === 0) return undefined;
|
|
497
|
-
const lines = ["Network route diagnostics:"];
|
|
498
|
-
for (const diagnostic of diagnostics) {
|
|
499
|
-
const target = diagnostic.requestId ? `[${diagnostic.requestId}] ${diagnostic.requestUrl ?? "request"}` : diagnostic.requestUrl ?? "request";
|
|
500
|
-
lines.push(`- ${diagnostic.reason}: ${target} matched route ${diagnostic.routePattern} (${diagnostic.mode}).`);
|
|
501
|
-
}
|
|
502
|
-
lines.push("If this route is intended as a mock, inspect the request/headers and treat failed, pending, or CORS-looking rows as unfulfilled until a mocked response is observed.");
|
|
503
|
-
return lines.join("\n");
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
export function buildNetworkRouteDiagnosticsNextActions(diagnostics: NetworkRouteDiagnostic[] | undefined, sessionName: string | undefined): AgentBrowserNextAction[] | undefined {
|
|
507
|
-
const diagnostic = diagnostics?.find((item) => item.requestId) ?? diagnostics?.[0];
|
|
508
|
-
if (!diagnostic) return undefined;
|
|
509
|
-
const actions: AgentBrowserNextAction[] = [];
|
|
510
|
-
if (diagnostic.requestId) {
|
|
511
|
-
actions.push({
|
|
512
|
-
id: "inspect-routed-network-request",
|
|
513
|
-
params: { args: withOptionalSessionArgs(sessionName, ["network", "request", diagnostic.requestId]) },
|
|
514
|
-
reason: `Inspect the routed request ${diagnostic.requestId} before assuming the route mock fulfilled normally.`,
|
|
515
|
-
safety: "Read-only request diagnostic; look for failed status, pending state, CORS/preflight errors, response body, and headers.",
|
|
516
|
-
tool: "agent_browser",
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
actions.push({
|
|
520
|
-
id: "start-network-har-capture-for-route-mock",
|
|
521
|
-
params: { args: withOptionalSessionArgs(sessionName, ["network", "har", "start"]) },
|
|
522
|
-
reason: "Capture a HAR before reproducing the route mock so pending/CORS behavior has request and response headers.",
|
|
523
|
-
safety: "HARs can contain URLs and headers; stop to an explicit path and avoid sharing sensitive captures.",
|
|
524
|
-
tool: "agent_browser",
|
|
525
|
-
});
|
|
526
|
-
return actions;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
export function buildNetworkRequestsNextActions(data: unknown, sessionName: string | undefined, routeDiagnostics?: NetworkRouteDiagnostic[]): AgentBrowserNextAction[] | undefined {
|
|
530
|
-
if (!isRecord(data)) return undefined;
|
|
531
|
-
const requests = getArrayField(data, "requests");
|
|
532
|
-
if (!requests) return undefined;
|
|
533
|
-
const candidates = requests.flatMap((item) => {
|
|
534
|
-
if (!isRecord(item)) return [];
|
|
535
|
-
const candidate = getNetworkRequestActionCandidate(item);
|
|
536
|
-
return candidate ? [candidate] : [];
|
|
537
|
-
});
|
|
538
|
-
const selected = chooseNetworkRequestActionCandidate(candidates);
|
|
539
|
-
if (!selected) return undefined;
|
|
540
|
-
const descriptor = formatNetworkRequestActionDescriptor(selected);
|
|
541
|
-
const actions: AgentBrowserNextAction[] = [
|
|
542
|
-
{
|
|
543
|
-
id: getNetworkRequestDetailActionId(selected),
|
|
544
|
-
params: { args: withOptionalSessionArgs(sessionName, ["network", "request", selected.requestId]) },
|
|
545
|
-
reason: `Inspect full request details for ${descriptor}.`,
|
|
546
|
-
safety: "Read-only network diagnostic; request inspection must not replace the active page/ref context.",
|
|
547
|
-
tool: "agent_browser",
|
|
548
|
-
},
|
|
549
|
-
];
|
|
550
|
-
if (selected.kind === "actionable") {
|
|
551
|
-
actions.push({
|
|
552
|
-
id: "trace-actionable-network-source",
|
|
553
|
-
params: { networkSourceLookup: { requestId: selected.requestId, ...(sessionName ? { session: sessionName } : {}) } },
|
|
554
|
-
reason: `Look for local source candidates related to ${descriptor}.`,
|
|
555
|
-
safety: "Read-only experimental helper; it reports bounded candidates and may miss bundled or dynamic call sites.",
|
|
556
|
-
tool: "agent_browser",
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
if (selected.filter) {
|
|
560
|
-
actions.push({
|
|
561
|
-
id: "filter-network-requests-by-path",
|
|
562
|
-
params: { args: withOptionalSessionArgs(sessionName, ["network", "requests", "--filter", selected.filter]) },
|
|
563
|
-
reason: `List captured requests matching ${selected.filter}.`,
|
|
564
|
-
safety: "Read-only request-list filter; absence from a compact preview is not proof the request did not happen.",
|
|
565
|
-
tool: "agent_browser",
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
actions.push({
|
|
569
|
-
id: "clear-network-requests-before-repro",
|
|
570
|
-
params: { args: withOptionalSessionArgs(sessionName, ["network", "requests", "--clear"]) },
|
|
571
|
-
reason: "Clear the aggregate request buffer before reproducing the current-page network behavior.",
|
|
572
|
-
safety: "This mutates only diagnostic buffers for the session; capture or inspect needed old rows first.",
|
|
573
|
-
tool: "agent_browser",
|
|
574
|
-
});
|
|
575
|
-
actions.push({
|
|
576
|
-
id: "start-network-har-capture",
|
|
577
|
-
params: { args: withOptionalSessionArgs(sessionName, ["network", "har", "start"]) },
|
|
578
|
-
reason: "Start HAR capture before reproducing the network behavior again.",
|
|
579
|
-
safety: "HARs can contain URLs and headers; stop to an explicit path, inspect metadata, and avoid sharing sensitive captures.",
|
|
580
|
-
tool: "agent_browser",
|
|
581
|
-
});
|
|
582
|
-
return [...(buildNetworkRouteDiagnosticsNextActions(routeDiagnostics, sessionName) ?? []), ...actions].slice(0, NETWORK_NEXT_ACTION_LIMIT);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
export function buildStreamNextActions(commandInfo: CommandInfo, data: unknown, sessionName: string | undefined): AgentBrowserNextAction[] | undefined {
|
|
586
|
-
if (commandInfo.command !== "stream" || commandInfo.subcommand !== "enable" || !isRecord(data) || data.alreadyEnabled !== true) return undefined;
|
|
587
|
-
return [
|
|
588
|
-
{
|
|
589
|
-
id: "check-stream-status-after-noop",
|
|
590
|
-
params: { args: withOptionalSessionArgs(sessionName, ["stream", "status"]) },
|
|
591
|
-
reason: "Read current stream port and connection details after the idempotent enable no-op.",
|
|
592
|
-
safety: "Read-only stream diagnostic.",
|
|
593
|
-
tool: "agent_browser",
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
id: "disable-existing-stream-when-done",
|
|
597
|
-
params: { args: withOptionalSessionArgs(sessionName, ["stream", "disable"]) },
|
|
598
|
-
reason: "Disable the existing stream when it is no longer needed.",
|
|
599
|
-
safety: "Only run when no other workflow is relying on the current stream.",
|
|
600
|
-
tool: "agent_browser",
|
|
601
|
-
},
|
|
602
|
-
];
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function formatConsoleText(data: Record<string, unknown>): string | undefined {
|
|
606
|
-
const messages = getArrayField(data, "messages");
|
|
607
|
-
if (!messages) return undefined;
|
|
608
|
-
if (messages.length === 0) return "No console messages. Scope: upstream session aggregate unless the upstream command output says it was cleared or filtered for this page.";
|
|
609
|
-
const shown = ["Scope: upstream session aggregate unless the upstream command output says it was cleared or filtered for this page; do not attribute old messages to the current page without URL/time evidence."];
|
|
610
|
-
shown.push(...messages.slice(0, DIAGNOSTIC_LOG_PREVIEW_LIMIT).map((item, index) => {
|
|
611
|
-
if (!isRecord(item)) return `${index + 1}. ${stringifyModelFacing(item)}`;
|
|
612
|
-
const type = redactModelFacingText(getStringField(item, "type") ?? "message");
|
|
613
|
-
const text = getStringField(item, "text") ?? stringifyModelFacing(item);
|
|
614
|
-
return `${index + 1}. [${type}] ${firstLine(redactModelFacingText(text).replace(/\s+/g, " ").trim(), 220)}`;
|
|
615
|
-
}));
|
|
616
|
-
const previewedMessageCount = Math.min(messages.length, DIAGNOSTIC_LOG_PREVIEW_LIMIT);
|
|
617
|
-
if (messages.length > previewedMessageCount) {
|
|
618
|
-
shown.push(`... (${messages.length - previewedMessageCount} additional console messages omitted from preview)`);
|
|
619
|
-
}
|
|
620
|
-
return shown.join("\n");
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function formatErrorsText(data: Record<string, unknown>): string | undefined {
|
|
624
|
-
const errors = getArrayField(data, "errors");
|
|
625
|
-
if (!errors) return undefined;
|
|
626
|
-
if (errors.length === 0) return "No page errors.";
|
|
627
|
-
const shown = errors.slice(0, DIAGNOSTIC_LOG_PREVIEW_LIMIT).map((item, index) => {
|
|
628
|
-
if (!isRecord(item)) return `${index + 1}. ${stringifyModelFacing(item)}`;
|
|
629
|
-
const text = getStringField(item, "text") ?? stringifyModelFacing(item);
|
|
630
|
-
const location = [
|
|
631
|
-
getStringField(item, "url"),
|
|
632
|
-
typeof item.line === "number" ? `line ${item.line}` : undefined,
|
|
633
|
-
typeof item.column === "number" ? `column ${item.column}` : undefined,
|
|
634
|
-
]
|
|
635
|
-
.filter(Boolean)
|
|
636
|
-
.map((item) => redactModelFacingText(String(item)))
|
|
637
|
-
.join(":");
|
|
638
|
-
const safeText = firstLine(redactModelFacingText(text), 220);
|
|
639
|
-
return location ? `${index + 1}. ${safeText} (${location})` : `${index + 1}. ${safeText}`;
|
|
640
|
-
});
|
|
641
|
-
if (errors.length > shown.length) {
|
|
642
|
-
shown.push(`... (${errors.length - shown.length} additional errors omitted from preview)`);
|
|
643
|
-
}
|
|
644
|
-
return shown.join("\n");
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
function formatDashboardText(data: Record<string, unknown>): string | undefined {
|
|
648
|
-
const lines: string[] = [];
|
|
649
|
-
if (typeof data.port === "number") lines.push(`Port: ${data.port}`);
|
|
650
|
-
if (typeof data.pid === "number") lines.push(`PID: ${data.pid}`);
|
|
651
|
-
if (typeof data.stopped === "boolean") lines.push(`Stopped: ${data.stopped}`);
|
|
652
|
-
const reason = getStringField(data, "reason");
|
|
653
|
-
if (reason) lines.push(`Reason: ${redactModelFacingText(reason)}`);
|
|
654
|
-
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function formatChatText(data: Record<string, unknown>): string | undefined {
|
|
658
|
-
const response = getStringField(data, "response") ?? getStringField(data, "message") ?? getStringField(data, "text") ?? getStringField(data, "result");
|
|
659
|
-
if (response) return redactModelFacingText(response);
|
|
660
|
-
const model = getStringField(data, "model");
|
|
661
|
-
const provider = getStringField(data, "provider");
|
|
662
|
-
const lines = [model ? `Model: ${redactModelFacingText(model)}` : undefined, provider ? `Provider: ${redactModelFacingText(provider)}` : undefined].filter(Boolean);
|
|
663
|
-
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
function formatDoctorText(data: Record<string, unknown>): string | undefined {
|
|
667
|
-
const lines: string[] = [];
|
|
668
|
-
const status = getStringField(data, "status") ?? getStringField(data, "result");
|
|
669
|
-
if (status) lines.push(`Status: ${redactModelFacingText(status)}`);
|
|
670
|
-
const summary = isRecord(data.summary) ? data.summary : undefined;
|
|
671
|
-
if (summary) {
|
|
672
|
-
const parts = ["pass", "warn", "fail"].flatMap((key) => typeof summary[key] === "number" ? [`${key}:${summary[key]}`] : []);
|
|
673
|
-
if (parts.length > 0) lines.push(`Summary: ${parts.join(", ")}`);
|
|
674
|
-
}
|
|
675
|
-
const checks = getArrayField(data, "checks");
|
|
676
|
-
if (checks) {
|
|
677
|
-
lines.push(`Checks: ${checks.length}`);
|
|
678
|
-
for (const [index, item] of checks.slice(0, 30).entries()) {
|
|
679
|
-
if (!isRecord(item)) {
|
|
680
|
-
lines.push(`${index + 1}. ${stringifyModelFacing(item)}`);
|
|
681
|
-
continue;
|
|
682
|
-
}
|
|
683
|
-
const checkStatus = getStringField(item, "status") ?? "info";
|
|
684
|
-
const id = getStringField(item, "id");
|
|
685
|
-
const category = getStringField(item, "category");
|
|
686
|
-
const message = getStringField(item, "message") ?? getStringField(item, "name") ?? getStringField(item, "title") ?? getStringField(item, "check") ?? stringifyModelFacing(item);
|
|
687
|
-
const label = [category, id].filter(Boolean).join("/");
|
|
688
|
-
lines.push(`${index + 1}. [${redactModelFacingText(checkStatus)}]${label ? ` ${redactModelFacingText(label)}:` : ""} ${firstLine(redactModelFacingText(message), 220)}`);
|
|
689
|
-
const fix = getStringField(item, "fix");
|
|
690
|
-
if (fix) lines.push(` fix: ${redactModelFacingText(fix)}`);
|
|
691
|
-
}
|
|
692
|
-
if (checks.length > 30) lines.push(`... (${checks.length - 30} additional checks omitted from preview)`);
|
|
693
|
-
}
|
|
694
|
-
for (const key of ["issues", "problems"] as const) {
|
|
695
|
-
const items = getArrayField(data, key);
|
|
696
|
-
if (items) lines.push(`${key}: ${items.length}`);
|
|
697
|
-
}
|
|
698
|
-
if (lines.length === 0) {
|
|
699
|
-
const keys = Object.keys(data).filter((key) => key !== "success");
|
|
700
|
-
if (keys.length > 0) return `Doctor diagnostics returned unrecognized fields: ${keys.map(redactModelFacingText).join(", ")}. See details.data for structured diagnostics.`;
|
|
701
|
-
}
|
|
702
|
-
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
function formatCookieRecordText(item: Record<string, unknown>, fallbackName: string): string {
|
|
706
|
-
const name = redactModelFacingText(getStringField(item, "name") ?? fallbackName);
|
|
707
|
-
const domain = getStringField(item, "domain");
|
|
708
|
-
const path = getStringField(item, "path");
|
|
709
|
-
const flags = [item.httpOnly === true ? "httpOnly" : undefined, item.secure === true ? "secure" : undefined].filter(Boolean).join(", ");
|
|
710
|
-
const location = [domain, path].filter(Boolean).join("");
|
|
711
|
-
return [name, location ? `(${redactModelFacingText(location)})` : undefined, flags ? `[${flags}]` : undefined].filter(Boolean).join(" ");
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
function formatCookiesText(data: Record<string, unknown>): string | undefined {
|
|
715
|
-
const cookies = getArrayField(data, "cookies");
|
|
716
|
-
if (cookies) {
|
|
717
|
-
if (cookies.length === 0) return "No cookies.";
|
|
718
|
-
return cookies
|
|
719
|
-
.map((item, index) => (isRecord(item) ? formatCookieRecordText(item, `(cookie ${index + 1})`) : `${index + 1}. [REDACTED]`))
|
|
720
|
-
.join("\n");
|
|
721
|
-
}
|
|
722
|
-
if (getStringField(data, "name") || getStringField(data, "domain") || getStringField(data, "path") || Object.hasOwn(data, "value")) {
|
|
723
|
-
return formatCookieRecordText(data, "cookie");
|
|
724
|
-
}
|
|
725
|
-
if (data.set === true) return "Cookie set.";
|
|
726
|
-
if (data.cleared === true || data.clear === true) return "Cookies cleared.";
|
|
727
|
-
return undefined;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
function valueContainsStorageSecret(value: unknown): boolean {
|
|
731
|
-
if (typeof value === "string") {
|
|
732
|
-
const trimmed = value.trim();
|
|
733
|
-
if (trimmed.length === 0) return false;
|
|
734
|
-
if (STORAGE_TOKEN_VALUE_PATTERN.test(trimmed) || STORAGE_SECRET_VALUE_WORD_PATTERN.test(trimmed) || STORAGE_EMAIL_VALUE_PATTERN.test(trimmed) || STORAGE_IDENTITY_VALUE_PATTERN.test(trimmed)) return true;
|
|
735
|
-
try {
|
|
736
|
-
const url = new URL(trimmed);
|
|
737
|
-
if (url.protocol === "http:" || url.protocol === "https:" || url.username || url.password || url.search) return true;
|
|
738
|
-
} catch {}
|
|
739
|
-
if (redactSensitiveText(trimmed) !== trimmed || redactModelFacingTextIfSensitive(trimmed) !== trimmed) return true;
|
|
740
|
-
try {
|
|
741
|
-
return valueContainsStorageSecret(JSON.parse(trimmed));
|
|
742
|
-
} catch {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
if (Array.isArray(value)) return value.some((item) => valueContainsStorageSecret(item));
|
|
747
|
-
if (!isRecord(value)) return false;
|
|
748
|
-
return Object.entries(value).some(([key, entryValue]) => STORAGE_SECRET_KEY_PATTERN.test(key) || valueContainsStorageSecret(entryValue));
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
function shouldRevealStorageValue(key: string | undefined, value: unknown): boolean {
|
|
752
|
-
if (!key || STORAGE_SECRET_KEY_PATTERN.test(key) || !STORAGE_BENIGN_KEY_PATTERN.test(key)) return false;
|
|
753
|
-
if (valueContainsStorageSecret(value)) return false;
|
|
754
|
-
if (typeof value === "string") return value.length <= STORAGE_VALUE_PREVIEW_MAX_CHARS;
|
|
755
|
-
return value === null || typeof value === "number" || typeof value === "boolean";
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function formatStorageValue(key: string | undefined, value: unknown): string {
|
|
759
|
-
if (!shouldRevealStorageValue(key, value)) return "[REDACTED]";
|
|
760
|
-
if (typeof value === "string") return redactModelFacingText(value);
|
|
761
|
-
return stringifyModelFacing(value);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
function redactStorageEntryValue(item: Record<string, unknown>): Record<string, unknown> {
|
|
765
|
-
if (!Object.hasOwn(item, "value")) return redactStructuredPresentationValue(item) as Record<string, unknown>;
|
|
766
|
-
const key = getStringField(item, "key") ?? getStringField(item, "name");
|
|
767
|
-
const value = item.value;
|
|
768
|
-
if (shouldRevealStorageValue(key, value)) return redactStructuredPresentationValue(item) as Record<string, unknown>;
|
|
769
|
-
return {
|
|
770
|
-
...redactStructuredPresentationValue({ ...item, value: undefined }) as Record<string, unknown>,
|
|
771
|
-
value: "[REDACTED]",
|
|
772
|
-
valueRedacted: true,
|
|
773
|
-
valueRedactionReason: key && STORAGE_SECRET_KEY_PATTERN.test(key) ? "sensitive-key" : "sensitive-value",
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
function redactStorageData(value: unknown): unknown {
|
|
778
|
-
if (Array.isArray(value)) return value.map((item) => redactStorageData(item));
|
|
779
|
-
if (!isRecord(value)) return redactStructuredPresentationValue(value);
|
|
780
|
-
const entries = Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
|
|
781
|
-
if ((key === "entries" || key === "items") && Array.isArray(entryValue)) return [key, entryValue.map((item) => isRecord(item) ? redactStorageEntryValue(item) : redactStructuredPresentationValue(item))];
|
|
782
|
-
if (key === "value") {
|
|
783
|
-
const itemKey = getStringField(value, "key") ?? getStringField(value, "name");
|
|
784
|
-
return [key, shouldRevealStorageValue(itemKey, entryValue) ? redactStructuredPresentationValue(entryValue) : "[REDACTED]"];
|
|
785
|
-
}
|
|
786
|
-
return [key, redactStructuredPresentationValue(entryValue)];
|
|
787
|
-
}));
|
|
788
|
-
if (Object.hasOwn(value, "value")) {
|
|
789
|
-
const key = getStringField(value, "key") ?? getStringField(value, "name");
|
|
790
|
-
if (!shouldRevealStorageValue(key, value.value)) {
|
|
791
|
-
entries.valueRedacted = true;
|
|
792
|
-
entries.valueRedactionReason = key && STORAGE_SECRET_KEY_PATTERN.test(key) ? "sensitive-key" : "sensitive-value";
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
return entries;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
function formatStorageText(data: Record<string, unknown>): string | undefined {
|
|
799
|
-
const type = getStringField(data, "type") ?? getStringField(data, "storage") ?? "storage";
|
|
800
|
-
const entries = getArrayField(data, "entries") ?? getArrayField(data, "items");
|
|
801
|
-
if (entries) {
|
|
802
|
-
if (entries.length === 0) return `${type}: no entries.`;
|
|
803
|
-
return entries
|
|
804
|
-
.map((item, index) => {
|
|
805
|
-
if (!isRecord(item)) return `${index + 1}. [REDACTED]`;
|
|
806
|
-
const rawKey = getStringField(item, "key") ?? getStringField(item, "name") ?? `(entry ${index + 1})`;
|
|
807
|
-
const key = redactModelFacingText(rawKey);
|
|
808
|
-
return Object.hasOwn(item, "value") ? `${key}: ${formatStorageValue(rawKey, item.value)}` : key;
|
|
809
|
-
})
|
|
810
|
-
.join("\n");
|
|
811
|
-
}
|
|
812
|
-
const key = getStringField(data, "key");
|
|
813
|
-
if (key && Object.hasOwn(data, "value")) return `${type} ${redactModelFacingText(key)}: ${formatStorageValue(key, data.value)}`;
|
|
814
|
-
if (key && data.set === true) return `${type} set: ${redactModelFacingText(key)}`;
|
|
815
|
-
if (data.cleared === true || data.clear === true) return `${type} cleared.`;
|
|
816
|
-
return undefined;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function formatDialogText(data: Record<string, unknown>): string | undefined {
|
|
820
|
-
const lines: string[] = [];
|
|
821
|
-
if (typeof data.open === "boolean") lines.push(data.open ? "Dialog open." : "No dialog open.");
|
|
822
|
-
const type = getStringField(data, "type");
|
|
823
|
-
if (type) lines.push(`Type: ${redactModelFacingText(type)}`);
|
|
824
|
-
const message = getStringField(data, "message");
|
|
825
|
-
if (message) lines.push(`Message: ${/(?:auth|authorization|bearer|cookie|pass(?:word)?|secret|session|token)/i.test(message) ? "[REDACTED]" : redactModelFacingText(message)}`);
|
|
826
|
-
if (data.accepted === true) lines.push("Accepted.");
|
|
827
|
-
if (data.dismissed === true) lines.push("Dismissed.");
|
|
828
|
-
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
function formatFrameText(data: Record<string, unknown>): string | undefined {
|
|
832
|
-
const frame = getStringField(data, "frame") ?? getStringField(data, "name") ?? getStringField(data, "selector");
|
|
833
|
-
const url = getStringField(data, "url");
|
|
834
|
-
const title = getStringField(data, "title");
|
|
835
|
-
const lines = [frame ? `Frame: ${redactModelFacingText(frame)}` : undefined, title ? `Title: ${redactModelFacingText(title)}` : undefined, url ? `URL: ${redactModelFacingTextIfSensitive(url)}` : undefined].filter(Boolean);
|
|
836
|
-
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
function formatStateText(data: Record<string, unknown>): string | undefined {
|
|
840
|
-
const states = getArrayField(data, "states") ?? getArrayField(data, "files");
|
|
841
|
-
if (states) {
|
|
842
|
-
if (states.length === 0) return "No saved states.";
|
|
843
|
-
return states
|
|
844
|
-
.map((item, index) => {
|
|
845
|
-
if (!isRecord(item)) return `${index + 1}. ${redactModelFacingTextIfSensitive(stringifyModelFacing(item))}`;
|
|
846
|
-
const name = getStringField(item, "name") ?? getStringField(item, "file") ?? getStringField(item, "path") ?? `(state ${index + 1})`;
|
|
847
|
-
const url = getStringField(item, "url");
|
|
848
|
-
return url ? `${index + 1}. ${redactModelFacingText(name)} — ${redactModelFacingTextIfSensitive(url)}` : `${index + 1}. ${redactModelFacingText(name)}`;
|
|
849
|
-
})
|
|
850
|
-
.join("\n");
|
|
851
|
-
}
|
|
852
|
-
if (data.loaded === true) return `State loaded: ${redactModelFacingText(getStringField(data, "path") ?? getStringField(data, "name") ?? "ok")}`;
|
|
853
|
-
if (data.cleared === true || data.clear === true) return "State cleared.";
|
|
854
|
-
return undefined;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
function isSensitivePresentationField(key: string): boolean {
|
|
858
|
-
return SENSITIVE_PRESENTATION_FIELD_PATTERN.test(key);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
function redactStructuredPresentationValue(value: unknown): unknown {
|
|
862
|
-
if (typeof value === "string") return redactModelFacingTextIfSensitive(value);
|
|
863
|
-
if (Array.isArray(value)) return value.map((item) => redactStructuredPresentationValue(item));
|
|
864
|
-
if (!isRecord(value)) return value;
|
|
865
|
-
return Object.fromEntries(
|
|
866
|
-
Object.entries(value).map(([key, entryValue]) => [
|
|
867
|
-
key,
|
|
868
|
-
isSensitivePresentationField(key) ? "[REDACTED]" : redactStructuredPresentationValue(entryValue),
|
|
869
|
-
]),
|
|
870
|
-
);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
function redactStatefulValues(value: unknown, sensitiveKeys: Set<string>): unknown {
|
|
874
|
-
if (Array.isArray(value)) return value.map((item) => redactStatefulValues(item, sensitiveKeys));
|
|
875
|
-
if (!isRecord(value)) return redactStructuredPresentationValue(value);
|
|
876
|
-
return Object.fromEntries(
|
|
877
|
-
Object.entries(value).map(([key, entryValue]) => [
|
|
878
|
-
key,
|
|
879
|
-
sensitiveKeys.has(key.toLowerCase()) ? "[REDACTED]" : redactStatefulValues(entryValue, sensitiveKeys),
|
|
880
|
-
]),
|
|
881
|
-
);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
export function redactPresentationData(commandInfo: CommandInfo, data: unknown): unknown {
|
|
885
|
-
if (commandInfo.command === "cookies") return redactStatefulValues(data, new Set(["value"]));
|
|
886
|
-
if (commandInfo.command === "storage") return redactStorageData(data);
|
|
887
|
-
return redactStructuredPresentationValue(data);
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
export function formatDiagnosticText(commandInfo: CommandInfo, data: Record<string, unknown>): string | undefined {
|
|
891
|
-
if (commandInfo.command === "session") return formatSessionText(data);
|
|
892
|
-
if (commandInfo.command === "profiles") {
|
|
893
|
-
const profiles = getArrayField(data, "profiles");
|
|
894
|
-
if (profiles) return formatProfilesText(profiles, "Chrome profiles");
|
|
895
|
-
}
|
|
896
|
-
if (commandInfo.command === "auth") {
|
|
897
|
-
const profiles = getArrayField(data, "profiles");
|
|
898
|
-
if (profiles) return formatProfilesText(profiles, "auth profiles");
|
|
899
|
-
if (commandInfo.subcommand === "show") return formatAuthShowText(data);
|
|
900
|
-
}
|
|
901
|
-
if (commandInfo.command === "cookies") return formatCookiesText(data);
|
|
902
|
-
if (commandInfo.command === "storage") return formatStorageText(data);
|
|
903
|
-
if (commandInfo.command === "dialog") return formatDialogText(data);
|
|
904
|
-
if (commandInfo.command === "frame") return formatFrameText(data);
|
|
905
|
-
if (commandInfo.command === "state") return formatStateText(data);
|
|
906
|
-
if (commandInfo.command === "network" && commandInfo.subcommand === "requests") return formatNetworkRequestsText(data);
|
|
907
|
-
if (commandInfo.command === "network" && commandInfo.subcommand === "request") return formatNetworkRequestText(data);
|
|
908
|
-
if (commandInfo.command === "diff") return stringifyModelFacing(data);
|
|
909
|
-
if (commandInfo.command === "clipboard") {
|
|
910
|
-
const text = getStringField(data, "text") ?? getStringField(data, "value") ?? getStringField(data, "result");
|
|
911
|
-
if (text) return redactModelFacingText(text);
|
|
912
|
-
}
|
|
913
|
-
if (commandInfo.command === "stream") {
|
|
914
|
-
const streamSummary = getStreamSummary(data);
|
|
915
|
-
if (streamSummary) return streamSummary;
|
|
916
|
-
}
|
|
917
|
-
if (commandInfo.command === "chat") return formatChatText(data);
|
|
918
|
-
if (commandInfo.command === "console") return formatConsoleText(data);
|
|
919
|
-
if (commandInfo.command === "errors") return formatErrorsText(data);
|
|
920
|
-
if (commandInfo.command === "dashboard") return formatDashboardText(data);
|
|
921
|
-
if (commandInfo.command === "doctor") return formatDoctorText(data);
|
|
922
|
-
return undefined;
|
|
923
|
-
}
|