pi-agent-browser-native 0.6.16 → 0.7.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 +18 -1
- package/README.md +59 -165
- package/dist/extensions/agent-browser/index.js +634 -501
- package/dist/extensions/agent-browser/lib/browser-transcript.js +33 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +8 -4
- package/dist/extensions/agent-browser/lib/execution-cwd.js +66 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +2 -213
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/params.js +146 -142
- package/dist/extensions/agent-browser/lib/input-modes/script.js +64 -92
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +170 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +27 -20
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +26 -13
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +21 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +41 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +3 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +79 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -4
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +19 -52
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +76 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +91 -278
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +9 -9
- package/dist/extensions/agent-browser/lib/playbook.js +46 -25
- package/dist/extensions/agent-browser/lib/process-identity.js +22 -6
- package/dist/extensions/agent-browser/lib/process.js +16 -13
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +2 -2
- package/dist/extensions/agent-browser/lib/results/next-actions.js +1 -2
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +25 -4
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +6 -2
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +39 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +3 -3
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +41 -1
- package/dist/extensions/agent-browser/lib/results/presentation.js +16 -11
- package/dist/extensions/agent-browser/lib/runtime.js +18 -6
- package/dist/extensions/agent-browser/lib/session-page-state.js +3 -4
- package/dist/extensions/agent-browser/lib/tool-surface.js +131 -0
- package/dist/extensions/agent-browser/script-worker.js +4 -1
- package/docs/ARCHITECTURE.md +47 -61
- package/docs/COMMAND_REFERENCE.md +78 -137
- package/docs/ELECTRON.md +51 -49
- package/docs/RELEASE.md +31 -15
- package/docs/REQUIREMENTS.md +18 -14
- package/docs/SUPPORT_MATRIX.md +33 -15
- package/docs/TOOL_CONTRACT.md +218 -401
- package/package.json +4 -4
- package/scripts/doctor.mjs +2 -2
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES,
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES, createAgentBrowserScriptCloseArgs, isAgentBrowserScriptSessionName, } from "../input-modes/script.js";
|
|
3
3
|
import { isRecord } from "../parsing.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { redactSensitiveText } from "../runtime.js";
|
|
5
|
+
import { attachInlineImage } from "../results/presentation/artifacts.js";
|
|
6
|
+
import { projectAgentBrowserObservation } from "../results/presentation/content.js";
|
|
6
7
|
import { redactPresentationData } from "../results/presentation/diagnostics.js";
|
|
7
|
-
import { truncateText } from "../results/text.js";
|
|
8
8
|
const SCRIPT_SESSION_ENTRY_TYPE = "agent-browser-script-session";
|
|
9
|
-
|
|
10
|
-
const SCRIPT_BROWSER_TEXT_MAX_CHARS = 8_192;
|
|
9
|
+
// Retire outstanding pre-0.7 isolated-session leases when an old transcript resumes.
|
|
11
10
|
export function getScriptSessionLeasesFromBranch(branch) {
|
|
12
11
|
const leases = new Map();
|
|
13
12
|
for (const entry of branch) {
|
|
@@ -16,284 +15,98 @@ export function getScriptSessionLeasesFromBranch(branch) {
|
|
|
16
15
|
const { cleanup, closeCommandArgs, launchAttempted, sessionName } = entry.data;
|
|
17
16
|
if (!isAgentBrowserScriptSessionName(sessionName))
|
|
18
17
|
continue;
|
|
19
|
-
const
|
|
20
|
-
if ((cleanup !== "active" && cleanup !== "closed" && cleanup !== "failed")
|
|
21
|
-
||
|
|
22
|
-
|| !
|
|
23
|
-
|| closeCommandArgs.length !== expectedCloseCommandArgs.length
|
|
24
|
-
|| !closeCommandArgs.every((token, index) => token === expectedCloseCommandArgs[index]))
|
|
18
|
+
const expected = createAgentBrowserScriptCloseArgs(sessionName);
|
|
19
|
+
if ((cleanup !== "active" && cleanup !== "closed" && cleanup !== "failed") || launchAttempted !== true
|
|
20
|
+
|| !Array.isArray(closeCommandArgs) || closeCommandArgs.length !== expected.length
|
|
21
|
+
|| !closeCommandArgs.every((token, index) => token === expected[index]))
|
|
25
22
|
continue;
|
|
26
|
-
leases.set(sessionName, { cleanup, closeCommandArgs:
|
|
23
|
+
leases.set(sessionName, { cleanup, closeCommandArgs: expected, launchAttempted: true, sessionName });
|
|
27
24
|
}
|
|
28
25
|
return leases;
|
|
29
26
|
}
|
|
30
27
|
export function appendScriptSessionLease(pi, sessionName, cleanup) {
|
|
31
|
-
pi.appendEntry(SCRIPT_SESSION_ENTRY_TYPE, {
|
|
32
|
-
cleanup,
|
|
33
|
-
closeCommandArgs: createAgentBrowserScriptCloseArgs(sessionName),
|
|
34
|
-
launchAttempted: true,
|
|
35
|
-
sessionName,
|
|
36
|
-
});
|
|
28
|
+
pi.appendEntry(SCRIPT_SESSION_ENTRY_TYPE, { cleanup, closeCommandArgs: createAgentBrowserScriptCloseArgs(sessionName), launchAttempted: true, sessionName });
|
|
37
29
|
}
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
&& (entry.storageScope === "persistent-session" || entry.storageScope === "process-temp")
|
|
45
|
-
&& (entry.retentionState === "live" || entry.retentionState === "ephemeral")))
|
|
46
|
-
return undefined;
|
|
47
|
-
let handle;
|
|
48
|
-
try {
|
|
49
|
-
handle = await open(path, "r");
|
|
50
|
-
const stats = await handle.stat();
|
|
51
|
-
if (!stats.isFile() || stats.size < 0 || stats.size > AGENT_BROWSER_SCRIPT_SPILL_MAX_BYTES)
|
|
52
|
-
return undefined;
|
|
53
|
-
const buffer = Buffer.alloc(stats.size);
|
|
54
|
-
let offset = 0;
|
|
55
|
-
while (offset < buffer.length) {
|
|
56
|
-
const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset);
|
|
57
|
-
if (bytesRead === 0)
|
|
58
|
-
break;
|
|
59
|
-
offset += bytesRead;
|
|
60
|
-
}
|
|
61
|
-
const text = buffer.subarray(0, offset).toString("utf8");
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(text);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
finally {
|
|
73
|
-
await handle?.close().catch(() => undefined);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function getToolResultText(result) {
|
|
77
|
-
return result.content
|
|
78
|
-
.filter((item) => item.type === "text")
|
|
79
|
-
.map((item) => item.text)
|
|
80
|
-
.join("\n\n");
|
|
81
|
-
}
|
|
82
|
-
function stripOwnedScriptSessionArgs(args, sessionName) {
|
|
83
|
-
if (!isAgentBrowserScriptSessionName(sessionName))
|
|
84
|
-
return args;
|
|
85
|
-
if (args[0] === "--namespace" && args[1] === "" && args[2] === "--session" && args[3] === sessionName)
|
|
86
|
-
return args.slice(4);
|
|
87
|
-
return args[0] === "--session" && args[1] === sessionName ? args.slice(2) : args;
|
|
88
|
-
}
|
|
89
|
-
function getScriptCompatibleNextActions(value, sessionName) {
|
|
90
|
-
if (!Array.isArray(value))
|
|
91
|
-
return undefined;
|
|
92
|
-
const actions = value.flatMap((action) => {
|
|
93
|
-
if (!isRecord(action))
|
|
94
|
-
return [];
|
|
95
|
-
if (!isRecord(action.params))
|
|
96
|
-
return typeof action.artifactPath === "string" ? [action] : [];
|
|
97
|
-
if (Object.keys(action.params).some((key) => key !== "args" && key !== "stdin"))
|
|
98
|
-
return [];
|
|
99
|
-
if (!Array.isArray(action.params.args) || action.params.args.some((token) => typeof token !== "string"))
|
|
100
|
-
return [];
|
|
101
|
-
if (action.params.stdin !== undefined && typeof action.params.stdin !== "string")
|
|
102
|
-
return [];
|
|
103
|
-
const normalizedParams = {
|
|
104
|
-
args: stripOwnedScriptSessionArgs(action.params.args, sessionName),
|
|
105
|
-
...(typeof action.params.stdin === "string" ? { stdin: action.params.stdin } : {}),
|
|
106
|
-
};
|
|
107
|
-
if (!validateAgentBrowserScriptBrowserParams(normalizedParams).params)
|
|
108
|
-
return [];
|
|
109
|
-
return [{ ...action, params: normalizedParams }];
|
|
110
|
-
});
|
|
111
|
-
return actions.length > 0 ? actions : undefined;
|
|
112
|
-
}
|
|
113
|
-
function scriptBrowserEnvelopeFitsIpc(envelope) {
|
|
114
|
-
try {
|
|
115
|
-
return Buffer.byteLength(JSON.stringify({ envelope, id: Number.MAX_SAFE_INTEGER, type: "response" }), "utf8") + 1 <= AGENT_BROWSER_SCRIPT_IPC_MESSAGE_MAX_BYTES;
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function buildOversizedScriptBrowserEnvelope() {
|
|
122
|
-
const text = "Browser result exceeds the script IPC response limit; narrow the inner browser call output.";
|
|
123
|
-
return {
|
|
124
|
-
data: null,
|
|
125
|
-
details: { failureCategory: "upstream-error", resultCategory: "failure" },
|
|
126
|
-
error: text,
|
|
127
|
-
failureCategory: "upstream-error",
|
|
128
|
-
ok: false,
|
|
129
|
-
resultCategory: "failure",
|
|
130
|
-
summary: text,
|
|
131
|
-
text,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
export async function buildScriptBrowserEnvelope(result, args, scriptSessionName) {
|
|
135
|
-
const details = isRecord(result.details) ? result.details : undefined;
|
|
136
|
-
const fullData = await readVerifiedScriptSpill(result);
|
|
137
|
-
const commandInfo = parseCommandInfo(args);
|
|
138
|
-
const data = redactPresentationData(commandInfo, fullData ?? details?.data ?? null);
|
|
139
|
-
const resultCategory = details?.resultCategory === "failure" || result.isError === true ? "failure" : "success";
|
|
140
|
-
const text = truncateText(redactSensitiveText(getToolResultText(result)), SCRIPT_BROWSER_TEXT_MAX_CHARS);
|
|
141
|
-
const summary = truncateText(redactSensitiveText(typeof details?.summary === "string" ? details.summary : text.split("\n", 1)[0] || "Browser call completed."), SCRIPT_BROWSER_SUMMARY_MAX_CHARS);
|
|
142
|
-
const compatibleNextActions = getScriptCompatibleNextActions(details?.nextActions, scriptSessionName);
|
|
143
|
-
const redactedNextActions = getScriptCompatibleNextActions(redactPresentationData(commandInfo, compatibleNextActions), undefined);
|
|
144
|
-
const failureCategory = resultCategory === "failure" && typeof details?.failureCategory === "string"
|
|
145
|
-
? details.failureCategory
|
|
146
|
-
: undefined;
|
|
147
|
-
const successCategory = resultCategory === "success" && typeof details?.successCategory === "string"
|
|
148
|
-
? details.successCategory
|
|
149
|
-
: undefined;
|
|
150
|
-
const envelopeDetails = redactPresentationData(commandInfo, {
|
|
151
|
-
artifactVerification: details?.artifactVerification,
|
|
152
|
-
artifacts: details?.artifacts,
|
|
153
|
-
failureCategory,
|
|
154
|
-
pageChangeSummary: details?.pageChangeSummary,
|
|
155
|
-
resultCategory,
|
|
156
|
-
successCategory,
|
|
157
|
-
});
|
|
158
|
-
const envelope = {
|
|
159
|
-
data,
|
|
160
|
-
details: isRecord(envelopeDetails) ? envelopeDetails : { resultCategory },
|
|
161
|
-
error: resultCategory === "failure" ? summary : undefined,
|
|
162
|
-
failureCategory,
|
|
163
|
-
nextActions: Array.isArray(redactedNextActions) ? redactedNextActions : undefined,
|
|
164
|
-
ok: resultCategory === "success",
|
|
165
|
-
resultCategory,
|
|
166
|
-
successCategory,
|
|
167
|
-
summary,
|
|
168
|
-
text,
|
|
169
|
-
};
|
|
170
|
-
return scriptBrowserEnvelopeFitsIpc(envelope) ? envelope : buildOversizedScriptBrowserEnvelope();
|
|
171
|
-
}
|
|
172
|
-
function collectUniqueArtifacts(results) {
|
|
173
|
-
const records = results.flatMap((result) => {
|
|
174
|
-
const details = isRecord(result.details) ? result.details : undefined;
|
|
175
|
-
return Array.isArray(details?.artifacts) ? details.artifacts.filter(isRecord) : [];
|
|
176
|
-
});
|
|
177
|
-
const unique = new Map(records.map((record) => [String(record.absolutePath ?? record.path ?? JSON.stringify(record)), record]));
|
|
178
|
-
return unique.size > 0 ? [...unique.values()] : undefined;
|
|
179
|
-
}
|
|
180
|
-
function collectScriptArtifactVerification(results) {
|
|
181
|
-
const entries = results.flatMap((result) => {
|
|
182
|
-
const details = isRecord(result.details) ? result.details : undefined;
|
|
183
|
-
const verification = isRecord(details?.artifactVerification) ? details.artifactVerification : undefined;
|
|
184
|
-
return Array.isArray(verification?.artifacts) ? verification.artifacts.filter(isRecord) : [];
|
|
185
|
-
});
|
|
186
|
-
const unique = [...new Map(entries.map((entry) => [String(entry.absolutePath ?? entry.path ?? JSON.stringify(entry)), entry])).values()];
|
|
187
|
-
if (unique.length === 0)
|
|
188
|
-
return undefined;
|
|
189
|
-
const count = (state) => unique.filter((entry) => entry.state === state).length;
|
|
190
|
-
return {
|
|
191
|
-
artifacts: unique,
|
|
192
|
-
missingCount: count("missing"),
|
|
193
|
-
pendingCount: count("pending"),
|
|
194
|
-
unverifiedCount: count("unverified"),
|
|
195
|
-
verified: unique.every((entry) => entry.state === "verified"),
|
|
196
|
-
verifiedCount: count("verified"),
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function getLatestScriptResultDetail(results, key) {
|
|
200
|
-
for (let index = results.length - 1; index >= 0; index -= 1) {
|
|
201
|
-
const rawDetails = results[index]?.details;
|
|
202
|
-
const details = isRecord(rawDetails) ? rawDetails : undefined;
|
|
203
|
-
if (details?.[key] !== undefined)
|
|
204
|
-
return details[key];
|
|
205
|
-
}
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
function buildScriptCloseNextAction(sessionName) {
|
|
30
|
+
export function createBrowserCodeOutput() {
|
|
31
|
+
const images = new Map();
|
|
32
|
+
const selected = new Map();
|
|
33
|
+
const receipts = new Map();
|
|
34
|
+
const fileArtifacts = new Map();
|
|
35
|
+
let selectedBytes = 0;
|
|
209
36
|
return {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
successfulCallCount,
|
|
284
|
-
timedOut: options.run.timedOut,
|
|
285
|
-
},
|
|
286
|
-
scriptSession: options.sessionName ? {
|
|
287
|
-
cleanup: options.cleanupError ? "failed" : "closed",
|
|
288
|
-
closeCommandArgs: createAgentBrowserScriptCloseArgs(options.sessionName),
|
|
289
|
-
...(options.cleanupError ? { error: redactSensitiveText(options.cleanupError) } : {}),
|
|
290
|
-
launchAttempted: true,
|
|
291
|
-
sessionName: options.sessionName,
|
|
292
|
-
} : undefined,
|
|
293
|
-
scriptSteps: steps,
|
|
294
|
-
successCategory: failed ? undefined : "completed",
|
|
295
|
-
summary,
|
|
37
|
+
async observe(result) {
|
|
38
|
+
const details = isRecord(result.details) ? result.details : {};
|
|
39
|
+
const observation = projectAgentBrowserObservation(details, result.isError !== true);
|
|
40
|
+
for (const entry of observation.artifactVerification?.artifacts ?? [])
|
|
41
|
+
receipts.set(entry.absolutePath ?? entry.path, entry);
|
|
42
|
+
for (const artifact of observation.artifacts ?? [])
|
|
43
|
+
fileArtifacts.set(artifact.absolutePath, artifact);
|
|
44
|
+
if (observation.imageObservations) {
|
|
45
|
+
observation.imageObservations = await Promise.all(observation.imageObservations.map(async (image) => {
|
|
46
|
+
const file = await stat(image.path);
|
|
47
|
+
const id = `image-${images.size + 1}`;
|
|
48
|
+
images.set(id, { observation: image, size: file.size, mtime: file.mtimeMs });
|
|
49
|
+
return { ...image, id };
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return observation;
|
|
53
|
+
},
|
|
54
|
+
async emitImage(value) {
|
|
55
|
+
const id = isRecord(value) && typeof value.id === "string" ? value.id : undefined;
|
|
56
|
+
const image = id ? images.get(id) : undefined;
|
|
57
|
+
if (!id || !image)
|
|
58
|
+
throw new Error("emitImage expects an imageObservations handle returned by browser() in this code call.");
|
|
59
|
+
if (selected.has(id))
|
|
60
|
+
return;
|
|
61
|
+
if (selected.size >= 8 || selectedBytes + image.size > 20 * 1_024 * 1_024)
|
|
62
|
+
throw new Error("Selected images exceed the code output limit (8 images / 20 MiB). Emit fewer images.");
|
|
63
|
+
const file = await stat(image.observation.path);
|
|
64
|
+
if (!file.isFile() || file.size !== image.size || file.mtimeMs !== image.mtime)
|
|
65
|
+
throw new Error("The selected image changed since capture. Capture it again before emitting it.");
|
|
66
|
+
const presentation = await attachInlineImage({ content: [], summary: "Selected browser image" }, image.observation.path);
|
|
67
|
+
if (!presentation.content.some(item => item.type === "image")) {
|
|
68
|
+
throw new Error(presentation.content.filter(item => item.type === "text").map(item => item.text).join("\n") || "The selected image could not be attached.");
|
|
69
|
+
}
|
|
70
|
+
selected.set(id, presentation.content.filter(item => item.type === "image"));
|
|
71
|
+
selectedBytes += file.size;
|
|
72
|
+
},
|
|
73
|
+
async finish(run, sessionName, namespace) {
|
|
74
|
+
let data;
|
|
75
|
+
let outputError;
|
|
76
|
+
try {
|
|
77
|
+
data = redactPresentationData({ command: "code" }, run.data);
|
|
78
|
+
if (data !== undefined && Buffer.byteLength(JSON.stringify(data), "utf8") > AGENT_BROWSER_SCRIPT_FINAL_OUTPUT_MAX_BYTES)
|
|
79
|
+
throw new Error("oversized");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
data = undefined;
|
|
83
|
+
outputError = "Code output could not be rendered as bounded JSON.";
|
|
84
|
+
}
|
|
85
|
+
const failureCategory = outputError ? "validation-error" : run.failureCategory
|
|
86
|
+
?? (run.rejectedCallCount > 0 ? "validation-error" : undefined);
|
|
87
|
+
const success = run.ok && failureCategory === undefined;
|
|
88
|
+
const artifacts = [...receipts.values()];
|
|
89
|
+
const count = (state) => artifacts.filter(entry => entry.state === state).length;
|
|
90
|
+
const artifactVerification = artifacts.length ? {
|
|
91
|
+
artifacts, missingCount: count("missing"), pendingCount: count("pending"), unverifiedCount: count("unverified"),
|
|
92
|
+
verifiedCount: count("verified"), verified: artifacts.every(entry => entry.state === "verified"),
|
|
93
|
+
} : undefined;
|
|
94
|
+
const observation = {
|
|
95
|
+
success, resultCategory: success ? "success" : "failure", failureCategory, data,
|
|
96
|
+
error: outputError ?? (run.error ? redactSensitiveText(run.error) : undefined),
|
|
97
|
+
sessionName, namespace,
|
|
98
|
+
codeRun: { callCount: run.callCount, emitCount: run.emitCount, failedCallCount: run.steps.filter(step => !step.ok).length, rejectedCallCount: run.rejectedCallCount, aborted: run.aborted, timedOut: run.timedOut },
|
|
99
|
+
failures: run.failures?.length ? run.failures : undefined,
|
|
100
|
+
artifactVerification,
|
|
101
|
+
artifacts: fileArtifacts.size ? [...fileArtifacts.values()] : undefined,
|
|
102
|
+
imageObservations: [...selected.keys()].map(id => ({ ...images.get(id).observation, id })),
|
|
103
|
+
};
|
|
104
|
+
const summary = success ? `Browser code completed (${run.callCount} calls).` : "Browser code failed.";
|
|
105
|
+
return {
|
|
106
|
+
content: [...selected.values()].flat(),
|
|
107
|
+
details: { ...observation, codeSteps: run.steps, summary },
|
|
108
|
+
isError: !success,
|
|
109
|
+
};
|
|
296
110
|
},
|
|
297
|
-
isError: failed,
|
|
298
111
|
};
|
|
299
112
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getKeybindings, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
+
import { BROWSER_RESULT_TOOLS } from "./browser-transcript.js";
|
|
2
3
|
import { compileAgentBrowserElectron } from "./input-modes/electron.js";
|
|
3
|
-
import {
|
|
4
|
+
import { compileAgentBrowserQaPreset } from "./input-modes/job.js";
|
|
4
5
|
import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup } from "./input-modes/lookups.js";
|
|
5
6
|
import { compileAgentBrowserSemanticAction } from "./input-modes/semantic-action.js";
|
|
6
7
|
import { isRecord } from "./parsing.js";
|
|
@@ -95,14 +96,12 @@ function formatVisualTruncationNotice(remainingLines, totalLines, theme, width)
|
|
|
95
96
|
return truncateToWidth(notice, Math.max(0, width));
|
|
96
97
|
}
|
|
97
98
|
function getStructuredModeInvocation(input) {
|
|
98
|
-
if (typeof input.
|
|
99
|
-
return { mode: "
|
|
99
|
+
if (typeof input.code === "string")
|
|
100
|
+
return { mode: "code", rawArgs: [], scriptSource: input.code };
|
|
100
101
|
if (Array.isArray(input.args))
|
|
101
102
|
return { rawArgs: input.args.filter((value) => typeof value === "string") };
|
|
102
103
|
if (input.semanticAction !== undefined)
|
|
103
104
|
return { mode: "semanticAction", rawArgs: compileAgentBrowserSemanticAction(input.semanticAction).compiled?.args ?? [] };
|
|
104
|
-
if (input.job !== undefined)
|
|
105
|
-
return { mode: "job", rawArgs: compileAgentBrowserJob(input.job).compiled?.args ?? [] };
|
|
106
105
|
if (input.qa !== undefined)
|
|
107
106
|
return { mode: "qa", rawArgs: compileAgentBrowserQaPreset(input.qa).compiled?.args ?? [] };
|
|
108
107
|
if (input.sourceLookup !== undefined)
|
|
@@ -137,9 +136,10 @@ export function formatAgentBrowserRenderCall(args, theme, expanded = false) {
|
|
|
137
136
|
const invocationPreview = scriptSource === undefined
|
|
138
137
|
? formatInvocationPreview(rawArgs)
|
|
139
138
|
: formatScriptSourceForDisplay(scriptSource, expanded);
|
|
140
|
-
let text = theme.fg("toolTitle", theme.bold("agent_browser"));
|
|
139
|
+
let text = theme.fg("toolTitle", theme.bold(mode === "code" ? "agent_browser_code" : "agent_browser"));
|
|
141
140
|
if (mode) {
|
|
142
|
-
|
|
141
|
+
if (mode !== "code")
|
|
142
|
+
text += ` ${theme.fg("accent", mode)}`;
|
|
143
143
|
if (scriptSource !== undefined && expanded) {
|
|
144
144
|
text += `\n${theme.fg("dim", "Source:")}\n${theme.fg("accent", invocationPreview)}`;
|
|
145
145
|
}
|
|
@@ -220,9 +220,9 @@ function appendModelVisibleFailureCategoryNotice(content, notice) {
|
|
|
220
220
|
: item);
|
|
221
221
|
}
|
|
222
222
|
export function buildAgentBrowserToolResultPatch(event) {
|
|
223
|
-
if (event.toolName
|
|
223
|
+
if (!BROWSER_RESULT_TOOLS.has(event.toolName))
|
|
224
224
|
return undefined;
|
|
225
|
-
const preservesParseableJson = agentBrowserToolResultRequestedJson(event) && agentBrowserToolResultHasParseableJsonContent(event.content);
|
|
225
|
+
const preservesParseableJson = (event.toolName === "agent_browser_code" || agentBrowserToolResultRequestedJson(event)) && agentBrowserToolResultHasParseableJsonContent(event.content);
|
|
226
226
|
const notice = preservesParseableJson ? undefined : formatModelVisibleFailureCategoryNotice(event.details);
|
|
227
227
|
const content = notice ? appendModelVisibleFailureCategoryNotice(event.content, notice) : undefined;
|
|
228
228
|
const shouldMarkError = isRecord(event.details) && event.details.resultCategory === "failure" && event.isError !== true;
|