pi-agent-browser-native 0.2.77 → 0.5.0
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 +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +22 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -1,13 +1,8 @@
|
|
|
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
1
|
import { readFile, stat } from "node:fs/promises";
|
|
7
2
|
import { extname, resolve } from "node:path";
|
|
3
|
+
import { getAgentBrowserSessionIdentityKey } from "../../argv-grammar.js";
|
|
8
4
|
import { isRecord, parsePositiveInteger } from "../../parsing.js";
|
|
9
|
-
import { formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "../artifact-manifest.js";
|
|
10
|
-
import { isPendingRecordingArtifact, isPendingRecordingCommand } from "../artifact-state.js";
|
|
5
|
+
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingArtifact, isPendingRecordingCommand, mergeSessionArtifactManifest, } from "../artifact-manifest.js";
|
|
11
6
|
import { classifyAgentBrowserSuccessCategory } from "../categories.js";
|
|
12
7
|
const IMAGE_EXTENSION_TO_MIME_TYPE = {
|
|
13
8
|
".gif": "image/gif",
|
|
@@ -18,6 +13,7 @@ const IMAGE_EXTENSION_TO_MIME_TYPE = {
|
|
|
18
13
|
};
|
|
19
14
|
const INLINE_IMAGE_MAX_BYTES_ENV = "PI_AGENT_BROWSER_INLINE_IMAGE_MAX_BYTES";
|
|
20
15
|
const DEFAULT_INLINE_IMAGE_MAX_BYTES = 5 * 1_024 * 1_024;
|
|
16
|
+
const ARTIFACT_MTIME_TOLERANCE_MS = 2_000;
|
|
21
17
|
function getImageMimeType(filePath) {
|
|
22
18
|
const extension = extname(filePath).toLowerCase();
|
|
23
19
|
return IMAGE_EXTENSION_TO_MIME_TYPE[extension];
|
|
@@ -42,14 +38,11 @@ function appendPresentationNotice(presentation, message) {
|
|
|
42
38
|
function shouldAppendArtifactRetentionNotice(entries) {
|
|
43
39
|
return entries.some((entry) => entry.retentionState === "evicted" || entry.storageScope !== "explicit-path");
|
|
44
40
|
}
|
|
45
|
-
function getManifestEntryKey(entry) {
|
|
46
|
-
return entry.storageScope === "explicit-path" && entry.absolutePath ? `${entry.storageScope}:${entry.absolutePath}` : `${entry.storageScope}:${entry.path}`;
|
|
47
|
-
}
|
|
48
41
|
export function manifestHasNewNoticeWorthyEntries(base, current) {
|
|
49
42
|
if (!current)
|
|
50
43
|
return false;
|
|
51
|
-
const baseKeys = new Set((base?.entries ?? []).map(
|
|
52
|
-
return current.entries.some((entry) => !baseKeys.has(
|
|
44
|
+
const baseKeys = new Set((base?.entries ?? []).map(getSessionArtifactManifestEntryKey));
|
|
45
|
+
return current.entries.some((entry) => !baseKeys.has(getSessionArtifactManifestEntryKey(entry)) && (entry.retentionState === "evicted" || entry.storageScope !== "explicit-path"));
|
|
53
46
|
}
|
|
54
47
|
export function applyArtifactManifest(presentation, baseManifest, entries) {
|
|
55
48
|
if (entries.length === 0)
|
|
@@ -92,6 +85,9 @@ const ARTIFACT_EXTENSION_TO_MEDIA_TYPE = {
|
|
|
92
85
|
".zip": "application/zip",
|
|
93
86
|
...IMAGE_EXTENSION_TO_MIME_TYPE,
|
|
94
87
|
};
|
|
88
|
+
function isDownloadWaitSubcommand(subcommand) {
|
|
89
|
+
return subcommand === "--download" || subcommand === "-d";
|
|
90
|
+
}
|
|
95
91
|
function getArtifactKind(commandInfo) {
|
|
96
92
|
if (commandInfo.command === "screenshot")
|
|
97
93
|
return "image";
|
|
@@ -101,7 +97,7 @@ function getArtifactKind(commandInfo) {
|
|
|
101
97
|
return "pdf";
|
|
102
98
|
if (commandInfo.command === "download")
|
|
103
99
|
return "download";
|
|
104
|
-
if (commandInfo.command === "wait" && commandInfo.subcommand
|
|
100
|
+
if (commandInfo.command === "wait" && isDownloadWaitSubcommand(commandInfo.subcommand))
|
|
105
101
|
return "download";
|
|
106
102
|
if (commandInfo.command === "state" && commandInfo.subcommand === "save")
|
|
107
103
|
return "file";
|
|
@@ -141,6 +137,11 @@ function extractPathStrings(data) {
|
|
|
141
137
|
}
|
|
142
138
|
return [...new Set(paths)];
|
|
143
139
|
}
|
|
140
|
+
function artifactMtimeIsOutsideCommandWindow(updatedAtMs, minUpdatedAtMs, maxUpdatedAtMs) {
|
|
141
|
+
return minUpdatedAtMs !== undefined
|
|
142
|
+
&& (updatedAtMs < minUpdatedAtMs - ARTIFACT_MTIME_TOLERANCE_MS
|
|
143
|
+
|| (maxUpdatedAtMs !== undefined && updatedAtMs > maxUpdatedAtMs + ARTIFACT_MTIME_TOLERANCE_MS));
|
|
144
|
+
}
|
|
144
145
|
async function buildFileArtifactMetadata(options) {
|
|
145
146
|
const kind = getArtifactKind(options.commandInfo);
|
|
146
147
|
if (!kind) {
|
|
@@ -152,11 +153,16 @@ async function buildFileArtifactMetadata(options) {
|
|
|
152
153
|
const pendingRecording = isPendingRecordingCommand(options.commandInfo.command, options.commandInfo.subcommand, kind);
|
|
153
154
|
let exists;
|
|
154
155
|
let sizeBytes;
|
|
156
|
+
let stale = false;
|
|
157
|
+
let updatedAtMs;
|
|
155
158
|
if (!pendingRecording) {
|
|
156
159
|
try {
|
|
157
160
|
const fileStats = await stat(absolutePath);
|
|
158
161
|
exists = true;
|
|
159
162
|
sizeBytes = fileStats.size;
|
|
163
|
+
updatedAtMs = fileStats.mtimeMs;
|
|
164
|
+
const commandCreatesArtifact = !(options.commandInfo.command === "wait" && isDownloadWaitSubcommand(options.commandInfo.subcommand));
|
|
165
|
+
stale = commandCreatesArtifact && artifactMtimeIsOutsideCommandWindow(updatedAtMs, options.artifactMinUpdatedAtMs, options.artifactMaxUpdatedAtMs);
|
|
160
166
|
}
|
|
161
167
|
catch {
|
|
162
168
|
exists = false;
|
|
@@ -171,31 +177,33 @@ async function buildFileArtifactMetadata(options) {
|
|
|
171
177
|
extension,
|
|
172
178
|
kind,
|
|
173
179
|
mediaType: extension ? ARTIFACT_EXTENSION_TO_MEDIA_TYPE[extension] : undefined,
|
|
180
|
+
namespace: options.namespace,
|
|
174
181
|
path: displayPath,
|
|
175
182
|
recordingState: pendingRecording ? "openRecording" : undefined,
|
|
176
183
|
requestedPath: options.artifactRequest?.path,
|
|
177
184
|
session: options.sessionName,
|
|
178
185
|
sizeBytes,
|
|
179
|
-
status:
|
|
186
|
+
status: pendingRecording ? "pending" : exists === false ? "missing" : stale ? "stale" : options.artifactRequest?.status ?? "saved",
|
|
180
187
|
subcommand: options.commandInfo.subcommand,
|
|
181
188
|
tempPath: options.artifactRequest?.tempPath,
|
|
189
|
+
updatedAtMs,
|
|
182
190
|
willExistOnStop: pendingRecording ? true : undefined,
|
|
183
191
|
};
|
|
184
192
|
}
|
|
185
193
|
async function buildPreviousRestartRecordingArtifact(options) {
|
|
186
194
|
if (options.commandInfo.command !== "record" || options.commandInfo.subcommand !== "restart")
|
|
187
195
|
return undefined;
|
|
196
|
+
const sessionKey = options.sessionName ? getAgentBrowserSessionIdentityKey(options.sessionName, options.namespace) : undefined;
|
|
188
197
|
const previousRecording = options.artifactManifest?.entries.find((entry) => (entry.command === "record" &&
|
|
189
198
|
(entry.subcommand === "start" || entry.subcommand === "restart") &&
|
|
190
199
|
entry.kind === "video" &&
|
|
191
|
-
(!
|
|
192
|
-
!options.currentPaths.has(entry.path) &&
|
|
193
|
-
(!entry.absolutePath || !options.currentPaths.has(entry.absolutePath))));
|
|
200
|
+
(!sessionKey || (entry.session && getAgentBrowserSessionIdentityKey(entry.session, entry.namespace) === sessionKey))));
|
|
194
201
|
if (!previousRecording)
|
|
195
202
|
return undefined;
|
|
196
203
|
const absolutePath = previousRecording.absolutePath ?? resolve(options.cwd, previousRecording.path);
|
|
197
204
|
try {
|
|
198
205
|
const fileStats = await stat(absolutePath);
|
|
206
|
+
const stale = artifactMtimeIsOutsideCommandWindow(fileStats.mtimeMs, options.artifactMinUpdatedAtMs, options.artifactMaxUpdatedAtMs);
|
|
199
207
|
return {
|
|
200
208
|
absolutePath,
|
|
201
209
|
artifactType: "video",
|
|
@@ -205,23 +213,39 @@ async function buildPreviousRestartRecordingArtifact(options) {
|
|
|
205
213
|
extension: previousRecording.extension ?? (extname(absolutePath).toLowerCase() || undefined),
|
|
206
214
|
kind: "video",
|
|
207
215
|
mediaType: previousRecording.mediaType,
|
|
216
|
+
namespace: previousRecording.namespace ?? options.namespace,
|
|
208
217
|
path: previousRecording.path,
|
|
209
218
|
requestedPath: previousRecording.requestedPath,
|
|
210
219
|
session: previousRecording.session ?? options.sessionName,
|
|
211
220
|
sizeBytes: fileStats.size,
|
|
212
|
-
status: "saved",
|
|
221
|
+
status: stale ? "stale" : "saved",
|
|
213
222
|
subcommand: "restart-previous",
|
|
223
|
+
updatedAtMs: fileStats.mtimeMs,
|
|
214
224
|
};
|
|
215
225
|
}
|
|
216
226
|
catch {
|
|
217
|
-
return
|
|
227
|
+
return {
|
|
228
|
+
absolutePath,
|
|
229
|
+
artifactType: "video",
|
|
230
|
+
command: "record",
|
|
231
|
+
cwd: previousRecording.cwd ?? options.cwd,
|
|
232
|
+
exists: false,
|
|
233
|
+
extension: previousRecording.extension ?? (extname(absolutePath).toLowerCase() || undefined),
|
|
234
|
+
kind: "video",
|
|
235
|
+
mediaType: previousRecording.mediaType,
|
|
236
|
+
namespace: previousRecording.namespace ?? options.namespace,
|
|
237
|
+
path: previousRecording.path,
|
|
238
|
+
requestedPath: previousRecording.requestedPath,
|
|
239
|
+
session: previousRecording.session ?? options.sessionName,
|
|
240
|
+
status: "missing",
|
|
241
|
+
subcommand: "restart-previous",
|
|
242
|
+
};
|
|
218
243
|
}
|
|
219
244
|
}
|
|
220
245
|
export async function extractFileArtifacts(options) {
|
|
221
246
|
const candidates = extractPathStrings(options.data);
|
|
222
247
|
const currentArtifacts = (await Promise.all(candidates.map((path) => buildFileArtifactMetadata({ ...options, path })))).filter((artifact) => artifact !== undefined);
|
|
223
|
-
const
|
|
224
|
-
const previousRestartRecordingArtifact = await buildPreviousRestartRecordingArtifact({ artifactManifest: options.artifactManifest, commandInfo: options.commandInfo, currentPaths, cwd: options.cwd, sessionName: options.sessionName });
|
|
248
|
+
const previousRestartRecordingArtifact = await buildPreviousRestartRecordingArtifact({ artifactManifest: options.artifactManifest, artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs, commandInfo: options.commandInfo, cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
|
|
225
249
|
return previousRestartRecordingArtifact ? [previousRestartRecordingArtifact, ...currentArtifacts] : currentArtifacts;
|
|
226
250
|
}
|
|
227
251
|
export function buildManifestEntriesForFileArtifacts(artifacts, nowMs = Date.now()) {
|
|
@@ -234,9 +258,10 @@ export function buildManifestEntriesForFileArtifacts(artifacts, nowMs = Date.now
|
|
|
234
258
|
extension: artifact.extension,
|
|
235
259
|
kind: artifact.kind,
|
|
236
260
|
mediaType: artifact.mediaType,
|
|
261
|
+
namespace: artifact.namespace,
|
|
237
262
|
path: artifact.path,
|
|
238
263
|
requestedPath: artifact.requestedPath,
|
|
239
|
-
retentionState: artifact.exists === false ? "missing" : "live",
|
|
264
|
+
retentionState: artifact.exists === false || artifact.status === "stale" ? "missing" : "live",
|
|
240
265
|
session: artifact.session,
|
|
241
266
|
sizeBytes: artifact.sizeBytes,
|
|
242
267
|
storageScope: "explicit-path",
|
|
@@ -244,6 +269,9 @@ export function buildManifestEntriesForFileArtifacts(artifacts, nowMs = Date.now
|
|
|
244
269
|
}));
|
|
245
270
|
}
|
|
246
271
|
export function isManifestFileArtifact(artifact) {
|
|
272
|
+
if (artifact.status === "stale") {
|
|
273
|
+
return artifact.kind === "video" && artifact.command === "record" && artifact.subcommand === "restart-previous";
|
|
274
|
+
}
|
|
247
275
|
return artifact.kind === "video" && artifact.command === "record" ? true : !isPendingRecordingArtifact(artifact);
|
|
248
276
|
}
|
|
249
277
|
function getArtifactVerificationEntry(artifact) {
|
|
@@ -265,20 +293,24 @@ function getArtifactVerificationEntry(artifact) {
|
|
|
265
293
|
willExistOnStop: artifact.willExistOnStop ?? true,
|
|
266
294
|
};
|
|
267
295
|
}
|
|
268
|
-
const state = artifact.
|
|
269
|
-
? "
|
|
270
|
-
: artifact.exists ===
|
|
271
|
-
? "
|
|
272
|
-
:
|
|
296
|
+
const state = artifact.status === "stale"
|
|
297
|
+
? "unverified"
|
|
298
|
+
: artifact.exists === true
|
|
299
|
+
? "verified"
|
|
300
|
+
: artifact.exists === false
|
|
301
|
+
? "missing"
|
|
302
|
+
: "unverified";
|
|
273
303
|
return {
|
|
274
304
|
absolutePath: artifact.absolutePath,
|
|
275
305
|
exists: artifact.exists,
|
|
276
306
|
kind: artifact.kind,
|
|
277
|
-
limitation:
|
|
278
|
-
? "The
|
|
279
|
-
: state === "
|
|
280
|
-
? "The wrapper
|
|
281
|
-
:
|
|
307
|
+
limitation: artifact.status === "stale"
|
|
308
|
+
? "The reported path's modification time fell outside this command's bounded artifact window. Treat the artifact as stale until regenerated."
|
|
309
|
+
: state === "missing"
|
|
310
|
+
? "The wrapper did not find the reported artifact at absolutePath. Treat the path as unverified until recovered or regenerated."
|
|
311
|
+
: state === "unverified"
|
|
312
|
+
? "The wrapper could not prove local filesystem existence for this artifact."
|
|
313
|
+
: undefined,
|
|
282
314
|
mediaType: artifact.mediaType,
|
|
283
315
|
path: artifact.path,
|
|
284
316
|
requestedPath: artifact.requestedPath,
|
|
@@ -287,6 +319,7 @@ function getArtifactVerificationEntry(artifact) {
|
|
|
287
319
|
state,
|
|
288
320
|
status: artifact.status,
|
|
289
321
|
storageScope: "explicit-path",
|
|
322
|
+
updatedAtMs: artifact.updatedAtMs,
|
|
290
323
|
};
|
|
291
324
|
}
|
|
292
325
|
function getManifestVerificationEntry(entry) {
|
|
@@ -341,17 +374,19 @@ export function buildArtifactVerificationSummary(artifacts, manifest, manifestPa
|
|
|
341
374
|
};
|
|
342
375
|
}
|
|
343
376
|
export function hasMissingFileArtifact(artifacts) {
|
|
344
|
-
return (artifacts ?? []).some((artifact) => !isPendingRecordingArtifact(artifact) && artifact.exists === false);
|
|
377
|
+
return (artifacts ?? []).some((artifact) => !isPendingRecordingArtifact(artifact) && (artifact.exists === false || artifact.status === "stale"));
|
|
345
378
|
}
|
|
346
379
|
export function formatMissingArtifactFailureText(artifacts) {
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
380
|
+
const failedArtifacts = (artifacts ?? []).filter((artifact) => !isPendingRecordingArtifact(artifact) && (artifact.exists === false || artifact.status === "stale"));
|
|
381
|
+
if (failedArtifacts.length === 0)
|
|
349
382
|
return undefined;
|
|
350
|
-
if (
|
|
351
|
-
const artifact =
|
|
352
|
-
return
|
|
383
|
+
if (failedArtifacts.length === 1) {
|
|
384
|
+
const artifact = failedArtifacts[0];
|
|
385
|
+
return artifact.status === "stale"
|
|
386
|
+
? `Artifact verification failed: requested ${artifact.kind} path ${artifact.absolutePath} has a modification time outside the current command window.`
|
|
387
|
+
: `Artifact verification failed: requested ${artifact.kind} was not found at ${artifact.absolutePath}.`;
|
|
353
388
|
}
|
|
354
|
-
return `Artifact verification failed: ${
|
|
389
|
+
return `Artifact verification failed: ${failedArtifacts.length} requested artifacts were missing or stale.`;
|
|
355
390
|
}
|
|
356
391
|
export function classifyPresentationSuccessCategory(options) {
|
|
357
392
|
if ((options.artifactVerification?.missingCount ?? 0) > 0 || (options.artifactVerification?.unverifiedCount ?? 0) > 0) {
|
|
@@ -363,9 +398,9 @@ function formatArtifactLabel(artifact) {
|
|
|
363
398
|
switch (artifact.kind) {
|
|
364
399
|
case "download":
|
|
365
400
|
if (artifact.exists !== true) {
|
|
366
|
-
return artifact.command === "wait" && artifact.subcommand
|
|
401
|
+
return artifact.command === "wait" && isDownloadWaitSubcommand(artifact.subcommand) ? "Download event reported; file not verified" : "Download reported; file not verified";
|
|
367
402
|
}
|
|
368
|
-
return artifact.command === "wait" && artifact.subcommand
|
|
403
|
+
return artifact.command === "wait" && isDownloadWaitSubcommand(artifact.subcommand) ? "Download saved and verified" : "Downloaded file verified";
|
|
369
404
|
case "file":
|
|
370
405
|
return artifact.command === "state" ? "State file" : "Saved file";
|
|
371
406
|
case "har":
|
|
@@ -381,11 +416,16 @@ function formatArtifactLabel(artifact) {
|
|
|
381
416
|
case "trace":
|
|
382
417
|
return "Saved trace";
|
|
383
418
|
case "video":
|
|
384
|
-
if (artifact.command === "record" && artifact.subcommand === "restart-previous")
|
|
419
|
+
if (artifact.command === "record" && artifact.subcommand === "restart-previous") {
|
|
420
|
+
if (artifact.status === "stale")
|
|
421
|
+
return "Previous recording stale";
|
|
422
|
+
if (artifact.exists === false)
|
|
423
|
+
return "Previous recording missing";
|
|
385
424
|
return "Previous recording saved";
|
|
425
|
+
}
|
|
386
426
|
if (!isPendingRecordingArtifact(artifact))
|
|
387
427
|
return "Saved recording";
|
|
388
|
-
return artifact.subcommand === "restart" ? "Recording restarted; output will be written on stop" : "Recording started; output will be written on stop";
|
|
428
|
+
return artifact.subcommand === "restart" ? "Recording restarted; output will be written on stop" : "Recording started in a fresh active page; output will be written on stop";
|
|
389
429
|
}
|
|
390
430
|
}
|
|
391
431
|
export function formatArtifactSummary(artifacts) {
|
|
@@ -415,6 +455,7 @@ export function formatArtifactMetadataLines(artifacts) {
|
|
|
415
455
|
`Status: ${artifact.status ?? "pending"}`,
|
|
416
456
|
`Recording state: ${artifact.recordingState ?? "openRecording"}`,
|
|
417
457
|
`Will exist on stop: ${artifact.willExistOnStop !== false}`,
|
|
458
|
+
artifact.subcommand === "start" ? "Page state: record start uses a fresh active page for video capture; prior in-page DOM and JavaScript state does not carry over. Take a fresh snapshot before continuing." : undefined,
|
|
418
459
|
artifact.session ? `Session: ${artifact.session}` : undefined,
|
|
419
460
|
artifact.cwd ? `CWD: ${artifact.cwd}` : undefined,
|
|
420
461
|
`Machine data: details.artifacts[${index}]`,
|
|
@@ -439,7 +480,7 @@ export function formatArtifactMetadataLines(artifacts) {
|
|
|
439
480
|
});
|
|
440
481
|
}
|
|
441
482
|
function isDownloadWaitCommand(commandInfo) {
|
|
442
|
-
return commandInfo.command === "wait" && commandInfo.subcommand
|
|
483
|
+
return commandInfo.command === "wait" && isDownloadWaitSubcommand(commandInfo.subcommand);
|
|
443
484
|
}
|
|
444
485
|
function extractSavedFilePath(data) {
|
|
445
486
|
return typeof data.path === "string" && data.path.trim().length > 0 ? data.path : undefined;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { isCloseCommand } from "../../command-taxonomy.js";
|
|
1
2
|
import { isRecord } from "../../parsing.js";
|
|
2
|
-
import {
|
|
3
|
+
import { getAgentBrowserSessionIdentityKey } from "../../argv-grammar.js";
|
|
4
|
+
import { extractUpstreamCommandTokens, parseCommandInfo, redactInvocationArgs, redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
3
5
|
import { buildAgentBrowserNextActions } from "../action-recommendations.js";
|
|
4
|
-
import { formatSessionArtifactRetentionSummary } from "../artifact-manifest.js";
|
|
6
|
+
import { formatSessionArtifactRetentionSummary, isPendingRecordingArtifact } from "../artifact-manifest.js";
|
|
5
7
|
import { classifyAgentBrowserFailureCategory } from "../categories.js";
|
|
6
8
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
7
9
|
import { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "../network-routes.js";
|
|
8
|
-
import { applyNamespaceToNextActions, withOptionalSessionArgs } from "../next-actions.js";
|
|
10
|
+
import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, withOptionalSessionArgs } from "../next-actions.js";
|
|
9
11
|
import { stringifyModelFacing } from "./common.js";
|
|
10
12
|
import { buildArtifactVerificationSummary, classifyPresentationSuccessCategory, manifestHasNewNoticeWorthyEntries } from "./artifacts.js";
|
|
11
13
|
import { formatBatchStepCommand, getPresentationImages, getPresentationPaths, getPresentationText, isStringArray } from "./content.js";
|
|
@@ -62,7 +64,7 @@ function hasModelFacingArgRedaction(args) {
|
|
|
62
64
|
function getStatefulCommandSensitiveValues(command) {
|
|
63
65
|
if (!command)
|
|
64
66
|
return [];
|
|
65
|
-
const tokens =
|
|
67
|
+
const tokens = extractUpstreamCommandTokens(command);
|
|
66
68
|
const values = [];
|
|
67
69
|
if (tokens[0] === "cookies" && tokens[1] === "set" && tokens[3])
|
|
68
70
|
values.push(tokens[3]);
|
|
@@ -181,10 +183,11 @@ function formatBatchStepsText(steps) {
|
|
|
181
183
|
return lines.join("\n\n");
|
|
182
184
|
}
|
|
183
185
|
async function buildBatchStepPresentation(options) {
|
|
184
|
-
const { artifactManifest, artifactRequest, buildNestedToolPresentation, cwd, index, item, namespace, networkRoutes, persistentArtifactStore, sessionName } = options;
|
|
186
|
+
const { artifactManifest, artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs, artifactRequest, buildNestedToolPresentation, cwd, index, item, namespace, networkRoutes, persistentArtifactStore, sessionName } = options;
|
|
185
187
|
const command = isStringArray(item.command) ? item.command : undefined;
|
|
186
188
|
const redactedCommand = command ? redactInvocationArgs(command) : undefined;
|
|
187
189
|
const commandText = formatBatchStepCommand(hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command, index);
|
|
190
|
+
const lifecycle = extractBatchStepLifecycle(item.result);
|
|
188
191
|
if (item.success === false) {
|
|
189
192
|
const redactedErrorData = command?.[0] === "clipboard"
|
|
190
193
|
? redactSensitiveValue(redactClipboardPermissionErrorValue({ command: "clipboard", subcommand: command[1] }, item.error, getClipboardWritePayloadCandidates(command)))
|
|
@@ -196,13 +199,14 @@ async function buildBatchStepPresentation(options) {
|
|
|
196
199
|
errorText,
|
|
197
200
|
});
|
|
198
201
|
const confirmationRequired = detectConfirmationRequired(item.error);
|
|
199
|
-
const nextActions = applyNamespaceToNextActions(mergePresentationNextActions(buildAgentBrowserNextActions({
|
|
202
|
+
const nextActions = applyNamespaceToNextActions(mergePresentationNextActions(isWaitTextAssertionCommand(command) ? [buildWaitTextAssertionFailureNextAction(sessionName)] : undefined, buildAgentBrowserNextActions({
|
|
200
203
|
args: command,
|
|
201
204
|
command: command?.[0],
|
|
202
205
|
confirmationId: confirmationRequired?.id,
|
|
203
206
|
failureCategory,
|
|
204
207
|
resultCategory: "failure",
|
|
205
|
-
|
|
208
|
+
sessionName,
|
|
209
|
+
})), namespace);
|
|
206
210
|
const presentation = {
|
|
207
211
|
content: [{ type: "text", text: errorText }],
|
|
208
212
|
failureCategory,
|
|
@@ -219,6 +223,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
219
223
|
data: redactedErrorData,
|
|
220
224
|
failureCategory,
|
|
221
225
|
index,
|
|
226
|
+
lifecycle,
|
|
222
227
|
nextActions,
|
|
223
228
|
resultCategory: "failure",
|
|
224
229
|
success: false,
|
|
@@ -235,12 +240,15 @@ async function buildBatchStepPresentation(options) {
|
|
|
235
240
|
: undefined;
|
|
236
241
|
const presentation = await buildNestedToolPresentation({
|
|
237
242
|
artifactManifest,
|
|
243
|
+
artifactMaxUpdatedAtMs,
|
|
244
|
+
artifactMinUpdatedAtMs,
|
|
238
245
|
artifactRequest,
|
|
239
246
|
commandInfo: commandInfoWithTokens,
|
|
240
247
|
cwd,
|
|
241
248
|
args: command,
|
|
242
249
|
envelope: { data: item.result, success: true },
|
|
243
250
|
networkRouteDiagnostics,
|
|
251
|
+
namespace,
|
|
244
252
|
persistentArtifactStore,
|
|
245
253
|
sessionName,
|
|
246
254
|
});
|
|
@@ -261,6 +269,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
261
269
|
failureCategory: presentation.failureCategory,
|
|
262
270
|
resultCategory: stepSucceeded ? "success" : "failure",
|
|
263
271
|
savedFilePath: presentation.savedFilePath,
|
|
272
|
+
sessionName,
|
|
264
273
|
successCategory: presentation.successCategory,
|
|
265
274
|
}), namespace);
|
|
266
275
|
const pageChangeSummary = buildPageChangeSummary({
|
|
@@ -284,6 +293,7 @@ async function buildBatchStepPresentation(options) {
|
|
|
284
293
|
imagePath: imagePaths[0],
|
|
285
294
|
imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
|
|
286
295
|
index,
|
|
296
|
+
lifecycle,
|
|
287
297
|
networkRouteDiagnostics: presentation.networkRouteDiagnostics,
|
|
288
298
|
nextActions,
|
|
289
299
|
pageChangeSummary,
|
|
@@ -298,6 +308,48 @@ async function buildBatchStepPresentation(options) {
|
|
|
298
308
|
presentation,
|
|
299
309
|
};
|
|
300
310
|
}
|
|
311
|
+
function extractBatchStepLifecycle(result) {
|
|
312
|
+
if (!isRecord(result) || !isRecord(result.lifecycle) || !isRecord(result.lifecycle.effectiveLaunch))
|
|
313
|
+
return undefined;
|
|
314
|
+
const browserLaunched = result.lifecycle.effectiveLaunch.browserLaunched;
|
|
315
|
+
return typeof browserLaunched === "boolean" ? { effectiveLaunch: { browserLaunched } } : undefined;
|
|
316
|
+
}
|
|
317
|
+
function abandonedRecordingArtifact(artifact) {
|
|
318
|
+
const { recordingState: _recordingState, willExistOnStop: _willExistOnStop, ...terminal } = artifact;
|
|
319
|
+
return { ...terminal, exists: false, status: "missing", subcommand: "close-abandoned" };
|
|
320
|
+
}
|
|
321
|
+
function coalesceTerminalBatchRecordingArtifacts(steps, sessionName, namespace) {
|
|
322
|
+
const artifacts = [];
|
|
323
|
+
const pendingIndexesBySession = new Map();
|
|
324
|
+
const removedPendingIndexes = new Set();
|
|
325
|
+
for (const step of steps) {
|
|
326
|
+
for (const artifact of step.presentation.artifacts ?? []) {
|
|
327
|
+
const index = artifacts.push(artifact) - 1;
|
|
328
|
+
if (artifact.command !== "record" || artifact.kind !== "video")
|
|
329
|
+
continue;
|
|
330
|
+
const session = artifact.session ? getAgentBrowserSessionIdentityKey(artifact.session, artifact.namespace) : "";
|
|
331
|
+
if (isPendingRecordingArtifact(artifact)) {
|
|
332
|
+
const pendingIndexes = pendingIndexesBySession.get(session) ?? [];
|
|
333
|
+
pendingIndexes.push(index);
|
|
334
|
+
pendingIndexesBySession.set(session, pendingIndexes);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const pendingIndex = pendingIndexesBySession.get(session)?.pop();
|
|
338
|
+
if (pendingIndex !== undefined)
|
|
339
|
+
removedPendingIndexes.add(pendingIndex);
|
|
340
|
+
}
|
|
341
|
+
const command = step.details.command;
|
|
342
|
+
if (step.details.success !== true || !command || !isCloseCommand(extractUpstreamCommandTokens(command)[0]))
|
|
343
|
+
continue;
|
|
344
|
+
const session = sessionName ? getAgentBrowserSessionIdentityKey(sessionName, namespace) : "";
|
|
345
|
+
for (const pendingIndex of pendingIndexesBySession.get(session) ?? []) {
|
|
346
|
+
const pending = artifacts[pendingIndex];
|
|
347
|
+
if (pending && !removedPendingIndexes.has(pendingIndex))
|
|
348
|
+
artifacts[pendingIndex] = abandonedRecordingArtifact(pending);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return artifacts.filter((_, index) => !removedPendingIndexes.has(index));
|
|
352
|
+
}
|
|
301
353
|
export async function buildBatchPresentation(options) {
|
|
302
354
|
const { artifactRequests, buildNestedToolPresentation, cwd, data, namespace, networkRoutes, persistentArtifactStore, sessionName, summary } = options;
|
|
303
355
|
const steps = [];
|
|
@@ -307,6 +359,8 @@ export async function buildBatchPresentation(options) {
|
|
|
307
359
|
for (const [index, item] of data.entries()) {
|
|
308
360
|
const step = await buildBatchStepPresentation({
|
|
309
361
|
artifactManifest: currentArtifactManifest,
|
|
362
|
+
artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs,
|
|
363
|
+
artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs,
|
|
310
364
|
artifactRequest: artifactRequests?.[index],
|
|
311
365
|
buildNestedToolPresentation,
|
|
312
366
|
cwd,
|
|
@@ -319,7 +373,7 @@ export async function buildBatchPresentation(options) {
|
|
|
319
373
|
});
|
|
320
374
|
steps.push(step);
|
|
321
375
|
currentArtifactManifest = step.presentation.artifactManifest ?? currentArtifactManifest;
|
|
322
|
-
currentNetworkRoutes = applyNetworkRouteRecords(currentNetworkRoutes, isStringArray(item.command) ?
|
|
376
|
+
currentNetworkRoutes = applyNetworkRouteRecords(currentNetworkRoutes, isStringArray(item.command) ? extractUpstreamCommandTokens(item.command) : undefined, item.success !== false && step.details.success);
|
|
323
377
|
protectedPersistentPaths.push(...getPresentationPaths({
|
|
324
378
|
primaryPath: step.presentation.fullOutputPath,
|
|
325
379
|
secondaryPaths: step.presentation.fullOutputPaths,
|
|
@@ -327,7 +381,7 @@ export async function buildBatchPresentation(options) {
|
|
|
327
381
|
}
|
|
328
382
|
const batchFailure = getBatchFailureDetails(steps);
|
|
329
383
|
const images = steps.flatMap((step) => getPresentationImages(step.presentation));
|
|
330
|
-
const artifacts = steps
|
|
384
|
+
const artifacts = coalesceTerminalBatchRecordingArtifacts(steps, sessionName, namespace);
|
|
331
385
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
332
386
|
const fullOutputPaths = steps.flatMap((step) => getPresentationPaths({
|
|
333
387
|
primaryPath: step.presentation.fullOutputPath,
|
|
@@ -356,9 +410,10 @@ export async function buildBatchPresentation(options) {
|
|
|
356
410
|
const contentText = artifactRetentionSummary && manifestHasNewNoticeWorthyEntries(options.artifactManifest, currentArtifactManifest)
|
|
357
411
|
? `${text}\n\n${artifactRetentionSummary}`
|
|
358
412
|
: text;
|
|
413
|
+
const artifactLifecycleActions = applyNamespaceToNextActions(buildAgentBrowserNextActions({ artifacts, command: "batch", resultCategory: batchFailure ? "failure" : "success", sessionName }), namespace);
|
|
359
414
|
const nextActions = batchFailure
|
|
360
|
-
? batchFailure.failedStep.nextActions
|
|
361
|
-
:
|
|
415
|
+
? appendUniqueAgentBrowserNextActions([...(batchFailure.failedStep.nextActions ?? [])], artifactLifecycleActions)
|
|
416
|
+
: artifactLifecycleActions;
|
|
362
417
|
const changedSteps = steps.map((step) => step.details).filter((details) => details.pageChangeSummary !== undefined);
|
|
363
418
|
const pageChangeSummary = artifacts.length > 0
|
|
364
419
|
? buildPageChangeSummary({
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Build browser profile/config recovery hints for launch/setup failures.
|
|
3
|
-
* Responsibilities: Recognize local Chrome/profile setup errors, avoid self-loop diagnostics, and emit canonical next actions.
|
|
4
|
-
* Scope: Error presentation only; upstream profile discovery and doctor execution remain normal agent_browser commands.
|
|
5
|
-
*/
|
|
6
1
|
import { isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
7
2
|
import { getStartupScopedFlags } from "../../runtime.js";
|
|
8
3
|
import { buildNextToolAction } from "../next-actions.js";
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Share small presentation formatting and redaction helpers across result presentation modules.
|
|
3
|
-
* Responsibilities: Normalize scalar fields, stringify model-facing values, and apply sensitive-text redaction.
|
|
4
|
-
* Scope: Leaf helpers only; command-family formatting lives in sibling modules.
|
|
5
|
-
*/
|
|
6
1
|
import { containsManagedSessionRestoreKey } from "../../managed-session-capabilities.js";
|
|
7
2
|
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
8
3
|
import { stringifyUnknown, truncateText } from "../text.js";
|
|
4
|
+
const UNTITLED_PAGE_SUMMARY = "(untitled page)";
|
|
9
5
|
export function stringifyModelFacing(value) {
|
|
10
6
|
return stringifyUnknown(redactSensitiveValue(value));
|
|
11
7
|
}
|
|
@@ -39,6 +35,23 @@ export function getStringField(data, key) {
|
|
|
39
35
|
const value = data[key];
|
|
40
36
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
41
37
|
}
|
|
38
|
+
// `lifecycle` is upstream launch/reuse bookkeeping, never page content, so it must not be the
|
|
39
|
+
// answer an agent reads when a command has no dedicated presenter.
|
|
40
|
+
export function omitUpstreamLifecycle(data) {
|
|
41
|
+
const { lifecycle: _lifecycle, ...rest } = data;
|
|
42
|
+
return rest;
|
|
43
|
+
}
|
|
44
|
+
export function getPageSummary(data) {
|
|
45
|
+
const title = typeof data.title === "string" ? data.title : undefined;
|
|
46
|
+
const url = typeof data.url === "string" ? data.url : undefined;
|
|
47
|
+
if (title === undefined && url === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (title && url)
|
|
50
|
+
return `${title}\n${url}`;
|
|
51
|
+
if (url)
|
|
52
|
+
return url;
|
|
53
|
+
return title || UNTITLED_PAGE_SUMMARY;
|
|
54
|
+
}
|
|
42
55
|
export function formatCount(count, singular, plural = `${singular}s`) {
|
|
43
56
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
44
57
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Share small ToolPresentation content helpers used by batch and compaction code.
|
|
3
|
-
* Responsibilities: Extract text/image/path fields and format batch step command labels.
|
|
4
|
-
* Scope: Pure ToolPresentation content helpers only.
|
|
5
|
-
*/
|
|
6
1
|
export function isStringArray(value) {
|
|
7
2
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8
3
|
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Render diagnostic command families and safe redacted diagnostic data.
|
|
3
|
-
* Responsibilities: Format sessions, profiles, auth/cookies/storage, network diagnostics, console/errors, stream/dashboard/chat, and build network follow-up actions.
|
|
4
|
-
* Scope: Diagnostic/result-state command presentation only; core orchestration stays in presentation.ts.
|
|
5
|
-
*/
|
|
6
1
|
import { isRecord } from "../../parsing.js";
|
|
7
2
|
import { isSensitiveFieldName, redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
8
3
|
import { classifyNetworkRequestFailure, isApiLikeNetworkRequest, isNetworkArtifactNoiseRequest, summarizeNetworkFailures } from "../network.js";
|
|
@@ -66,7 +61,9 @@ export function getTabSummary(data) {
|
|
|
66
61
|
? String(tab.index)
|
|
67
62
|
: String(index);
|
|
68
63
|
const labelText = label && label !== tabSelector ? ` label=${redactModelFacingText(label)}` : "";
|
|
69
|
-
|
|
64
|
+
const targetId = typeof tab.targetId === "string" && tab.targetId.trim().length > 0 ? tab.targetId.trim() : undefined;
|
|
65
|
+
const targetText = targetId && targetId !== tabSelector ? ` target=${redactModelFacingText(targetId)}` : "";
|
|
66
|
+
return `${marker} [${tabSelector}]${labelText}${targetText} ${title} — ${url}`;
|
|
70
67
|
});
|
|
71
68
|
return lines.join("\n");
|
|
72
69
|
}
|
|
@@ -353,6 +350,8 @@ function formatNetworkRequestLine(item, index) {
|
|
|
353
350
|
}
|
|
354
351
|
function formatNetworkRequestsText(data, commandInfo) {
|
|
355
352
|
const requests = getArrayField(data, "requests");
|
|
353
|
+
if (isClearDiagnosticCommand(commandInfo) && data.cleared === true && !requests)
|
|
354
|
+
return "Network request buffer cleared.";
|
|
356
355
|
if (!requests)
|
|
357
356
|
return undefined;
|
|
358
357
|
if (isClearDiagnosticCommand(commandInfo)) {
|
|
@@ -598,6 +597,8 @@ export function buildStreamNextActions(commandInfo, data, sessionName) {
|
|
|
598
597
|
}
|
|
599
598
|
function formatConsoleText(data, commandInfo) {
|
|
600
599
|
const messages = getArrayField(data, "messages");
|
|
600
|
+
if (isClearDiagnosticCommand(commandInfo) && data.cleared === true && !messages)
|
|
601
|
+
return "Console buffer cleared.";
|
|
601
602
|
if (!messages)
|
|
602
603
|
return undefined;
|
|
603
604
|
if (isClearDiagnosticCommand(commandInfo)) {
|
|
@@ -189,11 +189,12 @@ export function buildErrorPresentation(options) {
|
|
|
189
189
|
const nextActions = [
|
|
190
190
|
...(buildUnknownCommandSuggestionActions(unknownCommandSuggestions, sessionName) ?? []),
|
|
191
191
|
...(browserProfileConfigRecovery?.actions ?? []),
|
|
192
|
-
...(buildAgentBrowserNextActions({
|
|
192
|
+
...(browserProfileConfigRecovery ? [] : buildAgentBrowserNextActions({
|
|
193
193
|
args,
|
|
194
194
|
command: commandInfo.command,
|
|
195
195
|
failureCategory: categoryDetails.failureCategory,
|
|
196
196
|
resultCategory: "failure",
|
|
197
|
+
sessionName,
|
|
197
198
|
}) ?? []),
|
|
198
199
|
];
|
|
199
200
|
return {
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Compact oversized model-facing tool output into bounded previews and spill artifacts.
|
|
3
|
-
* Responsibilities: Write full output to persistent/session temp storage, update artifact manifests, and preserve safe previews.
|
|
4
|
-
* Scope: Large-output compaction only.
|
|
5
|
-
*/
|
|
6
1
|
import { writePersistentSessionArtifactFile, writeSecureTempFile, } from "../../temp.js";
|
|
7
2
|
import { buildEvictedSessionArtifactEntries } from "../artifact-manifest.js";
|
|
8
3
|
import { countLines, truncateText } from "../text.js";
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Remove wrapper-owned managed session and state capabilities from caller-visible list results.
|
|
3
|
-
* Responsibilities: Recognize managed list rows recursively and preserve the surrounding upstream result shape.
|
|
4
|
-
* Scope: Presentation filtering only; command authorization lives in managed-session-state-policy.ts.
|
|
5
|
-
*/
|
|
6
1
|
import { containsManagedSessionRestoreKey, isWrapperManagedSessionName } from "../../managed-session-capabilities.js";
|
|
7
2
|
import { isRecord } from "../../parsing.js";
|
|
8
3
|
function isWrapperManagedSessionListItem(item) {
|