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.
Files changed (185) hide show
  1. package/CHANGELOG.md +63 -19
  2. package/README.md +52 -19
  3. package/dist/extensions/agent-browser/index.js +785 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +448 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +816 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/ARCHITECTURE.md +10 -10
  95. package/docs/COMMAND_REFERENCE.md +35 -21
  96. package/docs/ELECTRON.md +3 -3
  97. package/docs/RELEASE.md +46 -26
  98. package/docs/REQUIREMENTS.md +1 -1
  99. package/docs/SUPPORT_MATRIX.md +35 -106
  100. package/docs/TOOL_CONTRACT.md +23 -21
  101. package/package.json +12 -8
  102. package/scripts/agent-browser-capability-baseline.mjs +6 -3
  103. package/scripts/config.mjs +8 -2
  104. package/scripts/doctor.mjs +19 -17
  105. package/scripts/platform-smoke.mjs +1 -1
  106. package/extensions/agent-browser/index.ts +0 -952
  107. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  108. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  109. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  110. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  111. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  112. package/extensions/agent-browser/lib/config-policy.js +0 -690
  113. package/extensions/agent-browser/lib/config.ts +0 -209
  114. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  115. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  116. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  117. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  118. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  119. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  120. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  121. package/extensions/agent-browser/lib/input-modes/job.ts +0 -451
  122. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  123. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  124. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  125. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  126. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  127. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  128. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  129. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  130. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  131. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  132. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -257
  133. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -912
  134. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -512
  135. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -1481
  137. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -564
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  139. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  140. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -564
  141. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  142. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  143. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  144. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -252
  145. package/extensions/agent-browser/lib/playbook.ts +0 -142
  146. package/extensions/agent-browser/lib/process.ts +0 -516
  147. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  148. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  149. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  150. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  151. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  152. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  153. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  154. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  155. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  156. package/extensions/agent-browser/lib/results/network.ts +0 -78
  157. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  158. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  159. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  160. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  161. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  162. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  163. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  164. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  165. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  166. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  167. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  168. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  169. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  170. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  171. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  172. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  173. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  174. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  175. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  176. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  177. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  178. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  179. package/extensions/agent-browser/lib/results/text.ts +0 -40
  180. package/extensions/agent-browser/lib/runtime.ts +0 -988
  181. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  182. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  183. package/extensions/agent-browser/lib/temp.ts +0 -577
  184. package/extensions/agent-browser/lib/web-search.ts +0 -721
  185. /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
- }