pi-agent-browser-native 0.8.3 → 0.9.1
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 +36 -0
- package/README.md +12 -11
- package/dist/extensions/agent-browser/index.js +96 -320
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +1 -1
- package/dist/extensions/agent-browser/lib/argv-grammar.js +3 -3
- package/dist/extensions/agent-browser/lib/browser-journal.js +33 -12
- package/dist/extensions/agent-browser/lib/browser-transcript.js +1 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +0 -3
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -13
- package/dist/extensions/agent-browser/lib/config.js +3 -48
- package/dist/extensions/agent-browser/lib/electron/launch.js +4 -4
- package/dist/extensions/agent-browser/lib/execution-cwd.js +0 -16
- package/dist/extensions/agent-browser/lib/input-modes/params.js +2 -3
- package/dist/extensions/agent-browser/lib/input-modes/script.js +28 -6
- package/dist/extensions/agent-browser/lib/input-modes/types.js +2 -2
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +1 -4
- package/dist/extensions/agent-browser/lib/native-output.js +46 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +0 -9
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-merge.js +22 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +2 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/execution-queue.js +86 -0
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +31 -17
- package/dist/extensions/agent-browser/lib/process.js +0 -3
- package/dist/extensions/agent-browser/lib/prompt-policy.js +16 -12
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +2 -2
- package/dist/extensions/agent-browser/lib/results/network-routes.js +1 -1
- package/dist/extensions/agent-browser/lib/results/network.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +4 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +15 -8
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +2 -2
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +1 -1
- package/dist/extensions/agent-browser/lib/runtime.js +4 -2
- package/dist/extensions/agent-browser/lib/session-page-state.js +1 -1
- package/dist/extensions/agent-browser/lib/tool-surface.js +39 -11
- package/dist/extensions/agent-browser/lib/web-search.js +100 -79
- package/docs/ARCHITECTURE.md +17 -19
- package/docs/COMMAND_REFERENCE.md +6 -2
- package/docs/RELEASE.md +26 -20
- package/docs/REQUIREMENTS.md +5 -5
- package/docs/SESSION_STORAGE.md +4 -4
- package/docs/SUPPORT_MATRIX.md +15 -13
- package/docs/TOOL_CONTRACT.md +12 -8
- package/package.json +6 -6
- package/scripts/doctor.mjs +12 -4
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getKeybindings, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
2
|
-
import { BROWSER_RESULT_TOOLS } from "./browser-transcript.js";
|
|
3
2
|
import { compileAgentBrowserElectron } from "./input-modes/electron.js";
|
|
4
3
|
import { compileAgentBrowserQaPreset } from "./input-modes/job.js";
|
|
5
4
|
import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup } from "./input-modes/lookups.js";
|
|
@@ -158,7 +157,7 @@ export function formatAgentBrowserRenderCall(args, theme, expanded = false) {
|
|
|
158
157
|
}
|
|
159
158
|
return text;
|
|
160
159
|
}
|
|
161
|
-
|
|
160
|
+
function formatAgentBrowserRenderResult(result, options, theme, isError) {
|
|
162
161
|
if (options.isPartial) {
|
|
163
162
|
return theme.fg("warning", "Running agent-browser...");
|
|
164
163
|
}
|
|
@@ -185,10 +184,10 @@ function formatModelVisibleFailureCategoryNotice(details) {
|
|
|
185
184
|
: undefined;
|
|
186
185
|
return `Result category: failure${failureCategory ? `; failureCategory: ${failureCategory}` : ""}; Pi tool isError: true.`;
|
|
187
186
|
}
|
|
188
|
-
function agentBrowserToolResultRequestedJson(
|
|
189
|
-
const details = isRecord(
|
|
187
|
+
function agentBrowserToolResultRequestedJson(result, input) {
|
|
188
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
190
189
|
const detailArgs = Array.isArray(details?.args) ? details.args : undefined;
|
|
191
|
-
const inputArgs = isRecord(
|
|
190
|
+
const inputArgs = isRecord(input) && Array.isArray(input.args) ? input.args : undefined;
|
|
192
191
|
return detailArgs?.includes("--json") === true || inputArgs?.includes("--json") === true;
|
|
193
192
|
}
|
|
194
193
|
function agentBrowserToolResultHasParseableJsonContent(content) {
|
|
@@ -219,26 +218,41 @@ function appendModelVisibleFailureCategoryNotice(content, notice) {
|
|
|
219
218
|
? { ...item, text: `${textItem.text}\n\n${notice}` }
|
|
220
219
|
: item);
|
|
221
220
|
}
|
|
222
|
-
export function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const content = notice ? appendModelVisibleFailureCategoryNotice(event.content, notice) : undefined;
|
|
228
|
-
const shouldMarkError = isRecord(event.details) && event.details.resultCategory === "failure" && event.isError !== true;
|
|
229
|
-
if (!shouldMarkError && !content)
|
|
230
|
-
return undefined;
|
|
221
|
+
export function finalizeAgentBrowserFailure(result, input) {
|
|
222
|
+
const failed = result.isError === true || (isRecord(result.details) && result.details.resultCategory === "failure");
|
|
223
|
+
const preservesParseableJson = (isRecord(input) && "code" in input || agentBrowserToolResultRequestedJson(result, input)) && agentBrowserToolResultHasParseableJsonContent(result.content);
|
|
224
|
+
const notice = preservesParseableJson ? undefined : formatModelVisibleFailureCategoryNotice(result.details);
|
|
225
|
+
const content = notice ? appendModelVisibleFailureCategoryNotice(result.content, notice) : undefined;
|
|
231
226
|
return {
|
|
232
|
-
...
|
|
233
|
-
|
|
227
|
+
...result,
|
|
228
|
+
content: content ?? result.content,
|
|
229
|
+
isError: failed,
|
|
234
230
|
};
|
|
235
231
|
}
|
|
236
232
|
export class AgentBrowserResultComponent {
|
|
237
233
|
expanded = false;
|
|
238
234
|
theme;
|
|
239
235
|
text = new Text("", 0, 0);
|
|
236
|
+
value;
|
|
237
|
+
formatKey;
|
|
238
|
+
setResult(result, options, theme, isError) {
|
|
239
|
+
const details = isRecord(result.details) ? result.details : undefined;
|
|
240
|
+
// Theme is a stable proxy in Pi; its resolved colors identify theme/terminal-color changes.
|
|
241
|
+
const key = [getPrimaryTextContent(result), details?.summary, details?.resultCategory, details?.failureCategory, options.isPartial, isError, theme, theme.colors, theme.fg];
|
|
242
|
+
if (!this.formatKey || key.some((value, index) => value !== this.formatKey[index])) {
|
|
243
|
+
this.formatKey = key;
|
|
244
|
+
this.setState(formatAgentBrowserRenderResult(result, options, theme, isError), options.expanded, theme);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.expanded = options.expanded;
|
|
248
|
+
this.theme = theme;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
240
251
|
setState(value, expanded, theme) {
|
|
241
|
-
this.
|
|
252
|
+
if (value !== this.value) {
|
|
253
|
+
this.text.setText(value);
|
|
254
|
+
this.value = value;
|
|
255
|
+
}
|
|
242
256
|
this.expanded = expanded;
|
|
243
257
|
this.theme = theme;
|
|
244
258
|
}
|
|
@@ -290,9 +290,6 @@ export async function getAgentBrowserSocketDirValidationError(socketDir, uid = t
|
|
|
290
290
|
return `the directory could not be inspected (${error.code ?? "unknown error"})`;
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
|
-
export async function ensureAgentBrowserSocketDir(socketDir, uid = typeof process.getuid === "function" ? process.getuid() : undefined) {
|
|
294
|
-
return await getAgentBrowserSocketDirValidationError(socketDir, uid) === undefined;
|
|
295
|
-
}
|
|
296
293
|
export function getAgentBrowserSocketPathValidationError(options) {
|
|
297
294
|
if ((options.platform ?? processPlatform) === "win32")
|
|
298
295
|
return undefined;
|
|
@@ -199,7 +199,7 @@ export function buildPromptPolicy(prompt) {
|
|
|
199
199
|
requestedArtifacts: extractPromptRequestedArtifacts(prompt),
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
|
-
function getMessageText(content) {
|
|
202
|
+
export function getMessageText(content) {
|
|
203
203
|
if (typeof content === "string")
|
|
204
204
|
return content;
|
|
205
205
|
if (!Array.isArray(content))
|
|
@@ -213,17 +213,21 @@ function getMessageText(content) {
|
|
|
213
213
|
.filter((text) => text.length > 0)
|
|
214
214
|
.join("\n");
|
|
215
215
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
216
|
+
/** Raw prompt intent survives compaction/context edits; only restoration walks ancestry. */
|
|
217
|
+
export function getLatestUserMessage(manager) {
|
|
218
|
+
let id = manager.getLeafId();
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
while (id !== null) {
|
|
221
|
+
if (typeof id !== "string" || seen.has(id))
|
|
222
|
+
throw new Error("Latest user prompt ancestry is invalid or cyclic.");
|
|
223
|
+
seen.add(id);
|
|
224
|
+
const entry = manager.getEntry(id);
|
|
225
|
+
if (!entry || entry.id !== id || entry.parentId !== null && typeof entry.parentId !== "string") {
|
|
226
|
+
throw new Error("Latest user prompt ancestry is incomplete.");
|
|
225
227
|
}
|
|
226
|
-
|
|
228
|
+
if (entry.type === "message" && entry.message.role === "user")
|
|
229
|
+
return entry.message;
|
|
230
|
+
id = entry.parentId;
|
|
227
231
|
}
|
|
228
|
-
return
|
|
232
|
+
return undefined;
|
|
229
233
|
}
|
|
@@ -7,8 +7,8 @@ export function isPendingRecordingArtifact(artifact) {
|
|
|
7
7
|
return artifact.recordingState === "openRecording" || artifact.status === "pending"
|
|
8
8
|
|| (artifact.status === undefined && isPendingRecordingCommand(artifact.command, artifact.subcommand, artifact.kind));
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const SESSION_ARTIFACT_MANIFEST_VERSION = 1;
|
|
11
|
+
const SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES";
|
|
12
12
|
export const DEFAULT_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES = 100;
|
|
13
13
|
function parsePositiveSafeInteger(value) {
|
|
14
14
|
if (value === undefined)
|
|
@@ -31,7 +31,7 @@ function getRouteDiagnosticReason(item, route) {
|
|
|
31
31
|
return "unfulfilled-routed-request";
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
function getNetworkRouteMode(args) {
|
|
35
35
|
if (args.includes("--abort"))
|
|
36
36
|
return "abort";
|
|
37
37
|
if (args.includes("--body"))
|
|
@@ -3,7 +3,7 @@ export function getStringRecordField(value, key) {
|
|
|
3
3
|
const field = value[key];
|
|
4
4
|
return typeof field === "string" && field.trim().length > 0 ? field.trim() : undefined;
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
function getNetworkRequestUrlPath(url) {
|
|
7
7
|
if (!url)
|
|
8
8
|
return undefined;
|
|
9
9
|
try {
|
|
@@ -68,7 +68,7 @@ function getStatefulCommandSensitiveValues(command) {
|
|
|
68
68
|
return [];
|
|
69
69
|
const tokens = extractUpstreamCommandTokens(command);
|
|
70
70
|
const values = [];
|
|
71
|
-
if (tokens[0] === "cookies" && tokens[1] === "set" && tokens[3])
|
|
71
|
+
if (tokens[0] === "cookies" && tokens[1] === "set" && !tokens.slice(2).includes("--curl") && tokens[3])
|
|
72
72
|
values.push(tokens[3]);
|
|
73
73
|
if (tokens[0] === "storage" && ["local", "session"].includes(tokens[1] ?? "") && tokens[2] === "set" && tokens[4])
|
|
74
74
|
values.push(tokens[4]);
|
|
@@ -2,6 +2,7 @@ import { isRecord } from "../../parsing.js";
|
|
|
2
2
|
import { redactSensitiveValue } from "../../runtime.js";
|
|
3
3
|
import { omitUpstreamLifecycle } from "./common.js";
|
|
4
4
|
import { redactTimeoutPartialProgress } from "../../orchestration/browser-run/diagnostics.js";
|
|
5
|
+
export const OBSERVATION_INLINE_MAX_CHARS = 16_000;
|
|
5
6
|
export function isStringArray(value) {
|
|
6
7
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
7
8
|
}
|
|
@@ -40,6 +41,9 @@ export function projectAgentBrowserObservation(details, succeeded) {
|
|
|
40
41
|
}
|
|
41
42
|
if (details.inspection === true && details.data === undefined && typeof details.stdout === "string")
|
|
42
43
|
observation.data = details.stdout;
|
|
44
|
+
// Identical error/summary strings double-count large failure text against the observation bound; keep error.
|
|
45
|
+
if (typeof observation.error === "string" && observation.error === observation.summary)
|
|
46
|
+
observation.summary = undefined;
|
|
43
47
|
if (isRecord(observation.data))
|
|
44
48
|
observation.data = omitUpstreamLifecycle(observation.data);
|
|
45
49
|
if (Array.isArray(details.batchSteps)) {
|
|
@@ -507,7 +507,7 @@ export function formatNetworkRouteDiagnosticsText(diagnostics) {
|
|
|
507
507
|
lines.push("If this route is intended as a mock, inspect the request/headers and treat failed, pending, or CORS-looking rows as unfulfilled until a mocked response is observed.");
|
|
508
508
|
return lines.join("\n");
|
|
509
509
|
}
|
|
510
|
-
|
|
510
|
+
function buildNetworkRouteDiagnosticsNextActions(diagnostics, sessionName) {
|
|
511
511
|
const diagnostic = diagnostics?.find((item) => item.requestId) ?? diagnostics?.[0];
|
|
512
512
|
if (!diagnostic)
|
|
513
513
|
return undefined;
|
|
@@ -2,7 +2,7 @@ import { writePersistentSessionArtifactFile, writeSecureTempFile, } from "../../
|
|
|
2
2
|
import { buildEvictedSessionArtifactEntries } from "../artifact-manifest.js";
|
|
3
3
|
import { countLines, truncateText } from "../text.js";
|
|
4
4
|
import { applyArtifactManifest } from "./artifacts.js";
|
|
5
|
-
import { getPresentationText, projectAgentBrowserObservation } from "./content.js";
|
|
5
|
+
import { getPresentationText, projectAgentBrowserObservation, OBSERVATION_INLINE_MAX_CHARS } from "./content.js";
|
|
6
6
|
import { redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
7
7
|
const LARGE_OUTPUT_INLINE_MAX_CHARS = 8_000;
|
|
8
8
|
const LARGE_OUTPUT_INLINE_MAX_LINES = 120;
|
|
@@ -88,16 +88,17 @@ function buildSpillArtifactEntries(options) {
|
|
|
88
88
|
...buildEvictedSessionArtifactEntries(options.evictedArtifacts, nowMs),
|
|
89
89
|
];
|
|
90
90
|
}
|
|
91
|
-
export
|
|
91
|
+
export { OBSERVATION_INLINE_MAX_CHARS } from "./content.js";
|
|
92
92
|
/** Bound the final model-visible result, after recovery/diagnostic assembly. Never truncate executable actions. */
|
|
93
93
|
export async function renderAgentBrowserObservation(options) {
|
|
94
94
|
const observation = projectAgentBrowserObservation(options.details, options.succeeded);
|
|
95
|
+
let structuredContent = observation;
|
|
95
96
|
const images = options.content.filter(part => part.type === "image");
|
|
96
97
|
const prose = options.content.filter(part => part.type === "text").map(part => part.text).join("\n\n");
|
|
97
98
|
const { data: _data, error: _error, summary: _summary, ...metadata } = observation;
|
|
98
99
|
let text = options.json ? JSON.stringify(observation, null, 2) : `${prose}\n\nObservation: ${JSON.stringify(metadata)}`;
|
|
99
100
|
let artifactManifest = options.details.artifactManifest;
|
|
100
|
-
if (text.length > OBSERVATION_INLINE_MAX_CHARS) {
|
|
101
|
+
if (text.length > OBSERVATION_INLINE_MAX_CHARS || JSON.stringify(observation).length > OBSERVATION_INLINE_MAX_CHARS) {
|
|
101
102
|
let spill;
|
|
102
103
|
let spillError;
|
|
103
104
|
try {
|
|
@@ -114,7 +115,8 @@ export async function renderAgentBrowserObservation(options) {
|
|
|
114
115
|
const compact = {
|
|
115
116
|
success: options.succeeded, resultCategory: observation.resultCategory,
|
|
116
117
|
failureCategory: observation.failureCategory, successCategory: observation.successCategory,
|
|
117
|
-
summary: typeof observation.summary === "string" ? truncateText(observation.summary, 700)
|
|
118
|
+
summary: typeof observation.summary === "string" ? truncateText(observation.summary, 700)
|
|
119
|
+
: typeof observation.error === "string" ? truncateText(observation.error, 700) : undefined,
|
|
118
120
|
compacted: true,
|
|
119
121
|
...(spill ? { observationPath: spill.path, retrieve: "Read observationPath for the complete redacted observation, including exact recovery actions and requested data." }
|
|
120
122
|
: { observationUnavailable: truncateText(redactModelFacingText(spillError ?? "Spill could not be written; request a smaller result."), 1_000) }),
|
|
@@ -123,11 +125,16 @@ export async function renderAgentBrowserObservation(options) {
|
|
|
123
125
|
if (observation[key] !== undefined && JSON.stringify({ ...compact, [key]: observation[key] }, null, 2).length <= OBSERVATION_INLINE_MAX_CHARS - 500)
|
|
124
126
|
compact[key] = observation[key];
|
|
125
127
|
}
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
text =
|
|
128
|
+
structuredContent = compact;
|
|
129
|
+
if (options.preserveContent)
|
|
130
|
+
text = prose;
|
|
131
|
+
else {
|
|
132
|
+
text = JSON.stringify(compact, null, 2);
|
|
133
|
+
if (!options.json)
|
|
134
|
+
text = `Browser observation compacted.\n${text}`;
|
|
135
|
+
}
|
|
129
136
|
}
|
|
130
|
-
return { content: [{ type: "text", text }, ...images], artifactManifest };
|
|
137
|
+
return { content: [{ type: "text", text }, ...images], artifactManifest, structuredContent: JSON.parse(JSON.stringify(structuredContent)) };
|
|
131
138
|
}
|
|
132
139
|
export async function compactLargePresentationOutput(options) {
|
|
133
140
|
const text = getPresentationText(options.presentation);
|
|
@@ -19,7 +19,7 @@ function formatSemanticActionTarget(compiled) {
|
|
|
19
19
|
const target = `${locator} ${quotedValue}`;
|
|
20
20
|
return name ? `${target} (name ${JSON.stringify(name)})` : target;
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
function formatSemanticActionCompactLine(compiled) {
|
|
23
23
|
const target = formatSemanticActionTarget(compiled);
|
|
24
24
|
switch (compiled.action) {
|
|
25
25
|
case "click":
|
|
@@ -34,7 +34,7 @@ export function formatSemanticActionCompactLine(compiled) {
|
|
|
34
34
|
return `${compiled.action}: ${target}`;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
function resolveSemanticPresentationCommand(compiled) {
|
|
38
38
|
if (!compiled || !SEMANTIC_PRESENTATION_ACTIONS.has(compiled.action))
|
|
39
39
|
return undefined;
|
|
40
40
|
if (compiled.action === "select")
|
|
@@ -256,6 +256,6 @@ export function formatRichInputRecoveryText(diagnostic) {
|
|
|
256
256
|
`- ${diagnostic.inputMethodHint}`,
|
|
257
257
|
].join("\n");
|
|
258
258
|
}
|
|
259
|
-
|
|
259
|
+
function normalizeSemanticActionAccessibleName(name) {
|
|
260
260
|
return name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
261
261
|
}
|
|
@@ -254,7 +254,7 @@ function redactBearerCredentials(text) {
|
|
|
254
254
|
return formatRedactedCredential(label, credential, trailing);
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
|
-
|
|
257
|
+
function isSensitiveFieldName(key) {
|
|
258
258
|
SENSITIVE_FIELD_NAME_PATTERN.lastIndex = 0;
|
|
259
259
|
return SENSITIVE_FIELD_NAME_PATTERN.test(key);
|
|
260
260
|
}
|
|
@@ -347,7 +347,9 @@ export function redactInvocationArgs(args) {
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
|
-
if (commandStartIndex !== undefined && args[commandStartIndex] === "cookies" && args[commandStartIndex + 1] === "set"
|
|
350
|
+
if (commandStartIndex !== undefined && args[commandStartIndex] === "cookies" && args[commandStartIndex + 1] === "set"
|
|
351
|
+
&& !extractUpstreamCommandTokens(args).slice(2).includes("--curl")
|
|
352
|
+
&& redacted[commandStartIndex + 3] !== undefined) {
|
|
351
353
|
redacted[commandStartIndex + 3] = "[REDACTED]";
|
|
352
354
|
}
|
|
353
355
|
if (commandStartIndex !== undefined
|
|
@@ -62,7 +62,7 @@ function extractStringResultField(data, fieldName) {
|
|
|
62
62
|
const text = data[fieldName].trim();
|
|
63
63
|
return text.length > 0 ? text : undefined;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
function extractSessionTabTargetFromData(data) {
|
|
66
66
|
const directTarget = normalizeSessionTabTarget({
|
|
67
67
|
targetId: isRecord(data) && typeof data.targetId === "string" ? data.targetId : undefined,
|
|
68
68
|
title: extractStringResultField(data, "title"),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { formatAgentBrowserRenderCall } from "./pi-tool-rendering.js";
|
|
3
|
+
import { JsonSchema } from "./json-schema.js";
|
|
4
|
+
import { AGENT_BROWSER_NAMESPACE, AGENT_BROWSER_OUTPUT_SCHEMA, finalizeAgentBrowserNativeResult } from "./native-output.js";
|
|
3
5
|
import { AGENT_BROWSER_PARAMS, AGENT_BROWSER_CODE_PARAMS, AGENT_BROWSER_ACTION_PARAMS, AGENT_BROWSER_QA_PARAMS, AGENT_BROWSER_ELECTRON_PARAMS, AGENT_BROWSER_SOURCE_PARAMS, AGENT_BROWSER_NETWORK_SOURCE_PARAMS, AGENT_BROWSER_TOOLS_PARAMS, } from "./input-modes/params.js";
|
|
4
6
|
export const AGENT_BROWSER_TOOL_INVENTORY = {
|
|
5
7
|
action: { name: "agent_browser_action", description: "Click, check, fill, or select a unique accessible target using stable locators or current refs." },
|
|
@@ -15,24 +17,28 @@ export const AGENT_BROWSER_INSTRUCTION_GROUP = {
|
|
|
15
17
|
const advancedNames = new Set(Object.values(AGENT_BROWSER_TOOL_INVENTORY).map(({ name }) => name));
|
|
16
18
|
/** Register once; advanced calls adapt only their input shape and reuse the ordinary executor. */
|
|
17
19
|
export function registerAgentBrowserToolSurface(pi, options) {
|
|
20
|
+
const execute = async (id, params, signal, onUpdate, ctx) => finalizeAgentBrowserNativeResult(await options.execute(id, params, signal, onUpdate, ctx), params);
|
|
21
|
+
const nativeOutput = { namespace: AGENT_BROWSER_NAMESPACE, outputSchema: AGENT_BROWSER_OUTPUT_SCHEMA };
|
|
18
22
|
pi.registerTool({
|
|
19
23
|
name: "agent_browser",
|
|
24
|
+
...nativeOutput,
|
|
20
25
|
label: "Agent Browser",
|
|
21
26
|
description: "Browse and interact through native agent-browser commands. One command in args; fixed sequences use batch --bail and JSON-array stdin. Use agent_browser_code for loops/branches and agent_browser_tools for advanced capabilities.",
|
|
22
27
|
promptSnippet: "Browse pages, inspect current refs, interact, and run native command batches.",
|
|
23
28
|
parameters: AGENT_BROWSER_PARAMS,
|
|
24
29
|
renderCall: options.renderCall,
|
|
25
30
|
renderResult: options.renderResult,
|
|
26
|
-
execute
|
|
31
|
+
execute,
|
|
27
32
|
executionMode: options.executionMode,
|
|
28
33
|
});
|
|
29
34
|
pi.registerTool({
|
|
30
35
|
name: "agent_browser_code",
|
|
36
|
+
...nativeOutput,
|
|
31
37
|
label: "Agent Browser Code",
|
|
32
38
|
description: "Run fresh JavaScript against a persistent browser. await browser({args,stdin?,timeoutMs?}) returns success/data/error/nextActions and imageObservations; emit(selected JSON) and emitImage(image handle) explicitly choose output. No host APIs or imports. Use native batch for fixed sequences.",
|
|
33
39
|
promptSnippet: "Branch, loop, and aggregate browser observations with explicit JSON/image output.",
|
|
34
40
|
parameters: AGENT_BROWSER_CODE_PARAMS,
|
|
35
|
-
execute: options.executeCode,
|
|
41
|
+
execute: async (id, params, signal, onUpdate, ctx) => finalizeAgentBrowserNativeResult(await options.executeCode(id, params, signal, onUpdate, ctx), params),
|
|
36
42
|
executionMode: options.executionMode,
|
|
37
43
|
renderCall(args, theme, context) {
|
|
38
44
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -43,56 +49,74 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
43
49
|
});
|
|
44
50
|
pi.registerTool({
|
|
45
51
|
...AGENT_BROWSER_TOOL_INVENTORY.action,
|
|
52
|
+
...nativeOutput,
|
|
53
|
+
defaultActive: false,
|
|
46
54
|
label: "Browser Action",
|
|
47
55
|
parameters: AGENT_BROWSER_ACTION_PARAMS,
|
|
48
56
|
renderResult: options.renderResult,
|
|
49
57
|
executionMode: options.executionMode,
|
|
50
58
|
execute(id, { outputPath, timeoutMs, ...semanticAction }, signal, onUpdate, ctx) {
|
|
51
|
-
return
|
|
59
|
+
return execute(id, { semanticAction, outputPath, timeoutMs }, signal, onUpdate, ctx);
|
|
52
60
|
},
|
|
53
61
|
});
|
|
54
62
|
pi.registerTool({
|
|
55
63
|
...AGENT_BROWSER_TOOL_INVENTORY.qa,
|
|
64
|
+
...nativeOutput,
|
|
65
|
+
defaultActive: false,
|
|
56
66
|
label: "Browser QA",
|
|
57
67
|
parameters: AGENT_BROWSER_QA_PARAMS,
|
|
58
68
|
renderResult: options.renderResult,
|
|
59
69
|
executionMode: options.executionMode,
|
|
60
70
|
execute(id, { outputPath, timeoutMs, sessionMode, ...qa }, signal, onUpdate, ctx) {
|
|
61
|
-
return
|
|
71
|
+
return execute(id, { qa, outputPath, timeoutMs, sessionMode }, signal, onUpdate, ctx);
|
|
62
72
|
},
|
|
63
73
|
});
|
|
64
74
|
pi.registerTool({
|
|
65
75
|
...AGENT_BROWSER_TOOL_INVENTORY.electron,
|
|
76
|
+
...nativeOutput,
|
|
77
|
+
defaultActive: false,
|
|
66
78
|
label: "Browser Electron",
|
|
67
79
|
parameters: AGENT_BROWSER_ELECTRON_PARAMS,
|
|
68
80
|
renderResult: options.renderResult,
|
|
69
81
|
executionMode: options.executionMode,
|
|
70
82
|
execute(id, { outputPath, ...electron }, signal, onUpdate, ctx) {
|
|
71
|
-
return
|
|
83
|
+
return execute(id, { electron, outputPath }, signal, onUpdate, ctx);
|
|
72
84
|
},
|
|
73
85
|
});
|
|
74
86
|
pi.registerTool({
|
|
75
87
|
...AGENT_BROWSER_TOOL_INVENTORY.source,
|
|
88
|
+
...nativeOutput,
|
|
89
|
+
defaultActive: false,
|
|
76
90
|
label: "Browser Source",
|
|
77
91
|
parameters: AGENT_BROWSER_SOURCE_PARAMS,
|
|
78
92
|
renderResult: options.renderResult,
|
|
79
93
|
executionMode: options.executionMode,
|
|
80
94
|
execute(id, { outputPath, timeoutMs, sessionMode, ...sourceLookup }, signal, onUpdate, ctx) {
|
|
81
|
-
return
|
|
95
|
+
return execute(id, { sourceLookup, outputPath, timeoutMs, sessionMode }, signal, onUpdate, ctx);
|
|
82
96
|
},
|
|
83
97
|
});
|
|
84
98
|
pi.registerTool({
|
|
85
99
|
...AGENT_BROWSER_TOOL_INVENTORY.network,
|
|
100
|
+
...nativeOutput,
|
|
101
|
+
defaultActive: false,
|
|
86
102
|
label: "Browser Network Source",
|
|
87
103
|
parameters: AGENT_BROWSER_NETWORK_SOURCE_PARAMS,
|
|
88
104
|
renderResult: options.renderResult,
|
|
89
105
|
executionMode: options.executionMode,
|
|
90
106
|
execute(id, { outputPath, timeoutMs, sessionMode, ...networkSourceLookup }, signal, onUpdate, ctx) {
|
|
91
|
-
return
|
|
107
|
+
return execute(id, { networkSourceLookup, outputPath, timeoutMs, sessionMode }, signal, onUpdate, ctx);
|
|
92
108
|
},
|
|
93
109
|
});
|
|
94
110
|
pi.registerTool({
|
|
95
111
|
name: "agent_browser_tools",
|
|
112
|
+
namespace: AGENT_BROWSER_NAMESPACE,
|
|
113
|
+
outputSchema: JsonSchema.Object({
|
|
114
|
+
inventory: JsonSchema.Array(JsonSchema.Object({
|
|
115
|
+
key: JsonSchema.String(), name: JsonSchema.String(), description: JsonSchema.String(),
|
|
116
|
+
available: JsonSchema.Boolean(), active: JsonSchema.Boolean(),
|
|
117
|
+
})),
|
|
118
|
+
added: JsonSchema.Array(JsonSchema.String()),
|
|
119
|
+
}),
|
|
96
120
|
label: "Browser Tools",
|
|
97
121
|
description: "List or enable advanced browser tools: action → agent_browser_action; qa → agent_browser_qa; electron → agent_browser_electron; source → agent_browser_source; network → agent_browser_network_source. Omit enable for inventory. Activation only adds tools; it preserves other active tools.",
|
|
98
122
|
promptSnippet: "Discover and enable specialized browser actions, QA, Electron, and source tools.",
|
|
@@ -111,11 +135,13 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
111
135
|
return {
|
|
112
136
|
content: [{ type: "text", text: inventory.map((tool) => `${tool.key}: ${tool.name} (${!tool.available ? "unavailable in this Pi tool selection" : tool.active ? "active" : "inactive"}) — ${tool.description}`).join("\n") }],
|
|
113
137
|
details: { inventory, added: [...new Set(added)] },
|
|
138
|
+
structuredContent: { inventory, added: [...new Set(added)] },
|
|
114
139
|
};
|
|
115
140
|
},
|
|
116
141
|
});
|
|
117
142
|
pi.on("session_start", async (_event, ctx) => {
|
|
118
|
-
//
|
|
143
|
+
// ponytail: official 1.0 SDK supplies initialActiveToolNames on resume, bypassing
|
|
144
|
+
// native transcript restoration. Remove this additive fallback once the supported SDK restores it.
|
|
119
145
|
const argv = process.argv.slice(2);
|
|
120
146
|
const delimiter = argv.indexOf("--");
|
|
121
147
|
if ((delimiter < 0 ? argv : argv.slice(0, delimiter)).some((arg) => arg === "--tools" || arg === "-t"))
|
|
@@ -125,7 +151,7 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
125
151
|
if (!["agent_browser", "agent_browser_code", "agent_browser_tools", ...advancedNames].every(name => available.has(name)))
|
|
126
152
|
return;
|
|
127
153
|
// Native dynamic imports bypass Pi's host-package aliases in consumer installs.
|
|
128
|
-
// Only tool names are needed; replay
|
|
154
|
+
// Only tool names are needed; replay native deltas without requiring the optional pi-ai peer.
|
|
129
155
|
const restored = new Set();
|
|
130
156
|
for (const message of ctx.sessionManager.buildSessionProjection().messages) {
|
|
131
157
|
if (message.role !== "system")
|
|
@@ -135,7 +161,9 @@ export function registerAgentBrowserToolSurface(pi, options) {
|
|
|
135
161
|
for (const added of message.toolsAdded ?? [])
|
|
136
162
|
restored.add(added.name);
|
|
137
163
|
}
|
|
138
|
-
const active = pi.getActiveTools()
|
|
139
|
-
|
|
164
|
+
const active = new Set(pi.getActiveTools());
|
|
165
|
+
const added = [...restored].filter((name) => advancedNames.has(name) && available.has(name) && !active.has(name));
|
|
166
|
+
if (added.length > 0)
|
|
167
|
+
pi.setActiveTools([...active, ...added]);
|
|
140
168
|
});
|
|
141
169
|
}
|