pi-agent-browser-native 0.2.24 → 0.2.25

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.
@@ -10,7 +10,7 @@ import { readFile, stat } from "node:fs/promises";
10
10
  import { extname, resolve } from "node:path";
11
11
 
12
12
  import { isRecord, parsePositiveInteger } from "../parsing.js";
13
- import { parseCommandInfo, redactSensitiveText, redactSensitiveValue, type CommandInfo } from "../runtime.js";
13
+ import { extractCommandTokens, parseCommandInfo, redactInvocationArgs, redactSensitiveText, redactSensitiveValue, type CommandInfo } from "../runtime.js";
14
14
  import {
15
15
  type PersistentSessionArtifactEviction,
16
16
  type PersistentSessionArtifactStore,
@@ -22,6 +22,13 @@ import { buildSnapshotPresentation, formatRawSnapshotText, formatSnapshotSummary
22
22
  import {
23
23
  type AgentBrowserBatchResult,
24
24
  type AgentBrowserEnvelope,
25
+ type AgentBrowserPageChangeSummary,
26
+ type ArtifactVerificationEntry,
27
+ type ArtifactVerificationSummary,
28
+ buildAgentBrowserNextActions,
29
+ buildAgentBrowserResultCategoryDetails,
30
+ classifyAgentBrowserFailureCategory,
31
+ classifyAgentBrowserSuccessCategory,
25
32
  type BatchFailurePresentationDetails,
26
33
  type BatchStepPresentationDetails,
27
34
  type ArtifactStorageScope,
@@ -50,6 +57,32 @@ const IMAGE_EXTENSION_TO_MIME_TYPE: Record<string, string> = {
50
57
  const INLINE_IMAGE_MAX_BYTES_ENV = "PI_AGENT_BROWSER_INLINE_IMAGE_MAX_BYTES";
51
58
  const DEFAULT_INLINE_IMAGE_MAX_BYTES = 5 * 1_024 * 1_024;
52
59
  const NAVIGATION_SUMMARY_COMMANDS = new Set(["back", "click", "dblclick", "forward", "reload"]);
60
+ const PAGE_CHANGE_SUMMARY_COMMANDS = new Set([
61
+ "back",
62
+ "check",
63
+ "click",
64
+ "dblclick",
65
+ "dialog",
66
+ "download",
67
+ "fill",
68
+ "forward",
69
+ "goto",
70
+ "hover",
71
+ "navigate",
72
+ "open",
73
+ "pdf",
74
+ "press",
75
+ "pushstate",
76
+ "reload",
77
+ "screenshot",
78
+ "scroll",
79
+ "scrollintoview",
80
+ "select",
81
+ "swipe",
82
+ "tap",
83
+ "type",
84
+ "uncheck",
85
+ ]);
53
86
  const NAVIGATION_SUMMARY_FIELD = "navigationSummary";
54
87
  const LARGE_OUTPUT_INLINE_MAX_CHARS = 8_000;
55
88
  const LARGE_OUTPUT_INLINE_MAX_LINES = 120;
@@ -229,13 +262,89 @@ function formatDiagnosticSummary(commandInfo: CommandInfo, data: Record<string,
229
262
  if (profiles) return `Auth profiles: ${profiles.length}`;
230
263
  const name = getStringField(data, "name") ?? getStringField(data, "profile") ?? commandInfo.subcommand;
231
264
  if (name && commandInfo.subcommand === "show") return `Auth profile: ${name}`;
265
+ if (name && ["save", "login", "delete"].includes(commandInfo.subcommand ?? "")) return `Auth ${commandInfo.subcommand}: ${name}`;
232
266
  }
233
267
 
234
- if (commandInfo.command === "network" && commandInfo.subcommand === "requests") {
235
- const requests = getArrayField(data, "requests");
236
- if (requests) return `Network requests: ${requests.length}`;
268
+ if (commandInfo.command === "cookies") {
269
+ const cookies = getArrayField(data, "cookies");
270
+ if (cookies) return `Cookies: ${cookies.length}`;
271
+ const name = getStringField(data, "name");
272
+ if (name) return name;
273
+ if (data.set === true) return "Cookie set";
274
+ if (data.cleared === true || data.clear === true) return "Cookies cleared";
237
275
  }
238
276
 
277
+ if (commandInfo.command === "storage") {
278
+ const entries = getArrayField(data, "entries") ?? getArrayField(data, "items");
279
+ if (entries) return `Storage entries: ${entries.length}`;
280
+ const key = getStringField(data, "key");
281
+ if (key && (commandInfo.subcommand === "set" || data.set === true || Object.hasOwn(data, "value"))) return `Storage set: ${key}`;
282
+ if (data.cleared === true || data.clear === true) return "Storage cleared";
283
+ }
284
+
285
+ if (commandInfo.command === "dialog") {
286
+ const open = typeof data.open === "boolean" ? data.open : undefined;
287
+ if (open !== undefined) return open ? "Dialog open" : "No dialog open";
288
+ if (data.accepted === true) return "Dialog accepted";
289
+ if (data.dismissed === true) return "Dialog dismissed";
290
+ }
291
+
292
+ if (commandInfo.command === "frame") {
293
+ const frame = getStringField(data, "frame") ?? getStringField(data, "name") ?? getStringField(data, "selector") ?? commandInfo.subcommand;
294
+ if (frame) return `Frame: ${frame}`;
295
+ }
296
+
297
+ if (commandInfo.command === "state") {
298
+ const states = getArrayField(data, "states") ?? getArrayField(data, "files");
299
+ if (states) return `States: ${states.length}`;
300
+ if (commandInfo.subcommand === "load") return undefined;
301
+ const stateName = getStringField(data, "name") ?? getStringField(data, "file") ?? getStringField(data, "path") ?? commandInfo.subcommand;
302
+ if (stateName) return `State ${commandInfo.subcommand ?? "result"}: ${stateName}`;
303
+ }
304
+
305
+ if (commandInfo.command === "network") {
306
+ if (commandInfo.subcommand === "requests") {
307
+ const requests = getArrayField(data, "requests");
308
+ if (requests) return `Network requests: ${requests.length}`;
309
+ }
310
+ if (commandInfo.subcommand === "route") {
311
+ const routed = getStringField(data, "routed") ?? getStringField(data, "url") ?? getStringField(data, "pattern");
312
+ return routed ? `Network route: ${redactModelFacingTextIfSensitive(routed)}` : "Network route configured";
313
+ }
314
+ if (commandInfo.subcommand === "unroute") {
315
+ const unrouted = getStringField(data, "unrouted") ?? getStringField(data, "url") ?? getStringField(data, "pattern");
316
+ return unrouted ? `Network unroute: ${redactModelFacingTextIfSensitive(unrouted)}` : "Network route removed";
317
+ }
318
+ if (commandInfo.subcommand === "har") {
319
+ const state = getStringField(data, "state") ?? getStringField(data, "status") ?? commandInfo.subcommand;
320
+ return `Network HAR: ${state}`;
321
+ }
322
+ }
323
+
324
+ if (commandInfo.command === "diff") {
325
+ if (commandInfo.subcommand === "snapshot") return "Snapshot diff completed";
326
+ if (commandInfo.subcommand === "url") return "URL diff completed";
327
+ }
328
+
329
+ if (["trace", "profiler"].includes(commandInfo.command ?? "")) {
330
+ const state = getStringField(data, "state") ?? getStringField(data, "status") ?? commandInfo.subcommand;
331
+ if (state) return `${commandInfo.command === "trace" ? "Trace" : "Profiler"}: ${state}`;
332
+ }
333
+
334
+ if (commandInfo.command === "highlight") return "Element highlighted";
335
+ if (commandInfo.command === "inspect") return "DevTools inspect opened";
336
+ if (commandInfo.command === "clipboard") return `Clipboard ${commandInfo.subcommand ?? "completed"}`;
337
+
338
+ if (commandInfo.command === "stream") {
339
+ if (commandInfo.subcommand === "enable") {
340
+ const port = typeof data.port === "number" ? ` on port ${data.port}` : "";
341
+ return `Stream enabled${port}`;
342
+ }
343
+ if (commandInfo.subcommand === "disable") return "Stream disabled";
344
+ }
345
+
346
+ if (commandInfo.command === "chat") return "Chat response";
347
+
239
348
  if (commandInfo.command === "console") {
240
349
  const messages = getArrayField(data, "messages");
241
350
  if (messages) return `Console messages: ${messages.length}`;
@@ -417,6 +526,7 @@ function formatNativeSkillContent(content: string): string {
417
526
 
418
527
  function formatSkillsText(commandInfo: CommandInfo, data: unknown): string | undefined {
419
528
  if (commandInfo.command !== "skills") return undefined;
529
+ if (commandInfo.subcommand === "path") return typeof data === "string" ? redactModelFacingText(data) : undefined;
420
530
  if (commandInfo.subcommand === "list" && Array.isArray(data)) return formatSkillsListText(data);
421
531
  const content = getSkillContent(data);
422
532
  if (content) {
@@ -557,6 +667,15 @@ function formatDashboardText(data: Record<string, unknown>): string | undefined
557
667
  return lines.length > 0 ? lines.join("\n") : undefined;
558
668
  }
559
669
 
670
+ function formatChatText(data: Record<string, unknown>): string | undefined {
671
+ const response = getStringField(data, "response") ?? getStringField(data, "message") ?? getStringField(data, "text") ?? getStringField(data, "result");
672
+ if (response) return redactModelFacingText(response);
673
+ const model = getStringField(data, "model");
674
+ const provider = getStringField(data, "provider");
675
+ const lines = [model ? `Model: ${redactModelFacingText(model)}` : undefined, provider ? `Provider: ${redactModelFacingText(provider)}` : undefined].filter(Boolean);
676
+ return lines.length > 0 ? lines.join("\n") : undefined;
677
+ }
678
+
560
679
  function formatDoctorText(data: Record<string, unknown>): string | undefined {
561
680
  const lines: string[] = [];
562
681
  const status = getStringField(data, "status") ?? getStringField(data, "result");
@@ -568,6 +687,123 @@ function formatDoctorText(data: Record<string, unknown>): string | undefined {
568
687
  return lines.length > 0 ? lines.join("\n") : undefined;
569
688
  }
570
689
 
690
+ function formatCookieRecordText(item: Record<string, unknown>, fallbackName: string): string {
691
+ const name = redactModelFacingText(getStringField(item, "name") ?? fallbackName);
692
+ const domain = getStringField(item, "domain");
693
+ const path = getStringField(item, "path");
694
+ const flags = [item.httpOnly === true ? "httpOnly" : undefined, item.secure === true ? "secure" : undefined].filter(Boolean).join(", ");
695
+ const location = [domain, path].filter(Boolean).join("");
696
+ return [name, location ? `(${redactModelFacingText(location)})` : undefined, flags ? `[${flags}]` : undefined].filter(Boolean).join(" ");
697
+ }
698
+
699
+ function formatCookiesText(data: Record<string, unknown>): string | undefined {
700
+ const cookies = getArrayField(data, "cookies");
701
+ if (cookies) {
702
+ if (cookies.length === 0) return "No cookies.";
703
+ return cookies
704
+ .map((item, index) => (isRecord(item) ? formatCookieRecordText(item, `(cookie ${index + 1})`) : `${index + 1}. [REDACTED]`))
705
+ .join("\n");
706
+ }
707
+ if (getStringField(data, "name") || getStringField(data, "domain") || getStringField(data, "path") || Object.hasOwn(data, "value")) {
708
+ return formatCookieRecordText(data, "cookie");
709
+ }
710
+ if (data.set === true) return "Cookie set.";
711
+ if (data.cleared === true || data.clear === true) return "Cookies cleared.";
712
+ return undefined;
713
+ }
714
+
715
+ function formatStorageText(data: Record<string, unknown>): string | undefined {
716
+ const type = getStringField(data, "type") ?? getStringField(data, "storage") ?? "storage";
717
+ const entries = getArrayField(data, "entries") ?? getArrayField(data, "items");
718
+ if (entries) {
719
+ if (entries.length === 0) return `${type}: no entries.`;
720
+ return entries
721
+ .map((item, index) => {
722
+ if (!isRecord(item)) return `${index + 1}. [REDACTED]`;
723
+ const rawKey = getStringField(item, "key") ?? getStringField(item, "name") ?? `(entry ${index + 1})`;
724
+ const key = redactModelFacingText(rawKey);
725
+ return Object.hasOwn(item, "value") ? `${key}: [REDACTED]` : key;
726
+ })
727
+ .join("\n");
728
+ }
729
+ const key = getStringField(data, "key");
730
+ if (key && Object.hasOwn(data, "value")) return `${type} ${redactModelFacingText(key)}: [REDACTED]`;
731
+ if (key && data.set === true) return `${type} set: ${redactModelFacingText(key)}`;
732
+ if (data.cleared === true || data.clear === true) return `${type} cleared.`;
733
+ return undefined;
734
+ }
735
+
736
+ function formatDialogText(data: Record<string, unknown>): string | undefined {
737
+ const lines: string[] = [];
738
+ if (typeof data.open === "boolean") lines.push(data.open ? "Dialog open." : "No dialog open.");
739
+ const type = getStringField(data, "type");
740
+ if (type) lines.push(`Type: ${redactModelFacingText(type)}`);
741
+ const message = getStringField(data, "message");
742
+ if (message) lines.push(`Message: ${/(?:auth|authorization|bearer|cookie|pass(?:word)?|secret|session|token)/i.test(message) ? "[REDACTED]" : redactModelFacingText(message)}`);
743
+ if (data.accepted === true) lines.push("Accepted.");
744
+ if (data.dismissed === true) lines.push("Dismissed.");
745
+ return lines.length > 0 ? lines.join("\n") : undefined;
746
+ }
747
+
748
+ function formatFrameText(data: Record<string, unknown>): string | undefined {
749
+ const frame = getStringField(data, "frame") ?? getStringField(data, "name") ?? getStringField(data, "selector");
750
+ const url = getStringField(data, "url");
751
+ const title = getStringField(data, "title");
752
+ const lines = [frame ? `Frame: ${redactModelFacingText(frame)}` : undefined, title ? `Title: ${redactModelFacingText(title)}` : undefined, url ? `URL: ${redactModelFacingTextIfSensitive(url)}` : undefined].filter(Boolean);
753
+ return lines.length > 0 ? lines.join("\n") : undefined;
754
+ }
755
+
756
+ function formatStateText(data: Record<string, unknown>): string | undefined {
757
+ const states = getArrayField(data, "states") ?? getArrayField(data, "files");
758
+ if (states) {
759
+ if (states.length === 0) return "No saved states.";
760
+ return states
761
+ .map((item, index) => {
762
+ if (!isRecord(item)) return `${index + 1}. ${redactModelFacingTextIfSensitive(stringifyModelFacing(item))}`;
763
+ const name = getStringField(item, "name") ?? getStringField(item, "file") ?? getStringField(item, "path") ?? `(state ${index + 1})`;
764
+ const url = getStringField(item, "url");
765
+ return url ? `${index + 1}. ${redactModelFacingText(name)} — ${redactModelFacingTextIfSensitive(url)}` : `${index + 1}. ${redactModelFacingText(name)}`;
766
+ })
767
+ .join("\n");
768
+ }
769
+ if (data.loaded === true) return `State loaded: ${redactModelFacingText(getStringField(data, "path") ?? getStringField(data, "name") ?? "ok")}`;
770
+ if (data.cleared === true || data.clear === true) return "State cleared.";
771
+ return undefined;
772
+ }
773
+
774
+ function isSensitivePresentationField(key: string): boolean {
775
+ return /^(?:access(?:_|-)?token|api(?:_|-)?key|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|secret|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|token|x(?:_|-)?api(?:_|-)?key)$/i.test(key);
776
+ }
777
+
778
+ function redactStructuredPresentationValue(value: unknown): unknown {
779
+ if (typeof value === "string") return redactModelFacingTextIfSensitive(value);
780
+ if (Array.isArray(value)) return value.map((item) => redactStructuredPresentationValue(item));
781
+ if (!isRecord(value)) return value;
782
+ return Object.fromEntries(
783
+ Object.entries(value).map(([key, entryValue]) => [
784
+ key,
785
+ isSensitivePresentationField(key) ? "[REDACTED]" : redactStructuredPresentationValue(entryValue),
786
+ ]),
787
+ );
788
+ }
789
+
790
+ function redactStatefulValues(value: unknown, sensitiveKeys: Set<string>): unknown {
791
+ if (Array.isArray(value)) return value.map((item) => redactStatefulValues(item, sensitiveKeys));
792
+ if (!isRecord(value)) return redactStructuredPresentationValue(value);
793
+ return Object.fromEntries(
794
+ Object.entries(value).map(([key, entryValue]) => [
795
+ key,
796
+ sensitiveKeys.has(key.toLowerCase()) ? "[REDACTED]" : redactStatefulValues(entryValue, sensitiveKeys),
797
+ ]),
798
+ );
799
+ }
800
+
801
+ function redactPresentationData(commandInfo: CommandInfo, data: unknown): unknown {
802
+ if (commandInfo.command === "cookies") return redactStatefulValues(data, new Set(["value"]));
803
+ if (commandInfo.command === "storage") return redactStatefulValues(data, new Set(["value"]));
804
+ return redactStructuredPresentationValue(data);
805
+ }
806
+
571
807
  function formatDiagnosticText(commandInfo: CommandInfo, data: Record<string, unknown>): string | undefined {
572
808
  if (commandInfo.command === "session") return formatSessionText(data);
573
809
  if (commandInfo.command === "profiles") {
@@ -579,8 +815,23 @@ function formatDiagnosticText(commandInfo: CommandInfo, data: Record<string, unk
579
815
  if (profiles) return formatProfilesText(profiles, "auth profiles");
580
816
  if (commandInfo.subcommand === "show") return formatAuthShowText(data);
581
817
  }
818
+ if (commandInfo.command === "cookies") return formatCookiesText(data);
819
+ if (commandInfo.command === "storage") return formatStorageText(data);
820
+ if (commandInfo.command === "dialog") return formatDialogText(data);
821
+ if (commandInfo.command === "frame") return formatFrameText(data);
822
+ if (commandInfo.command === "state") return formatStateText(data);
582
823
  if (commandInfo.command === "network" && commandInfo.subcommand === "requests") return formatNetworkRequestsText(data);
583
824
  if (commandInfo.command === "network" && commandInfo.subcommand === "request") return formatNetworkRequestText(data);
825
+ if (commandInfo.command === "diff") return stringifyModelFacing(data);
826
+ if (commandInfo.command === "clipboard") {
827
+ const text = getStringField(data, "text") ?? getStringField(data, "value") ?? getStringField(data, "result");
828
+ if (text) return redactModelFacingText(text);
829
+ }
830
+ if (commandInfo.command === "stream") {
831
+ const streamSummary = getStreamSummary(data);
832
+ if (streamSummary) return streamSummary;
833
+ }
834
+ if (commandInfo.command === "chat") return formatChatText(data);
584
835
  if (commandInfo.command === "console") return formatConsoleText(data);
585
836
  if (commandInfo.command === "errors") return formatErrorsText(data);
586
837
  if (commandInfo.command === "dashboard") return formatDashboardText(data);
@@ -627,7 +878,10 @@ const PATH_FIELD_CANDIDATES = [
627
878
  "filePath",
628
879
  "outputPath",
629
880
  "downloadPath",
881
+ "diffPath",
630
882
  "harPath",
883
+ "savedPath",
884
+ "statePath",
631
885
  "tracePath",
632
886
  "profilePath",
633
887
  "videoPath",
@@ -647,9 +901,11 @@ const ARTIFACT_EXTENSION_TO_MEDIA_TYPE: Record<string, string> = {
647
901
 
648
902
  function getArtifactKind(commandInfo: CommandInfo): FileArtifactKind | undefined {
649
903
  if (commandInfo.command === "screenshot") return "image";
904
+ if (commandInfo.command === "diff" && commandInfo.subcommand === "screenshot") return "image";
650
905
  if (commandInfo.command === "pdf") return "pdf";
651
906
  if (commandInfo.command === "download") return "download";
652
907
  if (commandInfo.command === "wait" && commandInfo.subcommand === "--download") return "download";
908
+ if (commandInfo.command === "state" && commandInfo.subcommand === "save") return "file";
653
909
  if (commandInfo.command === "trace") return "trace";
654
910
  if (commandInfo.command === "profiler") return "profile";
655
911
  if (commandInfo.command === "record") return "video";
@@ -773,16 +1029,124 @@ function isManifestFileArtifact(artifact: FileArtifactMetadata): boolean {
773
1029
  return !isRecordingStartArtifact(artifact);
774
1030
  }
775
1031
 
1032
+ function getArtifactVerificationEntry(artifact: FileArtifactMetadata): ArtifactVerificationEntry {
1033
+ if (isRecordingStartArtifact(artifact)) {
1034
+ return {
1035
+ absolutePath: artifact.absolutePath,
1036
+ exists: artifact.exists,
1037
+ kind: artifact.kind,
1038
+ limitation: "Recording output is pending until record stop completes.",
1039
+ mediaType: artifact.mediaType,
1040
+ path: artifact.path,
1041
+ requestedPath: artifact.requestedPath,
1042
+ retentionState: undefined,
1043
+ sizeBytes: artifact.sizeBytes,
1044
+ state: "pending",
1045
+ status: artifact.status,
1046
+ storageScope: undefined,
1047
+ };
1048
+ }
1049
+ const state = artifact.exists === true
1050
+ ? "verified"
1051
+ : artifact.exists === false
1052
+ ? "missing"
1053
+ : "unverified";
1054
+ return {
1055
+ absolutePath: artifact.absolutePath,
1056
+ exists: artifact.exists,
1057
+ kind: artifact.kind,
1058
+ limitation: state === "missing"
1059
+ ? "The wrapper did not find the reported artifact at absolutePath. Treat the path as unverified until recovered or regenerated."
1060
+ : state === "unverified"
1061
+ ? "The wrapper could not prove local filesystem existence for this artifact."
1062
+ : undefined,
1063
+ mediaType: artifact.mediaType,
1064
+ path: artifact.path,
1065
+ requestedPath: artifact.requestedPath,
1066
+ retentionState: artifact.exists === false ? "missing" : "live",
1067
+ sizeBytes: artifact.sizeBytes,
1068
+ state,
1069
+ status: artifact.status,
1070
+ storageScope: "explicit-path",
1071
+ };
1072
+ }
1073
+
1074
+ function getManifestVerificationEntry(entry: SessionArtifactManifestEntry): ArtifactVerificationEntry | undefined {
1075
+ if (entry.storageScope === "explicit-path") return undefined;
1076
+ const state = entry.retentionState === "live"
1077
+ ? "verified"
1078
+ : entry.retentionState === "missing" || entry.retentionState === "evicted"
1079
+ ? "missing"
1080
+ : "unverified";
1081
+ return {
1082
+ absolutePath: entry.absolutePath,
1083
+ exists: entry.exists,
1084
+ kind: entry.kind,
1085
+ limitation: entry.retentionState === "ephemeral"
1086
+ ? "This spill file is process-temporary and may not survive reload or restart."
1087
+ : entry.retentionState === "evicted"
1088
+ ? "This persisted spill file was evicted from the bounded session artifact store."
1089
+ : undefined,
1090
+ mediaType: entry.mediaType,
1091
+ path: entry.path,
1092
+ requestedPath: entry.requestedPath,
1093
+ retentionState: entry.retentionState,
1094
+ sizeBytes: entry.sizeBytes,
1095
+ state,
1096
+ storageScope: entry.storageScope,
1097
+ };
1098
+ }
1099
+
1100
+ function buildArtifactVerificationSummary(
1101
+ artifacts: FileArtifactMetadata[],
1102
+ manifest?: SessionArtifactManifest,
1103
+ manifestPaths?: ReadonlySet<string>,
1104
+ ): ArtifactVerificationSummary | undefined {
1105
+ const entries = [
1106
+ ...artifacts.map(getArtifactVerificationEntry),
1107
+ ...(manifest?.entries.flatMap((entry) => {
1108
+ if (manifestPaths && !manifestPaths.has(entry.path)) return [];
1109
+ const verificationEntry = getManifestVerificationEntry(entry);
1110
+ return verificationEntry ? [verificationEntry] : [];
1111
+ }) ?? []),
1112
+ ];
1113
+ if (entries.length === 0) return undefined;
1114
+ const verifiedCount = entries.filter((entry) => entry.state === "verified").length;
1115
+ const missingCount = entries.filter((entry) => entry.state === "missing").length;
1116
+ const pendingCount = entries.filter((entry) => entry.state === "pending").length;
1117
+ const unverifiedCount = entries.filter((entry) => entry.state === "unverified").length;
1118
+ return {
1119
+ artifacts: entries,
1120
+ missingCount,
1121
+ pendingCount,
1122
+ unverifiedCount,
1123
+ verified: entries.length > 0 && verifiedCount === entries.length,
1124
+ verifiedCount,
1125
+ };
1126
+ }
1127
+
1128
+ function classifyPresentationSuccessCategory(options: {
1129
+ artifactVerification?: ArtifactVerificationSummary;
1130
+ artifacts?: FileArtifactMetadata[];
1131
+ inspection?: boolean;
1132
+ savedFile?: SavedFilePresentationDetails;
1133
+ }) {
1134
+ if ((options.artifactVerification?.missingCount ?? 0) > 0 || (options.artifactVerification?.unverifiedCount ?? 0) > 0) {
1135
+ return "artifact-unverified" as const;
1136
+ }
1137
+ return classifyAgentBrowserSuccessCategory(options);
1138
+ }
1139
+
776
1140
  function formatArtifactLabel(artifact: FileArtifactMetadata): string {
777
1141
  switch (artifact.kind) {
778
1142
  case "download":
779
1143
  return artifact.command === "wait" && artifact.subcommand === "--download" ? "Download completed" : "Downloaded file";
780
1144
  case "file":
781
- return "Saved file";
1145
+ return artifact.command === "state" ? "State file" : "Saved file";
782
1146
  case "har":
783
1147
  return "Saved HAR";
784
1148
  case "image":
785
- return "Saved image";
1149
+ return artifact.command === "diff" && artifact.subcommand === "screenshot" ? "Saved diff image" : "Saved image";
786
1150
  case "pdf":
787
1151
  return "Saved PDF";
788
1152
  case "profile":
@@ -951,12 +1315,71 @@ function getNavigationSummary(data: Record<string, unknown>): NavigationSummary
951
1315
  return isNavigationSummary(candidate) ? candidate : undefined;
952
1316
  }
953
1317
 
1318
+ function getTopLevelNavigationSummary(data: Record<string, unknown>): NavigationSummary | undefined {
1319
+ return isNavigationSummary(data)
1320
+ ? {
1321
+ title: typeof data.title === "string" ? data.title : undefined,
1322
+ url: typeof data.url === "string" ? data.url : undefined,
1323
+ }
1324
+ : undefined;
1325
+ }
1326
+
1327
+ function getNormalizedNavigationSummary(summary: NavigationSummary | undefined): { title?: string; url?: string } | undefined {
1328
+ const title = typeof summary?.title === "string" && summary.title.trim().length > 0 ? summary.title.trim() : undefined;
1329
+ const url = typeof summary?.url === "string" && summary.url.trim().length > 0 ? summary.url.trim() : undefined;
1330
+ return title || url ? { title, url } : undefined;
1331
+ }
1332
+
954
1333
  function formatNavigationSummary(summary: NavigationSummary): string | undefined {
955
- const title = typeof summary.title === "string" && summary.title.trim().length > 0 ? summary.title.trim() : undefined;
956
- const url = typeof summary.url === "string" && summary.url.trim().length > 0 ? summary.url.trim() : undefined;
957
- if (!title && !url) return undefined;
958
- if (title && url) return `${title}\n${url}`;
959
- return title ?? url;
1334
+ const normalized = getNormalizedNavigationSummary(summary);
1335
+ if (!normalized) return undefined;
1336
+ if (normalized.title && normalized.url) return `${normalized.title}\n${normalized.url}`;
1337
+ return normalized.title ?? normalized.url;
1338
+ }
1339
+
1340
+ function isPageChangeSummaryCommand(command: string | undefined): boolean {
1341
+ return command !== undefined && PAGE_CHANGE_SUMMARY_COMMANDS.has(command);
1342
+ }
1343
+
1344
+ function buildPageChangeSummary(options: {
1345
+ artifacts?: FileArtifactMetadata[];
1346
+ commandInfo: CommandInfo;
1347
+ data: unknown;
1348
+ nextActions?: Array<{ id: string }>;
1349
+ savedFilePath?: string;
1350
+ summary: string;
1351
+ }): AgentBrowserPageChangeSummary | undefined {
1352
+ const { artifacts, commandInfo, data, nextActions, savedFilePath } = options;
1353
+ const artifactCount = artifacts?.length ?? 0;
1354
+ const navigation = isRecord(data)
1355
+ ? getNormalizedNavigationSummary(getNavigationSummary(data) ?? (isPageChangeSummaryCommand(commandInfo.command) ? getTopLevelNavigationSummary(data) : undefined))
1356
+ : undefined;
1357
+ const confirmationRequired = detectConfirmationRequired(data) !== undefined;
1358
+ if (!navigation && !confirmationRequired && artifactCount === 0 && !savedFilePath && !isPageChangeSummaryCommand(commandInfo.command)) {
1359
+ return undefined;
1360
+ }
1361
+ const changeType: AgentBrowserPageChangeSummary["changeType"] = savedFilePath || artifactCount > 0
1362
+ ? "artifact"
1363
+ : navigation
1364
+ ? "navigation"
1365
+ : confirmationRequired
1366
+ ? "confirmation"
1367
+ : "mutation";
1368
+ const parts = [commandInfo.command ?? "agent-browser", changeType];
1369
+ if (navigation?.title) parts.push(navigation.title);
1370
+ if (navigation?.url) parts.push(navigation.url);
1371
+ if (savedFilePath) parts.push(savedFilePath);
1372
+ else if (artifactCount > 0) parts.push(`${artifactCount} artifact${artifactCount === 1 ? "" : "s"}`);
1373
+ return {
1374
+ ...(artifactCount > 0 ? { artifactCount } : {}),
1375
+ changeType,
1376
+ ...(commandInfo.command ? { command: commandInfo.command } : {}),
1377
+ ...(nextActions ? { nextActionIds: nextActions.map((action) => action.id) } : {}),
1378
+ ...(savedFilePath ? { savedFilePath } : {}),
1379
+ summary: parts.join(" → "),
1380
+ ...(navigation?.title ? { title: navigation.title } : {}),
1381
+ ...(navigation?.url ? { url: navigation.url } : {}),
1382
+ };
960
1383
  }
961
1384
 
962
1385
  function stripNavigationSummary(data: Record<string, unknown>): Record<string, unknown> {
@@ -1083,6 +1506,36 @@ function getBatchFailureDetails(steps: Array<{ details: BatchStepPresentationDet
1083
1506
  };
1084
1507
  }
1085
1508
 
1509
+ function hasModelFacingArgRedaction(args: string[] | undefined): boolean {
1510
+ return args?.some((arg) => arg === "[REDACTED]" || arg.includes("%5BREDACTED%5D") || arg.includes("[REDACTED]")) === true;
1511
+ }
1512
+
1513
+ function getStatefulCommandSensitiveValues(command: string[] | undefined): string[] {
1514
+ if (!command) return [];
1515
+ const tokens = extractCommandTokens(command);
1516
+ const values: string[] = [];
1517
+ if (tokens[0] === "cookies" && tokens[1] === "set" && tokens[3]) values.push(tokens[3]);
1518
+ if (tokens[0] === "storage" && ["local", "session"].includes(tokens[1] ?? "") && tokens[2] === "set" && tokens[4]) values.push(tokens[4]);
1519
+ for (let index = 0; index < tokens.length; index += 1) {
1520
+ const token = tokens[index];
1521
+ if (token === "--password" && tokens[index + 1]) values.push(tokens[index + 1]);
1522
+ else if (token?.startsWith("--password=")) values.push(token.slice("--password=".length));
1523
+ }
1524
+ return values.filter((value) => value.length > 0);
1525
+ }
1526
+
1527
+ function redactExactValues(value: unknown, sensitiveValues: string[]): unknown {
1528
+ if (sensitiveValues.length === 0) return redactSensitiveValue(value);
1529
+ if (typeof value === "string") {
1530
+ let redacted = value;
1531
+ for (const sensitiveValue of sensitiveValues) redacted = redacted.split(sensitiveValue).join("[REDACTED]");
1532
+ return redactSensitiveText(redacted);
1533
+ }
1534
+ if (Array.isArray(value)) return value.map((item) => redactExactValues(item, sensitiveValues));
1535
+ if (!isRecord(value)) return value;
1536
+ return redactSensitiveValue(Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, redactExactValues(entryValue, sensitiveValues)])));
1537
+ }
1538
+
1086
1539
  async function buildBatchStepPresentation(options: {
1087
1540
  artifactManifest?: SessionArtifactManifest;
1088
1541
  artifactRequest?: ArtifactRequestContext;
@@ -1094,21 +1547,43 @@ async function buildBatchStepPresentation(options: {
1094
1547
  }): Promise<{ details: BatchStepPresentationDetails; presentation: ToolPresentation }> {
1095
1548
  const { artifactManifest, artifactRequest, cwd, index, item, persistentArtifactStore, sessionName } = options;
1096
1549
  const command = isStringArray(item.command) ? item.command : undefined;
1097
- const commandText = formatBatchStepCommand(command, index);
1550
+ const redactedCommand = command ? redactInvocationArgs(command) : undefined;
1551
+ const commandText = formatBatchStepCommand(hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command, index);
1098
1552
 
1099
1553
  if (item.success === false) {
1100
- const errorText = formatBatchStepError(item.error);
1554
+ const redactedErrorData = redactExactValues(item.error, getStatefulCommandSensitiveValues(command));
1555
+ const errorText = formatBatchStepError(redactedErrorData);
1556
+ const failureCategory = classifyAgentBrowserFailureCategory({
1557
+ args: command,
1558
+ command: command?.[0],
1559
+ errorText,
1560
+ });
1561
+ const confirmationRequired = detectConfirmationRequired(item.error);
1562
+ const nextActions = buildAgentBrowserNextActions({
1563
+ args: command,
1564
+ command: command?.[0],
1565
+ confirmationId: confirmationRequired?.id,
1566
+ failureCategory,
1567
+ resultCategory: "failure",
1568
+ });
1101
1569
  const presentation: ToolPresentation = {
1102
1570
  content: [{ type: "text", text: errorText }],
1571
+ failureCategory,
1572
+ nextActions,
1573
+ resultCategory: "failure",
1103
1574
  summary: errorText,
1104
1575
  };
1105
1576
  return {
1106
1577
  details: {
1578
+ artifactVerification: presentation.artifactVerification,
1107
1579
  artifacts: presentation.artifacts,
1108
- command,
1580
+ command: redactedCommand,
1109
1581
  commandText,
1110
- data: item.error,
1582
+ data: redactedErrorData,
1583
+ failureCategory,
1111
1584
  index,
1585
+ nextActions,
1586
+ resultCategory: "failure",
1112
1587
  success: false,
1113
1588
  summary: errorText,
1114
1589
  text: errorText,
@@ -1122,6 +1597,7 @@ async function buildBatchStepPresentation(options: {
1122
1597
  artifactRequest,
1123
1598
  commandInfo: parseCommandInfo(command ?? []),
1124
1599
  cwd,
1600
+ args: command,
1125
1601
  envelope: { data: item.result, success: true },
1126
1602
  persistentArtifactStore,
1127
1603
  sessionName,
@@ -1135,11 +1611,28 @@ async function buildBatchStepPresentation(options: {
1135
1611
  secondaryPaths: presentation.imagePaths,
1136
1612
  });
1137
1613
  const text = getPresentationText(presentation) || presentation.summary;
1614
+ const nextActions = buildAgentBrowserNextActions({
1615
+ artifacts: presentation.artifacts,
1616
+ args: command,
1617
+ command: command?.[0],
1618
+ resultCategory: "success",
1619
+ savedFilePath: presentation.savedFilePath,
1620
+ successCategory: presentation.successCategory,
1621
+ });
1622
+ const pageChangeSummary = buildPageChangeSummary({
1623
+ artifacts: presentation.artifacts,
1624
+ commandInfo: parseCommandInfo(command ?? []),
1625
+ data: presentation.data,
1626
+ nextActions,
1627
+ savedFilePath: presentation.savedFilePath,
1628
+ summary: presentation.summary,
1629
+ });
1138
1630
 
1139
1631
  return {
1140
1632
  details: {
1633
+ artifactVerification: presentation.artifactVerification,
1141
1634
  artifacts: presentation.artifacts,
1142
- command,
1635
+ command: redactedCommand,
1143
1636
  commandText,
1144
1637
  data: presentation.data,
1145
1638
  fullOutputPath: fullOutputPaths[0],
@@ -1147,9 +1640,13 @@ async function buildBatchStepPresentation(options: {
1147
1640
  imagePath: imagePaths[0],
1148
1641
  imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
1149
1642
  index,
1643
+ nextActions,
1644
+ pageChangeSummary,
1645
+ resultCategory: "success",
1150
1646
  savedFile: presentation.savedFile,
1151
1647
  savedFilePath: presentation.savedFilePath,
1152
1648
  success: true,
1649
+ successCategory: classifyPresentationSuccessCategory({ artifactVerification: presentation.artifactVerification, artifacts: presentation.artifacts, savedFile: presentation.savedFile }),
1153
1650
  summary: presentation.summary,
1154
1651
  text,
1155
1652
  },
@@ -1195,6 +1692,7 @@ async function buildBatchPresentation(options: {
1195
1692
  const batchFailure = getBatchFailureDetails(steps);
1196
1693
  const images = steps.flatMap((step) => getPresentationImages(step.presentation));
1197
1694
  const artifacts = steps.flatMap((step) => step.presentation.artifacts ?? []);
1695
+ const artifactVerification = buildArtifactVerificationSummary(artifacts);
1198
1696
  const fullOutputPaths = steps.flatMap((step) => getPresentationPaths({
1199
1697
  primaryPath: step.presentation.fullOutputPath,
1200
1698
  secondaryPaths: step.presentation.fullOutputPaths,
@@ -1203,6 +1701,11 @@ async function buildBatchPresentation(options: {
1203
1701
  primaryPath: step.presentation.imagePath,
1204
1702
  secondaryPaths: step.presentation.imagePaths,
1205
1703
  }));
1704
+ const redactedBatchData = steps.map(({ details }) => (
1705
+ details.success
1706
+ ? { command: details.command, result: details.data, success: true }
1707
+ : { command: details.command, error: details.text, success: false }
1708
+ ));
1206
1709
  const stepText =
1207
1710
  steps.length === 0
1208
1711
  ? "(no batch steps)"
@@ -1235,18 +1738,45 @@ async function buildBatchPresentation(options: {
1235
1738
  const artifactRetentionSummary = currentArtifactManifest ? formatSessionArtifactRetentionSummary(currentArtifactManifest) : undefined;
1236
1739
  const contentText = artifactRetentionSummary && manifestHasNewNoticeWorthyEntries(options.artifactManifest, currentArtifactManifest) ? `${text}\n\n${artifactRetentionSummary}` : text;
1237
1740
 
1741
+ const nextActions = batchFailure
1742
+ ? batchFailure.failedStep.nextActions
1743
+ : buildAgentBrowserNextActions({ artifacts, command: "batch", resultCategory: "success" });
1744
+ const changedSteps = steps.map((step) => step.details).filter((details) => details.pageChangeSummary !== undefined);
1745
+ const pageChangeSummary = artifacts.length > 0
1746
+ ? buildPageChangeSummary({
1747
+ artifacts,
1748
+ commandInfo: { command: "batch" },
1749
+ data,
1750
+ nextActions,
1751
+ summary,
1752
+ })
1753
+ : changedSteps.length > 0
1754
+ ? {
1755
+ changeType: "mutation" as const,
1756
+ command: "batch",
1757
+ nextActionIds: nextActions?.map((action) => action.id),
1758
+ summary: `batch → mutation → ${changedSteps.length} changed step${changedSteps.length === 1 ? "" : "s"}`,
1759
+ }
1760
+ : undefined;
1761
+
1238
1762
  return {
1239
1763
  artifactManifest: currentArtifactManifest,
1240
1764
  artifactRetentionSummary,
1765
+ artifactVerification,
1241
1766
  artifacts: artifacts.length > 0 ? artifacts : undefined,
1242
1767
  batchFailure,
1243
1768
  batchSteps: steps.map((step) => step.details),
1244
1769
  content: [{ type: "text", text: contentText }, ...images],
1245
- data,
1770
+ failureCategory: batchFailure?.failedStep.failureCategory,
1771
+ data: redactedBatchData,
1246
1772
  fullOutputPath: fullOutputPaths[0],
1247
1773
  fullOutputPaths: fullOutputPaths.length > 0 ? fullOutputPaths : undefined,
1248
1774
  imagePath: imagePaths[0],
1249
1775
  imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
1776
+ nextActions,
1777
+ pageChangeSummary,
1778
+ resultCategory: batchFailure ? "failure" : "success",
1779
+ successCategory: batchFailure ? undefined : classifyPresentationSuccessCategory({ artifactVerification, artifacts }),
1250
1780
  summary,
1251
1781
  };
1252
1782
  }
@@ -1270,6 +1800,9 @@ function formatSummary(commandInfo: CommandInfo, data: unknown): string {
1270
1800
  if (commandInfo.command === "skills" && commandInfo.subcommand === "get") {
1271
1801
  return "agent-browser skill loaded";
1272
1802
  }
1803
+ if (commandInfo.command === "skills" && commandInfo.subcommand === "path") {
1804
+ return "agent-browser skill path";
1805
+ }
1273
1806
  if (isRecord(data)) {
1274
1807
  const navigationSummary = getNavigationSummary(data);
1275
1808
  if (navigationSummary && isNavigationObservableCommand(commandInfo.command)) {
@@ -1319,6 +1852,10 @@ function formatContentText(commandInfo: CommandInfo, data: unknown): string {
1319
1852
  return formatConfirmationRequiredText(confirmationRequired);
1320
1853
  }
1321
1854
 
1855
+ const skillsText = formatSkillsText(commandInfo, data);
1856
+ if (skillsText) {
1857
+ return skillsText;
1858
+ }
1322
1859
  if (typeof data === "string") {
1323
1860
  return redactModelFacingText(data);
1324
1861
  }
@@ -1328,9 +1865,6 @@ function formatContentText(commandInfo: CommandInfo, data: unknown): string {
1328
1865
  if (Array.isArray(data) && commandInfo.command === "profiles") {
1329
1866
  return formatProfilesText(data, "Chrome profiles");
1330
1867
  }
1331
- if (Array.isArray(data) && commandInfo.command === "skills") {
1332
- return formatSkillsText(commandInfo, data) ?? stringifyModelFacing(data);
1333
- }
1334
1868
  if (!isRecord(data)) {
1335
1869
  return stringifyModelFacing(data);
1336
1870
  }
@@ -1359,10 +1893,6 @@ function formatContentText(commandInfo: CommandInfo, data: unknown): string {
1359
1893
  const screenshotSummary = getScreenshotSummary(data);
1360
1894
  if (screenshotSummary) return screenshotSummary;
1361
1895
  }
1362
- const skillsText = formatSkillsText(commandInfo, data);
1363
- if (skillsText) {
1364
- return skillsText;
1365
- }
1366
1896
  const extractionText = formatExtractionText(commandInfo, data);
1367
1897
  if (extractionText) {
1368
1898
  return extractionText;
@@ -1595,6 +2125,7 @@ async function compactLargePresentationOutput(options: {
1595
2125
 
1596
2126
  export async function buildToolPresentation(options: {
1597
2127
  artifactManifest?: SessionArtifactManifest;
2128
+ args?: string[];
1598
2129
  artifactRequest?: ArtifactRequestContext;
1599
2130
  batchArtifactRequests?: Array<ArtifactRequestContext | undefined>;
1600
2131
  commandInfo: CommandInfo;
@@ -1604,17 +2135,22 @@ export async function buildToolPresentation(options: {
1604
2135
  persistentArtifactStore?: PersistentSessionArtifactStore;
1605
2136
  sessionName?: string;
1606
2137
  }): Promise<ToolPresentation> {
1607
- const { artifactManifest, artifactRequest, commandInfo, cwd, envelope, errorText, persistentArtifactStore, sessionName } = options;
2138
+ const { args, artifactManifest, artifactRequest, commandInfo, cwd, envelope, errorText, persistentArtifactStore, sessionName } = options;
1608
2139
  if (errorText) {
1609
2140
  const hintedErrorText = appendSelectorRecoveryHint(redactModelFacingText(errorText));
2141
+ const categoryDetails = buildAgentBrowserResultCategoryDetails({ args: [commandInfo.command, commandInfo.subcommand].filter((item): item is string => item !== undefined), command: commandInfo.command, errorText: hintedErrorText, succeeded: false });
1610
2142
  return {
2143
+ ...categoryDetails,
1611
2144
  content: [{ type: "text", text: hintedErrorText }],
2145
+ nextActions: buildAgentBrowserNextActions({ args, command: commandInfo.command, failureCategory: categoryDetails.failureCategory, resultCategory: "failure" }),
1612
2146
  summary: hintedErrorText,
1613
2147
  };
1614
2148
  }
1615
2149
 
1616
2150
  const data = enrichStreamStatusData(commandInfo, envelope?.data);
2151
+ const presentationData = redactPresentationData(commandInfo, data);
1617
2152
  const artifacts = await extractFileArtifacts({ artifactRequest, commandInfo, cwd, data, sessionName });
2153
+ const artifactVerification = buildArtifactVerificationSummary(artifacts);
1618
2154
  const artifactSummary = formatArtifactSummary(artifacts);
1619
2155
  const summary = artifactSummary ?? formatSummary(commandInfo, data);
1620
2156
  const artifactText = artifacts.length > 0 ? formatArtifactMetadataLines(artifacts).join("\n") : undefined;
@@ -1624,14 +2160,16 @@ export async function buildToolPresentation(options: {
1624
2160
  : commandInfo.command === "snapshot" && isRecord(data)
1625
2161
  ? await buildSnapshotPresentation(data, persistentArtifactStore, artifactManifest)
1626
2162
  : {
2163
+ artifactVerification,
1627
2164
  artifacts: artifacts.length > 0 ? artifacts : undefined,
1628
2165
  content: [{ type: "text" as const, text: artifactText ?? formatContentText(commandInfo, data) }],
1629
- data,
2166
+ data: presentationData,
1630
2167
  summary,
1631
2168
  };
1632
2169
  if (artifacts.length > 0 && !presentation.artifacts) {
1633
2170
  presentation.artifacts = artifacts;
1634
2171
  }
2172
+ presentation.artifactVerification = presentation.artifactVerification ?? artifactVerification;
1635
2173
  if (isRecord(data)) {
1636
2174
  const savedFile = getSavedFileDetails(commandInfo, data);
1637
2175
  if (savedFile) {
@@ -1645,15 +2183,68 @@ export async function buildToolPresentation(options: {
1645
2183
  const compactedPresentation = await compactLargePresentationOutput({
1646
2184
  artifactManifest,
1647
2185
  commandInfo,
1648
- data,
2186
+ data: presentationData,
1649
2187
  persistentArtifactStore,
1650
2188
  presentation: presentationWithImage,
1651
2189
  });
1652
- return sanitizeModelFacingPresentation(
1653
- applyArtifactManifest(
1654
- compactedPresentation,
1655
- compactedPresentation.artifactManifest ?? artifactManifest,
1656
- buildManifestEntriesForFileArtifacts(artifacts.filter(isManifestFileArtifact)),
1657
- ),
2190
+ const presentationWithManifest = applyArtifactManifest(
2191
+ compactedPresentation,
2192
+ compactedPresentation.artifactManifest ?? artifactManifest,
2193
+ buildManifestEntriesForFileArtifacts(artifacts.filter(isManifestFileArtifact)),
1658
2194
  );
2195
+ const currentSpillPaths = new Set(getPresentationPaths({
2196
+ primaryPath: presentationWithManifest.fullOutputPath,
2197
+ secondaryPaths: presentationWithManifest.fullOutputPaths,
2198
+ }));
2199
+ presentationWithManifest.artifactVerification = buildArtifactVerificationSummary(
2200
+ artifacts,
2201
+ presentationWithManifest.artifactManifest,
2202
+ currentSpillPaths,
2203
+ ) ?? presentationWithManifest.artifactVerification;
2204
+ const confirmationRequired = detectConfirmationRequired(data);
2205
+ if (!presentationWithManifest.resultCategory) {
2206
+ const categoryDetails = buildAgentBrowserResultCategoryDetails({
2207
+ artifacts: presentationWithManifest.artifacts,
2208
+ command: commandInfo.command,
2209
+ confirmationRequired: confirmationRequired !== undefined,
2210
+ errorText: envelope?.success === false ? presentationWithManifest.summary : undefined,
2211
+ savedFile: presentationWithManifest.savedFile,
2212
+ succeeded: envelope?.success !== false,
2213
+ });
2214
+ presentationWithManifest.resultCategory = categoryDetails.resultCategory;
2215
+ presentationWithManifest.successCategory = categoryDetails.resultCategory === "success"
2216
+ ? classifyPresentationSuccessCategory({
2217
+ artifactVerification: presentationWithManifest.artifactVerification,
2218
+ artifacts: presentationWithManifest.artifacts,
2219
+ savedFile: presentationWithManifest.savedFile,
2220
+ })
2221
+ : categoryDetails.successCategory;
2222
+ presentationWithManifest.failureCategory = categoryDetails.failureCategory;
2223
+ }
2224
+ if (presentationWithManifest.resultCategory === "success") {
2225
+ presentationWithManifest.successCategory = classifyPresentationSuccessCategory({
2226
+ artifactVerification: presentationWithManifest.artifactVerification,
2227
+ artifacts: presentationWithManifest.artifacts,
2228
+ savedFile: presentationWithManifest.savedFile,
2229
+ });
2230
+ }
2231
+ presentationWithManifest.nextActions = presentationWithManifest.nextActions ?? buildAgentBrowserNextActions({
2232
+ artifacts: presentationWithManifest.artifacts,
2233
+ args,
2234
+ command: commandInfo.command,
2235
+ confirmationId: confirmationRequired?.id,
2236
+ failureCategory: presentationWithManifest.failureCategory,
2237
+ resultCategory: presentationWithManifest.resultCategory ?? "success",
2238
+ savedFilePath: presentationWithManifest.savedFilePath,
2239
+ successCategory: presentationWithManifest.successCategory,
2240
+ });
2241
+ presentationWithManifest.pageChangeSummary = presentationWithManifest.pageChangeSummary ?? buildPageChangeSummary({
2242
+ artifacts: presentationWithManifest.artifacts,
2243
+ commandInfo,
2244
+ data,
2245
+ nextActions: presentationWithManifest.nextActions,
2246
+ savedFilePath: presentationWithManifest.savedFilePath,
2247
+ summary: presentationWithManifest.summary,
2248
+ });
2249
+ return sanitizeModelFacingPresentation(presentationWithManifest);
1659
2250
  }