pi-agent-browser-native 0.2.32 → 0.2.33
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 +17 -0
- package/README.md +27 -16
- package/docs/ARCHITECTURE.md +3 -2
- package/docs/COMMAND_REFERENCE.md +18 -10
- package/docs/ELECTRON.md +23 -4
- package/docs/RELEASE.md +4 -2
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +28 -16
- package/docs/TOOL_CONTRACT.md +29 -24
- package/extensions/agent-browser/index.ts +404 -4371
- package/extensions/agent-browser/lib/input-modes/electron.ts +170 -0
- package/extensions/agent-browser/lib/input-modes/job.ts +203 -0
- package/extensions/agent-browser/lib/input-modes/lookups.ts +447 -0
- package/extensions/agent-browser/lib/input-modes/params.ts +188 -0
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +107 -0
- package/extensions/agent-browser/lib/input-modes/shared.ts +46 -0
- package/extensions/agent-browser/lib/input-modes/types.ts +221 -0
- package/extensions/agent-browser/lib/input-modes.ts +41 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +696 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +450 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +46 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +711 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +386 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +868 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +476 -0
- package/extensions/agent-browser/lib/orchestration/browser-run.ts +1 -0
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +338 -0
- package/extensions/agent-browser/lib/playbook.ts +12 -11
- package/extensions/agent-browser/lib/process.ts +106 -4
- package/extensions/agent-browser/lib/results/action-recommendations.ts +269 -0
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +114 -0
- package/extensions/agent-browser/lib/results/artifact-state.ts +13 -0
- package/extensions/agent-browser/lib/results/categories.ts +106 -0
- package/extensions/agent-browser/lib/results/contracts.ts +220 -0
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +72 -0
- package/extensions/agent-browser/lib/results/envelope.ts +2 -1
- package/extensions/agent-browser/lib/results/network.ts +64 -0
- package/extensions/agent-browser/lib/results/next-actions.ts +117 -0
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +506 -0
- package/extensions/agent-browser/lib/results/presentation/batch.ts +355 -0
- package/extensions/agent-browser/lib/results/presentation/common.ts +53 -0
- package/extensions/agent-browser/lib/results/presentation/content.ts +36 -0
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +730 -0
- package/extensions/agent-browser/lib/results/presentation/errors.ts +125 -0
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +182 -0
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +216 -0
- package/extensions/agent-browser/lib/results/presentation/registry.ts +154 -0
- package/extensions/agent-browser/lib/results/presentation/skills.ts +143 -0
- package/extensions/agent-browser/lib/results/presentation.ts +87 -2399
- package/extensions/agent-browser/lib/results/recovery-actions.ts +139 -0
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +71 -0
- package/extensions/agent-browser/lib/results/selector-recovery.ts +312 -0
- package/extensions/agent-browser/lib/results/shared.ts +17 -789
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +262 -0
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +100 -0
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +366 -0
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +63 -0
- package/extensions/agent-browser/lib/results/snapshot.ts +37 -489
- package/extensions/agent-browser/lib/results/text.ts +40 -0
- package/extensions/agent-browser/lib/results.ts +16 -5
- package/extensions/agent-browser/lib/session-page-state.ts +486 -0
- package/package.json +2 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { isRecord } from "../../parsing.js";
|
|
2
|
+
import { extractCommandTokens, parseCommandInfo, redactInvocationArgs, redactSensitiveText, redactSensitiveValue, type CommandInfo } from "../../runtime.js";
|
|
3
|
+
import type { PersistentSessionArtifactStore } from "../../temp.js";
|
|
4
|
+
import { buildAgentBrowserNextActions } from "../action-recommendations.js";
|
|
5
|
+
import { formatSessionArtifactRetentionSummary } from "../artifact-manifest.js";
|
|
6
|
+
import { classifyAgentBrowserFailureCategory } from "../categories.js";
|
|
7
|
+
import { detectConfirmationRequired } from "../confirmation.js";
|
|
8
|
+
import type {
|
|
9
|
+
AgentBrowserBatchResult,
|
|
10
|
+
AgentBrowserEnvelope,
|
|
11
|
+
AgentBrowserNextAction,
|
|
12
|
+
BatchFailurePresentationDetails,
|
|
13
|
+
BatchStepPresentationDetails,
|
|
14
|
+
SessionArtifactManifest,
|
|
15
|
+
ToolPresentation,
|
|
16
|
+
} from "../contracts.js";
|
|
17
|
+
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
18
|
+
import { stringifyModelFacing } from "./common.js";
|
|
19
|
+
import { buildArtifactVerificationSummary, classifyPresentationSuccessCategory, manifestHasNewNoticeWorthyEntries, type ArtifactRequestContext } from "./artifacts.js";
|
|
20
|
+
import { formatBatchStepCommand, getPresentationImages, getPresentationPaths, getPresentationText, isStringArray } from "./content.js";
|
|
21
|
+
import { buildPageChangeSummary } from "./navigation.js";
|
|
22
|
+
import { appendSelectorRecoveryHint } from "./errors.js";
|
|
23
|
+
|
|
24
|
+
export interface BuildNestedToolPresentationOptions {
|
|
25
|
+
artifactManifest?: SessionArtifactManifest;
|
|
26
|
+
artifactRequest?: ArtifactRequestContext;
|
|
27
|
+
args?: string[];
|
|
28
|
+
commandInfo: CommandInfo;
|
|
29
|
+
cwd: string;
|
|
30
|
+
envelope?: AgentBrowserEnvelope;
|
|
31
|
+
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
32
|
+
sessionName?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type BuildNestedToolPresentation = (options: BuildNestedToolPresentationOptions) => Promise<ToolPresentation>;
|
|
36
|
+
|
|
37
|
+
export function isAgentBrowserBatchResultArray(value: unknown): value is AgentBrowserBatchResult[] {
|
|
38
|
+
return Array.isArray(value) && value.every(isRecord);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isWaitTextAssertionCommand(command: string[] | undefined): boolean {
|
|
42
|
+
return command?.[0] === "wait" && command.includes("--text");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function buildWaitTextAssertionFailureNextAction(sessionName: string | undefined): AgentBrowserNextAction {
|
|
46
|
+
return {
|
|
47
|
+
id: "inspect-after-text-assertion-failure",
|
|
48
|
+
params: { args: withOptionalSessionArgs(sessionName, ["snapshot", "-i"]) },
|
|
49
|
+
reason: "Inspect the current page after the text assertion failed before concluding the expected text is absent.",
|
|
50
|
+
safety: "Read-only snapshot; use current refs or visible text from this page before retrying the assertion.",
|
|
51
|
+
tool: "agent_browser",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function mergePresentationNextActions(...groups: Array<AgentBrowserNextAction[] | undefined>): AgentBrowserNextAction[] | undefined {
|
|
56
|
+
const actions: AgentBrowserNextAction[] = [];
|
|
57
|
+
const seen = new Set<string>();
|
|
58
|
+
for (const group of groups) {
|
|
59
|
+
for (const action of group ?? []) {
|
|
60
|
+
if (seen.has(action.id)) continue;
|
|
61
|
+
actions.push(action);
|
|
62
|
+
seen.add(action.id);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return actions.length > 0 ? actions : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatBatchStepError(error: unknown): string {
|
|
69
|
+
const errorText = stringifyModelFacing(error).trim();
|
|
70
|
+
const formattedErrorText = errorText.length > 0 ? `Error: ${errorText}` : "Error: batch step failed.";
|
|
71
|
+
return appendSelectorRecoveryHint(formattedErrorText);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getBatchFailureDetails(steps: Array<{ details: BatchStepPresentationDetails }>): BatchFailurePresentationDetails | undefined {
|
|
75
|
+
const failedSteps = steps.filter((step) => step.details.success === false);
|
|
76
|
+
if (failedSteps.length === 0) return undefined;
|
|
77
|
+
const successCount = steps.length - failedSteps.length;
|
|
78
|
+
return {
|
|
79
|
+
failedStep: failedSteps[0].details,
|
|
80
|
+
failureCount: failedSteps.length,
|
|
81
|
+
successCount,
|
|
82
|
+
totalCount: steps.length,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hasModelFacingArgRedaction(args: string[] | undefined): boolean {
|
|
87
|
+
return args?.some((arg) => arg === "[REDACTED]" || arg.includes("%5BREDACTED%5D") || arg.includes("[REDACTED]")) === true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getStatefulCommandSensitiveValues(command: string[] | undefined): string[] {
|
|
91
|
+
if (!command) return [];
|
|
92
|
+
const tokens = extractCommandTokens(command);
|
|
93
|
+
const values: string[] = [];
|
|
94
|
+
if (tokens[0] === "cookies" && tokens[1] === "set" && tokens[3]) values.push(tokens[3]);
|
|
95
|
+
if (tokens[0] === "storage" && ["local", "session"].includes(tokens[1] ?? "") && tokens[2] === "set" && tokens[4]) values.push(tokens[4]);
|
|
96
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
97
|
+
const token = tokens[index];
|
|
98
|
+
if (token === "--password" && tokens[index + 1]) values.push(tokens[index + 1]);
|
|
99
|
+
else if (token?.startsWith("--password=")) values.push(token.slice("--password=".length));
|
|
100
|
+
}
|
|
101
|
+
return values.filter((value) => value.length > 0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function redactExactValues(value: unknown, sensitiveValues: string[]): unknown {
|
|
105
|
+
if (sensitiveValues.length === 0) return redactSensitiveValue(value);
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
let redacted = value;
|
|
108
|
+
for (const sensitiveValue of sensitiveValues) redacted = redacted.split(sensitiveValue).join("[REDACTED]");
|
|
109
|
+
return redactSensitiveText(redacted);
|
|
110
|
+
}
|
|
111
|
+
if (Array.isArray(value)) return value.map((item) => redactExactValues(item, sensitiveValues));
|
|
112
|
+
if (!isRecord(value)) return value;
|
|
113
|
+
return redactSensitiveValue(Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, redactExactValues(entryValue, sensitiveValues)])));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function buildBatchStepPresentation(options: {
|
|
117
|
+
artifactManifest?: SessionArtifactManifest;
|
|
118
|
+
artifactRequest?: ArtifactRequestContext;
|
|
119
|
+
buildNestedToolPresentation: BuildNestedToolPresentation;
|
|
120
|
+
cwd: string;
|
|
121
|
+
index: number;
|
|
122
|
+
item: AgentBrowserBatchResult;
|
|
123
|
+
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
124
|
+
sessionName?: string;
|
|
125
|
+
}): Promise<{ details: BatchStepPresentationDetails; presentation: ToolPresentation }> {
|
|
126
|
+
const { artifactManifest, artifactRequest, buildNestedToolPresentation, cwd, index, item, persistentArtifactStore, sessionName } = options;
|
|
127
|
+
const command = isStringArray(item.command) ? item.command : undefined;
|
|
128
|
+
const redactedCommand = command ? redactInvocationArgs(command) : undefined;
|
|
129
|
+
const commandText = formatBatchStepCommand(hasModelFacingArgRedaction(redactedCommand) ? redactedCommand : command, index);
|
|
130
|
+
|
|
131
|
+
if (item.success === false) {
|
|
132
|
+
const redactedErrorData = redactExactValues(item.error, getStatefulCommandSensitiveValues(command));
|
|
133
|
+
const errorText = formatBatchStepError(redactedErrorData);
|
|
134
|
+
const failureCategory = classifyAgentBrowserFailureCategory({
|
|
135
|
+
args: command,
|
|
136
|
+
command: command?.[0],
|
|
137
|
+
errorText,
|
|
138
|
+
});
|
|
139
|
+
const confirmationRequired = detectConfirmationRequired(item.error);
|
|
140
|
+
const nextActions = mergePresentationNextActions(
|
|
141
|
+
buildAgentBrowserNextActions({
|
|
142
|
+
args: command,
|
|
143
|
+
command: command?.[0],
|
|
144
|
+
confirmationId: confirmationRequired?.id,
|
|
145
|
+
failureCategory,
|
|
146
|
+
resultCategory: "failure",
|
|
147
|
+
}),
|
|
148
|
+
isWaitTextAssertionCommand(command) ? [buildWaitTextAssertionFailureNextAction(sessionName)] : undefined,
|
|
149
|
+
);
|
|
150
|
+
const presentation: ToolPresentation = {
|
|
151
|
+
content: [{ type: "text", text: errorText }],
|
|
152
|
+
failureCategory,
|
|
153
|
+
nextActions,
|
|
154
|
+
resultCategory: "failure",
|
|
155
|
+
summary: errorText,
|
|
156
|
+
};
|
|
157
|
+
return {
|
|
158
|
+
details: {
|
|
159
|
+
artifactVerification: presentation.artifactVerification,
|
|
160
|
+
artifacts: presentation.artifacts,
|
|
161
|
+
command: redactedCommand,
|
|
162
|
+
commandText,
|
|
163
|
+
data: redactedErrorData,
|
|
164
|
+
failureCategory,
|
|
165
|
+
index,
|
|
166
|
+
nextActions,
|
|
167
|
+
resultCategory: "failure",
|
|
168
|
+
success: false,
|
|
169
|
+
summary: errorText,
|
|
170
|
+
text: errorText,
|
|
171
|
+
},
|
|
172
|
+
presentation,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const presentation = await buildNestedToolPresentation({
|
|
177
|
+
artifactManifest,
|
|
178
|
+
artifactRequest,
|
|
179
|
+
commandInfo: parseCommandInfo(command ?? []),
|
|
180
|
+
cwd,
|
|
181
|
+
args: command,
|
|
182
|
+
envelope: { data: item.result, success: true },
|
|
183
|
+
persistentArtifactStore,
|
|
184
|
+
sessionName,
|
|
185
|
+
});
|
|
186
|
+
const fullOutputPaths = getPresentationPaths({
|
|
187
|
+
primaryPath: presentation.fullOutputPath,
|
|
188
|
+
secondaryPaths: presentation.fullOutputPaths,
|
|
189
|
+
});
|
|
190
|
+
const imagePaths = getPresentationPaths({
|
|
191
|
+
primaryPath: presentation.imagePath,
|
|
192
|
+
secondaryPaths: presentation.imagePaths,
|
|
193
|
+
});
|
|
194
|
+
const text = getPresentationText(presentation) || presentation.summary;
|
|
195
|
+
const nextActions = presentation.nextActions ?? buildAgentBrowserNextActions({
|
|
196
|
+
artifacts: presentation.artifacts,
|
|
197
|
+
args: command,
|
|
198
|
+
command: command?.[0],
|
|
199
|
+
resultCategory: "success",
|
|
200
|
+
savedFilePath: presentation.savedFilePath,
|
|
201
|
+
successCategory: presentation.successCategory,
|
|
202
|
+
});
|
|
203
|
+
const pageChangeSummary = buildPageChangeSummary({
|
|
204
|
+
artifacts: presentation.artifacts,
|
|
205
|
+
commandInfo: parseCommandInfo(command ?? []),
|
|
206
|
+
data: presentation.data,
|
|
207
|
+
nextActions,
|
|
208
|
+
savedFilePath: presentation.savedFilePath,
|
|
209
|
+
summary: presentation.summary,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
details: {
|
|
214
|
+
artifactVerification: presentation.artifactVerification,
|
|
215
|
+
artifacts: presentation.artifacts,
|
|
216
|
+
command: redactedCommand,
|
|
217
|
+
commandText,
|
|
218
|
+
data: presentation.data,
|
|
219
|
+
fullOutputPath: fullOutputPaths[0],
|
|
220
|
+
fullOutputPaths: fullOutputPaths.length > 0 ? fullOutputPaths : undefined,
|
|
221
|
+
imagePath: imagePaths[0],
|
|
222
|
+
imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
|
|
223
|
+
index,
|
|
224
|
+
nextActions,
|
|
225
|
+
pageChangeSummary,
|
|
226
|
+
resultCategory: "success",
|
|
227
|
+
savedFile: presentation.savedFile,
|
|
228
|
+
savedFilePath: presentation.savedFilePath,
|
|
229
|
+
success: true,
|
|
230
|
+
successCategory: classifyPresentationSuccessCategory({ artifactVerification: presentation.artifactVerification, artifacts: presentation.artifacts, savedFile: presentation.savedFile }),
|
|
231
|
+
summary: presentation.summary,
|
|
232
|
+
text,
|
|
233
|
+
},
|
|
234
|
+
presentation,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function buildBatchPresentation(options: {
|
|
239
|
+
artifactManifest?: SessionArtifactManifest;
|
|
240
|
+
artifactRequests?: Array<ArtifactRequestContext | undefined>;
|
|
241
|
+
buildNestedToolPresentation: BuildNestedToolPresentation;
|
|
242
|
+
cwd: string;
|
|
243
|
+
data: AgentBrowserBatchResult[];
|
|
244
|
+
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
245
|
+
sessionName?: string;
|
|
246
|
+
summary: string;
|
|
247
|
+
}): Promise<ToolPresentation> {
|
|
248
|
+
const { artifactRequests, buildNestedToolPresentation, cwd, data, persistentArtifactStore, sessionName, summary } = options;
|
|
249
|
+
const steps: Array<{ details: BatchStepPresentationDetails; presentation: ToolPresentation }> = [];
|
|
250
|
+
const protectedPersistentPaths: string[] = [];
|
|
251
|
+
let currentArtifactManifest = options.artifactManifest;
|
|
252
|
+
for (const [index, item] of data.entries()) {
|
|
253
|
+
const step = await buildBatchStepPresentation({
|
|
254
|
+
artifactManifest: currentArtifactManifest,
|
|
255
|
+
artifactRequest: artifactRequests?.[index],
|
|
256
|
+
buildNestedToolPresentation,
|
|
257
|
+
cwd,
|
|
258
|
+
index,
|
|
259
|
+
item,
|
|
260
|
+
persistentArtifactStore: persistentArtifactStore ? { ...persistentArtifactStore, protectedPaths: protectedPersistentPaths } : undefined,
|
|
261
|
+
sessionName,
|
|
262
|
+
});
|
|
263
|
+
steps.push(step);
|
|
264
|
+
currentArtifactManifest = step.presentation.artifactManifest ?? currentArtifactManifest;
|
|
265
|
+
protectedPersistentPaths.push(
|
|
266
|
+
...getPresentationPaths({
|
|
267
|
+
primaryPath: step.presentation.fullOutputPath,
|
|
268
|
+
secondaryPaths: step.presentation.fullOutputPaths,
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const batchFailure = getBatchFailureDetails(steps);
|
|
274
|
+
const images = steps.flatMap((step) => getPresentationImages(step.presentation));
|
|
275
|
+
const artifacts = steps.flatMap((step) => step.presentation.artifacts ?? []);
|
|
276
|
+
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
277
|
+
const fullOutputPaths = steps.flatMap((step) => getPresentationPaths({
|
|
278
|
+
primaryPath: step.presentation.fullOutputPath,
|
|
279
|
+
secondaryPaths: step.presentation.fullOutputPaths,
|
|
280
|
+
}));
|
|
281
|
+
const imagePaths = steps.flatMap((step) => getPresentationPaths({
|
|
282
|
+
primaryPath: step.presentation.imagePath,
|
|
283
|
+
secondaryPaths: step.presentation.imagePaths,
|
|
284
|
+
}));
|
|
285
|
+
const redactedBatchData = steps.map(({ details }) => (
|
|
286
|
+
details.success
|
|
287
|
+
? { command: details.command, result: details.data, success: true }
|
|
288
|
+
: { command: details.command, error: details.text, success: false }
|
|
289
|
+
));
|
|
290
|
+
const stepText = steps.length === 0
|
|
291
|
+
? "(no batch steps)"
|
|
292
|
+
: steps
|
|
293
|
+
.map(({ details, presentation }) => {
|
|
294
|
+
const inlineImageCount = getPresentationImages(presentation).length;
|
|
295
|
+
const status = details.success ? "succeeded" : "failed";
|
|
296
|
+
const lines = [`Step ${details.index + 1} — ${details.commandText} (${status})`];
|
|
297
|
+
if (details.text.length > 0) lines.push(details.text);
|
|
298
|
+
if (inlineImageCount > 0) lines.push(`(${inlineImageCount} inline image attachment${inlineImageCount === 1 ? "" : "s"} below)`);
|
|
299
|
+
return lines.join("\n");
|
|
300
|
+
})
|
|
301
|
+
.join("\n\n");
|
|
302
|
+
const failureHeader = batchFailure === undefined
|
|
303
|
+
? undefined
|
|
304
|
+
: [
|
|
305
|
+
summary,
|
|
306
|
+
`First failing step: ${batchFailure.failedStep.index + 1} — ${batchFailure.failedStep.commandText}`,
|
|
307
|
+
batchFailure.failureCount > 1 ? `${batchFailure.failureCount} steps failed. See the per-step results below.` : "See the per-step results below.",
|
|
308
|
+
].join("\n");
|
|
309
|
+
const text = failureHeader ? `${failureHeader}\n\n${stepText}` : stepText;
|
|
310
|
+
const artifactRetentionSummary = currentArtifactManifest ? formatSessionArtifactRetentionSummary(currentArtifactManifest) : undefined;
|
|
311
|
+
const contentText = artifactRetentionSummary && manifestHasNewNoticeWorthyEntries(options.artifactManifest, currentArtifactManifest)
|
|
312
|
+
? `${text}\n\n${artifactRetentionSummary}`
|
|
313
|
+
: text;
|
|
314
|
+
const nextActions = batchFailure
|
|
315
|
+
? batchFailure.failedStep.nextActions
|
|
316
|
+
: buildAgentBrowserNextActions({ artifacts, command: "batch", resultCategory: "success" });
|
|
317
|
+
const changedSteps = steps.map((step) => step.details).filter((details) => details.pageChangeSummary !== undefined);
|
|
318
|
+
const pageChangeSummary = artifacts.length > 0
|
|
319
|
+
? buildPageChangeSummary({
|
|
320
|
+
artifacts,
|
|
321
|
+
commandInfo: { command: "batch" },
|
|
322
|
+
data,
|
|
323
|
+
nextActions,
|
|
324
|
+
summary,
|
|
325
|
+
})
|
|
326
|
+
: changedSteps.length > 0
|
|
327
|
+
? {
|
|
328
|
+
changeType: "mutation" as const,
|
|
329
|
+
command: "batch",
|
|
330
|
+
nextActionIds: nextActions?.map((action) => action.id),
|
|
331
|
+
summary: `batch → mutation → ${changedSteps.length} changed step${changedSteps.length === 1 ? "" : "s"}`,
|
|
332
|
+
}
|
|
333
|
+
: undefined;
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
artifactManifest: currentArtifactManifest,
|
|
337
|
+
artifactRetentionSummary,
|
|
338
|
+
artifactVerification,
|
|
339
|
+
artifacts: artifacts.length > 0 ? artifacts : undefined,
|
|
340
|
+
batchFailure,
|
|
341
|
+
batchSteps: steps.map((step) => step.details),
|
|
342
|
+
content: [{ type: "text", text: contentText }, ...images],
|
|
343
|
+
failureCategory: batchFailure?.failedStep.failureCategory,
|
|
344
|
+
data: redactedBatchData,
|
|
345
|
+
fullOutputPath: fullOutputPaths[0],
|
|
346
|
+
fullOutputPaths: fullOutputPaths.length > 0 ? fullOutputPaths : undefined,
|
|
347
|
+
imagePath: imagePaths[0],
|
|
348
|
+
imagePaths: imagePaths.length > 0 ? imagePaths : undefined,
|
|
349
|
+
nextActions,
|
|
350
|
+
pageChangeSummary,
|
|
351
|
+
resultCategory: batchFailure ? "failure" : "success",
|
|
352
|
+
successCategory: batchFailure ? undefined : classifyPresentationSuccessCategory({ artifactVerification, artifacts }),
|
|
353
|
+
summary,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
|
|
7
|
+
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
8
|
+
import { stringifyUnknown, truncateText } from "../text.js";
|
|
9
|
+
|
|
10
|
+
export function stringifyModelFacing(value: unknown): string {
|
|
11
|
+
return stringifyUnknown(redactSensitiveValue(value));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseJsonPreviewString(value: string): unknown {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(trimmed) as unknown;
|
|
19
|
+
} catch {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function redactModelFacingText(text: string): string {
|
|
25
|
+
const parsed = parseJsonPreviewString(text);
|
|
26
|
+
if (parsed !== text) {
|
|
27
|
+
return stringifyModelFacing(parsed);
|
|
28
|
+
}
|
|
29
|
+
return redactSensitiveText(text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function redactModelFacingTextIfSensitive(text: string): string {
|
|
33
|
+
return /(?:@|\b(?:api[_-]?key|auth|authorization|basic|bearer|cookie|pass(?:word)?|secret|session[_-]?id|token)\b)/i.test(text)
|
|
34
|
+
? redactModelFacingText(text)
|
|
35
|
+
: text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getArrayField(data: Record<string, unknown>, key: string): unknown[] | undefined {
|
|
39
|
+
return Array.isArray(data[key]) ? data[key] : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getStringField(data: Record<string, unknown>, key: string): string | undefined {
|
|
43
|
+
const value = data[key];
|
|
44
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function formatCount(count: number, singular: string, plural = `${singular}s`): string {
|
|
48
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function firstLine(value: string, maxChars = 160): string {
|
|
52
|
+
return truncateText(value.split("\n", 1)[0] ?? value, maxChars);
|
|
53
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
|
|
7
|
+
import type { ToolPresentation } from "../contracts.js";
|
|
8
|
+
|
|
9
|
+
export function isStringArray(value: unknown): value is string[] {
|
|
10
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getPresentationText(presentation: ToolPresentation): string {
|
|
14
|
+
return presentation.content
|
|
15
|
+
.filter((part): part is Extract<ToolPresentation["content"][number], { type: "text" }> => part.type === "text")
|
|
16
|
+
.map((part) => part.text.trim())
|
|
17
|
+
.filter((text) => text.length > 0)
|
|
18
|
+
.join("\n\n");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getPresentationImages(presentation: ToolPresentation): Array<Extract<ToolPresentation["content"][number], { type: "image" }>> {
|
|
22
|
+
return presentation.content.filter(
|
|
23
|
+
(part): part is Extract<ToolPresentation["content"][number], { type: "image" }> => part.type === "image",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getPresentationPaths(options: {
|
|
28
|
+
primaryPath?: string;
|
|
29
|
+
secondaryPaths?: string[];
|
|
30
|
+
}): string[] {
|
|
31
|
+
return options.secondaryPaths ?? (options.primaryPath ? [options.primaryPath] : []);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatBatchStepCommand(command: string[] | undefined, index: number): string {
|
|
35
|
+
return command && command.length > 0 ? command.join(" ") : `step-${index + 1}`;
|
|
36
|
+
}
|