@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
- void this.launch(run, selfUrl).catch((err) => this.end(run, { outcome: "failed", error: err instanceof Error ? err.message : String(err) }));
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, { outcome: "failed", error: missing ? `The ${e.engine === "claude" ? "Claude Code" : "Codex"} CLI is not installed on this machine (\`${spec.command}\` was not found).` : err.message });
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" ? { error: redactText((r.error ?? "The run failed without a reason.").slice(0, 1000), run.secrets) } : {}),
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.85",
3
+ "version": "0.4.86",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",