pi-agent-browser-native 0.2.15 → 0.2.17
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 +23 -0
- package/README.md +6 -2
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/COMMAND_REFERENCE.md +11 -1
- package/docs/TOOL_CONTRACT.md +7 -5
- package/extensions/agent-browser/index.ts +426 -13
- package/extensions/agent-browser/lib/playbook.ts +1 -0
- package/extensions/agent-browser/lib/results/presentation.ts +93 -20
- package/extensions/agent-browser/lib/results/shared.ts +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.17 - 2026-05-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- close the active extension-managed `piab-*` browser session when the originating `pi` process quits, while preserving managed browser continuity across `/reload` and resumable session transitions
|
|
9
|
+
- added lifecycle regression coverage for quit-time managed-session cleanup and reload-time preservation
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- clarified that the managed-session idle timeout is now an abnormal-exit backstop, not the primary cleanup path for normal `pi` exits
|
|
13
|
+
|
|
14
|
+
## 0.2.16 - 2026-05-02
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- made screenshot artifact paths reliable for agent workflows by normalizing explicit screenshot output paths, including dot-directory paths such as `.dogfood/...`, to absolute paths before invoking upstream `agent-browser`
|
|
18
|
+
- repaired screenshot outputs from upstream temp files when needed and made the requested path the primary visible artifact path
|
|
19
|
+
- extended the screenshot path contract to annotated batch screenshots, so top-level `--annotate` batch calls preserve and verify per-step requested output paths
|
|
20
|
+
- blocked per-step batch `--annotate` screenshot forms that upstream parses unsafely and now point agents to the safe top-level `--annotate batch` form
|
|
21
|
+
- added wrapper-observed trace/profiler owner guards to prevent known conflicting start/stop sequences from corrupting upstream tracing state
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- artifact-producing commands now render direct visible artifact metadata, including artifact type, requested path, absolute path, existence, size, status, cwd, session, and temp path when repaired
|
|
25
|
+
- explicit `--json` calls now render valid JSON in visible tool content; `stream status` JSON is enriched with `wsUrl` and frame format metadata
|
|
26
|
+
- documented the artifact contract, batch annotation guidance, trace/profiler caveat, and package-development bash bypass for upstream debugging
|
|
27
|
+
|
|
5
28
|
## 0.2.15 - 2026-05-01
|
|
6
29
|
|
|
7
30
|
### Changed
|
package/README.md
CHANGED
|
@@ -242,9 +242,10 @@ Validated workflow examples:
|
|
|
242
242
|
- in configured-source lifecycle mode, verify `/reload` and full restart + `/resume` keep following the same implicit managed browser session
|
|
243
243
|
- run `batch` with JSON via `stdin`
|
|
244
244
|
- run `eval --stdin`
|
|
245
|
-
- take a screenshot with inline attachment support
|
|
245
|
+
- take a screenshot with inline attachment support and visible artifact metadata: artifact type, requested path, absolute path, existence, size, cwd, session, and repair/copy status when applicable
|
|
246
246
|
- inspect upstream help/version through native tool calls like `{ "args": ["--help"] }` and `{ "args": ["--version"] }` via the tool's stateless plain-text inspection fallback
|
|
247
247
|
- use `download <selector> <path>` for direct attachment/file-save workflows instead of trying to infer downloads from generic clicks or large eval dumps
|
|
248
|
+
- for `.dogfood/...` or other dot-directory screenshot paths, rely on the wrapper's path normalization/repair contract; the visible result reports the requested path and absolute path rather than only an upstream temp path
|
|
248
249
|
- use `click` plus `wait --download <path>` for asynchronous export flows, confirm `details.savedFilePath`/`details.savedFile` are present on the wait result or batch wait step, and check `details.artifacts[].exists` before relying on requested-path persistence
|
|
249
250
|
- confirm oversized outputs show the actual spill file path directly in tool content, not just a details key name
|
|
250
251
|
- inspect `details.artifactManifest` / `details.artifactRetentionSummary` during artifact-heavy flows to recover recent saved files, spill files, and visible eviction state after reload/resume
|
|
@@ -262,7 +263,7 @@ These calls return plain text and stay stateless: the extension does not inject
|
|
|
262
263
|
Current cautions:
|
|
263
264
|
- passing `--profile` is an explicit upstream choice; this extension does not add its own profile-cloning or isolation layer
|
|
264
265
|
- launch-scoped flags like `--profile`, `--session-name`, `--cdp`, `--state`, and `--auto-connect` are for the first command that launches a session; if the implicit session is already active, retry that call with `sessionMode: "fresh"` or provide an explicit `--session ...` for the new launch
|
|
265
|
-
- implicit `piab-*` sessions are extension-managed convenience sessions; they stay alive across `
|
|
266
|
+
- implicit `piab-*` sessions are extension-managed convenience sessions; they stay alive across `/reload` and resumable session transitions so later default calls can keep following the active managed browser on `/reload` or `/resume`, close when the originating `pi` process quits, rely on the configured idle timeout only as an abnormal-exit backstop, store persisted-session large snapshot spill files under a private session-scoped artifact directory with a bounded per-session budget so `details.fullOutputPath` and metadata-only `details.artifactManifest` survive reload/resume without unbounded growth, and still clean up process-private temp spill artifacts on shutdown
|
|
266
267
|
- `sessionMode: "fresh"` without an explicit `--session` rotates that extension-managed session to the new browser so later auto calls keep using it
|
|
267
268
|
- for local Unix launches, the wrapper uses a short private socket directory under `/tmp` so extension-generated session names do not trip upstream Unix socket-path limits in longer cwd/session-name combinations
|
|
268
269
|
- for direct headless local Chrome launches to `chat.com`, `chatgpt.com`, and `chat.openai.com`, the extension injects a normal Chrome user agent when the caller did not explicitly provide `--user-agent`; this keeps the default headless workflow usable without forcing `--headed` or `--auto-connect`
|
|
@@ -274,6 +275,9 @@ Current cautions:
|
|
|
274
275
|
- If a known session target unexpectedly reports about:blank, agent_browser preserves the prior intended target, best-effort re-selects it when it still exists, and reports exact recovery guidance when it cannot be re-selected.
|
|
275
276
|
<!-- agent-browser-playbook:end wrapper-tab-recovery -->
|
|
276
277
|
- oversized snapshots and oversized generic outputs compact inline content and print the actual spill file path directly in the tool result when a spill file exists; recent spills and explicit saved artifacts are also summarized in `details.artifactManifest`, including `evicted` entries when retention budgets remove older persisted files
|
|
278
|
+
- artifact-producing commands render direct readable artifact metadata in visible content and `details.artifacts`: `kind`/`artifactType`, `path`, `requestedPath`, `absolutePath`, `exists`, `sizeBytes`, `status`, `cwd`, `session`, and `tempPath` when the wrapper repaired an upstream temp fallback
|
|
279
|
+
- if the caller explicitly passes `--json`, the visible text content is valid JSON; for `stream status`, the wrapper enriches data with `wsUrl` and `frameFormat`
|
|
280
|
+
- `trace` and `profiler` share upstream tracing machinery; the wrapper blocks starts/stops that conflict with owner state it observed in the current Pi session, but the message says "wrapper believes" because upstream or external CLI calls can desynchronize that local state
|
|
277
281
|
- explicit caller-provided `--session` values are treated as user-managed and are not auto-closed by the extension
|
|
278
282
|
- explicit caller-provided `--user-agent` values win over the ChatGPT/OpenAI compatibility workaround
|
|
279
283
|
- tool progress/details redact sensitive invocation values such as `--headers`, proxy credentials, and auth-bearing URL parameters before echoing them back into Pi
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -87,8 +87,9 @@ V1 ownership rule:
|
|
|
87
87
|
- extension-managed sessions should be reusable during an active `pi` session and across `/reload` / `/resume`, while still being cleaned up predictably
|
|
88
88
|
|
|
89
89
|
Practical policy:
|
|
90
|
-
- preserve the current extension-managed session across
|
|
91
|
-
-
|
|
90
|
+
- preserve the current extension-managed session across `/reload` and resumable session transitions so persisted sessions can keep following the live browser after `/reload` or `/resume`
|
|
91
|
+
- close the active extension-managed session when the originating `pi` process quits, while leaving explicit caller-provided sessions alone
|
|
92
|
+
- set an idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures
|
|
92
93
|
- clean up process-private temp spill artifacts on shutdown, but keep persisted-session snapshot spill files in a private session-scoped artifact directory with a bounded per-session budget so `details.fullOutputPath` stays usable after reload/resume without unbounded growth
|
|
93
94
|
- reconstruct the current extension-managed session from persisted tool details on resume/reload so later default calls keep following the active managed browser
|
|
94
95
|
- if an unnamed fresh launch replaces an active extension-managed session, best-effort close the old managed session after the switch succeeds
|
|
@@ -128,11 +128,19 @@ A successful wait-based download renders a readable summary such as `Download co
|
|
|
128
128
|
Prefer `download <selector> <path>` when the target element itself is the downloadable link/control. Use `click` plus `wait --download [path]` when a previous action starts the download indirectly.
|
|
129
129
|
|
|
130
130
|
Wrapper result rendering is metadata-first for saved files:
|
|
131
|
-
- screenshots return a saved-path summary, structured `details.artifacts` metadata, and an inline image attachment when safe
|
|
131
|
+
- screenshots return a saved-path summary, visible artifact metadata, structured `details.artifacts` metadata, and an inline image attachment when safe; the visible block includes artifact type, requested path, absolute path, existence, size, cwd, session, and repair/copy status when applicable
|
|
132
132
|
- downloads, PDFs, `wait --download` files, traces, CPU profiles, completed WebM recordings from `record stop`, and path-bearing HAR captures return concise saved-path summaries plus structured `details.artifacts` metadata without inlining large files
|
|
133
133
|
- `record start <path>` reports that recording started and that output will be written on `record stop`; the target file may not exist until recording stops
|
|
134
134
|
- `batch` keeps each step's artifacts in `details.batchSteps[].artifacts` and aggregates them in top-level `details.artifacts` in step order
|
|
135
135
|
|
|
136
|
+
For screenshot paths under dot-directories such as `.dogfood/run/foo.png`, the wrapper normalizes the requested path to an absolute path before invoking upstream `agent-browser`, verifies the requested file exists, and repairs from an upstream temp screenshot when possible. The requested path remains visible as `Requested path`, while `Absolute path` shows the actual on-disk location.
|
|
137
|
+
|
|
138
|
+
For annotated screenshots in `batch`, put `--annotate` in top-level args instead of inside the screenshot step:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{ "args": ["--annotate", "batch"], "stdin": "[[\"screenshot\",\"/tmp/page.png\"]]" }
|
|
142
|
+
```
|
|
143
|
+
|
|
136
144
|
#### Artifact retention and dogfood-heavy QA runs
|
|
137
145
|
|
|
138
146
|
The wrapper keeps a bounded, metadata-only `details.artifactManifest` of recent artifacts so long sessions do not grow unbounded. The default recent window is 100 entries and can be raised for screenshot/video-heavy QA sessions with `PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES=<count>`.
|
|
@@ -325,6 +333,8 @@ Stable tab ids look like `t1`, `t2`, and `t3`. Optional user labels such as `doc
|
|
|
325
333
|
|
|
326
334
|
When these diagnostic commands are invoked through the native `agent_browser` tool, structured console and page-error outputs render as compact summaries with counts and key fields. Large outputs are previewed with a `Full output path:` spill file instead of dumping the entire payload into context.
|
|
327
335
|
|
|
336
|
+
`trace` and `profiler` share upstream Chrome tracing machinery. Do not run them at the same time. The wrapper tracks owner state it observes in the current Pi session and blocks conflicting starts/stops with "wrapper believes ..." wording because direct upstream CLI use or browser restarts can desynchronize wrapper-local state.
|
|
337
|
+
|
|
328
338
|
### Batch, auth, confirmations, sessions, chat, dashboard, and setup
|
|
329
339
|
|
|
330
340
|
| Command | Purpose |
|
package/docs/TOOL_CONTRACT.md
CHANGED
|
@@ -171,7 +171,7 @@ Additional structured fields can appear when relevant:
|
|
|
171
171
|
- `batchFailure` and `batchSteps` for `batch` rendering, including mixed-success runs
|
|
172
172
|
- `navigationSummary` for navigation-style commands like `click`, `back`, `forward`, and `reload`
|
|
173
173
|
- `imagePath` / `imagePaths` for screenshots and batched image outputs
|
|
174
|
-
- `artifacts` for upstream saved files such as screenshots, PDFs, downloads, `wait --download` files, traces, CPU profiles, completed WebM recordings, path-bearing HAR captures, and future recording output paths reported by `record start`. Each artifact includes the original saved or requested `path`, resolved `absolutePath`, `kind`, optional `mediaType`, optional `extension`,
|
|
174
|
+
- `artifacts` for upstream saved files such as screenshots, PDFs, downloads, `wait --download` files, traces, CPU profiles, completed WebM recordings, path-bearing HAR captures, and future recording output paths reported by `record start`. Each artifact includes the original saved or requested `path`, resolved `absolutePath`, `kind`/`artifactType`, optional `mediaType`, optional `extension`, best-effort disk metadata such as `exists` and `sizeBytes`, plus `requestedPath`, `status`, `cwd`, `session`, and `tempPath` when applicable.
|
|
175
175
|
- `savedFilePath` / `savedFile` for direct `download`, `pdf`, and `wait --download` saved-file workflows; batch results preserve the same fields on the relevant `batchSteps` entry.
|
|
176
176
|
- `batchSteps[].artifacts` for per-step artifacts in `batch` output; top-level `artifacts` aggregates all step artifacts in order
|
|
177
177
|
- `fullOutputPath` / `fullOutputPaths` when large snapshot output or other oversized tool output is compacted and spilled to a private file; persisted sessions keep that path under a private session-scoped artifact directory with a bounded per-session budget so it survives reload/resume without unbounded growth
|
|
@@ -189,15 +189,16 @@ For oversized snapshots and other oversized tool outputs, details should switch
|
|
|
189
189
|
"Rendering" here means how results appear inside `pi`, not embedding a browser UI.
|
|
190
190
|
|
|
191
191
|
Worth doing in v1:
|
|
192
|
-
- screenshots → saved-path summary, `details.artifacts` metadata, and inline image attachment when safe
|
|
192
|
+
- screenshots → saved-path summary, visible artifact metadata, `details.artifacts` metadata, and inline image attachment when safe; screenshot paths that upstream would treat ambiguously, such as `.dogfood/run/foo.png`, are normalized to absolute paths before launch and repaired from upstream temp output when possible
|
|
193
193
|
- file artifacts such as PDFs, downloads, `wait --download` files, traces, CPU profiles, completed WebM recordings, and path-bearing HAR captures → concise saved-path summaries plus metadata in `details.artifacts` and bounded recent metadata in `details.artifactManifest`; `record start` reports recording lifecycle state and the future output path without adding a missing manifest entry; direct saved-file workflows also expose `details.savedFilePath` / `details.savedFile`; large or binary artifacts are not inlined into model context; the recent manifest cap can age out explicit-file metadata but does not remove explicit saved files from disk
|
|
194
194
|
- snapshots → origin + ref count + main-content-first compact preview, with the raw snapshot spill path printed directly in content and kept in `details.fullOutputPath` plus `details.artifactManifest` when the inline result would otherwise be too large
|
|
195
195
|
- oversized generic outputs such as large `eval --stdin` payloads → compact preview plus the actual spill file path instead of dumping the whole payload into model context
|
|
196
196
|
- extraction-style commands like `eval --stdin` and `get title` → scalar-first text with lightweight origin context when available
|
|
197
197
|
- navigation actions like `click`, `back`, `forward`, and `reload` → lightweight post-action title/url summary when available
|
|
198
198
|
- tab lists → compact summary/table
|
|
199
|
-
- stream status → enabled/connected/port summary
|
|
199
|
+
- stream status → enabled/connected/port summary plus WebSocket URL and frame format when a port is known; if the caller explicitly passed `--json`, visible text is valid JSON instead of a prose summary
|
|
200
200
|
- diagnostic/status families (`session`, `session list`, `profiles`, `doctor`, `auth list`/`show`, `network requests`, `console`, `errors`, and dashboard start/stop/status outputs) → compact readable summaries with counts and stable fields; large log/request/error outputs use previews plus `fullOutputPath` spill files; sensitive nested auth/header/token fields are not expanded in the model-facing text
|
|
201
|
+
- trace/profiler owner conflicts → when the wrapper has observed one owner active for a session, block conflicting starts/stops with "wrapper believes ..." wording because upstream or external CLI use can desynchronize wrapper-local state
|
|
201
202
|
|
|
202
203
|
## Missing binary behavior
|
|
203
204
|
|
|
@@ -212,8 +213,9 @@ If `agent-browser` is not on `PATH`, fail with a message that:
|
|
|
212
213
|
- derive the base implicit session name from the official `pi` session id plus a cwd hash so same-named checkouts do not collide
|
|
213
214
|
- respect explicit upstream `--session` with minimal interference
|
|
214
215
|
- treat the extension-managed session as convenience state owned by the wrapper
|
|
215
|
-
- preserve the current extension-managed session across
|
|
216
|
-
-
|
|
216
|
+
- preserve the current extension-managed session across `/reload` and resumable session transitions so persisted sessions can keep following the live browser on `/reload` or `/resume`
|
|
217
|
+
- close the active extension-managed session when the originating `pi` process quits, while leaving explicit caller-provided sessions alone
|
|
218
|
+
- set an idle timeout on extension-managed sessions as a backstop for abnormal exits or cleanup failures
|
|
217
219
|
- clean up process-private temp spill artifacts on shutdown, while keeping persisted-session snapshot spill files in a private session-scoped artifact directory so `details.fullOutputPath` survives reload/restart and the oldest spill files are evicted if the per-session artifact budget is exceeded
|
|
218
220
|
- reconstruct the current extension-managed session and latest `artifactManifest` from persisted tool details on resume/reload so later default calls keep following the active managed browser and can continue reporting artifact retention state
|
|
219
221
|
- when an unnamed `sessionMode: "fresh"` launch succeeds, make it the new extension-managed session so later default calls keep using it
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { readFile, rm } from "node:fs/promises";
|
|
9
|
+
import { copyFile, mkdir, readFile, rm, stat } from "node:fs/promises";
|
|
10
|
+
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
10
11
|
|
|
11
12
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
12
13
|
import { isToolCallEventType, type AgentToolResult, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -64,6 +65,8 @@ import {
|
|
|
64
65
|
} from "./lib/results/shared.js";
|
|
65
66
|
|
|
66
67
|
const DEFAULT_SESSION_MODE = "auto" as const;
|
|
68
|
+
const DIRECT_AGENT_BROWSER_BASH_BYPASS_ENV = "PI_AGENT_BROWSER_ALLOW_DIRECT_BASH";
|
|
69
|
+
const PACKAGE_NAME = "pi-agent-browser-native";
|
|
67
70
|
|
|
68
71
|
const AGENT_BROWSER_PARAMS = Type.Object({
|
|
69
72
|
args: Type.Array(Type.String({ description: "Exact agent-browser CLI arguments, excluding the binary name." }), {
|
|
@@ -292,6 +295,23 @@ function isHarmlessAgentBrowserInspectionCommand(command: string): boolean {
|
|
|
292
295
|
return HARMLESS_AGENT_BROWSER_INSPECTION_PATTERN.test(command);
|
|
293
296
|
}
|
|
294
297
|
|
|
298
|
+
function isTruthyEnvValue(value: string | undefined): boolean {
|
|
299
|
+
return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function isPackageDevelopmentCwd(cwd: string): Promise<boolean> {
|
|
303
|
+
try {
|
|
304
|
+
const packageJson = JSON.parse(await readFile(join(cwd, "package.json"), "utf8")) as { name?: unknown };
|
|
305
|
+
return packageJson.name === PACKAGE_NAME;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function isDirectAgentBrowserBashAllowed(cwd: string): Promise<boolean> {
|
|
312
|
+
return isTruthyEnvValue(process.env[DIRECT_AGENT_BROWSER_BASH_BYPASS_ENV]) || await isPackageDevelopmentCwd(cwd);
|
|
313
|
+
}
|
|
314
|
+
|
|
295
315
|
const NAVIGATION_SUMMARY_COMMANDS = new Set(["back", "click", "dblclick", "forward", "reload"]);
|
|
296
316
|
|
|
297
317
|
interface NavigationSummary {
|
|
@@ -303,6 +323,330 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
303
323
|
return typeof value === "object" && value !== null;
|
|
304
324
|
}
|
|
305
325
|
|
|
326
|
+
const SCREENSHOT_VALUE_FLAGS = new Set(["--screenshot-dir", "--screenshot-format", "--screenshot-quality"]);
|
|
327
|
+
const SCREENSHOT_IMAGE_EXTENSIONS = new Set([".jpeg", ".jpg", ".png", ".webp"]);
|
|
328
|
+
|
|
329
|
+
interface ScreenshotPathRequest {
|
|
330
|
+
absolutePath: string;
|
|
331
|
+
path: string;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
interface PreparedAgentBrowserArgs {
|
|
335
|
+
args: string[];
|
|
336
|
+
batchScreenshotPathRequests?: Array<ScreenshotPathRequest | undefined>;
|
|
337
|
+
screenshotPathRequest?: ScreenshotPathRequest;
|
|
338
|
+
stdin?: string;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
interface ScreenshotArtifactRequest extends ScreenshotPathRequest {
|
|
342
|
+
status?: "missing" | "repaired-from-temp" | "saved" | "upstream-temp-only";
|
|
343
|
+
tempPath?: string;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
type TraceOwner = "profiler" | "trace";
|
|
347
|
+
|
|
348
|
+
function isImagePathToken(token: string): boolean {
|
|
349
|
+
const extension = extname(token).toLowerCase();
|
|
350
|
+
return SCREENSHOT_IMAGE_EXTENSIONS.has(extension);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function getScreenshotPathTokenIndex(commandTokens: string[]): number | undefined {
|
|
354
|
+
if (commandTokens[0] !== "screenshot") {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const positionalIndices: number[] = [];
|
|
359
|
+
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
360
|
+
const token = commandTokens[index];
|
|
361
|
+
if (token === "--") {
|
|
362
|
+
for (let positionalIndex = index + 1; positionalIndex < commandTokens.length; positionalIndex += 1) {
|
|
363
|
+
positionalIndices.push(positionalIndex);
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
if (token.startsWith("-")) {
|
|
368
|
+
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
369
|
+
if (SCREENSHOT_VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
|
|
370
|
+
index += 1;
|
|
371
|
+
}
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
positionalIndices.push(index);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (positionalIndices.length === 0) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
const candidateIndex = positionalIndices[positionalIndices.length - 1];
|
|
381
|
+
const candidate = commandTokens[candidateIndex];
|
|
382
|
+
if (positionalIndices.length >= 2 || isImagePathToken(candidate) || isAbsolute(candidate) || candidate.startsWith("./") || candidate.startsWith("../")) {
|
|
383
|
+
return candidateIndex;
|
|
384
|
+
}
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function normalizeScreenshotPathInTokens(commandTokens: string[], cwd: string): Promise<{
|
|
389
|
+
request?: ScreenshotPathRequest;
|
|
390
|
+
tokens: string[];
|
|
391
|
+
}> {
|
|
392
|
+
const screenshotPathTokenIndex = getScreenshotPathTokenIndex(commandTokens);
|
|
393
|
+
if (screenshotPathTokenIndex === undefined) {
|
|
394
|
+
return { tokens: commandTokens };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const requestedPath = commandTokens[screenshotPathTokenIndex];
|
|
398
|
+
const absolutePath = resolve(cwd, requestedPath);
|
|
399
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
400
|
+
|
|
401
|
+
const tokens = [...commandTokens];
|
|
402
|
+
tokens[screenshotPathTokenIndex] = absolutePath;
|
|
403
|
+
const terminatorIndex = tokens.indexOf("--");
|
|
404
|
+
if (terminatorIndex >= 0) {
|
|
405
|
+
tokens.splice(terminatorIndex, 1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
request: {
|
|
410
|
+
absolutePath,
|
|
411
|
+
path: requestedPath,
|
|
412
|
+
},
|
|
413
|
+
tokens,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function prepareBatchScreenshotPaths(args: string[], stdin: string | undefined, cwd: string): Promise<PreparedAgentBrowserArgs | undefined> {
|
|
418
|
+
const commandTokens = extractCommandTokens(args);
|
|
419
|
+
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
let steps: unknown;
|
|
423
|
+
try {
|
|
424
|
+
steps = JSON.parse(stdin);
|
|
425
|
+
} catch {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
if (!Array.isArray(steps)) {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let changed = false;
|
|
433
|
+
const batchScreenshotPathRequests: Array<ScreenshotPathRequest | undefined> = [];
|
|
434
|
+
const preparedSteps = await Promise.all(steps.map(async (step, index) => {
|
|
435
|
+
if (!Array.isArray(step) || !step.every((item) => typeof item === "string") || step[0] !== "screenshot") {
|
|
436
|
+
return step;
|
|
437
|
+
}
|
|
438
|
+
const normalized = await normalizeScreenshotPathInTokens(step, cwd);
|
|
439
|
+
batchScreenshotPathRequests[index] = normalized.request;
|
|
440
|
+
if (normalized.request) {
|
|
441
|
+
changed = true;
|
|
442
|
+
}
|
|
443
|
+
return normalized.tokens;
|
|
444
|
+
}));
|
|
445
|
+
|
|
446
|
+
return changed
|
|
447
|
+
? {
|
|
448
|
+
args,
|
|
449
|
+
batchScreenshotPathRequests,
|
|
450
|
+
stdin: JSON.stringify(preparedSteps),
|
|
451
|
+
}
|
|
452
|
+
: undefined;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function prepareAgentBrowserArgs(args: string[], stdin: string | undefined, cwd: string): Promise<PreparedAgentBrowserArgs> {
|
|
456
|
+
const preparedBatch = await prepareBatchScreenshotPaths(args, stdin, cwd);
|
|
457
|
+
if (preparedBatch) {
|
|
458
|
+
return preparedBatch;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const commandTokens = extractCommandTokens(args);
|
|
462
|
+
const normalized = await normalizeScreenshotPathInTokens(commandTokens, cwd);
|
|
463
|
+
if (!normalized.request) {
|
|
464
|
+
return { args };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const commandStartIndex = args.length - commandTokens.length;
|
|
468
|
+
return {
|
|
469
|
+
args: [...args.slice(0, commandStartIndex), ...normalized.tokens],
|
|
470
|
+
screenshotPathRequest: normalized.request,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
475
|
+
try {
|
|
476
|
+
await stat(path);
|
|
477
|
+
return true;
|
|
478
|
+
} catch {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function repairScreenshotData(options: {
|
|
484
|
+
cwd: string;
|
|
485
|
+
data: Record<string, unknown>;
|
|
486
|
+
request: ScreenshotPathRequest;
|
|
487
|
+
}): Promise<{ data: Record<string, unknown>; request: ScreenshotArtifactRequest }> {
|
|
488
|
+
const { cwd, data, request } = options;
|
|
489
|
+
const reportedPath = typeof data.path === "string" ? data.path : undefined;
|
|
490
|
+
const reportedAbsolutePath = reportedPath ? resolve(cwd, reportedPath) : undefined;
|
|
491
|
+
let status: ScreenshotArtifactRequest["status"] = await pathExists(request.absolutePath) ? "saved" : "missing";
|
|
492
|
+
let tempPath: string | undefined;
|
|
493
|
+
|
|
494
|
+
if (reportedAbsolutePath && reportedAbsolutePath !== request.absolutePath) {
|
|
495
|
+
tempPath = reportedAbsolutePath;
|
|
496
|
+
if (status === "missing" && await pathExists(reportedAbsolutePath)) {
|
|
497
|
+
await mkdir(dirname(request.absolutePath), { recursive: true });
|
|
498
|
+
await copyFile(reportedAbsolutePath, request.absolutePath);
|
|
499
|
+
status = "repaired-from-temp";
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
data: {
|
|
505
|
+
...data,
|
|
506
|
+
path: request.absolutePath,
|
|
507
|
+
},
|
|
508
|
+
request: {
|
|
509
|
+
...request,
|
|
510
|
+
status,
|
|
511
|
+
tempPath,
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function repairScreenshotArtifact(options: {
|
|
517
|
+
cwd: string;
|
|
518
|
+
envelope?: AgentBrowserEnvelope;
|
|
519
|
+
request?: ScreenshotPathRequest;
|
|
520
|
+
}): Promise<{ envelope?: AgentBrowserEnvelope; request?: ScreenshotArtifactRequest }> {
|
|
521
|
+
const { cwd, envelope, request } = options;
|
|
522
|
+
if (!request || !envelope || !isRecord(envelope.data)) {
|
|
523
|
+
return { envelope, request };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const repaired = await repairScreenshotData({ cwd, data: envelope.data, request });
|
|
527
|
+
return {
|
|
528
|
+
envelope: { ...envelope, data: repaired.data },
|
|
529
|
+
request: repaired.request,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function repairBatchScreenshotArtifacts(options: {
|
|
534
|
+
cwd: string;
|
|
535
|
+
envelope?: AgentBrowserEnvelope;
|
|
536
|
+
requests?: Array<ScreenshotPathRequest | undefined>;
|
|
537
|
+
}): Promise<{ envelope?: AgentBrowserEnvelope; requests?: Array<ScreenshotArtifactRequest | undefined> }> {
|
|
538
|
+
const { cwd, envelope, requests } = options;
|
|
539
|
+
if (!envelope || !Array.isArray(envelope.data) || !requests?.some((request) => request !== undefined)) {
|
|
540
|
+
return { envelope, requests };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const repairedRequests: Array<ScreenshotArtifactRequest | undefined> = [];
|
|
544
|
+
const repairedData = await Promise.all(envelope.data.map(async (item, index) => {
|
|
545
|
+
const request = requests[index];
|
|
546
|
+
if (!request || !isRecord(item) || !isRecord(item.result)) {
|
|
547
|
+
return item;
|
|
548
|
+
}
|
|
549
|
+
const repaired = await repairScreenshotData({ cwd, data: item.result, request });
|
|
550
|
+
repairedRequests[index] = repaired.request;
|
|
551
|
+
return {
|
|
552
|
+
...item,
|
|
553
|
+
result: repaired.data,
|
|
554
|
+
};
|
|
555
|
+
}));
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
envelope: { ...envelope, data: repairedData },
|
|
559
|
+
requests: repairedRequests,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function buildJsonVisibleContent(options: {
|
|
564
|
+
error: unknown;
|
|
565
|
+
presentation: Awaited<ReturnType<typeof buildToolPresentation>>;
|
|
566
|
+
succeeded: boolean;
|
|
567
|
+
warnings?: string[];
|
|
568
|
+
}): Array<{ text: string; type: "text" } | { data: string; mimeType: string; type: "image" }> {
|
|
569
|
+
const { error, presentation, succeeded, warnings } = options;
|
|
570
|
+
const payload = redactSensitiveValue({
|
|
571
|
+
artifacts: presentation.artifacts,
|
|
572
|
+
data: presentation.data,
|
|
573
|
+
error,
|
|
574
|
+
success: succeeded,
|
|
575
|
+
warnings: warnings && warnings.length > 0 ? warnings : undefined,
|
|
576
|
+
});
|
|
577
|
+
if (isRecord(payload) && isRecord(payload.data) && isRecord(presentation.data) && typeof presentation.data.wsUrl === "string") {
|
|
578
|
+
payload.data.wsUrl = presentation.data.wsUrl;
|
|
579
|
+
}
|
|
580
|
+
const images = presentation.content.filter((item): item is { data: string; mimeType: string; type: "image" } => item.type === "image");
|
|
581
|
+
return [{ type: "text", text: JSON.stringify(payload, null, 2) }, ...images];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function getBatchAnnotateValidationError(args: string[], stdin: string | undefined): string | undefined {
|
|
585
|
+
const commandTokens = extractCommandTokens(args);
|
|
586
|
+
if (commandTokens[0] !== "batch" || stdin === undefined) {
|
|
587
|
+
return undefined;
|
|
588
|
+
}
|
|
589
|
+
let steps: unknown;
|
|
590
|
+
try {
|
|
591
|
+
steps = JSON.parse(stdin);
|
|
592
|
+
} catch {
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
if (!Array.isArray(steps)) {
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
const badStepIndex = steps.findIndex((step) => Array.isArray(step) && step[0] === "screenshot" && step.includes("--annotate"));
|
|
599
|
+
if (badStepIndex < 0) {
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
602
|
+
return [
|
|
603
|
+
`Unsupported batch screenshot annotation in step ${badStepIndex + 1}: put --annotate in top-level args, not inside the batch step.`,
|
|
604
|
+
`Use: { "args": ["--annotate", "batch"], "stdin": "[[\\"screenshot\\",\\"/path/to/image.png\\"]]" }`,
|
|
605
|
+
].join("\n");
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function getTraceOwner(command: string | undefined): TraceOwner | undefined {
|
|
609
|
+
return command === "trace" || command === "profiler" ? command : undefined;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function getTraceOwnerGuardMessage(options: {
|
|
613
|
+
command: string | undefined;
|
|
614
|
+
sessionName: string | undefined;
|
|
615
|
+
subcommand: string | undefined;
|
|
616
|
+
traceOwners: Map<string, TraceOwner>;
|
|
617
|
+
}): string | undefined {
|
|
618
|
+
const owner = getTraceOwner(options.command);
|
|
619
|
+
if (!owner || !options.sessionName || (options.subcommand !== "start" && options.subcommand !== "stop")) {
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
const activeOwner = options.traceOwners.get(options.sessionName);
|
|
623
|
+
if (!activeOwner || activeOwner === owner) {
|
|
624
|
+
return undefined;
|
|
625
|
+
}
|
|
626
|
+
return options.subcommand === "start"
|
|
627
|
+
? `Wrapper believes ${activeOwner} tracing is active for session ${options.sessionName}; stop ${activeOwner} before starting ${owner}.`
|
|
628
|
+
: `Wrapper believes tracing for session ${options.sessionName} is owned by ${activeOwner}; run ${activeOwner} stop instead of ${owner} stop.`;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function updateTraceOwnerState(options: {
|
|
632
|
+
command: string | undefined;
|
|
633
|
+
sessionName: string | undefined;
|
|
634
|
+
subcommand: string | undefined;
|
|
635
|
+
succeeded: boolean;
|
|
636
|
+
traceOwners: Map<string, TraceOwner>;
|
|
637
|
+
}): void {
|
|
638
|
+
const owner = getTraceOwner(options.command);
|
|
639
|
+
if (!owner || !options.sessionName || !options.succeeded) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (options.subcommand === "start") {
|
|
643
|
+
options.traceOwners.set(options.sessionName, owner);
|
|
644
|
+
}
|
|
645
|
+
if (options.subcommand === "stop" && options.traceOwners.get(options.sessionName) === owner) {
|
|
646
|
+
options.traceOwners.delete(options.sessionName);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
306
650
|
function shouldCaptureNavigationSummary(command: string | undefined, data: unknown): boolean {
|
|
307
651
|
return (
|
|
308
652
|
command !== undefined &&
|
|
@@ -1025,6 +1369,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1025
1369
|
let freshSessionOrdinal = 0;
|
|
1026
1370
|
let sessionTabTargets = new Map<string, OrderedSessionTabTarget>();
|
|
1027
1371
|
let sessionTabTargetUpdateOrder = 0;
|
|
1372
|
+
let traceOwners = new Map<string, TraceOwner>();
|
|
1028
1373
|
let artifactManifest: SessionArtifactManifest | undefined;
|
|
1029
1374
|
const managedSessionExecutionQueue = new AsyncExecutionQueue();
|
|
1030
1375
|
|
|
@@ -1040,10 +1385,21 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1040
1385
|
artifactManifest = restoreArtifactManifestFromBranch(ctx.sessionManager.getBranch());
|
|
1041
1386
|
});
|
|
1042
1387
|
|
|
1043
|
-
pi.on("session_shutdown", async () => {
|
|
1388
|
+
pi.on("session_shutdown", async (event) => {
|
|
1389
|
+
if (event?.reason === "quit") {
|
|
1390
|
+
await managedSessionExecutionQueue.run(async () => {
|
|
1391
|
+
if (!managedSessionActive) return;
|
|
1392
|
+
await closeManagedSession({
|
|
1393
|
+
cwd: managedSessionCwd,
|
|
1394
|
+
sessionName: managedSessionName,
|
|
1395
|
+
timeoutMs: implicitSessionCloseTimeoutMs,
|
|
1396
|
+
});
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1044
1399
|
managedSessionActive = false;
|
|
1045
1400
|
sessionTabTargets = new Map<string, OrderedSessionTabTarget>();
|
|
1046
1401
|
sessionTabTargetUpdateOrder = 0;
|
|
1402
|
+
traceOwners = new Map<string, TraceOwner>();
|
|
1047
1403
|
artifactManifest = undefined;
|
|
1048
1404
|
await cleanupSecureTempArtifacts();
|
|
1049
1405
|
});
|
|
@@ -1063,7 +1419,8 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1063
1419
|
isToolCallEventType("bash", event) &&
|
|
1064
1420
|
!promptPolicy.allowLegacyAgentBrowserBash &&
|
|
1065
1421
|
looksLikeDirectAgentBrowserBash(event.input.command) &&
|
|
1066
|
-
!isHarmlessAgentBrowserInspectionCommand(event.input.command)
|
|
1422
|
+
!isHarmlessAgentBrowserInspectionCommand(event.input.command) &&
|
|
1423
|
+
!(await isDirectAgentBrowserBashAllowed(ctx.cwd))
|
|
1067
1424
|
) {
|
|
1068
1425
|
return {
|
|
1069
1426
|
block: true,
|
|
@@ -1083,7 +1440,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1083
1440
|
parameters: AGENT_BROWSER_PARAMS,
|
|
1084
1441
|
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
1085
1442
|
const redactedArgs = redactInvocationArgs(params.args);
|
|
1086
|
-
const validationError = validateToolArgs(params.args);
|
|
1443
|
+
const validationError = validateToolArgs(params.args) ?? getBatchAnnotateValidationError(params.args, params.stdin);
|
|
1087
1444
|
if (validationError) {
|
|
1088
1445
|
return {
|
|
1089
1446
|
content: [{ type: "text", text: validationError }],
|
|
@@ -1091,12 +1448,14 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1091
1448
|
isError: true,
|
|
1092
1449
|
};
|
|
1093
1450
|
}
|
|
1451
|
+
const preparedArgs = await prepareAgentBrowserArgs(params.args, params.stdin, ctx.cwd);
|
|
1452
|
+
const userRequestedJson = params.args.includes("--json");
|
|
1094
1453
|
|
|
1095
1454
|
const tabTargetUpdateOrder = ++sessionTabTargetUpdateOrder;
|
|
1096
1455
|
const runTool = async (): Promise<AgentBrowserToolResult> => {
|
|
1097
1456
|
const sessionMode = params.sessionMode ?? DEFAULT_SESSION_MODE;
|
|
1098
1457
|
const freshSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1);
|
|
1099
|
-
const executionPlan = buildExecutionPlan(
|
|
1458
|
+
const executionPlan = buildExecutionPlan(preparedArgs.args, {
|
|
1100
1459
|
freshSessionName,
|
|
1101
1460
|
managedSessionActive,
|
|
1102
1461
|
managedSessionName,
|
|
@@ -1124,7 +1483,28 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1124
1483
|
};
|
|
1125
1484
|
}
|
|
1126
1485
|
|
|
1127
|
-
const commandTokens = extractCommandTokens(
|
|
1486
|
+
const commandTokens = extractCommandTokens(preparedArgs.args);
|
|
1487
|
+
const traceOwnerGuardMessage = getTraceOwnerGuardMessage({
|
|
1488
|
+
command: executionPlan.commandInfo.command,
|
|
1489
|
+
sessionName: executionPlan.sessionName,
|
|
1490
|
+
subcommand: executionPlan.commandInfo.subcommand,
|
|
1491
|
+
traceOwners,
|
|
1492
|
+
});
|
|
1493
|
+
if (traceOwnerGuardMessage) {
|
|
1494
|
+
return {
|
|
1495
|
+
content: [{ type: "text", text: traceOwnerGuardMessage }],
|
|
1496
|
+
details: {
|
|
1497
|
+
args: redactedArgs,
|
|
1498
|
+
command: executionPlan.commandInfo.command,
|
|
1499
|
+
compatibilityWorkaround,
|
|
1500
|
+
effectiveArgs: redactedEffectiveArgs,
|
|
1501
|
+
sessionMode,
|
|
1502
|
+
validationError: traceOwnerGuardMessage,
|
|
1503
|
+
...buildSessionDetailFields(executionPlan.sessionName, executionPlan.usedImplicitSession),
|
|
1504
|
+
},
|
|
1505
|
+
isError: true,
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1128
1508
|
const stdinValidationError = validateStdinCommandContract({
|
|
1129
1509
|
command: executionPlan.commandInfo.command,
|
|
1130
1510
|
commandTokens,
|
|
@@ -1152,7 +1532,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1152
1532
|
let includePinnedNavigationSummary = false;
|
|
1153
1533
|
let sessionTabCorrection: OpenResultTabCorrection | undefined;
|
|
1154
1534
|
let processArgs = executionPlan.effectiveArgs;
|
|
1155
|
-
let processStdin = params.stdin;
|
|
1535
|
+
let processStdin = preparedArgs.stdin ?? params.stdin;
|
|
1156
1536
|
if (
|
|
1157
1537
|
priorSessionTabTarget &&
|
|
1158
1538
|
shouldPinSessionTabForCommand({
|
|
@@ -1283,6 +1663,20 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1283
1663
|
presentationEnvelope = pinnedBatchResult.envelope ?? presentationEnvelope;
|
|
1284
1664
|
navigationSummary = pinnedBatchResult.navigationSummary;
|
|
1285
1665
|
}
|
|
1666
|
+
const repairedScreenshot = await repairScreenshotArtifact({
|
|
1667
|
+
cwd: ctx.cwd,
|
|
1668
|
+
envelope: presentationEnvelope,
|
|
1669
|
+
request: preparedArgs.screenshotPathRequest,
|
|
1670
|
+
});
|
|
1671
|
+
presentationEnvelope = repairedScreenshot.envelope;
|
|
1672
|
+
const repairedBatchScreenshots = await repairBatchScreenshotArtifacts({
|
|
1673
|
+
cwd: ctx.cwd,
|
|
1674
|
+
envelope: presentationEnvelope,
|
|
1675
|
+
requests: preparedArgs.batchScreenshotPathRequests,
|
|
1676
|
+
});
|
|
1677
|
+
presentationEnvelope = repairedBatchScreenshots.envelope;
|
|
1678
|
+
const screenshotArtifactRequest = repairedScreenshot.request;
|
|
1679
|
+
const batchScreenshotArtifactRequests = repairedBatchScreenshots.requests;
|
|
1286
1680
|
const parseFailureOutput = parseError
|
|
1287
1681
|
? await preserveParseFailureOutput({
|
|
1288
1682
|
artifactManifest,
|
|
@@ -1296,6 +1690,13 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1296
1690
|
const envelopeSuccess = plainTextInspection ? true : presentationEnvelope?.success !== false;
|
|
1297
1691
|
const succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
|
|
1298
1692
|
const inspectionText = plainTextInspection ? processResult.stdout.trim() : undefined;
|
|
1693
|
+
updateTraceOwnerState({
|
|
1694
|
+
command: executionPlan.commandInfo.command,
|
|
1695
|
+
sessionName: executionPlan.sessionName,
|
|
1696
|
+
subcommand: executionPlan.commandInfo.subcommand,
|
|
1697
|
+
succeeded,
|
|
1698
|
+
traceOwners,
|
|
1699
|
+
});
|
|
1299
1700
|
|
|
1300
1701
|
if (succeeded && !navigationSummary && shouldCaptureNavigationSummary(executionPlan.commandInfo.command, presentationEnvelope?.data)) {
|
|
1301
1702
|
navigationSummary = await collectNavigationSummary({
|
|
@@ -1477,11 +1878,14 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1477
1878
|
}
|
|
1478
1879
|
: await buildToolPresentation({
|
|
1479
1880
|
artifactManifest,
|
|
1881
|
+
artifactRequest: screenshotArtifactRequest,
|
|
1882
|
+
batchArtifactRequests: batchScreenshotArtifactRequests,
|
|
1480
1883
|
commandInfo: executionPlan.commandInfo,
|
|
1481
1884
|
cwd: ctx.cwd,
|
|
1482
1885
|
envelope: presentationEnvelope,
|
|
1483
1886
|
errorText,
|
|
1484
1887
|
persistentArtifactStore,
|
|
1888
|
+
sessionName: executionPlan.sessionName,
|
|
1485
1889
|
});
|
|
1486
1890
|
if (parseFailureOutput.artifactManifest) {
|
|
1487
1891
|
presentation.artifactManifest = parseFailureOutput.artifactManifest;
|
|
@@ -1504,20 +1908,29 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
1504
1908
|
if (presentation.artifactManifest) {
|
|
1505
1909
|
artifactManifest = presentation.artifactManifest;
|
|
1506
1910
|
}
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
|
|
1911
|
+
const warningText = aboutBlankSessionMismatch ? buildAboutBlankWarning(aboutBlankSessionMismatch) : undefined;
|
|
1912
|
+
const contentWithSessionWarnings = userRequestedJson && !plainTextInspection
|
|
1913
|
+
? buildJsonVisibleContent({
|
|
1914
|
+
error: presentationEnvelope?.error,
|
|
1915
|
+
presentation,
|
|
1916
|
+
succeeded,
|
|
1917
|
+
warnings: warningText ? [warningText] : undefined,
|
|
1918
|
+
})
|
|
1919
|
+
: warningText
|
|
1920
|
+
? [...presentation.content]
|
|
1921
|
+
: presentation.content;
|
|
1922
|
+
if (warningText && !userRequestedJson) {
|
|
1510
1923
|
if (contentWithSessionWarnings[0]?.type === "text") {
|
|
1511
1924
|
contentWithSessionWarnings[0] = {
|
|
1512
1925
|
...contentWithSessionWarnings[0],
|
|
1513
|
-
text: `${
|
|
1926
|
+
text: `${warningText}\n\n${contentWithSessionWarnings[0].text}`,
|
|
1514
1927
|
};
|
|
1515
1928
|
} else {
|
|
1516
|
-
contentWithSessionWarnings.unshift({ type: "text", text:
|
|
1929
|
+
contentWithSessionWarnings.unshift({ type: "text", text: warningText });
|
|
1517
1930
|
}
|
|
1518
1931
|
}
|
|
1519
1932
|
const redactedContent = contentWithSessionWarnings.map((item) =>
|
|
1520
|
-
item.type === "text" ? { ...item, text: redactSensitiveText(item.text) } : item,
|
|
1933
|
+
item.type === "text" && !(userRequestedJson && !plainTextInspection) ? { ...item, text: redactSensitiveText(item.text) } : item,
|
|
1521
1934
|
);
|
|
1522
1935
|
|
|
1523
1936
|
return {
|
|
@@ -18,6 +18,7 @@ export const QUICK_START_GUIDELINES = [
|
|
|
18
18
|
"Common first calls: { args: [\"open\", \"https://example.com\"] } then { args: [\"snapshot\", \"-i\"] }; after navigation, use { args: [\"click\", \"@e2\"] } then { args: [\"snapshot\", \"-i\"] }.",
|
|
19
19
|
"Common advanced calls: { args: [\"batch\"], stdin: \"[[\\\"open\\\",\\\"https://example.com\\\"],[\\\"snapshot\\\",\\\"-i\\\"]]\" }, { args: [\"eval\", \"--stdin\"], stdin: \"document.title\" }, and { args: [\"--profile\", \"Default\", \"open\", \"https://example.com/account\"], sessionMode: \"fresh\" }.",
|
|
20
20
|
"High-value command reference: download <selector> <path> saves a file triggered by a click; get title/url/text/html/value/attr/count reads page state; screenshot [path] captures an image; pdf <path> saves a PDF; tab list and tab <tab-id-or-label> inspect or recover the active tab.",
|
|
21
|
+
"For artifact-producing commands, read the visible artifact block for requested path, absolute path, existence, size, type, cwd, and session; details.artifacts contains the same machine-readable metadata. For annotated screenshots inside batch, put --annotate in top-level args (for example { args: [\"--annotate\", \"batch\"], stdin: \"[[\\\"screenshot\\\",\\\"/tmp/page.png\\\"]]\" }) rather than inside the screenshot step.",
|
|
21
22
|
] as const;
|
|
22
23
|
|
|
23
24
|
export const BRAVE_SEARCH_PROMPT_GUIDELINE =
|
|
@@ -173,10 +173,27 @@ function getStreamSummary(data: Record<string, unknown>): string | undefined {
|
|
|
173
173
|
];
|
|
174
174
|
if (typeof data.port === "number") {
|
|
175
175
|
lines.push(`Port: ${data.port}`);
|
|
176
|
+
lines.push(`WebSocket URL: ${getStreamWebSocketUrl(data.port)}`);
|
|
177
|
+
lines.push(`Frame format: JSON messages with base64 JPEG frame data`);
|
|
176
178
|
}
|
|
177
179
|
return lines.join("\n");
|
|
178
180
|
}
|
|
179
181
|
|
|
182
|
+
function getStreamWebSocketUrl(port: number): string {
|
|
183
|
+
return `ws://127.0.0.1:${port}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function enrichStreamStatusData(commandInfo: CommandInfo, data: unknown): unknown {
|
|
187
|
+
if (commandInfo.command !== "stream" || commandInfo.subcommand !== "status" || !isRecord(data) || typeof data.port !== "number") {
|
|
188
|
+
return data;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...data,
|
|
192
|
+
frameFormat: "JSON messages with base64 JPEG frame data",
|
|
193
|
+
wsUrl: getStreamWebSocketUrl(data.port),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
180
197
|
function getArrayField(data: Record<string, unknown>, key: string): unknown[] | undefined {
|
|
181
198
|
return Array.isArray(data[key]) ? data[key] : undefined;
|
|
182
199
|
}
|
|
@@ -662,18 +679,28 @@ function extractPathStrings(data: unknown): string[] {
|
|
|
662
679
|
return [...new Set(paths)];
|
|
663
680
|
}
|
|
664
681
|
|
|
682
|
+
interface ArtifactRequestContext {
|
|
683
|
+
absolutePath: string;
|
|
684
|
+
path: string;
|
|
685
|
+
status?: FileArtifactMetadata["status"];
|
|
686
|
+
tempPath?: string;
|
|
687
|
+
}
|
|
688
|
+
|
|
665
689
|
async function buildFileArtifactMetadata(options: {
|
|
690
|
+
artifactRequest?: ArtifactRequestContext;
|
|
666
691
|
commandInfo: CommandInfo;
|
|
667
692
|
cwd: string;
|
|
668
693
|
path: string;
|
|
694
|
+
sessionName?: string;
|
|
669
695
|
}): Promise<FileArtifactMetadata | undefined> {
|
|
670
696
|
const kind = getArtifactKind(options.commandInfo);
|
|
671
697
|
if (!kind) {
|
|
672
698
|
return undefined;
|
|
673
699
|
}
|
|
674
700
|
|
|
675
|
-
const absolutePath = resolve(options.cwd, options.path);
|
|
676
|
-
const
|
|
701
|
+
const absolutePath = options.artifactRequest?.absolutePath ?? resolve(options.cwd, options.path);
|
|
702
|
+
const displayPath = options.artifactRequest?.path ?? options.path;
|
|
703
|
+
const extension = extname(absolutePath || options.path).toLowerCase() || undefined;
|
|
677
704
|
let exists: boolean | undefined;
|
|
678
705
|
let sizeBytes: number | undefined;
|
|
679
706
|
try {
|
|
@@ -686,20 +713,32 @@ async function buildFileArtifactMetadata(options: {
|
|
|
686
713
|
|
|
687
714
|
return {
|
|
688
715
|
absolutePath,
|
|
716
|
+
artifactType: kind,
|
|
689
717
|
command: options.commandInfo.command,
|
|
718
|
+
cwd: options.cwd,
|
|
690
719
|
exists,
|
|
691
720
|
extension,
|
|
692
721
|
kind,
|
|
693
722
|
mediaType: extension ? ARTIFACT_EXTENSION_TO_MEDIA_TYPE[extension] : undefined,
|
|
694
|
-
path:
|
|
723
|
+
path: displayPath,
|
|
724
|
+
requestedPath: options.artifactRequest?.path,
|
|
725
|
+
session: options.sessionName,
|
|
695
726
|
sizeBytes,
|
|
727
|
+
status: options.artifactRequest?.status ?? (exists === false ? "missing" : "saved"),
|
|
696
728
|
subcommand: options.commandInfo.subcommand,
|
|
729
|
+
tempPath: options.artifactRequest?.tempPath,
|
|
697
730
|
};
|
|
698
731
|
}
|
|
699
732
|
|
|
700
|
-
async function extractFileArtifacts(
|
|
701
|
-
|
|
702
|
-
|
|
733
|
+
async function extractFileArtifacts(options: {
|
|
734
|
+
artifactRequest?: ArtifactRequestContext;
|
|
735
|
+
commandInfo: CommandInfo;
|
|
736
|
+
cwd: string;
|
|
737
|
+
data: unknown;
|
|
738
|
+
sessionName?: string;
|
|
739
|
+
}): Promise<FileArtifactMetadata[]> {
|
|
740
|
+
const candidates = extractPathStrings(options.data);
|
|
741
|
+
const artifacts = await Promise.all(candidates.map((path) => buildFileArtifactMetadata({ ...options, path })));
|
|
703
742
|
return artifacts.filter((artifact): artifact is FileArtifactMetadata => artifact !== undefined);
|
|
704
743
|
}
|
|
705
744
|
|
|
@@ -708,12 +747,15 @@ function buildManifestEntriesForFileArtifacts(artifacts: FileArtifactMetadata[],
|
|
|
708
747
|
absolutePath: artifact.absolutePath,
|
|
709
748
|
command: artifact.command,
|
|
710
749
|
createdAtMs: nowMs,
|
|
750
|
+
cwd: artifact.cwd,
|
|
711
751
|
exists: artifact.exists,
|
|
712
752
|
extension: artifact.extension,
|
|
713
753
|
kind: artifact.kind,
|
|
714
754
|
mediaType: artifact.mediaType,
|
|
715
755
|
path: artifact.path,
|
|
756
|
+
requestedPath: artifact.requestedPath,
|
|
716
757
|
retentionState: artifact.exists === false ? "missing" : "live",
|
|
758
|
+
session: artifact.session,
|
|
717
759
|
sizeBytes: artifact.sizeBytes,
|
|
718
760
|
storageScope: "explicit-path",
|
|
719
761
|
subcommand: artifact.subcommand,
|
|
@@ -761,17 +803,37 @@ function formatArtifactSummary(artifacts: FileArtifactMetadata[]): string | unde
|
|
|
761
803
|
}
|
|
762
804
|
|
|
763
805
|
function formatArtifactMetadataLines(artifacts: FileArtifactMetadata[]): string[] {
|
|
764
|
-
return artifacts.map((artifact) => {
|
|
806
|
+
return artifacts.map((artifact, index) => {
|
|
765
807
|
if (isRecordingStartArtifact(artifact)) {
|
|
766
|
-
return
|
|
808
|
+
return [
|
|
809
|
+
`${formatArtifactLabel(artifact)}: ${artifact.path}`,
|
|
810
|
+
`Artifact type: ${artifact.kind}`,
|
|
811
|
+
`Requested path: ${artifact.requestedPath ?? artifact.path}`,
|
|
812
|
+
`Absolute path: ${artifact.absolutePath}`,
|
|
813
|
+
`Exists: ${artifact.exists === true}`,
|
|
814
|
+
`Status: ${artifact.status ?? (artifact.exists === false ? "missing" : "saved")}`,
|
|
815
|
+
artifact.session ? `Session: ${artifact.session}` : undefined,
|
|
816
|
+
artifact.cwd ? `CWD: ${artifact.cwd}` : undefined,
|
|
817
|
+
`Machine data: details.artifacts[${index}]`,
|
|
818
|
+
].filter((item): item is string => item !== undefined).join("\n");
|
|
767
819
|
}
|
|
768
820
|
|
|
769
|
-
|
|
770
|
-
artifact.
|
|
771
|
-
|
|
821
|
+
return [
|
|
822
|
+
`${formatArtifactLabel(artifact)}: ${artifact.path}`,
|
|
823
|
+
`Artifact type: ${artifact.kind}`,
|
|
824
|
+
`Requested path: ${artifact.requestedPath ?? artifact.path}`,
|
|
825
|
+
`Absolute path: ${artifact.absolutePath}`,
|
|
826
|
+
`Exists: ${artifact.exists === true}`,
|
|
772
827
|
artifact.exists === false ? "not found on disk" : undefined,
|
|
773
|
-
|
|
774
|
-
|
|
828
|
+
typeof artifact.sizeBytes === "number" ? `Size: ${formatByteCount(artifact.sizeBytes)}` : undefined,
|
|
829
|
+
typeof artifact.sizeBytes === "number" ? `Size bytes: ${artifact.sizeBytes}` : undefined,
|
|
830
|
+
`Status: ${artifact.status ?? (artifact.exists === false ? "missing" : "saved")}`,
|
|
831
|
+
artifact.tempPath ? `Temp path: ${artifact.tempPath}` : undefined,
|
|
832
|
+
artifact.mediaType ? `Media type: ${artifact.mediaType}` : undefined,
|
|
833
|
+
artifact.session ? `Session: ${artifact.session}` : undefined,
|
|
834
|
+
artifact.cwd ? `CWD: ${artifact.cwd}` : undefined,
|
|
835
|
+
`Machine data: details.artifacts[${index}]`,
|
|
836
|
+
].filter((item): item is string => item !== undefined).join("\n");
|
|
775
837
|
});
|
|
776
838
|
}
|
|
777
839
|
|
|
@@ -1020,12 +1082,14 @@ function getBatchFailureDetails(steps: Array<{ details: BatchStepPresentationDet
|
|
|
1020
1082
|
|
|
1021
1083
|
async function buildBatchStepPresentation(options: {
|
|
1022
1084
|
artifactManifest?: SessionArtifactManifest;
|
|
1085
|
+
artifactRequest?: ArtifactRequestContext;
|
|
1023
1086
|
cwd: string;
|
|
1024
1087
|
index: number;
|
|
1025
1088
|
item: AgentBrowserBatchResult;
|
|
1026
1089
|
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
1090
|
+
sessionName?: string;
|
|
1027
1091
|
}): Promise<{ details: BatchStepPresentationDetails; presentation: ToolPresentation }> {
|
|
1028
|
-
const { artifactManifest, cwd, index, item, persistentArtifactStore } = options;
|
|
1092
|
+
const { artifactManifest, artifactRequest, cwd, index, item, persistentArtifactStore, sessionName } = options;
|
|
1029
1093
|
const command = isStringArray(item.command) ? item.command : undefined;
|
|
1030
1094
|
const commandText = formatBatchStepCommand(command, index);
|
|
1031
1095
|
|
|
@@ -1052,10 +1116,12 @@ async function buildBatchStepPresentation(options: {
|
|
|
1052
1116
|
|
|
1053
1117
|
const presentation = await buildToolPresentation({
|
|
1054
1118
|
artifactManifest,
|
|
1119
|
+
artifactRequest,
|
|
1055
1120
|
commandInfo: parseCommandInfo(command ?? []),
|
|
1056
1121
|
cwd,
|
|
1057
1122
|
envelope: { data: item.result, success: true },
|
|
1058
1123
|
persistentArtifactStore,
|
|
1124
|
+
sessionName,
|
|
1059
1125
|
});
|
|
1060
1126
|
const fullOutputPaths = getPresentationPaths({
|
|
1061
1127
|
primaryPath: presentation.fullOutputPath,
|
|
@@ -1090,24 +1156,28 @@ async function buildBatchStepPresentation(options: {
|
|
|
1090
1156
|
|
|
1091
1157
|
async function buildBatchPresentation(options: {
|
|
1092
1158
|
artifactManifest?: SessionArtifactManifest;
|
|
1159
|
+
artifactRequests?: Array<ArtifactRequestContext | undefined>;
|
|
1093
1160
|
cwd: string;
|
|
1094
1161
|
data: AgentBrowserBatchResult[];
|
|
1095
1162
|
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
1163
|
+
sessionName?: string;
|
|
1096
1164
|
summary: string;
|
|
1097
1165
|
}): Promise<ToolPresentation> {
|
|
1098
|
-
const { cwd, data, persistentArtifactStore, summary } = options;
|
|
1166
|
+
const { artifactRequests, cwd, data, persistentArtifactStore, sessionName, summary } = options;
|
|
1099
1167
|
const steps: Array<{ details: BatchStepPresentationDetails; presentation: ToolPresentation }> = [];
|
|
1100
1168
|
const protectedPersistentPaths: string[] = [];
|
|
1101
1169
|
let currentArtifactManifest = options.artifactManifest;
|
|
1102
1170
|
for (const [index, item] of data.entries()) {
|
|
1103
1171
|
const step = await buildBatchStepPresentation({
|
|
1104
1172
|
artifactManifest: currentArtifactManifest,
|
|
1173
|
+
artifactRequest: artifactRequests?.[index],
|
|
1105
1174
|
cwd,
|
|
1106
1175
|
index,
|
|
1107
1176
|
item,
|
|
1108
1177
|
persistentArtifactStore: persistentArtifactStore
|
|
1109
1178
|
? { ...persistentArtifactStore, protectedPaths: protectedPersistentPaths }
|
|
1110
1179
|
: undefined,
|
|
1180
|
+
sessionName,
|
|
1111
1181
|
});
|
|
1112
1182
|
steps.push(step);
|
|
1113
1183
|
currentArtifactManifest = step.presentation.artifactManifest ?? currentArtifactManifest;
|
|
@@ -1522,13 +1592,16 @@ async function compactLargePresentationOutput(options: {
|
|
|
1522
1592
|
|
|
1523
1593
|
export async function buildToolPresentation(options: {
|
|
1524
1594
|
artifactManifest?: SessionArtifactManifest;
|
|
1595
|
+
artifactRequest?: ArtifactRequestContext;
|
|
1596
|
+
batchArtifactRequests?: Array<ArtifactRequestContext | undefined>;
|
|
1525
1597
|
commandInfo: CommandInfo;
|
|
1526
1598
|
cwd: string;
|
|
1527
1599
|
envelope?: AgentBrowserEnvelope;
|
|
1528
1600
|
errorText?: string;
|
|
1529
1601
|
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
1602
|
+
sessionName?: string;
|
|
1530
1603
|
}): Promise<ToolPresentation> {
|
|
1531
|
-
const { artifactManifest, commandInfo, cwd, envelope, errorText, persistentArtifactStore } = options;
|
|
1604
|
+
const { artifactManifest, artifactRequest, commandInfo, cwd, envelope, errorText, persistentArtifactStore, sessionName } = options;
|
|
1532
1605
|
if (errorText) {
|
|
1533
1606
|
const hintedErrorText = appendSelectorRecoveryHint(redactModelFacingText(errorText));
|
|
1534
1607
|
return {
|
|
@@ -1537,14 +1610,14 @@ export async function buildToolPresentation(options: {
|
|
|
1537
1610
|
};
|
|
1538
1611
|
}
|
|
1539
1612
|
|
|
1540
|
-
const data = envelope?.data;
|
|
1541
|
-
const artifacts = await extractFileArtifacts(commandInfo, cwd, data);
|
|
1613
|
+
const data = enrichStreamStatusData(commandInfo, envelope?.data);
|
|
1614
|
+
const artifacts = await extractFileArtifacts({ artifactRequest, commandInfo, cwd, data, sessionName });
|
|
1542
1615
|
const artifactSummary = formatArtifactSummary(artifacts);
|
|
1543
1616
|
const summary = artifactSummary ?? formatSummary(commandInfo, data);
|
|
1544
1617
|
const artifactText = artifacts.length > 0 ? formatArtifactMetadataLines(artifacts).join("\n") : undefined;
|
|
1545
1618
|
const presentation =
|
|
1546
1619
|
commandInfo.command === "batch" && Array.isArray(data)
|
|
1547
|
-
? await buildBatchPresentation({ artifactManifest, cwd, data: data as AgentBrowserBatchResult[], persistentArtifactStore, summary })
|
|
1620
|
+
? await buildBatchPresentation({ artifactManifest, artifactRequests: options.batchArtifactRequests, cwd, data: data as AgentBrowserBatchResult[], persistentArtifactStore, sessionName, summary })
|
|
1548
1621
|
: commandInfo.command === "snapshot" && isRecord(data)
|
|
1549
1622
|
? await buildSnapshotPresentation(data, persistentArtifactStore, artifactManifest)
|
|
1550
1623
|
: {
|
|
@@ -1564,7 +1637,7 @@ export async function buildToolPresentation(options: {
|
|
|
1564
1637
|
}
|
|
1565
1638
|
}
|
|
1566
1639
|
|
|
1567
|
-
const imagePath = extractImagePath(commandInfo, cwd, data);
|
|
1640
|
+
const imagePath = artifactRequest?.absolutePath ?? extractImagePath(commandInfo, cwd, data);
|
|
1568
1641
|
const presentationWithImage = imagePath ? await attachInlineImage(presentation, imagePath) : presentation;
|
|
1569
1642
|
const compactedPresentation = await compactLargePresentationOutput({
|
|
1570
1643
|
artifactManifest,
|
|
@@ -21,16 +21,24 @@ export interface AgentBrowserBatchResult {
|
|
|
21
21
|
|
|
22
22
|
export type FileArtifactKind = "download" | "file" | "har" | "image" | "pdf" | "profile" | "trace" | "video";
|
|
23
23
|
|
|
24
|
+
export type FileArtifactStatus = "missing" | "repaired-from-temp" | "saved" | "upstream-temp-only";
|
|
25
|
+
|
|
24
26
|
export interface FileArtifactMetadata {
|
|
25
27
|
absolutePath: string;
|
|
28
|
+
artifactType?: FileArtifactKind;
|
|
26
29
|
command?: string;
|
|
30
|
+
cwd?: string;
|
|
27
31
|
exists?: boolean;
|
|
28
32
|
extension?: string;
|
|
29
33
|
kind: FileArtifactKind;
|
|
30
34
|
mediaType?: string;
|
|
31
35
|
path: string;
|
|
36
|
+
requestedPath?: string;
|
|
37
|
+
session?: string;
|
|
32
38
|
sizeBytes?: number;
|
|
39
|
+
status?: FileArtifactStatus;
|
|
33
40
|
subcommand?: string;
|
|
41
|
+
tempPath?: string;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
export interface SavedFilePresentationDetails {
|
|
@@ -49,13 +57,16 @@ export interface SessionArtifactManifestEntry {
|
|
|
49
57
|
absolutePath?: string;
|
|
50
58
|
command?: string;
|
|
51
59
|
createdAtMs: number;
|
|
60
|
+
cwd?: string;
|
|
52
61
|
evictedAtMs?: number;
|
|
53
62
|
exists?: boolean;
|
|
54
63
|
extension?: string;
|
|
55
64
|
kind: FileArtifactKind | "spill";
|
|
56
65
|
mediaType?: string;
|
|
57
66
|
path: string;
|
|
67
|
+
requestedPath?: string;
|
|
58
68
|
retentionState: ArtifactRetentionState;
|
|
69
|
+
session?: string;
|
|
59
70
|
sizeBytes?: number;
|
|
60
71
|
storageScope: ArtifactStorageScope;
|
|
61
72
|
subcommand?: string;
|
package/package.json
CHANGED