pi-agent-browser-native 0.2.47 → 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 +63 -19
- package/README.md +52 -19
- 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/ARCHITECTURE.md +10 -10
- package/docs/COMMAND_REFERENCE.md +35 -21
- package/docs/ELECTRON.md +3 -3
- package/docs/RELEASE.md +46 -26
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +35 -106
- package/docs/TOOL_CONTRACT.md +23 -21
- package/package.json +12 -8
- package/scripts/agent-browser-capability-baseline.mjs +6 -3
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +19 -17
- package/scripts/platform-smoke.mjs +1 -1
- package/extensions/agent-browser/index.ts +0 -952
- 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 -209
- 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 -451
- 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/click-dispatch.ts +0 -257
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -912
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -512
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -1481
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -564
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -564
- 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 -252
- 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 -721
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -1,588 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own file artifact detection, verification, manifest merging, and inline image attachment for tool presentation.
|
|
3
|
-
* Responsibilities: Build artifact metadata, verification summaries, saved-file details, artifact retention notices, and safe image content.
|
|
4
|
-
* Scope: Artifact and image presentation only.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { readFile, stat } from "node:fs/promises";
|
|
8
|
-
import { extname, resolve } from "node:path";
|
|
9
|
-
|
|
10
|
-
import { isRecord, parsePositiveInteger } from "../../parsing.js";
|
|
11
|
-
import type { CommandInfo } from "../../runtime.js";
|
|
12
|
-
import {
|
|
13
|
-
formatSessionArtifactRetentionSummary,
|
|
14
|
-
mergeSessionArtifactManifest,
|
|
15
|
-
} from "../artifact-manifest.js";
|
|
16
|
-
import { isPendingRecordingArtifact, isPendingRecordingCommand } from "../artifact-state.js";
|
|
17
|
-
import { classifyAgentBrowserSuccessCategory } from "../categories.js";
|
|
18
|
-
import type {
|
|
19
|
-
ArtifactVerificationEntry,
|
|
20
|
-
ArtifactVerificationSummary,
|
|
21
|
-
FileArtifactKind,
|
|
22
|
-
FileArtifactMetadata,
|
|
23
|
-
SavedFilePresentationDetails,
|
|
24
|
-
SessionArtifactManifest,
|
|
25
|
-
SessionArtifactManifestEntry,
|
|
26
|
-
ToolPresentation,
|
|
27
|
-
} from "../contracts.js";
|
|
28
|
-
|
|
29
|
-
const IMAGE_EXTENSION_TO_MIME_TYPE: Record<string, string> = {
|
|
30
|
-
".gif": "image/gif",
|
|
31
|
-
".jpeg": "image/jpeg",
|
|
32
|
-
".jpg": "image/jpeg",
|
|
33
|
-
".png": "image/png",
|
|
34
|
-
".webp": "image/webp",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const INLINE_IMAGE_MAX_BYTES_ENV = "PI_AGENT_BROWSER_INLINE_IMAGE_MAX_BYTES";
|
|
38
|
-
|
|
39
|
-
const DEFAULT_INLINE_IMAGE_MAX_BYTES = 5 * 1_024 * 1_024;
|
|
40
|
-
|
|
41
|
-
function getImageMimeType(filePath: string): string | undefined {
|
|
42
|
-
const extension = extname(filePath).toLowerCase();
|
|
43
|
-
return IMAGE_EXTENSION_TO_MIME_TYPE[extension];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getInlineImageMaxBytes(env: NodeJS.ProcessEnv = process.env): number {
|
|
47
|
-
return parsePositiveInteger(env[INLINE_IMAGE_MAX_BYTES_ENV]) ?? DEFAULT_INLINE_IMAGE_MAX_BYTES;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function formatByteCount(bytes: number): string {
|
|
51
|
-
if (bytes < 1_024) return `${bytes} B`;
|
|
52
|
-
if (bytes < 1_024 * 1_024) return `${(bytes / 1_024).toFixed(1)} KiB`;
|
|
53
|
-
return `${(bytes / (1_024 * 1_024)).toFixed(1)} MiB`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function appendPresentationNotice(presentation: ToolPresentation, message: string): void {
|
|
57
|
-
const existingText = presentation.content[0]?.type === "text" ? presentation.content[0].text : "";
|
|
58
|
-
presentation.content[0] = {
|
|
59
|
-
type: "text",
|
|
60
|
-
text: existingText.length > 0 ? `${existingText}\n\n${message}` : message,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function shouldAppendArtifactRetentionNotice(entries: SessionArtifactManifestEntry[]): boolean {
|
|
65
|
-
return entries.some((entry) => entry.retentionState === "evicted" || entry.storageScope !== "explicit-path");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getManifestEntryKey(entry: SessionArtifactManifestEntry): string {
|
|
69
|
-
return entry.storageScope === "explicit-path" && entry.absolutePath ? `${entry.storageScope}:${entry.absolutePath}` : `${entry.storageScope}:${entry.path}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function manifestHasNewNoticeWorthyEntries(base: SessionArtifactManifest | undefined, current: SessionArtifactManifest | undefined): boolean {
|
|
73
|
-
if (!current) return false;
|
|
74
|
-
const baseKeys = new Set((base?.entries ?? []).map(getManifestEntryKey));
|
|
75
|
-
return current.entries.some((entry) => !baseKeys.has(getManifestEntryKey(entry)) && (entry.retentionState === "evicted" || entry.storageScope !== "explicit-path"));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function applyArtifactManifest(presentation: ToolPresentation, baseManifest: SessionArtifactManifest | undefined, entries: SessionArtifactManifestEntry[]): ToolPresentation {
|
|
79
|
-
if (entries.length === 0) return presentation;
|
|
80
|
-
const artifactManifest = mergeSessionArtifactManifest({ base: baseManifest, entries });
|
|
81
|
-
if (!artifactManifest) return presentation;
|
|
82
|
-
presentation.artifactManifest = artifactManifest;
|
|
83
|
-
presentation.artifactRetentionSummary = formatSessionArtifactRetentionSummary(artifactManifest);
|
|
84
|
-
if (shouldAppendArtifactRetentionNotice(entries)) {
|
|
85
|
-
appendPresentationNotice(presentation, presentation.artifactRetentionSummary);
|
|
86
|
-
}
|
|
87
|
-
return presentation;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function getScreenshotSummary(data: Record<string, unknown>): string | undefined {
|
|
91
|
-
return typeof data.path === "string" ? `Saved image: ${data.path}` : undefined;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const PATH_FIELD_CANDIDATES = [
|
|
95
|
-
"path",
|
|
96
|
-
"file",
|
|
97
|
-
"filePath",
|
|
98
|
-
"outputPath",
|
|
99
|
-
"downloadPath",
|
|
100
|
-
"diffPath",
|
|
101
|
-
"harPath",
|
|
102
|
-
"savedPath",
|
|
103
|
-
"statePath",
|
|
104
|
-
"tracePath",
|
|
105
|
-
"profilePath",
|
|
106
|
-
"videoPath",
|
|
107
|
-
] as const;
|
|
108
|
-
|
|
109
|
-
const ARTIFACT_EXTENSION_TO_MEDIA_TYPE: Record<string, string> = {
|
|
110
|
-
".cpuprofile": "application/json",
|
|
111
|
-
".har": "application/json",
|
|
112
|
-
".html": "text/html",
|
|
113
|
-
".json": "application/json",
|
|
114
|
-
".pdf": "application/pdf",
|
|
115
|
-
".txt": "text/plain",
|
|
116
|
-
".webm": "video/webm",
|
|
117
|
-
".zip": "application/zip",
|
|
118
|
-
...IMAGE_EXTENSION_TO_MIME_TYPE,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
function getArtifactKind(commandInfo: CommandInfo): FileArtifactKind | undefined {
|
|
122
|
-
if (commandInfo.command === "screenshot") return "image";
|
|
123
|
-
if (commandInfo.command === "diff" && commandInfo.subcommand === "screenshot") return "image";
|
|
124
|
-
if (commandInfo.command === "pdf") return "pdf";
|
|
125
|
-
if (commandInfo.command === "download") return "download";
|
|
126
|
-
if (commandInfo.command === "wait" && commandInfo.subcommand === "--download") return "download";
|
|
127
|
-
if (commandInfo.command === "state" && commandInfo.subcommand === "save") return "file";
|
|
128
|
-
if (commandInfo.command === "trace") return "trace";
|
|
129
|
-
if (commandInfo.command === "profiler") return "profile";
|
|
130
|
-
if (commandInfo.command === "record") return "video";
|
|
131
|
-
if (commandInfo.command === "network" && commandInfo.subcommand === "har") return "har";
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function isNonFileArtifactPathCandidate(path: string): boolean {
|
|
136
|
-
return /^(?:data|blob|https?|javascript|mailto):/i.test(path.trim());
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function extractPathStrings(data: unknown): string[] {
|
|
140
|
-
if (typeof data === "string") {
|
|
141
|
-
return data.trim().length > 0 && !isNonFileArtifactPathCandidate(data) ? [data] : [];
|
|
142
|
-
}
|
|
143
|
-
if (!isRecord(data)) {
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const paths: string[] = [];
|
|
148
|
-
for (const key of PATH_FIELD_CANDIDATES) {
|
|
149
|
-
const value = data[key];
|
|
150
|
-
if (typeof value === "string" && value.trim().length > 0 && !isNonFileArtifactPathCandidate(value)) {
|
|
151
|
-
paths.push(value);
|
|
152
|
-
}
|
|
153
|
-
if (Array.isArray(value)) {
|
|
154
|
-
for (const item of value) {
|
|
155
|
-
if (typeof item === "string" && item.trim().length > 0 && !isNonFileArtifactPathCandidate(item)) {
|
|
156
|
-
paths.push(item);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return [...new Set(paths)];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface ArtifactRequestContext {
|
|
165
|
-
absolutePath: string;
|
|
166
|
-
path: string;
|
|
167
|
-
status?: FileArtifactMetadata["status"];
|
|
168
|
-
tempPath?: string;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function buildFileArtifactMetadata(options: {
|
|
172
|
-
artifactRequest?: ArtifactRequestContext;
|
|
173
|
-
commandInfo: CommandInfo;
|
|
174
|
-
cwd: string;
|
|
175
|
-
path: string;
|
|
176
|
-
sessionName?: string;
|
|
177
|
-
}): Promise<FileArtifactMetadata | undefined> {
|
|
178
|
-
const kind = getArtifactKind(options.commandInfo);
|
|
179
|
-
if (!kind) {
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const absolutePath = options.artifactRequest?.absolutePath ?? resolve(options.cwd, options.path);
|
|
184
|
-
const displayPath = options.artifactRequest?.path ?? options.path;
|
|
185
|
-
const extension = extname(absolutePath || options.path).toLowerCase() || undefined;
|
|
186
|
-
const pendingRecording = isPendingRecordingCommand(options.commandInfo.command, options.commandInfo.subcommand, kind);
|
|
187
|
-
let exists: boolean | undefined;
|
|
188
|
-
let sizeBytes: number | undefined;
|
|
189
|
-
if (!pendingRecording) {
|
|
190
|
-
try {
|
|
191
|
-
const fileStats = await stat(absolutePath);
|
|
192
|
-
exists = true;
|
|
193
|
-
sizeBytes = fileStats.size;
|
|
194
|
-
} catch {
|
|
195
|
-
exists = false;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
absolutePath,
|
|
201
|
-
artifactType: kind,
|
|
202
|
-
command: options.commandInfo.command,
|
|
203
|
-
cwd: options.cwd,
|
|
204
|
-
exists,
|
|
205
|
-
extension,
|
|
206
|
-
kind,
|
|
207
|
-
mediaType: extension ? ARTIFACT_EXTENSION_TO_MEDIA_TYPE[extension] : undefined,
|
|
208
|
-
path: displayPath,
|
|
209
|
-
recordingState: pendingRecording ? "openRecording" : undefined,
|
|
210
|
-
requestedPath: options.artifactRequest?.path,
|
|
211
|
-
session: options.sessionName,
|
|
212
|
-
sizeBytes,
|
|
213
|
-
status: options.artifactRequest?.status ?? (pendingRecording ? "pending" : exists === false ? "missing" : "saved"),
|
|
214
|
-
subcommand: options.commandInfo.subcommand,
|
|
215
|
-
tempPath: options.artifactRequest?.tempPath,
|
|
216
|
-
willExistOnStop: pendingRecording ? true : undefined,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async function buildPreviousRestartRecordingArtifact(options: {
|
|
221
|
-
artifactManifest?: SessionArtifactManifest;
|
|
222
|
-
commandInfo: CommandInfo;
|
|
223
|
-
currentPaths: ReadonlySet<string>;
|
|
224
|
-
cwd: string;
|
|
225
|
-
sessionName?: string;
|
|
226
|
-
}): Promise<FileArtifactMetadata | undefined> {
|
|
227
|
-
if (options.commandInfo.command !== "record" || options.commandInfo.subcommand !== "restart") return undefined;
|
|
228
|
-
const previousRecording = options.artifactManifest?.entries.find((entry) => (
|
|
229
|
-
entry.command === "record" &&
|
|
230
|
-
(entry.subcommand === "start" || entry.subcommand === "restart") &&
|
|
231
|
-
entry.kind === "video" &&
|
|
232
|
-
(!options.sessionName || !entry.session || entry.session === options.sessionName) &&
|
|
233
|
-
!options.currentPaths.has(entry.path) &&
|
|
234
|
-
(!entry.absolutePath || !options.currentPaths.has(entry.absolutePath))
|
|
235
|
-
));
|
|
236
|
-
if (!previousRecording) return undefined;
|
|
237
|
-
const absolutePath = previousRecording.absolutePath ?? resolve(options.cwd, previousRecording.path);
|
|
238
|
-
try {
|
|
239
|
-
const fileStats = await stat(absolutePath);
|
|
240
|
-
return {
|
|
241
|
-
absolutePath,
|
|
242
|
-
artifactType: "video",
|
|
243
|
-
command: "record",
|
|
244
|
-
cwd: previousRecording.cwd ?? options.cwd,
|
|
245
|
-
exists: true,
|
|
246
|
-
extension: previousRecording.extension ?? (extname(absolutePath).toLowerCase() || undefined),
|
|
247
|
-
kind: "video",
|
|
248
|
-
mediaType: previousRecording.mediaType,
|
|
249
|
-
path: previousRecording.path,
|
|
250
|
-
requestedPath: previousRecording.requestedPath,
|
|
251
|
-
session: previousRecording.session ?? options.sessionName,
|
|
252
|
-
sizeBytes: fileStats.size,
|
|
253
|
-
status: "saved",
|
|
254
|
-
subcommand: "restart-previous",
|
|
255
|
-
};
|
|
256
|
-
} catch {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export async function extractFileArtifacts(options: {
|
|
262
|
-
artifactManifest?: SessionArtifactManifest;
|
|
263
|
-
artifactRequest?: ArtifactRequestContext;
|
|
264
|
-
commandInfo: CommandInfo;
|
|
265
|
-
cwd: string;
|
|
266
|
-
data: unknown;
|
|
267
|
-
sessionName?: string;
|
|
268
|
-
}): Promise<FileArtifactMetadata[]> {
|
|
269
|
-
const candidates = extractPathStrings(options.data);
|
|
270
|
-
const currentArtifacts = (await Promise.all(candidates.map((path) => buildFileArtifactMetadata({ ...options, path })))).filter((artifact): artifact is FileArtifactMetadata => artifact !== undefined);
|
|
271
|
-
const currentPaths = new Set(currentArtifacts.flatMap((artifact) => [artifact.path, artifact.absolutePath]));
|
|
272
|
-
const previousRestartRecordingArtifact = await buildPreviousRestartRecordingArtifact({ artifactManifest: options.artifactManifest, commandInfo: options.commandInfo, currentPaths, cwd: options.cwd, sessionName: options.sessionName });
|
|
273
|
-
return previousRestartRecordingArtifact ? [previousRestartRecordingArtifact, ...currentArtifacts] : currentArtifacts;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export function buildManifestEntriesForFileArtifacts(artifacts: FileArtifactMetadata[], nowMs = Date.now()): SessionArtifactManifestEntry[] {
|
|
277
|
-
return artifacts.map((artifact) => ({
|
|
278
|
-
absolutePath: artifact.absolutePath,
|
|
279
|
-
command: artifact.command,
|
|
280
|
-
createdAtMs: nowMs,
|
|
281
|
-
cwd: artifact.cwd,
|
|
282
|
-
exists: artifact.exists,
|
|
283
|
-
extension: artifact.extension,
|
|
284
|
-
kind: artifact.kind,
|
|
285
|
-
mediaType: artifact.mediaType,
|
|
286
|
-
path: artifact.path,
|
|
287
|
-
requestedPath: artifact.requestedPath,
|
|
288
|
-
retentionState: artifact.exists === false ? "missing" : "live",
|
|
289
|
-
session: artifact.session,
|
|
290
|
-
sizeBytes: artifact.sizeBytes,
|
|
291
|
-
storageScope: "explicit-path",
|
|
292
|
-
subcommand: artifact.subcommand,
|
|
293
|
-
}));
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function isManifestFileArtifact(artifact: FileArtifactMetadata): boolean {
|
|
297
|
-
return artifact.kind === "video" && artifact.command === "record" ? true : !isPendingRecordingArtifact(artifact);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function getArtifactVerificationEntry(artifact: FileArtifactMetadata): ArtifactVerificationEntry {
|
|
301
|
-
if (isPendingRecordingArtifact(artifact)) {
|
|
302
|
-
return {
|
|
303
|
-
absolutePath: artifact.absolutePath,
|
|
304
|
-
exists: artifact.exists,
|
|
305
|
-
kind: artifact.kind,
|
|
306
|
-
limitation: "Recording output is pending until record stop completes.",
|
|
307
|
-
mediaType: artifact.mediaType,
|
|
308
|
-
path: artifact.path,
|
|
309
|
-
recordingState: artifact.recordingState ?? "openRecording",
|
|
310
|
-
requestedPath: artifact.requestedPath,
|
|
311
|
-
retentionState: undefined,
|
|
312
|
-
sizeBytes: artifact.sizeBytes,
|
|
313
|
-
state: "pending",
|
|
314
|
-
status: artifact.status ?? "pending",
|
|
315
|
-
storageScope: undefined,
|
|
316
|
-
willExistOnStop: artifact.willExistOnStop ?? true,
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
const state = artifact.exists === true
|
|
320
|
-
? "verified"
|
|
321
|
-
: artifact.exists === false
|
|
322
|
-
? "missing"
|
|
323
|
-
: "unverified";
|
|
324
|
-
return {
|
|
325
|
-
absolutePath: artifact.absolutePath,
|
|
326
|
-
exists: artifact.exists,
|
|
327
|
-
kind: artifact.kind,
|
|
328
|
-
limitation: state === "missing"
|
|
329
|
-
? "The wrapper did not find the reported artifact at absolutePath. Treat the path as unverified until recovered or regenerated."
|
|
330
|
-
: state === "unverified"
|
|
331
|
-
? "The wrapper could not prove local filesystem existence for this artifact."
|
|
332
|
-
: undefined,
|
|
333
|
-
mediaType: artifact.mediaType,
|
|
334
|
-
path: artifact.path,
|
|
335
|
-
requestedPath: artifact.requestedPath,
|
|
336
|
-
retentionState: artifact.exists === false ? "missing" : "live",
|
|
337
|
-
sizeBytes: artifact.sizeBytes,
|
|
338
|
-
state,
|
|
339
|
-
status: artifact.status,
|
|
340
|
-
storageScope: "explicit-path",
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function getManifestVerificationEntry(entry: SessionArtifactManifestEntry): ArtifactVerificationEntry | undefined {
|
|
345
|
-
if (entry.storageScope === "explicit-path") return undefined;
|
|
346
|
-
const state = entry.retentionState === "live"
|
|
347
|
-
? "verified"
|
|
348
|
-
: entry.retentionState === "missing" || entry.retentionState === "evicted"
|
|
349
|
-
? "missing"
|
|
350
|
-
: "unverified";
|
|
351
|
-
return {
|
|
352
|
-
absolutePath: entry.absolutePath,
|
|
353
|
-
exists: entry.exists,
|
|
354
|
-
kind: entry.kind,
|
|
355
|
-
limitation: entry.retentionState === "ephemeral"
|
|
356
|
-
? "This spill file is process-temporary and may not survive reload or restart."
|
|
357
|
-
: entry.retentionState === "evicted"
|
|
358
|
-
? "This persisted spill file was evicted from the bounded session artifact store."
|
|
359
|
-
: undefined,
|
|
360
|
-
mediaType: entry.mediaType,
|
|
361
|
-
path: entry.path,
|
|
362
|
-
requestedPath: entry.requestedPath,
|
|
363
|
-
retentionState: entry.retentionState,
|
|
364
|
-
sizeBytes: entry.sizeBytes,
|
|
365
|
-
state,
|
|
366
|
-
storageScope: entry.storageScope,
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function buildArtifactVerificationSummary(
|
|
371
|
-
artifacts: FileArtifactMetadata[],
|
|
372
|
-
manifest?: SessionArtifactManifest,
|
|
373
|
-
manifestPaths?: ReadonlySet<string>,
|
|
374
|
-
): ArtifactVerificationSummary | undefined {
|
|
375
|
-
const entries = [
|
|
376
|
-
...artifacts.map(getArtifactVerificationEntry),
|
|
377
|
-
...(manifest?.entries.flatMap((entry) => {
|
|
378
|
-
if (manifestPaths && !manifestPaths.has(entry.path)) return [];
|
|
379
|
-
const verificationEntry = getManifestVerificationEntry(entry);
|
|
380
|
-
return verificationEntry ? [verificationEntry] : [];
|
|
381
|
-
}) ?? []),
|
|
382
|
-
];
|
|
383
|
-
if (entries.length === 0) return undefined;
|
|
384
|
-
const verifiedCount = entries.filter((entry) => entry.state === "verified").length;
|
|
385
|
-
const missingCount = entries.filter((entry) => entry.state === "missing").length;
|
|
386
|
-
const pendingCount = entries.filter((entry) => entry.state === "pending").length;
|
|
387
|
-
const unverifiedCount = entries.filter((entry) => entry.state === "unverified").length;
|
|
388
|
-
return {
|
|
389
|
-
artifacts: entries,
|
|
390
|
-
missingCount,
|
|
391
|
-
pendingCount,
|
|
392
|
-
unverifiedCount,
|
|
393
|
-
verified: entries.length > 0 && verifiedCount === entries.length,
|
|
394
|
-
verifiedCount,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export function hasMissingFileArtifact(artifacts: FileArtifactMetadata[] | undefined): boolean {
|
|
399
|
-
return (artifacts ?? []).some((artifact) => !isPendingRecordingArtifact(artifact) && artifact.exists === false);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export function formatMissingArtifactFailureText(artifacts: FileArtifactMetadata[] | undefined): string | undefined {
|
|
403
|
-
const missingArtifacts = (artifacts ?? []).filter((artifact) => !isPendingRecordingArtifact(artifact) && artifact.exists === false);
|
|
404
|
-
if (missingArtifacts.length === 0) return undefined;
|
|
405
|
-
if (missingArtifacts.length === 1) {
|
|
406
|
-
const artifact = missingArtifacts[0];
|
|
407
|
-
return `Artifact verification failed: requested ${artifact.kind} was not found at ${artifact.absolutePath}.`;
|
|
408
|
-
}
|
|
409
|
-
return `Artifact verification failed: ${missingArtifacts.length} requested artifacts were not found on disk.`;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
export function classifyPresentationSuccessCategory(options: {
|
|
413
|
-
artifactVerification?: ArtifactVerificationSummary;
|
|
414
|
-
artifacts?: FileArtifactMetadata[];
|
|
415
|
-
inspection?: boolean;
|
|
416
|
-
savedFile?: SavedFilePresentationDetails;
|
|
417
|
-
}) {
|
|
418
|
-
if ((options.artifactVerification?.missingCount ?? 0) > 0 || (options.artifactVerification?.unverifiedCount ?? 0) > 0) {
|
|
419
|
-
return "artifact-unverified" as const;
|
|
420
|
-
}
|
|
421
|
-
return classifyAgentBrowserSuccessCategory(options);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function formatArtifactLabel(artifact: FileArtifactMetadata): string {
|
|
425
|
-
switch (artifact.kind) {
|
|
426
|
-
case "download":
|
|
427
|
-
if (artifact.exists !== true) {
|
|
428
|
-
return artifact.command === "wait" && artifact.subcommand === "--download" ? "Download event reported; file not verified" : "Download reported; file not verified";
|
|
429
|
-
}
|
|
430
|
-
return artifact.command === "wait" && artifact.subcommand === "--download" ? "Download saved and verified" : "Downloaded file verified";
|
|
431
|
-
case "file":
|
|
432
|
-
return artifact.command === "state" ? "State file" : "Saved file";
|
|
433
|
-
case "har":
|
|
434
|
-
return "Saved HAR";
|
|
435
|
-
case "image":
|
|
436
|
-
if (artifact.exists !== true) return artifact.command === "diff" && artifact.subcommand === "screenshot" ? "Diff image reported; file not verified" : "Image reported; file not verified";
|
|
437
|
-
return artifact.command === "diff" && artifact.subcommand === "screenshot" ? "Saved diff image" : "Saved image";
|
|
438
|
-
case "pdf":
|
|
439
|
-
return "Saved PDF";
|
|
440
|
-
case "profile":
|
|
441
|
-
return "Saved profile";
|
|
442
|
-
case "trace":
|
|
443
|
-
return "Saved trace";
|
|
444
|
-
case "video":
|
|
445
|
-
if (artifact.command === "record" && artifact.subcommand === "restart-previous") return "Previous recording saved";
|
|
446
|
-
if (!isPendingRecordingArtifact(artifact)) return "Saved recording";
|
|
447
|
-
return artifact.subcommand === "restart" ? "Recording restarted; output will be written on stop" : "Recording started; output will be written on stop";
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export function formatArtifactSummary(artifacts: FileArtifactMetadata[]): string | undefined {
|
|
452
|
-
if (artifacts.length === 0) {
|
|
453
|
-
return undefined;
|
|
454
|
-
}
|
|
455
|
-
if (artifacts.length === 1) {
|
|
456
|
-
const artifact = artifacts[0];
|
|
457
|
-
return `${formatArtifactLabel(artifact)}: ${artifact.path}`;
|
|
458
|
-
}
|
|
459
|
-
const restartArtifact = artifacts.find((artifact) => isPendingRecordingArtifact(artifact) && artifact.subcommand === "restart");
|
|
460
|
-
const previousRecordingArtifacts = artifacts.filter((artifact) => artifact.command === "record" && artifact.subcommand === "restart-previous");
|
|
461
|
-
if (restartArtifact && previousRecordingArtifacts.length > 0) {
|
|
462
|
-
return [...previousRecordingArtifacts, restartArtifact].map((artifact) => `${formatArtifactLabel(artifact)}: ${artifact.path}`).join("\n");
|
|
463
|
-
}
|
|
464
|
-
return `Saved ${artifacts.length} artifacts: ${artifacts.map((artifact) => `${artifact.kind} ${artifact.path}`).join(", ")}`;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export function formatArtifactMetadataLines(artifacts: FileArtifactMetadata[]): string[] {
|
|
468
|
-
return artifacts.map((artifact, index) => {
|
|
469
|
-
if (isPendingRecordingArtifact(artifact)) {
|
|
470
|
-
return [
|
|
471
|
-
`${formatArtifactLabel(artifact)}: ${artifact.path}`,
|
|
472
|
-
`Artifact type: ${artifact.kind}`,
|
|
473
|
-
`Requested path: ${artifact.requestedPath ?? artifact.path}`,
|
|
474
|
-
`Absolute path: ${artifact.absolutePath}`,
|
|
475
|
-
"Exists: pending until record stop",
|
|
476
|
-
`Status: ${artifact.status ?? "pending"}`,
|
|
477
|
-
`Recording state: ${artifact.recordingState ?? "openRecording"}`,
|
|
478
|
-
`Will exist on stop: ${artifact.willExistOnStop !== false}`,
|
|
479
|
-
artifact.session ? `Session: ${artifact.session}` : undefined,
|
|
480
|
-
artifact.cwd ? `CWD: ${artifact.cwd}` : undefined,
|
|
481
|
-
`Machine data: details.artifacts[${index}]`,
|
|
482
|
-
].filter((item): item is string => item !== undefined).join("\n");
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return [
|
|
486
|
-
`${formatArtifactLabel(artifact)}: ${artifact.path}`,
|
|
487
|
-
`Artifact type: ${artifact.kind}`,
|
|
488
|
-
`Requested path: ${artifact.requestedPath ?? artifact.path}`,
|
|
489
|
-
`Absolute path: ${artifact.absolutePath}`,
|
|
490
|
-
`Exists: ${artifact.exists === true}`,
|
|
491
|
-
artifact.exists === false ? "not found on disk" : undefined,
|
|
492
|
-
typeof artifact.sizeBytes === "number" ? `Size: ${formatByteCount(artifact.sizeBytes)}` : undefined,
|
|
493
|
-
typeof artifact.sizeBytes === "number" ? `Size bytes: ${artifact.sizeBytes}` : undefined,
|
|
494
|
-
`Status: ${artifact.status ?? (artifact.exists === false ? "missing" : "saved")}`,
|
|
495
|
-
artifact.tempPath ? `Temp path: ${artifact.tempPath}` : undefined,
|
|
496
|
-
artifact.mediaType ? `Media type: ${artifact.mediaType}` : undefined,
|
|
497
|
-
artifact.session ? `Session: ${artifact.session}` : undefined,
|
|
498
|
-
artifact.cwd ? `CWD: ${artifact.cwd}` : undefined,
|
|
499
|
-
`Machine data: details.artifacts[${index}]`,
|
|
500
|
-
].filter((item): item is string => item !== undefined).join("\n");
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function isDownloadWaitCommand(commandInfo: CommandInfo): boolean {
|
|
505
|
-
return commandInfo.command === "wait" && commandInfo.subcommand === "--download";
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function extractSavedFilePath(data: Record<string, unknown>): string | undefined {
|
|
509
|
-
return typeof data.path === "string" && data.path.trim().length > 0 ? data.path : undefined;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
export function getSavedFileDetails(commandInfo: CommandInfo, data: Record<string, unknown>): SavedFilePresentationDetails | undefined {
|
|
513
|
-
const path = extractSavedFilePath(data);
|
|
514
|
-
if (!path || isNonFileArtifactPathCandidate(path)) {
|
|
515
|
-
return undefined;
|
|
516
|
-
}
|
|
517
|
-
const savedFileCommand = isDownloadWaitCommand(commandInfo)
|
|
518
|
-
? "wait"
|
|
519
|
-
: commandInfo.command === "download" || commandInfo.command === "pdf"
|
|
520
|
-
? commandInfo.command
|
|
521
|
-
: undefined;
|
|
522
|
-
if (!savedFileCommand) {
|
|
523
|
-
return undefined;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const { path: _path, ...metadata } = data;
|
|
527
|
-
const details: SavedFilePresentationDetails = {
|
|
528
|
-
command: savedFileCommand,
|
|
529
|
-
kind: savedFileCommand === "pdf" ? "pdf" : "download",
|
|
530
|
-
path,
|
|
531
|
-
};
|
|
532
|
-
if (Object.keys(metadata).length > 0) {
|
|
533
|
-
details.metadata = metadata;
|
|
534
|
-
}
|
|
535
|
-
if (commandInfo.subcommand) {
|
|
536
|
-
details.subcommand = commandInfo.subcommand;
|
|
537
|
-
}
|
|
538
|
-
return details;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function isTrustedScreenshotOutput(commandInfo: CommandInfo): boolean {
|
|
542
|
-
return commandInfo.command === "screenshot";
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
export function extractImagePath(commandInfo: CommandInfo, cwd: string, data: unknown): string | undefined {
|
|
546
|
-
if (!isTrustedScreenshotOutput(commandInfo)) {
|
|
547
|
-
return undefined;
|
|
548
|
-
}
|
|
549
|
-
if (typeof data === "string") {
|
|
550
|
-
const mimeType = getImageMimeType(data);
|
|
551
|
-
return mimeType ? resolve(cwd, data) : undefined;
|
|
552
|
-
}
|
|
553
|
-
if (!isRecord(data) || typeof data.path !== "string") {
|
|
554
|
-
return undefined;
|
|
555
|
-
}
|
|
556
|
-
const mimeType = getImageMimeType(data.path);
|
|
557
|
-
return mimeType ? resolve(cwd, data.path) : undefined;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
export async function attachInlineImage(presentation: ToolPresentation, imagePath: string): Promise<ToolPresentation> {
|
|
561
|
-
const mimeType = getImageMimeType(imagePath);
|
|
562
|
-
if (!mimeType) {
|
|
563
|
-
return presentation;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
const fileStats = await stat(imagePath);
|
|
568
|
-
const inlineImageMaxBytes = getInlineImageMaxBytes();
|
|
569
|
-
if (fileStats.size > inlineImageMaxBytes) {
|
|
570
|
-
appendPresentationNotice(
|
|
571
|
-
presentation,
|
|
572
|
-
`Image attachment skipped: ${formatByteCount(fileStats.size)} exceeds the inline limit of ${formatByteCount(inlineImageMaxBytes)}.`,
|
|
573
|
-
);
|
|
574
|
-
presentation.imagePath = imagePath;
|
|
575
|
-
return presentation;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const file = await readFile(imagePath);
|
|
579
|
-
presentation.content.push({ type: "image", data: file.toString("base64"), mimeType });
|
|
580
|
-
presentation.imagePath = imagePath;
|
|
581
|
-
return presentation;
|
|
582
|
-
} catch (error) {
|
|
583
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
-
appendPresentationNotice(presentation, `Image attachment failed: ${message}`);
|
|
585
|
-
presentation.imagePath = imagePath;
|
|
586
|
-
return presentation;
|
|
587
|
-
}
|
|
588
|
-
}
|