pi-agent-browser-native 0.2.48 → 0.2.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +16 -6
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Build generic nextAction recommendations from result categories, artifacts, Electron lifecycle state, and recovery context.
|
|
3
|
-
* Responsibilities: Preserve stable action ids/order while keeping recommendation policy out of generic shared helpers.
|
|
4
|
-
* Scope: Generic result-level recommendations only; feature-specific diagnostics append their own actions in the extension entrypoint.
|
|
5
|
-
* Usage: Called by presentation and extension result assembly.
|
|
6
|
-
* Invariants/Assumptions: Action ids are public machine-readable contracts; preserve first-observed order.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { isOpenNavigationCommand, isPageMutationCommand } from "../command-taxonomy.js";
|
|
10
|
-
import { isPendingRecordingArtifact } from "./artifact-state.js";
|
|
11
|
-
import type {
|
|
12
|
-
AgentBrowserFailureCategory,
|
|
13
|
-
AgentBrowserResultCategory,
|
|
14
|
-
AgentBrowserSuccessCategory,
|
|
15
|
-
FileArtifactMetadata,
|
|
16
|
-
} from "./contracts.js";
|
|
17
|
-
import { buildNextToolAction, type AgentBrowserNextAction } from "./next-actions.js";
|
|
18
|
-
import {
|
|
19
|
-
AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS,
|
|
20
|
-
buildRecoveryNextActions,
|
|
21
|
-
type AgentBrowserRecoveryContext,
|
|
22
|
-
} from "./recovery-actions.js";
|
|
23
|
-
|
|
24
|
-
function buildArtifactAction(path: string): AgentBrowserNextAction {
|
|
25
|
-
return {
|
|
26
|
-
artifactPath: path,
|
|
27
|
-
id: "use-saved-artifact",
|
|
28
|
-
reason: "Use the saved artifact path from the structured result instead of scraping it from text.",
|
|
29
|
-
safety: "Verify artifact metadata such as exists/status before treating the file as durable.",
|
|
30
|
-
tool: "agent_browser",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function buildArtifactVerificationAction(artifact: FileArtifactMetadata): AgentBrowserNextAction {
|
|
35
|
-
return {
|
|
36
|
-
artifactPath: artifact.path,
|
|
37
|
-
id: "verify-artifact-path",
|
|
38
|
-
reason: "The wrapper has artifact metadata but did not verify this file as present on disk.",
|
|
39
|
-
safety: "Check details.artifactVerification and the filesystem before treating the artifact as durable.",
|
|
40
|
-
tool: "agent_browser",
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function buildElectronToolAction(options: {
|
|
45
|
-
action: "cleanup" | "probe" | "status";
|
|
46
|
-
id: string;
|
|
47
|
-
launchId: string;
|
|
48
|
-
reason: string;
|
|
49
|
-
safety?: string;
|
|
50
|
-
}): AgentBrowserNextAction {
|
|
51
|
-
return {
|
|
52
|
-
id: options.id,
|
|
53
|
-
params: { electron: { action: options.action, launchId: options.launchId } },
|
|
54
|
-
reason: options.reason,
|
|
55
|
-
...(options.safety ? { safety: options.safety } : {}),
|
|
56
|
-
tool: "agent_browser",
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function getDownloadRetryPath(args: string[] | undefined, fallback: string | undefined): string | undefined {
|
|
61
|
-
if (fallback) return fallback;
|
|
62
|
-
if (!args || args.length === 0) return undefined;
|
|
63
|
-
const downloadFlagIndex = args.indexOf("--download");
|
|
64
|
-
if (downloadFlagIndex >= 0) {
|
|
65
|
-
const candidate = args[downloadFlagIndex + 1];
|
|
66
|
-
return candidate && !candidate.startsWith("-") ? candidate : undefined;
|
|
67
|
-
}
|
|
68
|
-
const downloadCommandIndex = args.indexOf("download");
|
|
69
|
-
if (downloadCommandIndex >= 0 && args.length > downloadCommandIndex + 2) {
|
|
70
|
-
return args[args.length - 1];
|
|
71
|
-
}
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function buildAgentBrowserNextActions(options: {
|
|
76
|
-
artifacts?: FileArtifactMetadata[];
|
|
77
|
-
args?: string[];
|
|
78
|
-
command?: string;
|
|
79
|
-
confirmationId?: string;
|
|
80
|
-
electron?: {
|
|
81
|
-
launchId?: string;
|
|
82
|
-
sessionName?: string;
|
|
83
|
-
status?: "active" | "cleaned" | "dead" | "failed" | "partial" | "succeeded";
|
|
84
|
-
};
|
|
85
|
-
failureCategory?: AgentBrowserFailureCategory;
|
|
86
|
-
resultCategory: AgentBrowserResultCategory;
|
|
87
|
-
recovery?: AgentBrowserRecoveryContext;
|
|
88
|
-
savedFilePath?: string;
|
|
89
|
-
successCategory?: AgentBrowserSuccessCategory;
|
|
90
|
-
}): AgentBrowserNextAction[] | undefined {
|
|
91
|
-
const actions: AgentBrowserNextAction[] = [];
|
|
92
|
-
if (options.recovery) {
|
|
93
|
-
actions.push(...buildRecoveryNextActions(options.recovery));
|
|
94
|
-
}
|
|
95
|
-
if (options.electron?.launchId) {
|
|
96
|
-
const { launchId, sessionName, status } = options.electron;
|
|
97
|
-
if (options.resultCategory === "success" && status !== "cleaned") {
|
|
98
|
-
actions.push(
|
|
99
|
-
buildElectronToolAction({
|
|
100
|
-
action: "status",
|
|
101
|
-
id: "status-electron-launch",
|
|
102
|
-
launchId,
|
|
103
|
-
reason: "Check the wrapper-tracked Electron launch liveness and current CDP targets without mutating the app.",
|
|
104
|
-
}),
|
|
105
|
-
buildElectronToolAction({
|
|
106
|
-
action: "probe",
|
|
107
|
-
id: "probe-electron-launch",
|
|
108
|
-
launchId,
|
|
109
|
-
reason: "Probe the attached Electron managed session and carry the wrapper launchId for follow-up diagnostics.",
|
|
110
|
-
}),
|
|
111
|
-
buildElectronToolAction({
|
|
112
|
-
action: "cleanup",
|
|
113
|
-
id: "cleanup-electron-launch",
|
|
114
|
-
launchId,
|
|
115
|
-
reason: "Clean the wrapper-owned Electron process and isolated userDataDir when the run is complete.",
|
|
116
|
-
safety: "Only operates on the launchId created by electron.launch; explicit artifacts and manually launched apps remain host-owned.",
|
|
117
|
-
}),
|
|
118
|
-
);
|
|
119
|
-
if (sessionName) {
|
|
120
|
-
actions.push(
|
|
121
|
-
buildNextToolAction({
|
|
122
|
-
args: ["--session", sessionName, "tab", "list"],
|
|
123
|
-
id: "list-electron-tabs",
|
|
124
|
-
reason: "Inspect attached Electron page/webview targets before choosing the active tab.",
|
|
125
|
-
}),
|
|
126
|
-
buildNextToolAction({
|
|
127
|
-
args: ["--session", sessionName, "snapshot", "-i"],
|
|
128
|
-
id: "snapshot-electron-session",
|
|
129
|
-
reason: "Refresh interactive refs for the attached Electron session.",
|
|
130
|
-
safety: "Use current Electron refs only after a fresh snapshot for this session.",
|
|
131
|
-
}),
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
} else if (options.resultCategory === "failure" && options.failureCategory === "cleanup-failed") {
|
|
135
|
-
actions.push(
|
|
136
|
-
buildElectronToolAction({
|
|
137
|
-
action: "status",
|
|
138
|
-
id: "status-electron-launch",
|
|
139
|
-
launchId,
|
|
140
|
-
reason: "Inspect which wrapper-tracked Electron resources remain after partial cleanup.",
|
|
141
|
-
}),
|
|
142
|
-
buildElectronToolAction({
|
|
143
|
-
action: "cleanup",
|
|
144
|
-
id: "retry-electron-cleanup",
|
|
145
|
-
launchId,
|
|
146
|
-
reason: "Retry cleanup for the same wrapper-owned Electron launch after reviewing remaining resources.",
|
|
147
|
-
safety: "Only retry for the same launchId; do not use cleanup for manually launched Electron apps.",
|
|
148
|
-
}),
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (options.resultCategory === "success") {
|
|
153
|
-
if (isOpenNavigationCommand(options.command)) {
|
|
154
|
-
actions.push(buildNextToolAction({
|
|
155
|
-
args: ["snapshot", "-i"],
|
|
156
|
-
id: "inspect-opened-page",
|
|
157
|
-
reason: "Inspect the opened page before choosing interactive refs.",
|
|
158
|
-
}));
|
|
159
|
-
} else if (isPageMutationCommand(options.command)) {
|
|
160
|
-
actions.push(buildNextToolAction({
|
|
161
|
-
args: ["snapshot", "-i"],
|
|
162
|
-
id: "inspect-after-mutation",
|
|
163
|
-
reason: "Refresh interactive refs after a browser mutation, navigation, scroll, or rerender.",
|
|
164
|
-
safety: "Do not reuse prior @refs until a fresh snapshot confirms they still exist.",
|
|
165
|
-
}));
|
|
166
|
-
}
|
|
167
|
-
const artifacts = options.artifacts ?? [];
|
|
168
|
-
const savedFileArtifact = options.savedFilePath ? artifacts.find((artifact) => artifact.path === options.savedFilePath) : undefined;
|
|
169
|
-
if (options.savedFilePath && savedFileArtifact?.exists !== false) {
|
|
170
|
-
actions.push(buildArtifactAction(options.savedFilePath));
|
|
171
|
-
}
|
|
172
|
-
for (const artifact of artifacts) {
|
|
173
|
-
if (isPendingRecordingArtifact(artifact)) {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
if (artifact.exists === false) {
|
|
177
|
-
if (artifact.kind === "download") {
|
|
178
|
-
actions.push(buildNextToolAction({
|
|
179
|
-
args: ["wait", "--download", artifact.path],
|
|
180
|
-
id: "wait-for-download",
|
|
181
|
-
reason: "Upstream reported a download path, but the wrapper did not verify the file on disk.",
|
|
182
|
-
safety: "Use an explicit wait timeout; if you set top-level timeoutMs, keep it above the wait duration plus a small grace window.",
|
|
183
|
-
}));
|
|
184
|
-
} else {
|
|
185
|
-
actions.push(buildArtifactVerificationAction(artifact));
|
|
186
|
-
}
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (artifact.path !== options.savedFilePath) {
|
|
190
|
-
actions.push(buildArtifactAction(artifact.path));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
switch (options.failureCategory) {
|
|
195
|
-
case "artifact-missing":
|
|
196
|
-
for (const artifact of options.artifacts ?? []) {
|
|
197
|
-
if (isPendingRecordingArtifact(artifact) || artifact.exists !== false) continue;
|
|
198
|
-
if (artifact.kind === "download") {
|
|
199
|
-
actions.push(buildNextToolAction({
|
|
200
|
-
args: ["wait", "--download", artifact.path],
|
|
201
|
-
id: "wait-for-download",
|
|
202
|
-
reason: "The requested download artifact was not found on disk after upstream reported completion.",
|
|
203
|
-
safety: "Use an explicit wait timeout; if you set top-level timeoutMs, keep it above the wait duration plus a small grace window.",
|
|
204
|
-
}));
|
|
205
|
-
} else {
|
|
206
|
-
actions.push(buildArtifactVerificationAction(artifact));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
break;
|
|
210
|
-
case "confirmation-required":
|
|
211
|
-
if (options.confirmationId) {
|
|
212
|
-
actions.push(
|
|
213
|
-
buildNextToolAction({
|
|
214
|
-
args: ["confirm", options.confirmationId],
|
|
215
|
-
id: "approve-confirmation",
|
|
216
|
-
reason: "Approve the pending upstream confirmation when the requested action is safe.",
|
|
217
|
-
safety: "Only confirm after reviewing the guarded action shown in the result.",
|
|
218
|
-
}),
|
|
219
|
-
buildNextToolAction({
|
|
220
|
-
args: ["deny", options.confirmationId],
|
|
221
|
-
id: "deny-confirmation",
|
|
222
|
-
reason: "Deny the pending upstream confirmation when the guarded action is unsafe or unintended.",
|
|
223
|
-
}),
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
break;
|
|
227
|
-
case "stale-ref":
|
|
228
|
-
case "selector-not-found":
|
|
229
|
-
case "selector-unsupported":
|
|
230
|
-
actions.push(buildNextToolAction({
|
|
231
|
-
args: ["snapshot", "-i"],
|
|
232
|
-
id: "refresh-interactive-refs",
|
|
233
|
-
reason: "Get current interactive refs before retrying the element action.",
|
|
234
|
-
safety: "Prefer a current @ref or a stable find locator; do not retry stale refs blindly.",
|
|
235
|
-
}));
|
|
236
|
-
break;
|
|
237
|
-
case "download-not-verified":
|
|
238
|
-
{
|
|
239
|
-
const retryPath = getDownloadRetryPath(options.args, options.savedFilePath);
|
|
240
|
-
actions.push(buildNextToolAction({
|
|
241
|
-
args: retryPath ? ["wait", "--download", retryPath] : ["wait", "--download"],
|
|
242
|
-
id: "wait-for-download",
|
|
243
|
-
reason: "Wait for the browser download and let the wrapper verify saved-file metadata.",
|
|
244
|
-
safety: "Use an explicit wait timeout; if you set top-level timeoutMs, keep it above the wait duration plus a small grace window.",
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
break;
|
|
248
|
-
case "tab-drift":
|
|
249
|
-
if (options.recovery?.kind === "about-blank" || options.recovery?.kind === "tab-drift") {
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
actions.push(
|
|
253
|
-
buildNextToolAction({
|
|
254
|
-
args: ["tab", "list"],
|
|
255
|
-
id: AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS.genericTabDriftListTabs,
|
|
256
|
-
reason: "Inspect available tabs before selecting the intended target.",
|
|
257
|
-
safety: "Read-only. Retry snapshot only after selecting or confirming the intended stable tab.",
|
|
258
|
-
}),
|
|
259
|
-
);
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return actions.length > 0 ? actions : undefined;
|
|
264
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own persistent session artifact manifest merge, retention, and validation logic.
|
|
3
|
-
* Responsibilities: Parse manifest bounds, recognize manifest entries, merge new artifact rows, and format retention summaries.
|
|
4
|
-
* Scope: Manifest accounting only; artifact detection and presentation live in presentation modules.
|
|
5
|
-
* Usage: Imported by presentation and snapshot artifact persistence paths.
|
|
6
|
-
* Invariants/Assumptions: Explicit-path artifacts are host-owned while persistent-session spill files are bounded by the manifest cap.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { isRecord } from "../parsing.js";
|
|
10
|
-
import type { SessionArtifactManifest, SessionArtifactManifestEntry } from "./contracts.js";
|
|
11
|
-
|
|
12
|
-
export const SESSION_ARTIFACT_MANIFEST_VERSION = 1;
|
|
13
|
-
export const SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES";
|
|
14
|
-
export const DEFAULT_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES = 100;
|
|
15
|
-
|
|
16
|
-
function parsePositiveSafeInteger(value: string | undefined): number | undefined {
|
|
17
|
-
if (value === undefined) return undefined;
|
|
18
|
-
const parsed = Number(value);
|
|
19
|
-
if (!Number.isSafeInteger(parsed) || parsed <= 0) return undefined;
|
|
20
|
-
return parsed;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function getSessionArtifactManifestMaxEntries(env: NodeJS.ProcessEnv = process.env): number {
|
|
24
|
-
return parsePositiveSafeInteger(env[SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES_ENV]) ?? DEFAULT_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isManifestEntry(value: unknown): value is SessionArtifactManifestEntry {
|
|
28
|
-
if (!isRecord(value)) return false;
|
|
29
|
-
if (typeof value.path !== "string" || value.path.trim().length === 0) return false;
|
|
30
|
-
if (typeof value.createdAtMs !== "number" || !Number.isFinite(value.createdAtMs)) return false;
|
|
31
|
-
if (!["evicted", "ephemeral", "live", "missing"].includes(String(value.retentionState))) return false;
|
|
32
|
-
if (!["explicit-path", "persistent-session", "process-temp"].includes(String(value.storageScope))) return false;
|
|
33
|
-
if (typeof value.kind !== "string" || value.kind.trim().length === 0) return false;
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function isSessionArtifactManifest(value: unknown): value is SessionArtifactManifest {
|
|
38
|
-
if (!isRecord(value)) return false;
|
|
39
|
-
if (value.version !== SESSION_ARTIFACT_MANIFEST_VERSION) return false;
|
|
40
|
-
if (!Array.isArray(value.entries) || !value.entries.every(isManifestEntry)) return false;
|
|
41
|
-
if (typeof value.updatedAtMs !== "number" || !Number.isFinite(value.updatedAtMs)) return false;
|
|
42
|
-
if (typeof value.maxEntries !== "number" || !Number.isSafeInteger(value.maxEntries) || value.maxEntries <= 0) return false;
|
|
43
|
-
if (typeof value.liveCount !== "number" || !Number.isSafeInteger(value.liveCount) || value.liveCount < 0) return false;
|
|
44
|
-
if (typeof value.evictedCount !== "number" || !Number.isSafeInteger(value.evictedCount) || value.evictedCount < 0) return false;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function buildEvictedSessionArtifactEntries(
|
|
49
|
-
evictedArtifacts: Array<{ mtimeMs: number; path: string; sizeBytes: number }>,
|
|
50
|
-
nowMs: number,
|
|
51
|
-
): SessionArtifactManifestEntry[] {
|
|
52
|
-
return evictedArtifacts.map((artifact) => ({
|
|
53
|
-
createdAtMs: artifact.mtimeMs,
|
|
54
|
-
evictedAtMs: nowMs,
|
|
55
|
-
kind: "spill",
|
|
56
|
-
path: artifact.path,
|
|
57
|
-
retentionState: "evicted",
|
|
58
|
-
sizeBytes: artifact.sizeBytes,
|
|
59
|
-
storageScope: "persistent-session",
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function formatSessionArtifactRetentionSummary(manifest: SessionArtifactManifest): string {
|
|
64
|
-
const ephemeralCount = manifest.entries.filter((entry) => entry.retentionState === "ephemeral").length;
|
|
65
|
-
const missingCount = manifest.entries.filter((entry) => entry.retentionState === "missing").length;
|
|
66
|
-
const parts = [`${manifest.liveCount} live`, `${manifest.evictedCount} evicted`];
|
|
67
|
-
if (ephemeralCount > 0) parts.push(`${ephemeralCount} ephemeral`);
|
|
68
|
-
if (missingCount > 0) parts.push(`${missingCount} missing`);
|
|
69
|
-
return `Session artifacts: ${parts.join(", ")} (${manifest.entries.length}/${manifest.maxEntries} recent).`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function mergeSessionArtifactManifest(options: {
|
|
73
|
-
base?: SessionArtifactManifest;
|
|
74
|
-
entries?: SessionArtifactManifestEntry[];
|
|
75
|
-
nowMs?: number;
|
|
76
|
-
}): SessionArtifactManifest | undefined {
|
|
77
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
78
|
-
const maxEntries = getSessionArtifactManifestMaxEntries();
|
|
79
|
-
const getEntryKey = (entry: SessionArtifactManifestEntry) =>
|
|
80
|
-
entry.storageScope === "explicit-path" && entry.absolutePath ? `${entry.storageScope}:${entry.absolutePath}` : `${entry.storageScope}:${entry.path}`;
|
|
81
|
-
const byPath = new Map<string, SessionArtifactManifestEntry>();
|
|
82
|
-
for (const entry of options.base?.entries ?? []) {
|
|
83
|
-
byPath.set(getEntryKey(entry), entry);
|
|
84
|
-
}
|
|
85
|
-
for (const entry of options.entries ?? []) {
|
|
86
|
-
const key = getEntryKey(entry);
|
|
87
|
-
const existing = byPath.get(key);
|
|
88
|
-
byPath.set(key, {
|
|
89
|
-
...existing,
|
|
90
|
-
...entry,
|
|
91
|
-
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
|
|
92
|
-
evictedAtMs: entry.retentionState === "evicted" ? (entry.evictedAtMs ?? nowMs) : entry.evictedAtMs,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
if (byPath.size === 0) return undefined;
|
|
96
|
-
const entries = [...byPath.values()]
|
|
97
|
-
.sort((left, right) => {
|
|
98
|
-
const leftTime = left.evictedAtMs ?? left.createdAtMs;
|
|
99
|
-
const rightTime = right.evictedAtMs ?? right.createdAtMs;
|
|
100
|
-
return rightTime - leftTime || left.path.localeCompare(right.path);
|
|
101
|
-
})
|
|
102
|
-
.slice(0, maxEntries);
|
|
103
|
-
return {
|
|
104
|
-
entries,
|
|
105
|
-
evictedCount: entries.filter((entry) => entry.retentionState === "evicted").length,
|
|
106
|
-
liveCount: entries.filter((entry) => entry.retentionState === "live").length,
|
|
107
|
-
maxEntries,
|
|
108
|
-
updatedAtMs: nowMs,
|
|
109
|
-
version: SESSION_ARTIFACT_MANIFEST_VERSION,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Classify successful and failed agent-browser outcomes into stable result categories.
|
|
3
|
-
* Responsibilities: Map artifacts, inspection calls, errors, refs, selectors, downloads, drift, and timeouts to small enums.
|
|
4
|
-
* Scope: Category policy only; next-action recommendations and presentation formatting live elsewhere.
|
|
5
|
-
* Usage: Called by presentation and extension result assembly before details are exposed to Pi.
|
|
6
|
-
* Invariants/Assumptions: Category strings are public machine-readable contracts covered by tests and docs.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
AgentBrowserFailureCategory,
|
|
11
|
-
AgentBrowserResultCategoryDetails,
|
|
12
|
-
AgentBrowserSuccessCategory,
|
|
13
|
-
FileArtifactMetadata,
|
|
14
|
-
SavedFilePresentationDetails,
|
|
15
|
-
} from "./contracts.js";
|
|
16
|
-
import { isPendingRecordingArtifact } from "./artifact-state.js";
|
|
17
|
-
|
|
18
|
-
function hasUnverifiedFileArtifact(artifacts: FileArtifactMetadata[] | undefined): boolean {
|
|
19
|
-
return (artifacts ?? []).some((artifact) => !isPendingRecordingArtifact(artifact) && artifact.exists !== true);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function classifyAgentBrowserSuccessCategory(options: {
|
|
23
|
-
artifacts?: FileArtifactMetadata[];
|
|
24
|
-
inspection?: boolean;
|
|
25
|
-
savedFile?: SavedFilePresentationDetails;
|
|
26
|
-
}): AgentBrowserSuccessCategory {
|
|
27
|
-
if (options.inspection) return "inspection";
|
|
28
|
-
if ((options.artifacts ?? []).length > 0) return hasUnverifiedFileArtifact(options.artifacts) ? "artifact-unverified" : "artifact-saved";
|
|
29
|
-
if (options.savedFile) return "artifact-saved";
|
|
30
|
-
return "completed";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function classifyAgentBrowserFailureCategory(options: {
|
|
34
|
-
args?: string[];
|
|
35
|
-
command?: string;
|
|
36
|
-
confirmationRequired?: boolean;
|
|
37
|
-
errorText?: string;
|
|
38
|
-
parseError?: string;
|
|
39
|
-
spawnError?: string;
|
|
40
|
-
stderr?: string;
|
|
41
|
-
tabDrift?: boolean;
|
|
42
|
-
timedOut?: boolean;
|
|
43
|
-
validationError?: string;
|
|
44
|
-
}): AgentBrowserFailureCategory {
|
|
45
|
-
const text = [options.errorText, options.validationError, options.parseError, options.spawnError, options.stderr].filter(Boolean).join("\n");
|
|
46
|
-
const command = options.command ?? "";
|
|
47
|
-
const usedRef = options.args?.some((arg) => /^@e\d+\b/.test(arg)) ?? false;
|
|
48
|
-
if (options.confirmationRequired || /confirmation required|pending confirmation|requires confirmation/i.test(text)) return "confirmation-required";
|
|
49
|
-
if (options.timedOut || /timeout|timed out|watchdog|IPC read timeout|must stay under its 30s IPC read timeout/i.test(text)) return "timeout";
|
|
50
|
-
if (/ENOENT|not found on PATH|could not find.*agent-browser|agent-browser is required but was not found/i.test(text)) return "missing-binary";
|
|
51
|
-
if (options.parseError || /invalid JSON|missing boolean success|success field must be boolean|returned no JSON output/i.test(text)) return "parse-failure";
|
|
52
|
-
if (/aborted/i.test(text)) return "aborted";
|
|
53
|
-
if (/policy[- ]blocked|blocked by caller policy|caller deny policy|caller allow policy/i.test(text)) return "policy-blocked";
|
|
54
|
-
if (/cleanup failed|cleanup.*partial|partial cleanup|remaining resources/i.test(text)) return "cleanup-failed";
|
|
55
|
-
if (options.validationError) return "validation-error";
|
|
56
|
-
if (options.tabDrift || /could not re-select the intended tab|about:blank|selected tab looks wrong|tab drift|tab.*wrong/i.test(text)) return "tab-drift";
|
|
57
|
-
if (/\bUnknown ref\b|\bstale ref\b|@ref may be stale|\bref\b.*\b(?:not found|missing|expired)\b/i.test(text)) return "stale-ref";
|
|
58
|
-
if (usedRef && /could not locate element|element not found|no element/i.test(text)) return "stale-ref";
|
|
59
|
-
const mentionsPlaywrightSelectorDialect = /(?:\btext=|:has-text\(|\bgetByRole\b|\bgetByText\b)/i.test(text);
|
|
60
|
-
const reportsSelectorMatchFailure =
|
|
61
|
-
/\b(?:no elements? found|failed to find|could not find|unable to find)\b.*\b(?:selector|locator)\b/i.test(text) ||
|
|
62
|
-
/\b(?:selector|locator)\b.*\b(?:no elements? found|not found|missing|failed to find|could not find|unable to find)\b/i.test(text);
|
|
63
|
-
if (
|
|
64
|
-
/\b(?:unsupported|unknown|invalid)\s+(?:selector|locator)\b/i.test(text) ||
|
|
65
|
-
/\bfailed to parse selector\b/i.test(text) ||
|
|
66
|
-
/\bselector\b.*\b(?:parse|syntax|unsupported|invalid)\b/i.test(text) ||
|
|
67
|
-
(mentionsPlaywrightSelectorDialect && reportsSelectorMatchFailure)
|
|
68
|
-
) {
|
|
69
|
-
return "selector-unsupported";
|
|
70
|
-
}
|
|
71
|
-
if (command === "find" && /could not locate element|element not found|no elements? found|unable to find/i.test(text)) return "selector-not-found";
|
|
72
|
-
if (reportsSelectorMatchFailure) return "selector-not-found";
|
|
73
|
-
if ((command === "download" || text.includes("wait --download") || /\bdownload\b/i.test(text)) && /missing|not verified|not found|failed|timeout|timed out/i.test(text)) {
|
|
74
|
-
return "download-not-verified";
|
|
75
|
-
}
|
|
76
|
-
return "upstream-error";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
export function buildAgentBrowserResultCategoryDetails(options: {
|
|
81
|
-
artifacts?: FileArtifactMetadata[];
|
|
82
|
-
args?: string[];
|
|
83
|
-
command?: string;
|
|
84
|
-
confirmationRequired?: boolean;
|
|
85
|
-
errorText?: string;
|
|
86
|
-
failureCategory?: AgentBrowserFailureCategory;
|
|
87
|
-
inspection?: boolean;
|
|
88
|
-
parseError?: string;
|
|
89
|
-
savedFile?: SavedFilePresentationDetails;
|
|
90
|
-
spawnError?: string;
|
|
91
|
-
succeeded: boolean;
|
|
92
|
-
tabDrift?: boolean;
|
|
93
|
-
timedOut?: boolean;
|
|
94
|
-
validationError?: string;
|
|
95
|
-
}): AgentBrowserResultCategoryDetails {
|
|
96
|
-
if (options.succeeded) {
|
|
97
|
-
return {
|
|
98
|
-
resultCategory: "success",
|
|
99
|
-
successCategory: classifyAgentBrowserSuccessCategory(options),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
failureCategory: options.failureCategory ?? classifyAgentBrowserFailureCategory(options),
|
|
104
|
-
resultCategory: "failure",
|
|
105
|
-
};
|
|
106
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Detect upstream guarded-action confirmation-needed result shapes without creating wrapper-owned confirmation state.
|
|
3
|
-
* Responsibilities: Recognize confirmation-required markers, extract the pending upstream confirmation id, and optionally surface a short upstream action label.
|
|
4
|
-
* Scope: Pure result-shape detection shared by presentation and error derivation; command execution, approval state, and redaction stay in their existing modules.
|
|
5
|
-
* Usage: Imported by result presentation to render recovery commands and by envelope error handling to avoid hiding actionable confirmation payloads behind generic failure text.
|
|
6
|
-
* Invariants/Assumptions: Detection must be conservative: a confirmation marker and a non-empty upstream id are both required before a result is treated as actionable.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { isRecord } from "../parsing.js";
|
|
10
|
-
|
|
11
|
-
export interface ConfirmationRequiredPresentation {
|
|
12
|
-
id: string;
|
|
13
|
-
actionText?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const CONFIRMATION_REQUIRED_FIELD_NAMES = [
|
|
17
|
-
"confirmation_required",
|
|
18
|
-
"confirmationRequired",
|
|
19
|
-
"requires_confirmation",
|
|
20
|
-
"requiresConfirmation",
|
|
21
|
-
] as const;
|
|
22
|
-
const CONFIRMATION_REQUIRED_RECORD_FIELD_NAMES = ["confirmation", "pendingConfirmation", "pending_confirmation"] as const;
|
|
23
|
-
const CONFIRMATION_ID_FIELD_NAMES = ["confirmation_id", "confirmationId", "id"] as const;
|
|
24
|
-
const CONFIRMATION_ACTION_TEXT_FIELD_NAMES = ["action", "description", "message", "summary"] as const;
|
|
25
|
-
const CONFIRMATION_REQUIRED_MARKER = "confirmation_required";
|
|
26
|
-
|
|
27
|
-
function getTrimmedStringField(data: Record<string, unknown>, fieldNames: readonly string[]): string | undefined {
|
|
28
|
-
for (const fieldName of fieldNames) {
|
|
29
|
-
const value = data[fieldName];
|
|
30
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
31
|
-
return value.trim();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function hasConfirmationRequiredMarker(data: Record<string, unknown>): boolean {
|
|
38
|
-
return CONFIRMATION_REQUIRED_FIELD_NAMES.some((fieldName) => data[fieldName] === true)
|
|
39
|
-
|| data.type === CONFIRMATION_REQUIRED_MARKER
|
|
40
|
-
|| data.status === CONFIRMATION_REQUIRED_MARKER
|
|
41
|
-
|| data.kind === CONFIRMATION_REQUIRED_MARKER;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getNestedConfirmationRecord(data: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
45
|
-
for (const fieldName of CONFIRMATION_REQUIRED_RECORD_FIELD_NAMES) {
|
|
46
|
-
const value = data[fieldName];
|
|
47
|
-
if (isRecord(value)) {
|
|
48
|
-
return value;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function detectConfirmationRequired(data: unknown): ConfirmationRequiredPresentation | undefined {
|
|
55
|
-
if (!isRecord(data)) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const nestedRecord = getNestedConfirmationRecord(data);
|
|
60
|
-
const candidateRecords = nestedRecord ? [data, nestedRecord] : [data];
|
|
61
|
-
if (!candidateRecords.some(hasConfirmationRequiredMarker)) {
|
|
62
|
-
return undefined;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
for (const record of candidateRecords) {
|
|
66
|
-
const id = getTrimmedStringField(record, CONFIRMATION_ID_FIELD_NAMES);
|
|
67
|
-
if (!id) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
actionText: getTrimmedStringField(record, CONFIRMATION_ACTION_TEXT_FIELD_NAMES),
|
|
72
|
-
id,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|