pi-agent-browser-native 0.2.77 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +22 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Format scalar extraction results, navigation summaries, and page-change summaries.
|
|
3
|
-
* Responsibilities: Keep navigation/extraction presentation separate from core tool result orchestration.
|
|
4
|
-
* Scope: Navigation and get/eval extraction formatting only.
|
|
5
|
-
*/
|
|
6
1
|
import { isNavigationObservableCommandName, isPageChangeSummaryCommand } from "../../command-taxonomy.js";
|
|
7
2
|
import { isRecord } from "../../parsing.js";
|
|
8
3
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
9
|
-
import { redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
4
|
+
import { omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
10
5
|
const NAVIGATION_SUMMARY_FIELD = "navigationSummary";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
const GET_RESULT_FIELDS = {
|
|
7
|
+
attr: "value",
|
|
8
|
+
count: "count",
|
|
9
|
+
html: "html",
|
|
10
|
+
text: "text",
|
|
11
|
+
title: "title",
|
|
12
|
+
url: "url",
|
|
13
|
+
value: "value",
|
|
14
|
+
};
|
|
15
|
+
function getScalarExtractionResult(commandInfo, data) {
|
|
16
|
+
const fallbackField = commandInfo.command === "get" && commandInfo.subcommand ? GET_RESULT_FIELDS[commandInfo.subcommand] ?? "" : "";
|
|
17
|
+
const resultField = Object.hasOwn(data, "result") ? "result" : fallbackField.length > 0 && Object.hasOwn(data, fallbackField) ? fallbackField : undefined;
|
|
18
|
+
if (resultField === undefined)
|
|
19
|
+
return undefined;
|
|
20
|
+
const result = data[resultField];
|
|
21
|
+
if (typeof result === "string")
|
|
22
|
+
return result.trim().length > 0 ? result : "(empty string)";
|
|
23
|
+
if (typeof result === "number" || typeof result === "boolean")
|
|
17
24
|
return String(result);
|
|
18
|
-
|
|
25
|
+
if (result === null || result === undefined)
|
|
26
|
+
return "null";
|
|
27
|
+
if (typeof result === "object")
|
|
28
|
+
return JSON.stringify(result);
|
|
19
29
|
return undefined;
|
|
20
30
|
}
|
|
21
31
|
function getExtractionOrigin(data) {
|
|
@@ -37,7 +47,7 @@ function formatGetSummaryLabel(subcommand) {
|
|
|
37
47
|
return `${subcommand.slice(0, 1).toUpperCase()}${subcommand.slice(1)}`;
|
|
38
48
|
}
|
|
39
49
|
export function formatExtractionSummary(commandInfo, data) {
|
|
40
|
-
const scalarResult = getScalarExtractionResult(data);
|
|
50
|
+
const scalarResult = getScalarExtractionResult(commandInfo, data);
|
|
41
51
|
if (!scalarResult) {
|
|
42
52
|
return undefined;
|
|
43
53
|
}
|
|
@@ -55,7 +65,7 @@ export function formatExtractionText(commandInfo, data) {
|
|
|
55
65
|
if (commandInfo.command !== "get" && commandInfo.command !== "eval") {
|
|
56
66
|
return undefined;
|
|
57
67
|
}
|
|
58
|
-
const scalarResult = getScalarExtractionResult(data);
|
|
68
|
+
const scalarResult = getScalarExtractionResult(commandInfo, data);
|
|
59
69
|
if (!scalarResult) {
|
|
60
70
|
return undefined;
|
|
61
71
|
}
|
|
@@ -137,7 +147,7 @@ function stripNavigationSummary(data) {
|
|
|
137
147
|
return rest;
|
|
138
148
|
}
|
|
139
149
|
export function formatNavigationActionResult(data) {
|
|
140
|
-
const actionData = stripNavigationSummary(data);
|
|
150
|
+
const actionData = omitUpstreamLifecycle(stripNavigationSummary(data));
|
|
141
151
|
const lines = [];
|
|
142
152
|
if (typeof actionData.clicked === "string" || typeof actionData.clicked === "boolean") {
|
|
143
153
|
lines.push(`Clicked: ${String(actionData.clicked)}`);
|
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
import { isRecord } from "../../parsing.js";
|
|
2
2
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
3
3
|
import { formatRawSnapshotText, formatSnapshotSummary } from "../snapshot.js";
|
|
4
|
-
import { redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
4
|
+
import { getPageSummary, omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
5
5
|
import { formatDiagnosticSummary, formatDiagnosticText, formatProfilesText, getStreamSummary, getTabSummary } from "./diagnostics.js";
|
|
6
6
|
import { getScreenshotSummary } from "./artifacts.js";
|
|
7
7
|
import { formatSkillsText } from "./skills.js";
|
|
8
8
|
import { formatExtractionSummary, formatExtractionText, formatNavigationActionResult, formatNavigationSummary, getNavigationSummary, isNavigationObservableCommand, } from "./navigation.js";
|
|
9
9
|
import { formatSemanticActionPresentationSummary, formatSemanticActionPresentationText, resolvePresentationCommandInfo, } from "./semantic-action.js";
|
|
10
|
-
function getPageSummary(data) {
|
|
11
|
-
const title = typeof data.title === "string" ? data.title : undefined;
|
|
12
|
-
const url = typeof data.url === "string" ? data.url : undefined;
|
|
13
|
-
if (!title && !url)
|
|
14
|
-
return undefined;
|
|
15
|
-
if (title && url)
|
|
16
|
-
return `${title}\n${url}`;
|
|
17
|
-
return title ?? url;
|
|
18
|
-
}
|
|
19
10
|
function formatConfirmationRequiredSummary(confirmation) {
|
|
20
11
|
return `Confirmation required: ${confirmation.id}`;
|
|
21
12
|
}
|
|
13
|
+
const SIMPLE_ACTION_RESULTS = {
|
|
14
|
+
check: { field: "checked", label: "Checked" },
|
|
15
|
+
click: { field: "clicked", label: "Clicked" },
|
|
16
|
+
fill: { field: "filled", label: "Filled" },
|
|
17
|
+
focus: { field: "focused", label: "Focused" },
|
|
18
|
+
hover: { field: "hovered", label: "Hovered" },
|
|
19
|
+
press: { field: "pressed", label: "Pressed" },
|
|
20
|
+
select: { field: "selected", label: "Selected" },
|
|
21
|
+
type: { field: "typed", label: "Typed" },
|
|
22
|
+
uncheck: { field: "unchecked", label: "Unchecked" },
|
|
23
|
+
};
|
|
24
|
+
function formatSimpleActionResult(command, data) {
|
|
25
|
+
if (!isRecord(data))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (command === "click" && getNavigationSummary(data))
|
|
28
|
+
return undefined;
|
|
29
|
+
const definition = SIMPLE_ACTION_RESULTS[command];
|
|
30
|
+
if (!definition)
|
|
31
|
+
return undefined;
|
|
32
|
+
const value = data[definition.field];
|
|
33
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
|
|
34
|
+
return undefined;
|
|
35
|
+
return `${definition.label}: ${redactModelFacingText(String(value))}`;
|
|
36
|
+
}
|
|
37
|
+
function formatWaitResult(data) {
|
|
38
|
+
if (!isRecord(data))
|
|
39
|
+
return undefined;
|
|
40
|
+
if (data.waited === "timeout")
|
|
41
|
+
return "Fixed wait elapsed; no page condition was verified.";
|
|
42
|
+
for (const field of ["selector", "text", "url", "state", "waited"]) {
|
|
43
|
+
const value = data[field];
|
|
44
|
+
if (typeof value === "string" && value.length > 0)
|
|
45
|
+
return `Wait completed: ${redactModelFacingText(value)}`;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
function formatCloseResult(data) {
|
|
50
|
+
return isRecord(data) && data.closed === true ? "Browser session closed." : undefined;
|
|
51
|
+
}
|
|
22
52
|
const VITALS_METRICS = ["lcp", "fcp", "ttfb", "inp", "cls"];
|
|
23
53
|
function coerceVitalsMetricValue(value) {
|
|
24
54
|
if (typeof value === "number" && Number.isFinite(value))
|
|
@@ -80,6 +110,14 @@ function formatConfirmationRequiredText(confirmation) {
|
|
|
80
110
|
return lines.join("\n");
|
|
81
111
|
}
|
|
82
112
|
const COMMAND_PRESENTERS = {
|
|
113
|
+
...Object.fromEntries(Object.keys(SIMPLE_ACTION_RESULTS).map((command) => [command, {
|
|
114
|
+
summary: (_commandInfo, data) => formatSimpleActionResult(command, data),
|
|
115
|
+
text: (_commandInfo, data) => formatSimpleActionResult(command, data),
|
|
116
|
+
}])),
|
|
117
|
+
close: { summary: (_commandInfo, data) => formatCloseResult(data), text: (_commandInfo, data) => formatCloseResult(data) },
|
|
118
|
+
exit: { summary: (_commandInfo, data) => formatCloseResult(data), text: (_commandInfo, data) => formatCloseResult(data) },
|
|
119
|
+
quit: { summary: (_commandInfo, data) => formatCloseResult(data), text: (_commandInfo, data) => formatCloseResult(data) },
|
|
120
|
+
wait: { summary: (_commandInfo, data) => formatWaitResult(data), text: (_commandInfo, data) => formatWaitResult(data) },
|
|
83
121
|
profiles: {
|
|
84
122
|
summary: (_commandInfo, data) => Array.isArray(data) ? `Chrome profiles: ${data.length}` : undefined,
|
|
85
123
|
text: (_commandInfo, data) => Array.isArray(data) ? formatProfilesText(data, "Chrome profiles") : undefined,
|
|
@@ -118,8 +156,12 @@ const COMMAND_PRESENTERS = {
|
|
|
118
156
|
text: (commandInfo, data) => isRecord(data) && commandInfo.subcommand === "status" ? getStreamSummary(data) : undefined,
|
|
119
157
|
},
|
|
120
158
|
tab: {
|
|
121
|
-
summary: (_commandInfo, data) => isRecord(data) && Array.isArray(data.tabs)
|
|
122
|
-
|
|
159
|
+
summary: (_commandInfo, data) => isRecord(data) && Array.isArray(data.tabs)
|
|
160
|
+
? `Tabs: ${data.tabs.length}`
|
|
161
|
+
: isRecord(data) && data.closed === true ? "Tab closed" : undefined,
|
|
162
|
+
text: (_commandInfo, data) => isRecord(data) && data.closed === true
|
|
163
|
+
? `Tab closed${typeof data.tabId === "string" ? `: ${redactModelFacingText(data.tabId)}` : "."}`
|
|
164
|
+
: isRecord(data) ? getTabSummary(data) : undefined,
|
|
123
165
|
},
|
|
124
166
|
vitals: {
|
|
125
167
|
summary: (_commandInfo, data) => isRecord(data) ? formatVitalsSummary(data) : undefined,
|
|
@@ -216,5 +258,8 @@ export function formatPresentationContentText(commandInfo, data, compiledSemanti
|
|
|
216
258
|
const pageSummary = getPageSummary(data);
|
|
217
259
|
if (pageSummary)
|
|
218
260
|
return redactModelFacingText(pageSummary);
|
|
219
|
-
|
|
261
|
+
const { navigationSummary: _navigationSummary, ...pageData } = omitUpstreamLifecycle(data);
|
|
262
|
+
if (Object.keys(pageData).length > 0)
|
|
263
|
+
return stringifyModelFacing(pageData);
|
|
264
|
+
return `${presentationCommandInfo.command ?? commandInfo.command ?? "agent-browser"} completed`;
|
|
220
265
|
}
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Map successful semanticAction results to the same presentation signals as direct ref commands.
|
|
3
|
-
* Responsibilities: Resolve presentation command names, compact action prose, and navigation-summary probe gates.
|
|
4
|
-
* Scope: semanticAction success presentation only.
|
|
5
|
-
*/
|
|
6
1
|
import { getCompiledSemanticActionCommandIndex, isCompiledSemanticActionFindCommand, } from "../../input-modes/semantic-action.js";
|
|
7
2
|
import { isRecord } from "../../parsing.js";
|
|
8
3
|
import { formatNavigationSummary, getNavigationSummary, isNavigationObservableCommand, } from "./navigation.js";
|
|
9
|
-
import { redactModelFacingText } from "./common.js";
|
|
4
|
+
import { getPageSummary, redactModelFacingText } from "./common.js";
|
|
10
5
|
const SEMANTIC_NAVIGATION_PROBE_ACTIONS = new Set(["check", "click"]);
|
|
11
6
|
const SEMANTIC_PRESENTATION_ACTIONS = new Set(["check", "click", "fill", "select"]);
|
|
12
|
-
function getPageSummary(data) {
|
|
13
|
-
const title = typeof data.title === "string" ? data.title : undefined;
|
|
14
|
-
const url = typeof data.url === "string" ? data.url : undefined;
|
|
15
|
-
if (!title && !url)
|
|
16
|
-
return undefined;
|
|
17
|
-
if (title && url)
|
|
18
|
-
return `${title}\n${url}`;
|
|
19
|
-
return title ?? url;
|
|
20
|
-
}
|
|
21
7
|
function formatSemanticActionTarget(compiled) {
|
|
22
8
|
if (compiled.action === "select") {
|
|
23
9
|
const selector = compiled.selector ?? "selector";
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Render upstream agent-browser skills output as native pi tool guidance.
|
|
3
|
-
* Responsibilities: Format skills list/path/get results and translate agent-browser shell snippets to agent_browser tool calls.
|
|
4
|
-
* Scope: Skills command presentation only.
|
|
5
|
-
*/
|
|
6
1
|
import { isRecord } from "../../parsing.js";
|
|
7
2
|
import { getStringField, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
8
3
|
function formatSkillsListText(skills) {
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Purpose: Render parsed agent-browser results into concise pi-facing summaries, text content, and optional inline image attachments.
|
|
3
|
-
* Responsibilities: Orchestrate specialized presentation modules, attach inline images within size limits, and keep generic record formatting distinct from envelope parsing.
|
|
4
|
-
* Scope: Presentation shaping only; upstream stdout parsing and snapshot compaction internals live in separate modules.
|
|
5
|
-
*/
|
|
6
1
|
import { isRecord } from "../parsing.js";
|
|
7
|
-
import {
|
|
2
|
+
import { extractUpstreamCommandTokens } from "../runtime.js";
|
|
8
3
|
import { buildAgentBrowserNextActions } from "./action-recommendations.js";
|
|
9
4
|
import { buildAgentBrowserResultCategoryDetails } from "./categories.js";
|
|
10
5
|
import { detectConfirmationRequired } from "./confirmation.js";
|
|
@@ -38,14 +33,14 @@ function shouldAddAnnotatedScreenshotGuidance(commandInfo, args) {
|
|
|
38
33
|
}
|
|
39
34
|
export async function buildToolPresentation(options) {
|
|
40
35
|
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, namespace, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
41
|
-
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens:
|
|
36
|
+
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens: extractUpstreamCommandTokens(args) };
|
|
42
37
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfoWithTokens, compiledSemanticAction);
|
|
43
38
|
if (errorText) {
|
|
44
39
|
return buildErrorPresentation({ args, commandInfo, errorText, sessionName });
|
|
45
40
|
}
|
|
46
41
|
const data = enrichStreamStatusData(commandInfoWithTokens, envelope?.data);
|
|
47
42
|
const presentationData = redactPresentationData(commandInfoWithTokens, data);
|
|
48
|
-
const artifacts = await extractFileArtifacts({ artifactManifest, artifactRequest, commandInfo: presentationCommandInfo, cwd, data, sessionName });
|
|
43
|
+
const artifacts = await extractFileArtifacts({ artifactManifest, artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs, artifactRequest, commandInfo: presentationCommandInfo, cwd, data, namespace, sessionName });
|
|
49
44
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
50
45
|
const artifactSummary = formatArtifactSummary(artifacts);
|
|
51
46
|
const summary = artifactSummary ?? formatPresentationSummary(commandInfoWithTokens, data, compiledSemanticAction);
|
|
@@ -54,6 +49,8 @@ export async function buildToolPresentation(options) {
|
|
|
54
49
|
if (commandInfo.command === "batch" && isAgentBrowserBatchResultArray(data)) {
|
|
55
50
|
presentation = await buildBatchPresentation({
|
|
56
51
|
artifactManifest,
|
|
52
|
+
artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs,
|
|
53
|
+
artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs,
|
|
57
54
|
artifactRequests: options.batchArtifactRequests,
|
|
58
55
|
buildNestedToolPresentation: buildToolPresentation,
|
|
59
56
|
cwd,
|
|
@@ -161,6 +158,7 @@ export async function buildToolPresentation(options) {
|
|
|
161
158
|
failureCategory: presentationWithManifest.failureCategory,
|
|
162
159
|
resultCategory: presentationWithManifest.resultCategory ?? "success",
|
|
163
160
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
161
|
+
sessionName,
|
|
164
162
|
successCategory: presentationWithManifest.successCategory,
|
|
165
163
|
});
|
|
166
164
|
const networkNextActions = commandInfoWithTokens.command === "network" && commandInfoWithTokens.subcommand === "requests" && presentationWithManifest.resultCategory === "success"
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Centralize recovery-oriented nextAction ids and action construction.
|
|
3
|
-
* Responsibilities: Build tab/about:blank/no-active-page/connected-session follow-ups and rich-input recovery ids.
|
|
4
|
-
* Scope: Recovery action contracts only; result category classification and artifact follow-ups live elsewhere.
|
|
5
|
-
* Usage: Imported by shared result action builders and the extension entrypoint.
|
|
6
|
-
* Invariants/Assumptions: Ids are public machine-readable contracts mirrored by docs and tests.
|
|
7
|
-
*/
|
|
8
1
|
import { buildNextToolAction, withOptionalSessionArgs } from "./next-actions.js";
|
|
9
2
|
export const AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS = {
|
|
10
3
|
aboutBlankListTabs: "list-tabs-for-about-blank-recovery",
|
|
@@ -15,6 +8,8 @@ export const AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS = {
|
|
|
15
8
|
selectIntendedTabAfterDrift: "select-intended-tab-after-drift",
|
|
16
9
|
snapshotAfterTabRecovery: "snapshot-after-tab-recovery",
|
|
17
10
|
tabDriftListTabs: "list-tabs-for-tab-drift-recovery",
|
|
11
|
+
tabGoneListTabs: "list-tabs-after-tab-gone",
|
|
12
|
+
tabGoneNewTab: "open-tab-after-tab-gone",
|
|
18
13
|
};
|
|
19
14
|
export const AGENT_BROWSER_RICH_INPUT_RECOVERY_NEXT_ACTION_IDS = {
|
|
20
15
|
click: "click-current-editable-ref",
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Build session-aware recovery nextActions that combine result category policy with known session/tab context.
|
|
3
|
-
* Responsibilities: Prefix recovery argv with the active session and adapt tab/about:blank/no-active/stale-ref contexts into stable nextAction lists.
|
|
4
|
-
* Scope: Recovery nextAction assembly only; diagnostic detection and action-list merge ordering stay in the extension entrypoint.
|
|
5
|
-
* Usage: Imported by the extension entrypoint when adding recovery nextActions to tool details.
|
|
6
|
-
* Invariants/Assumptions: Action ids and argv ordering are public contracts; session prefixing must not double-prefix explicit --session args.
|
|
7
|
-
*/
|
|
8
1
|
import { buildAgentBrowserNextActions } from "./action-recommendations.js";
|
|
9
2
|
import { withOptionalSessionArgs } from "./next-actions.js";
|
|
10
3
|
export function buildConnectedSessionNextActions(sessionName) {
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own pure selector-miss recovery diagnostics for visible refs and rich editable inputs.
|
|
3
|
-
* Responsibilities: Parse find/semantic action targets, match current snapshot refs, build public diagnostics, text, and safe nextActions.
|
|
4
|
-
* Scope: Selector recovery policy only; subprocess snapshot probing and result orchestration stay in the extension entrypoint.
|
|
5
|
-
* Usage: The extension entrypoint supplies command tokens plus snapshot data after a selector-not-found failure.
|
|
6
|
-
* Invariants/Assumptions: Public fill recovery must never echo or auto-submit the user-provided fill text; guarded semanticAction fill pre-resolution may execute only one exact current editable ref.
|
|
7
|
-
*/
|
|
8
1
|
import { isRecord } from "../parsing.js";
|
|
9
2
|
import { extractRefSnapshotFromData } from "../session-page-state.js";
|
|
10
3
|
import { getEditableRefEvidence } from "./editable-ref-evidence.js";
|
|
@@ -23,7 +16,17 @@ function isSelectorRecoveryActionName(action) {
|
|
|
23
16
|
function getFindNameFlagValue(args, startIndex) {
|
|
24
17
|
const nameFlagIndex = args.indexOf("--name", startIndex);
|
|
25
18
|
const name = nameFlagIndex >= 0 ? args[nameFlagIndex + 1] : undefined;
|
|
26
|
-
return name &&
|
|
19
|
+
return typeof name === "string" && name.length > 0 ? name : undefined;
|
|
20
|
+
}
|
|
21
|
+
function collectFindTrailingValues(args, startIndex) {
|
|
22
|
+
const values = [];
|
|
23
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
24
|
+
const token = args[index];
|
|
25
|
+
if (!token || token.startsWith("-"))
|
|
26
|
+
break;
|
|
27
|
+
values.push(token);
|
|
28
|
+
}
|
|
29
|
+
return values;
|
|
27
30
|
}
|
|
28
31
|
function getFindVisibleRefFallbackTarget(args, options = {}) {
|
|
29
32
|
const findIndex = args[0] === "--session" ? 2 : 0;
|
|
@@ -32,8 +35,21 @@ function getFindVisibleRefFallbackTarget(args, options = {}) {
|
|
|
32
35
|
const locator = args[findIndex + 1];
|
|
33
36
|
const value = args[findIndex + 2];
|
|
34
37
|
const action = args[findIndex + 3];
|
|
35
|
-
if (!locator || !value || !isSelectorRecoveryActionName(action)
|
|
38
|
+
if (!locator || !value || !isSelectorRecoveryActionName(action))
|
|
36
39
|
return undefined;
|
|
40
|
+
if (action === "select") {
|
|
41
|
+
const optionValues = collectFindTrailingValues(args, findIndex + 4);
|
|
42
|
+
if (locator === "role") {
|
|
43
|
+
if (!/^(?:combobox|listbox)$/i.test(value))
|
|
44
|
+
return undefined;
|
|
45
|
+
const targetName = getFindNameFlagValue(args, findIndex + 4);
|
|
46
|
+
return targetName ? { action, optionValues, roles: [value.toLowerCase()], targetName } : undefined;
|
|
47
|
+
}
|
|
48
|
+
if (locator === "label") {
|
|
49
|
+
return { action, optionValues, roles: ["combobox", "listbox"], targetName: value };
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
37
53
|
const text = action === "fill" ? args[findIndex + 4] : undefined;
|
|
38
54
|
if (action === "fill" && (!text || (!options.allowLeadingDashFillText && text.startsWith("-"))))
|
|
39
55
|
return undefined;
|
|
@@ -76,7 +92,13 @@ function getVisibleRefFallbackCandidates(target, snapshotData) {
|
|
|
76
92
|
return [];
|
|
77
93
|
if (target.action === "fill" && editableEvidence === false && EDITABLE_CONTROL_ROLES.has(role.toLowerCase()))
|
|
78
94
|
return [];
|
|
79
|
-
const directRefArgs = target.action === "fill"
|
|
95
|
+
const directRefArgs = target.action === "fill"
|
|
96
|
+
? undefined
|
|
97
|
+
: target.action === "select" && target.optionValues && target.optionValues.length > 0
|
|
98
|
+
? ["select", `@${ref}`, ...target.optionValues]
|
|
99
|
+
: target.action === "select"
|
|
100
|
+
? undefined
|
|
101
|
+
: [target.action, `@${ref}`];
|
|
80
102
|
return [{
|
|
81
103
|
action: target.action,
|
|
82
104
|
...(directRefArgs ? { args: directRefArgs } : {}),
|
|
@@ -108,19 +130,33 @@ export function buildVisibleRefFallbackDiagnosticFromSnapshot(options) {
|
|
|
108
130
|
}
|
|
109
131
|
export function resolveVisibleRefActionFromSnapshot(options) {
|
|
110
132
|
const target = getFindVisibleRefFallbackTarget(options.compiledAction.args, { allowLeadingDashFillText: true });
|
|
111
|
-
if (!target
|
|
133
|
+
if (!target)
|
|
112
134
|
return undefined;
|
|
113
135
|
const snapshot = extractRefSnapshotFromData(options.snapshotData);
|
|
114
136
|
if (!snapshot)
|
|
115
137
|
return undefined;
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
const selectOptionValues = options.compiledAction.values && options.compiledAction.values.length > 0
|
|
139
|
+
? options.compiledAction.values
|
|
140
|
+
: target.optionValues;
|
|
141
|
+
const effectiveTarget = target.action === "select" && selectOptionValues
|
|
142
|
+
? { ...target, optionValues: selectOptionValues }
|
|
143
|
+
: target;
|
|
144
|
+
const candidates = getVisibleRefFallbackCandidates(effectiveTarget, options.snapshotData);
|
|
145
|
+
if (effectiveTarget.action === "fill") {
|
|
146
|
+
if (!options.allowFill || candidates.length !== 1 || effectiveTarget.text === undefined)
|
|
119
147
|
return undefined;
|
|
120
148
|
const [candidate] = candidates;
|
|
121
149
|
if (!candidate || candidate.editableEvidence === false || !EDITABLE_CONTROL_ROLES.has(candidate.role.toLowerCase()))
|
|
122
150
|
return undefined;
|
|
123
|
-
return { args: ["fill", candidate.ref,
|
|
151
|
+
return { args: ["fill", candidate.ref, effectiveTarget.text], snapshot };
|
|
152
|
+
}
|
|
153
|
+
if (effectiveTarget.action === "select") {
|
|
154
|
+
if (candidates.length !== 1 || !selectOptionValues || selectOptionValues.length === 0)
|
|
155
|
+
return undefined;
|
|
156
|
+
const [candidate] = candidates;
|
|
157
|
+
if (!candidate)
|
|
158
|
+
return undefined;
|
|
159
|
+
return { args: ["select", candidate.ref, ...selectOptionValues], snapshot };
|
|
124
160
|
}
|
|
125
161
|
const candidate = candidates.find((item) => item.args !== undefined);
|
|
126
162
|
if (!candidate?.args)
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Select the omitted snapshot refs most likely to be actionable controls.
|
|
3
|
-
* Responsibilities: Classify editable, surface, primary-action, and role-based control refs with deterministic diversity and top-up rules.
|
|
4
|
-
* Scope: High-value control ranking only; snapshot parsing, noise filtering, and presentation text live in neighboring modules.
|
|
5
|
-
* Usage: Snapshot presentation passes already-visible, non-noise omitted refs and receives the bounded high-value subset to surface.
|
|
6
|
-
* Invariants/Assumptions: Ranking must preserve scarce control categories before filling dominant buckets so dense desktop hosts stay navigable.
|
|
7
|
-
*/
|
|
8
1
|
import { compareRefIds } from "./text.js";
|
|
9
2
|
const SNAPSHOT_HIGH_VALUE_EDITABLE_REF_FILL_TARGET_LINES = 4;
|
|
10
3
|
const SNAPSHOT_HIGH_VALUE_SURFACE_REF_FILL_TARGET_LINES = 3;
|
|
11
4
|
const SNAPSHOT_HIGH_VALUE_PRIMARY_ACTION_REF_FILL_TARGET_LINES = 3;
|
|
5
|
+
const SNAPSHOT_HIGH_VALUE_NAMED_LINK_REF_FILL_TARGET_LINES = 6;
|
|
6
|
+
const SNAPSHOT_HIGH_VALUE_LINK_NAME_MAX_LENGTH = 80;
|
|
12
7
|
const SNAPSHOT_HIGH_VALUE_CONTROL_ROLES = new Set([
|
|
13
8
|
"button",
|
|
14
9
|
"checkbox",
|
|
@@ -40,9 +35,6 @@ const SNAPSHOT_PRIMARY_ACTION_BUTTON_NAME_PATTERNS = [
|
|
|
40
35
|
/^(?:add|apply|ask|attach|choose|confirm|connect|continue|create|deploy|done|download|go|insert|launch|log in|new|next|ok|open|publish|refresh|retry|run|save|search|select|send|sign in|sign up|start|submit|upload)$/i,
|
|
41
36
|
/^(?:add|apply|ask|confirm|connect|continue|create|launch|new|open|refresh|retry|run|save|search|send|start|submit)\b/i,
|
|
42
37
|
];
|
|
43
|
-
const SNAPSHOT_HIGH_VALUE_LINK_NAME_PATTERNS = [
|
|
44
|
-
/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i,
|
|
45
|
-
];
|
|
46
38
|
function getHighValueControlRole(entry) {
|
|
47
39
|
return entry.isEditable === true && (entry.role === "unknown" || entry.role === "generic") ? "textbox" : entry.role;
|
|
48
40
|
}
|
|
@@ -89,22 +81,29 @@ const SNAPSHOT_HIGH_VALUE_CONTROL_CATEGORY_RULES = [
|
|
|
89
81
|
matches: (entry) => isPrimaryActionButtonRef(entry),
|
|
90
82
|
priority: 2,
|
|
91
83
|
},
|
|
84
|
+
{
|
|
85
|
+
bucketKey: () => "named-link",
|
|
86
|
+
fillTarget: SNAPSHOT_HIGH_VALUE_NAMED_LINK_REF_FILL_TARGET_LINES,
|
|
87
|
+
id: "named-link",
|
|
88
|
+
matches: (entry) => getHighValueControlRole(entry) === "link" && isNamedActionLinkRef(entry),
|
|
89
|
+
priority: 3,
|
|
90
|
+
},
|
|
92
91
|
{
|
|
93
92
|
bucketKey: (_entry, role) => role,
|
|
94
93
|
id: "role",
|
|
95
94
|
matches: () => true,
|
|
96
|
-
priority:
|
|
95
|
+
priority: 4,
|
|
97
96
|
},
|
|
98
97
|
];
|
|
99
|
-
function
|
|
100
|
-
return entry.name.length > 0 &&
|
|
98
|
+
function isNamedActionLinkRef(entry) {
|
|
99
|
+
return entry.name.length > 0 && entry.name.length <= SNAPSHOT_HIGH_VALUE_LINK_NAME_MAX_LENGTH;
|
|
101
100
|
}
|
|
102
101
|
export function isHighValueControlEntry(entry) {
|
|
103
102
|
const role = getHighValueControlRole(entry);
|
|
104
103
|
if (!SNAPSHOT_HIGH_VALUE_CONTROL_ROLES.has(role))
|
|
105
104
|
return false;
|
|
106
105
|
if (role === "link")
|
|
107
|
-
return
|
|
106
|
+
return isNamedActionLinkRef(entry);
|
|
108
107
|
if (entry.isEditable === false && (role === "searchbox" || role === "textbox" || role === "combobox"))
|
|
109
108
|
return false;
|
|
110
109
|
return entry.name.length > 0 || isEditableControlRef(entry);
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own canonical parsing and enrichment of refs from agent-browser snapshot payloads.
|
|
3
|
-
* Responsibilities: Read structured refs, correlate them with raw snapshot lines, and infer editable/ref role evidence once for consumers.
|
|
4
|
-
* Scope: Snapshot ref metadata only; section preview, high-value ranking, and presentation assembly live in neighboring modules.
|
|
5
|
-
* Usage: Imported by snapshot presentation and recovery diagnostics that need consistent ref/name/role/editable evidence.
|
|
6
|
-
* Invariants/Assumptions: Snapshot text parsing is best-effort and must tolerate upstream formatting changes by preserving structured ref data when line parsing fails.
|
|
7
|
-
*/
|
|
8
1
|
import { isRecord } from "../parsing.js";
|
|
9
2
|
import { getEditableRefEvidence } from "./editable-ref-evidence.js";
|
|
10
3
|
import { compareRefIds, normalizeWhitespace } from "./text.js";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Parse raw agent-browser snapshot text into previewable sections.
|
|
3
|
-
* Responsibilities: Identify signal lines, rank content segments, build structured previews, and provide raw-outline fallback previews.
|
|
4
|
-
* Scope: Snapshot text/section planning only; ref enrichment, high-value control ranking, spill files, and final presentation live elsewhere.
|
|
5
|
-
* Usage: Snapshot presentation uses this module before selecting displayed refs.
|
|
6
|
-
* Invariants/Assumptions: Parsing is best-effort and must stay resilient to upstream snapshot text format changes.
|
|
7
|
-
*/
|
|
8
1
|
import { normalizeWhitespace, truncateText } from "./text.js";
|
|
9
2
|
const SNAPSHOT_MAX_ADDITIONAL_SECTIONS = 2;
|
|
10
3
|
const SNAPSHOT_FALLBACK_PREVIEW_MAX_LINES = 12;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Persist full compacted snapshot payloads when model-facing output is shortened.
|
|
3
|
-
* Responsibilities: Write persistent or secure-temp snapshot spill files and merge spill retention metadata into the session artifact manifest.
|
|
4
|
-
* Scope: Snapshot spill artifact lifecycle only; preview planning and presentation text live in snapshot.ts and sibling modules.
|
|
5
|
-
* Usage: Snapshot presentation calls these helpers after deciding a snapshot is too large for inline output.
|
|
6
|
-
* Invariants/Assumptions: Explicit full-output paths are reported but not deleted here; retention state mirrors the backing storage scope.
|
|
7
|
-
*/
|
|
8
1
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../temp.js";
|
|
9
2
|
import { buildEvictedSessionArtifactEntries, formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "./artifact-manifest.js";
|
|
10
3
|
const SNAPSHOT_SPILL_FILE_PREFIX = "pi-agent-browser-snapshot";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Compact large agent-browser snapshots into actionable pi-facing previews while preserving access to the raw payload when budgets allow.
|
|
3
|
-
* Responsibilities: Parse the current raw snapshot text format, detect when structured parsing is trustworthy, derive primary/additional content sections, rank high-value refs, and fall back to raw-outline previews when the upstream snapshot text format is unfamiliar.
|
|
4
|
-
* Scope: Snapshot-specific rendering only; generic envelope parsing, non-snapshot summaries, and image attachment live in neighboring modules.
|
|
5
|
-
* Usage: Imported by the focused presentation module for snapshot content and summary rendering.
|
|
6
|
-
* Invariants/Assumptions: Snapshot compaction should stay helpful even if upstream snapshot text formatting shifts, so structured parsing is best-effort and always has a resilient raw-outline fallback.
|
|
7
|
-
*/
|
|
8
1
|
import { isRecord } from "../parsing.js";
|
|
9
2
|
import { isHighValueControlEntry, selectHighValueControlEntries } from "./snapshot-high-value-controls.js";
|
|
10
3
|
import { buildFallbackSnapshotOutline, buildRefLineOrderMap, buildSegmentPreview, buildSnapshotSegments, canUseStructuredSnapshotPreview, chooseAdditionalSegments, choosePrimarySegment, getMeaningfulSegmentLines, getSnapshotRolePriority, isChromeSectionName, isNoiseName, parseSnapshotLines, } from "./snapshot-segments.js";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Hold tiny text and ref-id helpers shared by result renderers.
|
|
3
|
-
* Responsibilities: Convert unknown values to text, count lines, normalize whitespace, truncate labels, and sort ref ids naturally.
|
|
4
|
-
* Scope: Generic pure utilities only; no agent-browser command policy belongs here.
|
|
5
|
-
* Usage: Imported by envelope, presentation, snapshot, and diagnostic helpers.
|
|
6
|
-
* Invariants/Assumptions: Helpers are deterministic and side-effect free.
|
|
7
|
-
*/
|
|
8
1
|
export function stringifyUnknown(value) {
|
|
9
2
|
if (typeof value === "string")
|
|
10
3
|
return value;
|