@bridge4dev/runner 0.49.0 → 0.50.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.
@@ -58,8 +58,39 @@ export interface ProbeResult {
58
58
  stdout: string;
59
59
  stderr: string;
60
60
  }
61
- /** Run one probe. Rejects on a non-zero exit, a timeout or a missing binary. */
62
- export type ProbeRunner = (file: string, argv: readonly string[], timeoutMs: number) => Promise<ProbeResult>;
61
+ export interface ProbeOptions {
62
+ /**
63
+ * Keep the output of a command that exited non-zero.
64
+ *
65
+ * **This is the bug the flag exists for, and it is worth stating in full.**
66
+ * `codex doctor --json` exits 1 whenever ANY of its checks fails — and three
67
+ * of them are network checks (`network.provider_reachability`,
68
+ * `network.websocket_reachability`, `updates.status`) that have nothing to
69
+ * do with the question we are asking. Measured on a live machine
70
+ * (codex-cli 0.153.4): with the network reachable, exit 0; with it blocked,
71
+ * **exit 1 while `runtime.provenance` still reports `status: ok` and
72
+ * `install method: npm (…)` on stdout**.
73
+ *
74
+ * So a passing hiccup on somebody's dev server used to throw away an answer
75
+ * we already held, the machine reported «install method unknown», and the
76
+ * «Update» button disappeared from that agent's row for the hour the
77
+ * measurement is cached. It came back on its own later, which is why it read
78
+ * as a permanent property of three machines rather than as a fault.
79
+ *
80
+ * A verdict about the machine's health is not a verdict about whether the
81
+ * command told us what we asked. Timeouts and «no such binary» are still
82
+ * failures — those really are «no answer».
83
+ */
84
+ tolerateExitCode?: boolean;
85
+ }
86
+ /** Run one probe. Rejects on a timeout, a missing binary, or (by default) a non-zero exit. */
87
+ export type ProbeRunner = (file: string, argv: readonly string[], timeoutMs: number, options?: ProbeOptions) => Promise<ProbeResult>;
88
+ /**
89
+ * Exported for its own tests: the interesting behaviour of this function is
90
+ * entirely in how it reads `execFile`'s error, and a stub `ProbeRunner` — which
91
+ * is what every other test here uses — cannot reproduce that by construction.
92
+ */
93
+ export declare const runProbe: ProbeRunner;
63
94
  /**
64
95
  * Fold whatever an agent calls its install method into the five values the
65
96
  * product reasons about.
@@ -26,12 +26,30 @@ let cached = null;
26
26
  export function invalidateAgentVersions() {
27
27
  cached = null;
28
28
  }
29
- const runProbe = (file, argv, timeoutMs) => new Promise((resolve, reject) => {
29
+ /**
30
+ * Exported for its own tests: the interesting behaviour of this function is
31
+ * entirely in how it reads `execFile`'s error, and a stub `ProbeRunner` — which
32
+ * is what every other test here uses — cannot reproduce that by construction.
33
+ */
34
+ export const runProbe = (file, argv, timeoutMs, options) => new Promise((resolve, reject) => {
30
35
  const child = execFile(file, [...argv], { timeout: timeoutMs, maxBuffer: PROBE_MAX_BUFFER }, (error, stdout, stderr) => {
31
- if (error)
32
- reject(error);
33
- else
36
+ if (!error) {
37
+ resolve({ stdout, stderr });
38
+ return;
39
+ }
40
+ /*
41
+ * `killed` is how Node reports the timeout it was given, and a process
42
+ * we killed produced a TRUNCATED page of JSON — parsing that would be
43
+ * reading half an answer. A missing binary never runs at all and has no
44
+ * numeric exit code, so it falls through to `reject` too.
45
+ */
46
+ const exited = typeof error.code === 'number';
47
+ const killed = error.killed === true;
48
+ if (options?.tolerateExitCode && exited && !killed) {
34
49
  resolve({ stdout, stderr });
50
+ return;
51
+ }
52
+ reject(error);
35
53
  });
36
54
  // `claude doctor` reads stdin. An `execFile` child inherits an open pipe
37
55
  // nobody ever writes to, so without this EOF the probe would sit there
@@ -117,10 +135,16 @@ export async function measureAgent(runtime, run = runProbe) {
117
135
  }
118
136
  // The install method is a nice-to-have: it decides whether a BUTTON appears,
119
137
  // never whether the version is shown. A doctor that failed leaves `unknown`.
138
+ //
139
+ // `tolerateExitCode` — see `ProbeOptions`. The diagnostics command grades the
140
+ // whole machine and fails its own exit code over an unreachable endpoint,
141
+ // while answering our question perfectly on stdout.
120
142
  let managedBy = 'unknown';
121
143
  try {
122
144
  const probe = runtime.managedByProbe;
123
- const { stdout, stderr } = await run(binary, probe.argv, PROBE_TIMEOUT_MS);
145
+ const { stdout, stderr } = await run(binary, probe.argv, PROBE_TIMEOUT_MS, {
146
+ tolerateExitCode: true,
147
+ });
124
148
  const output = `${stdout}\n${stderr}`;
125
149
  const source = probe.jsonPath ? readJsonPath(stdout, probe.jsonPath) : output;
126
150
  managedBy = normalizeManagedBy(source ? matchProbe(probe, source) : null);
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const RUNNER_VERSION = "0.49.0";
1
+ export declare const RUNNER_VERSION = "0.50.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in sync with package.json by the release script (manual for now).
2
- export const RUNNER_VERSION = '0.49.0';
2
+ export const RUNNER_VERSION = '0.50.0';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge4dev/runner",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "DevBridge dev runner — connects a dev server to DevBridge and runs agent sessions (Claude Code / Codex)",
5
5
  "homepage": "https://bridge4.dev",
6
6
  "license": "MIT",