pi-agent-browser-native 0.2.42 → 0.2.44
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 +32 -0
- package/README.md +14 -9
- package/docs/COMMAND_REFERENCE.md +9 -10
- package/docs/RELEASE.md +10 -4
- package/docs/SUPPORT_MATRIX.md +7 -6
- package/docs/TOOL_CONTRACT.md +27 -24
- package/docs/platform-smoke.md +13 -8
- package/extensions/agent-browser/index.ts +71 -2
- package/extensions/agent-browser/lib/input-modes/params.ts +1 -1
- package/extensions/agent-browser/lib/input-modes/types.ts +1 -1
- package/extensions/agent-browser/lib/navigation-policy.ts +95 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +2 -7
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +1 -0
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +2 -2
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +103 -12
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +20 -3
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +6 -1
- package/extensions/agent-browser/lib/playbook.ts +4 -4
- package/extensions/agent-browser/lib/results/action-recommendations.ts +15 -0
- package/extensions/agent-browser/lib/results/contracts.ts +17 -0
- package/extensions/agent-browser/lib/results/network-routes.ts +80 -0
- package/extensions/agent-browser/lib/results/network.ts +10 -2
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +14 -0
- package/extensions/agent-browser/lib/results/presentation/batch.ts +36 -13
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +154 -16
- package/extensions/agent-browser/lib/results/presentation/errors.ts +62 -2
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +2 -4
- package/extensions/agent-browser/lib/results/presentation.ts +31 -1
- package/extensions/agent-browser/lib/results/selector-recovery.ts +11 -3
- package/extensions/agent-browser/lib/results/shared.ts +1 -0
- package/extensions/agent-browser/lib/results.ts +3 -0
- package/extensions/agent-browser/lib/runtime.ts +6 -0
- package/package.json +4 -4
- package/platform-smoke.config.mjs +10 -1
- package/scripts/doctor.mjs +70 -1
- package/scripts/platform-smoke/crabbox-runner.mjs +57 -29
- package/scripts/platform-smoke/doctor.mjs +22 -9
- package/scripts/platform-smoke/targets.mjs +58 -21
- package/scripts/platform-smoke.mjs +1 -0
|
@@ -14,6 +14,7 @@ import { detectConfirmationRequired } from "./confirmation.js";
|
|
|
14
14
|
import type {
|
|
15
15
|
AgentBrowserEnvelope,
|
|
16
16
|
AgentBrowserNextAction,
|
|
17
|
+
NetworkRouteDiagnostic,
|
|
17
18
|
SessionArtifactManifest,
|
|
18
19
|
ToolPresentation,
|
|
19
20
|
} from "./contracts.js";
|
|
@@ -29,7 +30,9 @@ import {
|
|
|
29
30
|
extractImagePath,
|
|
30
31
|
formatArtifactMetadataLines,
|
|
31
32
|
formatArtifactSummary,
|
|
33
|
+
formatMissingArtifactFailureText,
|
|
32
34
|
getSavedFileDetails,
|
|
35
|
+
hasMissingFileArtifact,
|
|
33
36
|
isManifestFileArtifact,
|
|
34
37
|
type ArtifactRequestContext,
|
|
35
38
|
} from "./presentation/artifacts.js";
|
|
@@ -37,7 +40,9 @@ import { buildBatchPresentation, isAgentBrowserBatchResultArray } from "./presen
|
|
|
37
40
|
import { getPresentationPaths } from "./presentation/content.js";
|
|
38
41
|
import {
|
|
39
42
|
buildNetworkRequestsNextActions,
|
|
43
|
+
buildStreamNextActions,
|
|
40
44
|
enrichStreamStatusData,
|
|
45
|
+
formatNetworkRouteDiagnosticsText,
|
|
41
46
|
redactPresentationData,
|
|
42
47
|
} from "./presentation/diagnostics.js";
|
|
43
48
|
import { buildErrorPresentation } from "./presentation/errors.js";
|
|
@@ -71,6 +76,8 @@ export async function buildToolPresentation(options: {
|
|
|
71
76
|
cwd: string;
|
|
72
77
|
envelope?: AgentBrowserEnvelope;
|
|
73
78
|
errorText?: string;
|
|
79
|
+
networkRouteDiagnostics?: NetworkRouteDiagnostic[];
|
|
80
|
+
networkRoutes?: import("./contracts.js").NetworkRouteRecord[];
|
|
74
81
|
persistentArtifactStore?: PersistentSessionArtifactStore;
|
|
75
82
|
sessionName?: string;
|
|
76
83
|
}): Promise<ToolPresentation> {
|
|
@@ -83,6 +90,8 @@ export async function buildToolPresentation(options: {
|
|
|
83
90
|
cwd,
|
|
84
91
|
envelope,
|
|
85
92
|
errorText,
|
|
93
|
+
networkRouteDiagnostics,
|
|
94
|
+
networkRoutes,
|
|
86
95
|
persistentArtifactStore,
|
|
87
96
|
sessionName,
|
|
88
97
|
} = options;
|
|
@@ -108,6 +117,7 @@ export async function buildToolPresentation(options: {
|
|
|
108
117
|
buildNestedToolPresentation: buildToolPresentation,
|
|
109
118
|
cwd,
|
|
110
119
|
data,
|
|
120
|
+
networkRoutes,
|
|
111
121
|
persistentArtifactStore,
|
|
112
122
|
sessionName,
|
|
113
123
|
summary,
|
|
@@ -124,6 +134,11 @@ export async function buildToolPresentation(options: {
|
|
|
124
134
|
};
|
|
125
135
|
}
|
|
126
136
|
|
|
137
|
+
if (networkRouteDiagnostics && networkRouteDiagnostics.length > 0 && presentation.content[0]?.type === "text") {
|
|
138
|
+
const diagnosticText = formatNetworkRouteDiagnosticsText(networkRouteDiagnostics);
|
|
139
|
+
if (diagnosticText) presentation.content[0] = { ...presentation.content[0], text: `${diagnosticText}\n\n${presentation.content[0].text}` };
|
|
140
|
+
presentation.networkRouteDiagnostics = networkRouteDiagnostics;
|
|
141
|
+
}
|
|
127
142
|
if (artifacts.length > 0 && !presentation.artifacts) {
|
|
128
143
|
presentation.artifacts = artifacts;
|
|
129
144
|
}
|
|
@@ -161,6 +176,19 @@ export async function buildToolPresentation(options: {
|
|
|
161
176
|
) ?? presentationWithManifest.artifactVerification;
|
|
162
177
|
|
|
163
178
|
const confirmationRequired = detectConfirmationRequired(data);
|
|
179
|
+
const missingArtifactFailureText = formatMissingArtifactFailureText(presentationWithManifest.artifacts);
|
|
180
|
+
if (missingArtifactFailureText && hasMissingFileArtifact(presentationWithManifest.artifacts)) {
|
|
181
|
+
presentationWithManifest.resultCategory = "failure";
|
|
182
|
+
presentationWithManifest.failureCategory = "artifact-missing";
|
|
183
|
+
presentationWithManifest.successCategory = undefined;
|
|
184
|
+
presentationWithManifest.summary = missingArtifactFailureText;
|
|
185
|
+
if (presentationWithManifest.content[0]?.type === "text") {
|
|
186
|
+
presentationWithManifest.content[0] = { ...presentationWithManifest.content[0], text: `${missingArtifactFailureText}\n\n${presentationWithManifest.content[0].text}` };
|
|
187
|
+
} else {
|
|
188
|
+
presentationWithManifest.content.unshift({ type: "text", text: missingArtifactFailureText });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
164
192
|
if (!presentationWithManifest.resultCategory) {
|
|
165
193
|
const categoryDetails = buildAgentBrowserResultCategoryDetails({
|
|
166
194
|
artifacts: presentationWithManifest.artifacts,
|
|
@@ -199,12 +227,14 @@ export async function buildToolPresentation(options: {
|
|
|
199
227
|
successCategory: presentationWithManifest.successCategory,
|
|
200
228
|
});
|
|
201
229
|
const networkNextActions = commandInfo.command === "network" && commandInfo.subcommand === "requests" && presentationWithManifest.resultCategory === "success"
|
|
202
|
-
? buildNetworkRequestsNextActions(data, sessionName)
|
|
230
|
+
? buildNetworkRequestsNextActions(data, sessionName, presentationWithManifest.networkRouteDiagnostics)
|
|
203
231
|
: undefined;
|
|
232
|
+
const streamNextActions = presentationWithManifest.resultCategory === "success" ? buildStreamNextActions(commandInfo, data, sessionName) : undefined;
|
|
204
233
|
presentationWithManifest.nextActions = mergeNextActions(
|
|
205
234
|
presentationWithManifest.nextActions,
|
|
206
235
|
genericNextActions,
|
|
207
236
|
networkNextActions,
|
|
237
|
+
streamNextActions,
|
|
208
238
|
);
|
|
209
239
|
presentationWithManifest.pageChangeSummary = presentationWithManifest.pageChangeSummary ?? buildPageChangeSummary({
|
|
210
240
|
artifacts: presentationWithManifest.artifacts,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Responsibilities: Parse find/semantic action targets, match current snapshot refs, build public diagnostics, text, and safe nextActions.
|
|
4
4
|
* Scope: Selector recovery policy only; subprocess snapshot probing and result orchestration stay in the extension entrypoint.
|
|
5
5
|
* Usage: The extension entrypoint supplies command tokens plus snapshot data after a selector-not-found failure.
|
|
6
|
-
* Invariants/Assumptions:
|
|
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
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { isRecord } from "../parsing.js";
|
|
@@ -199,14 +199,22 @@ export interface VisibleRefActionResolution {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
export function resolveVisibleRefActionFromSnapshot(options: {
|
|
202
|
+
allowFill?: boolean;
|
|
202
203
|
compiledAction: SelectorRecoveryCompiledAction;
|
|
203
204
|
snapshotData: unknown;
|
|
204
205
|
}): VisibleRefActionResolution | undefined {
|
|
205
206
|
const target = getFindVisibleRefFallbackTarget(options.compiledAction.args, { allowLeadingDashFillText: true });
|
|
206
|
-
if (!target || target.action === "
|
|
207
|
+
if (!target || target.action === "select") return undefined;
|
|
207
208
|
const snapshot = extractRefSnapshotFromData(options.snapshotData);
|
|
208
209
|
if (!snapshot) return undefined;
|
|
209
|
-
const
|
|
210
|
+
const candidates = getVisibleRefFallbackCandidates(target, options.snapshotData);
|
|
211
|
+
if (target.action === "fill") {
|
|
212
|
+
if (!options.allowFill || candidates.length !== 1 || target.text === undefined) return undefined;
|
|
213
|
+
const [candidate] = candidates;
|
|
214
|
+
if (!candidate || candidate.editableEvidence === false || !EDITABLE_CONTROL_ROLES.has(candidate.role.toLowerCase())) return undefined;
|
|
215
|
+
return { args: ["fill", candidate.ref, target.text], snapshot };
|
|
216
|
+
}
|
|
217
|
+
const candidate = candidates.find((item) => item.args !== undefined);
|
|
210
218
|
if (!candidate?.args) return undefined;
|
|
211
219
|
return { args: candidate.args, snapshot };
|
|
212
220
|
}
|
|
@@ -13,6 +13,7 @@ export * from "./artifact-manifest.js";
|
|
|
13
13
|
export * from "./artifact-state.js";
|
|
14
14
|
export * from "./editable-ref-evidence.js";
|
|
15
15
|
export * from "./network.js";
|
|
16
|
+
export * from "./network-routes.js";
|
|
16
17
|
export * from "./next-actions.js";
|
|
17
18
|
export * from "./recovery-actions.js";
|
|
18
19
|
export * from "./recovery-next-actions.js";
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
export { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "./results/envelope.js";
|
|
10
10
|
export { buildToolPresentation } from "./results/presentation.js";
|
|
11
|
+
export { applyNetworkRouteRecords, buildNetworkRouteDiagnostics } from "./results/network-routes.js";
|
|
11
12
|
export {
|
|
12
13
|
buildAgentBrowserResultCategoryDetails,
|
|
13
14
|
classifyAgentBrowserFailureCategory,
|
|
@@ -30,6 +31,8 @@ export type {
|
|
|
30
31
|
AgentBrowserResultCategory,
|
|
31
32
|
AgentBrowserResultCategoryDetails,
|
|
32
33
|
AgentBrowserSuccessCategory,
|
|
34
|
+
NetworkRouteDiagnostic,
|
|
35
|
+
NetworkRouteRecord,
|
|
33
36
|
FileArtifactKind,
|
|
34
37
|
FileArtifactMetadata,
|
|
35
38
|
ToolPresentation,
|
|
@@ -355,6 +355,12 @@ export function redactInvocationArgs(args: string[]): string[] {
|
|
|
355
355
|
redacted[commandStartIndex + 4] = "[REDACTED]";
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
if (commandStartIndex !== undefined && args[commandStartIndex] === "clipboard" && args[commandStartIndex + 1] === "write") {
|
|
359
|
+
for (let index = commandStartIndex + 2; index < redacted.length; index += 1) {
|
|
360
|
+
redacted[index] = "[REDACTED]";
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
358
364
|
return redacted;
|
|
359
365
|
}
|
|
360
366
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agent-browser-native",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.44",
|
|
4
4
|
"description": "pi extension that exposes agent-browser as a native tool for browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"typebox": "*"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@earendil-works/pi-ai": "^0.78.
|
|
66
|
-
"@earendil-works/pi-coding-agent": "^0.78.
|
|
67
|
-
"@earendil-works/pi-tui": "^0.78.
|
|
65
|
+
"@earendil-works/pi-ai": "^0.78.1",
|
|
66
|
+
"@earendil-works/pi-coding-agent": "^0.78.1",
|
|
67
|
+
"@earendil-works/pi-tui": "^0.78.1",
|
|
68
68
|
"@types/node": "^25.6.1",
|
|
69
69
|
"tsx": "^4.21.0",
|
|
70
70
|
"typebox": "^1.1.38",
|
|
@@ -8,11 +8,20 @@ export default {
|
|
|
8
8
|
artifactRoot: ".artifacts/platform-smoke",
|
|
9
9
|
requiredTargets: ["macos", "ubuntu", "windows-native"],
|
|
10
10
|
requiredSuites: ["platform-build", "browser-dogfood-smoke"],
|
|
11
|
+
supportedTargets: ["macos", "ubuntu", "windows-native"],
|
|
11
12
|
requiredCrabbox: {
|
|
12
13
|
install: "Homebrew package or PLATFORM_SMOKE_CRABBOX override",
|
|
13
|
-
minVersion: "0.
|
|
14
|
+
minVersion: "0.26.0",
|
|
15
|
+
},
|
|
16
|
+
macos: {
|
|
17
|
+
host: "localhost",
|
|
18
|
+
port: 22,
|
|
14
19
|
},
|
|
15
20
|
ubuntuContainerImage: "pi-agent-browser-native-platform:node24-agent-browser0.27.1",
|
|
21
|
+
windowsParallels: {
|
|
22
|
+
sourceVm: "pi-extension-windows-template",
|
|
23
|
+
snapshot: "crabbox-ready",
|
|
24
|
+
},
|
|
16
25
|
nodeValidationMajor: 22,
|
|
17
26
|
agentBrowserVersion: CAPABILITY_BASELINE.targetVersion,
|
|
18
27
|
};
|
package/scripts/doctor.mjs
CHANGED
|
@@ -22,6 +22,7 @@ const PACKAGE_NAME = "pi-agent-browser-native";
|
|
|
22
22
|
const REPO_URL_FRAGMENT = "github.com/fitchmultz/pi-agent-browser-native";
|
|
23
23
|
const EXTENSION_ENTRYPOINT = "extensions/agent-browser/index.ts";
|
|
24
24
|
const EXPECTED_VERSION = CAPABILITY_BASELINE.targetVersion;
|
|
25
|
+
const RECOMMENDED_PI_VERSION = "0.78.1";
|
|
25
26
|
const DEFAULT_AGENT_DIR = resolve(homedir(), ".pi/agent");
|
|
26
27
|
const THIS_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
27
28
|
|
|
@@ -29,6 +30,27 @@ export function normalizeAgentBrowserVersion(output) {
|
|
|
29
30
|
return String(output ?? "").trim().replace(/^agent-browser\s+/, "");
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
export function normalizePiVersion(output) {
|
|
34
|
+
return String(output ?? "").trim().replace(/^pi\s+/, "");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseVersionParts(version) {
|
|
38
|
+
const match = String(version ?? "").match(/^(\d+)\.(\d+)\.(\d+)(?:\b|[-+])/);
|
|
39
|
+
if (!match) return undefined;
|
|
40
|
+
return match.slice(1).map((part) => Number.parseInt(part, 10));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function versionAtLeast(actual, minimum) {
|
|
44
|
+
const actualParts = parseVersionParts(actual);
|
|
45
|
+
const minimumParts = parseVersionParts(minimum);
|
|
46
|
+
if (!actualParts || !minimumParts) return undefined;
|
|
47
|
+
for (let index = 0; index < minimumParts.length; index += 1) {
|
|
48
|
+
if (actualParts[index] > minimumParts[index]) return true;
|
|
49
|
+
if (actualParts[index] < minimumParts[index]) return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
function printHelp() {
|
|
33
55
|
console.log(`pi-agent-browser-doctor
|
|
34
56
|
|
|
@@ -45,7 +67,8 @@ Options:
|
|
|
45
67
|
Checks:
|
|
46
68
|
1. agent-browser is installed on PATH.
|
|
47
69
|
2. agent-browser --version matches the package capability baseline.
|
|
48
|
-
3.
|
|
70
|
+
3. pi --version is at least the recommended Pi floor for this release.
|
|
71
|
+
4. Pi settings and repo-local autoload locations do not point at multiple active pi-agent-browser-native sources.
|
|
49
72
|
|
|
50
73
|
Examples:
|
|
51
74
|
pi-agent-browser-doctor
|
|
@@ -101,6 +124,11 @@ async function defaultRunAgentBrowser(args) {
|
|
|
101
124
|
return `${stdout}${stderr}`;
|
|
102
125
|
}
|
|
103
126
|
|
|
127
|
+
async function defaultRunPi(args) {
|
|
128
|
+
const { stdout, stderr } = await execFile("pi", args, { maxBuffer: 1024 * 1024 });
|
|
129
|
+
return `${stdout}${stderr}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
104
132
|
async function defaultPathExists(path) {
|
|
105
133
|
try {
|
|
106
134
|
await access(path);
|
|
@@ -270,6 +298,43 @@ async function collectRepoLocalSources({ cwd, pathExists }) {
|
|
|
270
298
|
return sources;
|
|
271
299
|
}
|
|
272
300
|
|
|
301
|
+
async function checkPiVersion({ runPi }) {
|
|
302
|
+
try {
|
|
303
|
+
const rawOutput = await runPi(["--version"]);
|
|
304
|
+
const version = normalizePiVersion(rawOutput);
|
|
305
|
+
const supported = versionAtLeast(version, RECOMMENDED_PI_VERSION);
|
|
306
|
+
if (supported === false) {
|
|
307
|
+
return {
|
|
308
|
+
status: "warn",
|
|
309
|
+
title: `Pi ${RECOMMENDED_PI_VERSION} or newer is recommended; found ${version || "<empty>"}.`,
|
|
310
|
+
lines: [
|
|
311
|
+
"This package does not hard-pin Pi 0.78.1, but this release was audited against Pi 0.78.1 extension/package behavior.",
|
|
312
|
+
"Update Pi before release validation or lifecycle debugging if you see tool routing, /reload, exact-session, or package-install differences.",
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (supported === undefined) {
|
|
317
|
+
return {
|
|
318
|
+
status: "warn",
|
|
319
|
+
title: `Could not parse pi --version output: ${version || "<empty>"}.`,
|
|
320
|
+
lines: [`Pi ${RECOMMENDED_PI_VERSION} or newer is recommended for this release's validation baseline.`],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return { status: "pass", title: `Pi version is within the recommended baseline: ${version}`, lines: [] };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
const code = error && typeof error === "object" ? error.code : undefined;
|
|
326
|
+
return {
|
|
327
|
+
status: "warn",
|
|
328
|
+
title: "Could not inspect pi --version.",
|
|
329
|
+
lines: [
|
|
330
|
+
`Pi ${RECOMMENDED_PI_VERSION} or newer is recommended for this release's validation baseline, but it is not hard-pinned as a runtime requirement.`,
|
|
331
|
+
"Make sure the same shell that launches pi can run `pi --version` when debugging lifecycle or package-install behavior.",
|
|
332
|
+
code && code !== "ENOENT" ? `Spawn error: ${String(code)}` : undefined,
|
|
333
|
+
].filter(Boolean),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
273
338
|
async function checkAgentBrowserVersion({ runAgentBrowser }) {
|
|
274
339
|
try {
|
|
275
340
|
const rawOutput = await runAgentBrowser(["--version"]);
|
|
@@ -358,6 +423,7 @@ export async function evaluateDoctor(options = {}) {
|
|
|
358
423
|
const readText = options.readText ?? ((path) => readFile(path, "utf8"));
|
|
359
424
|
const pathExists = options.pathExists ?? defaultPathExists;
|
|
360
425
|
const runAgentBrowser = options.runAgentBrowser ?? defaultRunAgentBrowser;
|
|
426
|
+
const runPi = options.runPi ?? defaultRunPi;
|
|
361
427
|
const checks = [];
|
|
362
428
|
const failures = [];
|
|
363
429
|
const warnings = [];
|
|
@@ -366,6 +432,9 @@ export async function evaluateDoctor(options = {}) {
|
|
|
366
432
|
checks.push(versionCheck);
|
|
367
433
|
if (versionCheck.status === "fail") failures.push(versionCheck);
|
|
368
434
|
|
|
435
|
+
const piVersionCheck = await checkPiVersion({ runPi });
|
|
436
|
+
checks.push(piVersionCheck);
|
|
437
|
+
|
|
369
438
|
if (!options.skipSourceCheck) {
|
|
370
439
|
const sourceCheck = await checkPiSources({ cwd, agentDir, settingsPaths, readText, pathExists });
|
|
371
440
|
checks.push(sourceCheck);
|
|
@@ -14,49 +14,77 @@ function packageSlug(config = {}) {
|
|
|
14
14
|
return process.env.PLATFORM_SMOKE_PACKAGE_SLUG || config.packageName || "pi-agent-browser-native";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export function
|
|
17
|
+
export function describeTarget(targetName, config = {}) {
|
|
18
|
+
const slug = packageSlug(config);
|
|
18
19
|
switch (targetName) {
|
|
19
20
|
case "macos": {
|
|
20
21
|
const user = env("PLATFORM_SMOKE_MAC_USER") || env("USER");
|
|
21
|
-
const host = env("PLATFORM_SMOKE_MAC_HOST") || "localhost";
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const host = env("PLATFORM_SMOKE_MAC_HOST") || config.macos?.host || "localhost";
|
|
23
|
+
const port = String(env("PLATFORM_SMOKE_MAC_PORT") || config.macos?.port || 22);
|
|
24
|
+
const workRoot = env("PLATFORM_SMOKE_MAC_WORK_ROOT") || config.macos?.workRoot || `/Users/${user}/crabbox/${slug}`;
|
|
25
|
+
return {
|
|
26
|
+
provider: "ssh",
|
|
27
|
+
crabboxTarget: "macos",
|
|
28
|
+
shell: "posix",
|
|
29
|
+
workRoot,
|
|
30
|
+
args: [
|
|
31
|
+
"--provider", "ssh",
|
|
32
|
+
"--target", "macos",
|
|
33
|
+
"--static-host", host,
|
|
34
|
+
"--static-user", user,
|
|
35
|
+
"--static-port", port,
|
|
36
|
+
"--static-work-root", workRoot,
|
|
37
|
+
],
|
|
38
|
+
};
|
|
31
39
|
}
|
|
32
40
|
case "ubuntu": {
|
|
33
41
|
const image = env("PLATFORM_SMOKE_UBUNTU_IMAGE") || config.ubuntuContainerImage || "pi-agent-browser-native-platform:node24-agent-browser0.27.1";
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
42
|
+
return {
|
|
43
|
+
provider: "local-container",
|
|
44
|
+
crabboxTarget: "linux",
|
|
45
|
+
shell: "posix",
|
|
46
|
+
image,
|
|
47
|
+
workRoot: config.localContainer?.workRoot || "/work/crabbox",
|
|
48
|
+
args: [
|
|
49
|
+
"--provider", "local-container",
|
|
50
|
+
"--target", "linux",
|
|
51
|
+
"--local-container-image", image,
|
|
52
|
+
],
|
|
53
|
+
};
|
|
39
54
|
}
|
|
40
55
|
case "windows-native": {
|
|
41
|
-
const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || "pi-extension-windows-template";
|
|
42
|
-
const snapshot = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || "crabbox-ready";
|
|
43
|
-
const user = env("PLATFORM_SMOKE_WINDOWS_USER") || env("USER");
|
|
44
|
-
const workRoot = env("PLATFORM_SMOKE_WINDOWS_WORK_ROOT") || `C:\\crabbox\\${
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
const vm = env("PLATFORM_SMOKE_WINDOWS_VM") || config.windowsParallels?.sourceVm || "pi-extension-windows-template";
|
|
57
|
+
const snapshot = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || config.windowsParallels?.snapshot || "crabbox-ready";
|
|
58
|
+
const user = env("PLATFORM_SMOKE_WINDOWS_USER") || config.windowsParallels?.user || env("USER");
|
|
59
|
+
const workRoot = env("PLATFORM_SMOKE_WINDOWS_WORK_ROOT") || config.windowsParallels?.workRoot || `C:\\crabbox\\${slug}`;
|
|
60
|
+
return {
|
|
61
|
+
provider: "parallels",
|
|
62
|
+
crabboxTarget: "windows",
|
|
63
|
+
shell: "powershell",
|
|
64
|
+
workRoot,
|
|
65
|
+
windowsMode: "normal",
|
|
66
|
+
sourceVm: vm,
|
|
67
|
+
snapshot,
|
|
68
|
+
args: [
|
|
69
|
+
"--provider", "parallels",
|
|
70
|
+
"--target", "windows",
|
|
71
|
+
"--windows-mode", "normal",
|
|
72
|
+
"--parallels-source", vm,
|
|
73
|
+
"--parallels-source-snapshot", snapshot,
|
|
74
|
+
"--parallels-user", user,
|
|
75
|
+
"--parallels-work-root", workRoot,
|
|
76
|
+
],
|
|
77
|
+
};
|
|
54
78
|
}
|
|
55
79
|
default:
|
|
56
80
|
throw new Error(`unknown platform smoke target: ${targetName}`);
|
|
57
81
|
}
|
|
58
82
|
}
|
|
59
83
|
|
|
84
|
+
export function buildTargetBaseArgs(targetName, config = {}) {
|
|
85
|
+
return describeTarget(targetName, config).args;
|
|
86
|
+
}
|
|
87
|
+
|
|
60
88
|
export function leaseIdFor(targetName, slug) {
|
|
61
89
|
if (targetName === "macos") return "static_localhost";
|
|
62
90
|
return slug;
|
|
@@ -105,6 +105,17 @@ function checkForbiddenProjectFiles(failures) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function crabboxProviders(cbox) {
|
|
108
|
+
const jsonOutput = silent(cbox, ["providers", "--json"]);
|
|
109
|
+
if (jsonOutput) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(jsonOutput);
|
|
112
|
+
if (Array.isArray(parsed)) return parsed.map((provider) => provider.name ?? provider.id ?? provider.provider).filter(Boolean);
|
|
113
|
+
if (Array.isArray(parsed.providers)) return parsed.providers.map((provider) => provider.name ?? provider.id ?? provider.provider).filter(Boolean);
|
|
114
|
+
if (typeof parsed === "object" && parsed) return Object.keys(parsed.providers ?? parsed);
|
|
115
|
+
} catch {
|
|
116
|
+
// Fall through to text parsing for older or non-JSON provider output.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
108
119
|
const output = silent(cbox, ["providers"]);
|
|
109
120
|
if (!output) return [];
|
|
110
121
|
return output.split(/\r?\n/)
|
|
@@ -212,9 +223,10 @@ export async function runDoctor(config) {
|
|
|
212
223
|
const ubuntuImage = env("PLATFORM_SMOKE_UBUNTU_IMAGE") || config?.ubuntuContainerImage || "pi-agent-browser-native-platform:node24-agent-browser0.27.1";
|
|
213
224
|
checkCrabboxProvider(cbox, ["--provider", "local-container", "--local-container-image", ubuntuImage], "ubuntu local-container", failures);
|
|
214
225
|
const macUser = env("PLATFORM_SMOKE_MAC_USER") || env("USER");
|
|
215
|
-
const macHost = env("PLATFORM_SMOKE_MAC_HOST") || "localhost";
|
|
216
|
-
const
|
|
217
|
-
|
|
226
|
+
const macHost = env("PLATFORM_SMOKE_MAC_HOST") || config?.macos?.host || "localhost";
|
|
227
|
+
const macPort = String(env("PLATFORM_SMOKE_MAC_PORT") || config?.macos?.port || 22);
|
|
228
|
+
const macRoot = env("PLATFORM_SMOKE_MAC_WORK_ROOT") || config?.macos?.workRoot || `/Users/${macUser}/crabbox/${packageName}`;
|
|
229
|
+
checkCrabboxProvider(cbox, ["--provider", "ssh", "--target", "macos", "--static-host", macHost, "--static-user", macUser, "--static-port", macPort, "--static-work-root", macRoot], "macOS ssh", failures);
|
|
218
230
|
}
|
|
219
231
|
|
|
220
232
|
console.log("\n── Docker / Ubuntu ──");
|
|
@@ -226,8 +238,9 @@ export async function runDoctor(config) {
|
|
|
226
238
|
|
|
227
239
|
console.log("\n── macOS SSH ──");
|
|
228
240
|
const sshUser = env("PLATFORM_SMOKE_MAC_USER") || env("USER");
|
|
229
|
-
const sshHost = env("PLATFORM_SMOKE_MAC_HOST") || "localhost";
|
|
230
|
-
const
|
|
241
|
+
const sshHost = env("PLATFORM_SMOKE_MAC_HOST") || config?.macos?.host || "localhost";
|
|
242
|
+
const sshPort = String(env("PLATFORM_SMOKE_MAC_PORT") || config?.macos?.port || 22);
|
|
243
|
+
const sshProbe = shell(`ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no -p ${sshPort} ${sshUser}@${sshHost} 'node --version && npm --version && git --version && agent-browser --version'`);
|
|
231
244
|
if (sshProbe) {
|
|
232
245
|
ok(`SSH ${sshUser}@${sshHost}: ${sshProbe.split(/\r?\n/).join(" | ")}`);
|
|
233
246
|
if (agentBrowserVersion && !sshProbe.includes(agentBrowserVersion)) fail(`macOS SSH agent-browser does not match expected ${agentBrowserVersion}`, failures);
|
|
@@ -241,10 +254,10 @@ export async function runDoctor(config) {
|
|
|
241
254
|
fail("prlctl not found", failures);
|
|
242
255
|
} else {
|
|
243
256
|
ok("prlctl found");
|
|
244
|
-
const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") || "pi-extension-windows-template";
|
|
245
|
-
const snapshot = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || "crabbox-ready";
|
|
246
|
-
const user = env("PLATFORM_SMOKE_WINDOWS_USER") || env("USER");
|
|
247
|
-
const workRoot = env("PLATFORM_SMOKE_WINDOWS_WORK_ROOT") || `C:\\crabbox\\${packageName}`;
|
|
257
|
+
const vmName = env("PLATFORM_SMOKE_WINDOWS_VM") || config?.windowsParallels?.sourceVm || "pi-extension-windows-template";
|
|
258
|
+
const snapshot = env("PLATFORM_SMOKE_WINDOWS_SNAPSHOT") || config?.windowsParallels?.snapshot || "crabbox-ready";
|
|
259
|
+
const user = env("PLATFORM_SMOKE_WINDOWS_USER") || config?.windowsParallels?.user || env("USER");
|
|
260
|
+
const workRoot = env("PLATFORM_SMOKE_WINDOWS_WORK_ROOT") || config?.windowsParallels?.workRoot || `C:\\crabbox\\${packageName}`;
|
|
248
261
|
const list = shell("prlctl list -a --no-header 2>/dev/null");
|
|
249
262
|
if (!list) {
|
|
250
263
|
fail("prlctl list returned no VMs", failures);
|