@proagentstore/cli 0.4.85 → 0.4.86
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.
|
@@ -50,6 +50,7 @@ export const LOCAL_BROWSER_EVENT_TYPES = [
|
|
|
50
50
|
export const LOCAL_BROWSER_PLATFORM_EVENT_TYPES = ["run.requested", "runner.dispatched", "run.ended", "review.decision"];
|
|
51
51
|
export const LOCAL_BROWSER_ENGINE_AUTHS = ["subscription", "machine-login", "api-key", "missing_login", "unknown"];
|
|
52
52
|
export const LOCAL_BROWSER_SOURCE_FAILURE_REASONS = ["login_required", "captcha", "paywall", "access_denied", "robots", "timeout", "error"];
|
|
53
|
+
export const LOCAL_BROWSER_ERROR_CODES = ["browser_unavailable", "engine_not_installed", "engine_not_signed_in", "bridge_unused", "consent_declined", "time_limit", "cancelled", "engine_failed"];
|
|
53
54
|
export const LOCAL_BROWSER_CAPS = { findings: 200, sourceFailures: 200, text: 4000, fieldCount: 40 };
|
|
54
55
|
const SENSITIVE_KEY = /cookie|password|passwd|secret|token|authorization|api[_-]?key|otp|form[_-]?values?|credential/i;
|
|
55
56
|
/** An env var NAME whose value is a credential. */
|
|
@@ -209,7 +210,13 @@ export function parseLocalBrowserResult(raw) {
|
|
|
209
210
|
// Redacted again here, whatever the runner did: an error is CLI output, and CLIs print keys.
|
|
210
211
|
const rawError = str(o.error, 1000);
|
|
211
212
|
const error = rawError === null ? null : redactText(rawError);
|
|
212
|
-
if (outcome === "failed")
|
|
213
|
+
if (outcome === "failed") {
|
|
213
214
|
out.error = error ?? "The run failed without a reason.";
|
|
215
|
+
// #944: the runner's own name for the cause, when it gave one. An unrecognised code is
|
|
216
|
+
// dropped rather than passed through, so the stored vocabulary stays closed.
|
|
217
|
+
const code = oneOf(LOCAL_BROWSER_ERROR_CODES, o.errorCode);
|
|
218
|
+
if (code)
|
|
219
|
+
out.errorCode = code;
|
|
220
|
+
}
|
|
214
221
|
return { result: out };
|
|
215
222
|
}
|
|
@@ -28,6 +28,26 @@ import { BrowserBridge, domainWithin } from "./bridge.js";
|
|
|
28
28
|
import { BRIDGE_ENV } from "./bridge-stdio.js";
|
|
29
29
|
import { LOCAL_BROWSER_AUTH_MODES, LOCAL_BROWSER_ENGINES, LOCAL_BROWSER_TASK_TYPE, redactDetail, redactText, secretEnvValues, } from "./contract.js";
|
|
30
30
|
import { bridgeUnused, buildEngineSpec, finalText, missingLogin, observedEngineAuth, researchPrompt, signInHelp } from "./engine.js";
|
|
31
|
+
/**
|
|
32
|
+
* Why the run could not be launched, in words the owner can act on (#944).
|
|
33
|
+
*
|
|
34
|
+
* Two shapes reach here: the owner declining the signed-in profile, which is a DECISION and not a
|
|
35
|
+
* fault, and the browser failing to start — the "missing browser" state the acceptance criteria
|
|
36
|
+
* ask for. Everything a browser can refuse at startup is fixed at the machine (install Chrome, free
|
|
37
|
+
* the profile another window is holding, allow the automation port), so the sentence says that; the
|
|
38
|
+
* raw message rides along, because it is the half that names which of those it was.
|
|
39
|
+
*/
|
|
40
|
+
function launchFailure(err) {
|
|
41
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42
|
+
if (/did not allow research in their signed-in browser profile/i.test(message)) {
|
|
43
|
+
return { outcome: "failed", error: message, errorCode: "consent_declined" };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
outcome: "failed",
|
|
47
|
+
error: `The browser for this run could not be started on this machine: ${message.slice(0, 400)}. Check that Chrome or Chromium is installed, that no other window is holding the profile this run uses, and that \`pags up\` may launch it — then start the run again.`,
|
|
48
|
+
errorCode: "browser_unavailable",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
31
51
|
const MAX_EVENTS = 2000;
|
|
32
52
|
const OUTPUT_LINES = 400;
|
|
33
53
|
const SAFE_ID = /^[A-Za-z0-9_.:-]{1,100}$/;
|
|
@@ -172,7 +192,10 @@ export class LocalBrowserRuntime {
|
|
|
172
192
|
this.runs.set(envelope.runId, run);
|
|
173
193
|
// Not awaited: a signed-in-profile consent pause can hold the launch for minutes, and the
|
|
174
194
|
// relay command that started the run must answer now. Everything after this is in `status`.
|
|
175
|
-
|
|
195
|
+
// #944: a launch failure is the one the acceptance criteria call "missing browser", and it was
|
|
196
|
+
// the one cause with no name: it ended the run with whatever the browser threw, which the
|
|
197
|
+
// cloud then filed as `engine_failed` — the CLI blamed for a browser that never started.
|
|
198
|
+
void this.launch(run, selfUrl).catch((err) => this.end(run, launchFailure(err)));
|
|
176
199
|
return { runId: envelope.runId, taskId: envelope.runId, status: run.state, existing: false };
|
|
177
200
|
}
|
|
178
201
|
async launch(run, selfUrl) {
|
|
@@ -230,7 +253,12 @@ export class LocalBrowserRuntime {
|
|
|
230
253
|
child.stderr?.on("data", keep);
|
|
231
254
|
child.on("error", (err) => {
|
|
232
255
|
const missing = err.code === "ENOENT";
|
|
233
|
-
this.end(run, {
|
|
256
|
+
this.end(run, {
|
|
257
|
+
outcome: "failed",
|
|
258
|
+
...(missing
|
|
259
|
+
? { error: `The ${e.engine === "claude" ? "Claude Code" : "Codex"} CLI is not installed on this machine (\`${spec.command}\` was not found).`, errorCode: "engine_not_installed" }
|
|
260
|
+
: { error: err.message }),
|
|
261
|
+
});
|
|
234
262
|
});
|
|
235
263
|
child.on("close", (code) => {
|
|
236
264
|
if (buf.trim())
|
|
@@ -377,12 +405,12 @@ export class LocalBrowserRuntime {
|
|
|
377
405
|
const summary = bridge?.summary ?? finalText(e.engine, run.output);
|
|
378
406
|
const found = (bridge?.findings.length ?? 0) > 0;
|
|
379
407
|
if (run.cancelled)
|
|
380
|
-
return { outcome: "failed", error: "Cancelled by the owner" };
|
|
408
|
+
return { outcome: "failed", error: "Cancelled by the owner", errorCode: "cancelled" };
|
|
381
409
|
if (missingLogin(e.engine, run.output.join("\n")) && !found)
|
|
382
|
-
return { outcome: "failed", error: signInHelp(e.engine), engineAuth: "missing_login" };
|
|
410
|
+
return { outcome: "failed", error: signInHelp(e.engine), errorCode: "engine_not_signed_in", engineAuth: "missing_login" };
|
|
383
411
|
const limit = `Stopped at the ${e.limits.maxMinutes}-minute limit.`;
|
|
384
412
|
if (run.timedOut)
|
|
385
|
-
return { outcome: found ? "completed" : "failed", summary: summary || limit, error: limit };
|
|
413
|
+
return { outcome: found ? "completed" : "failed", summary: summary || limit, error: limit, errorCode: "time_limit" };
|
|
386
414
|
if (code !== 0 && !bridge?.summary) {
|
|
387
415
|
const tail = run.output.slice(-5).join("\n").slice(-800);
|
|
388
416
|
return { outcome: "failed", summary, error: `The ${e.engine} CLI exited with code ${code}${tail ? `: ${tail}` : ""}` };
|
|
@@ -391,7 +419,7 @@ export class LocalBrowserRuntime {
|
|
|
391
419
|
// says. Live run aac758dc (#952) ended `completed` with "the browser bridge required approval":
|
|
392
420
|
// no page opened, and the run looked like a success with nothing found.
|
|
393
421
|
if (!bridge?.calls)
|
|
394
|
-
return { outcome: "failed", summary, error: bridgeUnused(e.engine, summary) };
|
|
422
|
+
return { outcome: "failed", summary, error: bridgeUnused(e.engine, summary), errorCode: "bridge_unused" };
|
|
395
423
|
return { outcome: "completed", summary };
|
|
396
424
|
}
|
|
397
425
|
end(run, r) {
|
|
@@ -412,7 +440,13 @@ export class LocalBrowserRuntime {
|
|
|
412
440
|
traceId: run.envelope.runId,
|
|
413
441
|
engineAuth: r.engineAuth ?? run.engineAuth,
|
|
414
442
|
// CLI output becomes this text, and a CLI prints whatever it was given: redacted before it is kept.
|
|
415
|
-
...(r.outcome === "failed"
|
|
443
|
+
...(r.outcome === "failed"
|
|
444
|
+
? {
|
|
445
|
+
error: redactText((r.error ?? "The run failed without a reason.").slice(0, 1000), run.secrets),
|
|
446
|
+
// #944: WHICH cause, named here rather than guessed by the cloud from `engineAuth`.
|
|
447
|
+
errorCode: r.errorCode ?? "engine_failed",
|
|
448
|
+
}
|
|
449
|
+
: {}),
|
|
416
450
|
};
|
|
417
451
|
if (run.child && run.child.exitCode === null)
|
|
418
452
|
this.kill(run);
|