pi-agent-browser-native 0.2.48 → 0.2.50

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 (189) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +21 -11
  3. package/dist/extensions/agent-browser/index.js +808 -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 +669 -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 +363 -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 +956 -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 +855 -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 +5 -5
  95. package/docs/COMMAND_REFERENCE.md +4 -4
  96. package/docs/RELEASE.md +22 -11
  97. package/docs/REQUIREMENTS.md +1 -1
  98. package/docs/SUPPORT_MATRIX.md +5 -4
  99. package/docs/TOOL_CONTRACT.md +1 -1
  100. package/package.json +9 -5
  101. package/scripts/config.mjs +14 -20
  102. package/scripts/doctor.mjs +8 -7
  103. package/extensions/agent-browser/index.ts +0 -961
  104. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  105. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  106. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  107. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  108. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  109. package/extensions/agent-browser/lib/config-policy.js +0 -690
  110. package/extensions/agent-browser/lib/config.ts +0 -211
  111. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  112. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  113. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  114. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  115. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  116. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  117. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  118. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  119. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  120. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  121. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  122. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  123. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  124. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  125. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  126. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  127. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  128. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  129. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  130. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  131. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  132. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  133. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  139. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  140. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  141. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  142. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  143. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  144. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  145. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  146. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  147. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  148. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  149. package/extensions/agent-browser/lib/playbook.ts +0 -142
  150. package/extensions/agent-browser/lib/process.ts +0 -516
  151. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  152. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  153. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  154. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  155. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  156. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  157. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  158. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  159. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  160. package/extensions/agent-browser/lib/results/network.ts +0 -78
  161. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  162. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  163. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  164. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  165. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  166. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  167. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  168. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  169. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  170. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  171. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  172. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  173. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  174. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  175. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  176. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  177. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  178. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  179. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  180. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  181. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  182. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  183. package/extensions/agent-browser/lib/results/text.ts +0 -40
  184. package/extensions/agent-browser/lib/runtime.ts +0 -988
  185. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  186. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  187. package/extensions/agent-browser/lib/temp.ts +0 -577
  188. package/extensions/agent-browser/lib/web-search.ts +0 -728
  189. /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
- }