byollm 0.1.0-alpha.75 → 0.1.0-alpha.76

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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!WARNING]
2
- > **Alpha (`0.1.0-alpha.75`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.76`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > Install it deliberately: `npx byollm@alpha`, or `npm install byollm@alpha`.
5
5
  >
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-ID6QL5NR.js";
4
+ } from "./chunk-MSRBBLRQ.js";
5
5
 
6
6
  // src/bin.ts
7
7
  process.exitCode = await main(process.argv.slice(2));
@@ -53,6 +53,32 @@ function asHttpOrigin(candidate) {
53
53
  // src/cli.ts
54
54
  import { access } from "fs/promises";
55
55
 
56
+ // src/emphasis.ts
57
+ var REVERSE = "\x1B[7m";
58
+ var BOLD = "\x1B[1m";
59
+ var RESET = "\x1B[0m";
60
+ function emphasisAllowed(context2) {
61
+ if (context2.env["NO_COLOR"] !== void 0) return false;
62
+ if (context2.env["FORCE_COLOR"] !== void 0) return true;
63
+ return context2.tty;
64
+ }
65
+ function emphasise(value, context2) {
66
+ return emphasisAllowed(context2) ? `${REVERSE}${BOLD} ${value} ${RESET}` : value;
67
+ }
68
+ function terminalContext() {
69
+ return {
70
+ // `@types/node` declares `isTTY` as `boolean`, and Node sets it to
71
+ // `undefined` when the stream is not a terminal. So this comparison is
72
+ // load-bearing even though the type says it cannot be — the linter is
73
+ // reasoning from a declaration that is wrong about its own runtime, and
74
+ // deleting it puts `undefined` into a field typed `boolean`. The same
75
+ // note stands over `setup.ts`, which met this first.
76
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare
77
+ tty: process.stdout.isTTY === true,
78
+ env: process.env
79
+ };
80
+ }
81
+
56
82
  // src/model.ts
57
83
  import { readFile, writeFile } from "fs/promises";
58
84
 
@@ -218,8 +244,8 @@ function until(message, now) {
218
244
  if (Number.isNaN(parsed)) return void 0;
219
245
  return parsed > now ? parsed : void 0;
220
246
  }
221
- function quotaBlock(message, now) {
222
- if (!OBSERVED.some((seen) => seen.pattern.test(message))) return void 0;
247
+ function quotaBlock(message, now, corpus = OBSERVED) {
248
+ if (!corpus.some((seen) => seen.pattern.test(message))) return void 0;
223
249
  const at = until(message, now);
224
250
  return { detail: message, ...at === void 0 ? {} : { until: at } };
225
251
  }
@@ -234,7 +260,6 @@ async function runProcessJob(job) {
234
260
  ok: false,
235
261
  code: "backend-error",
236
262
  message: "no time limit was set for this call",
237
- retryable: false,
238
263
  durationMs: 0
239
264
  };
240
265
  }
@@ -253,7 +278,6 @@ function spawnIn(job, scratch) {
253
278
  ok: false,
254
279
  code: "canceled",
255
280
  message: "the job was canceled before it started",
256
- retryable: false,
257
281
  durationMs: Date.now() - started
258
282
  });
259
283
  return;
@@ -316,7 +340,6 @@ function spawnIn(job, scratch) {
316
340
  ok: false,
317
341
  code: "backend-unreachable",
318
342
  message: `could not start ${displayName}: ${error.message}`,
319
- retryable: false,
320
343
  durationMs: Date.now() - started
321
344
  });
322
345
  });
@@ -328,7 +351,6 @@ function spawnIn(job, scratch) {
328
351
  ok: false,
329
352
  code: "canceled",
330
353
  message: "the job was canceled",
331
- retryable: false,
332
354
  durationMs
333
355
  });
334
356
  return;
@@ -338,7 +360,6 @@ function spawnIn(job, scratch) {
338
360
  ok: false,
339
361
  code: "timeout",
340
362
  message: `the model did not answer within ${String(request.timeoutMs)}ms`,
341
- retryable: true,
342
363
  durationMs
343
364
  });
344
365
  return;
@@ -348,7 +369,6 @@ function spawnIn(job, scratch) {
348
369
  ok: false,
349
370
  code: "output-too-large",
350
371
  message: `the model produced more than ${String(request.maxOutputBytes)} bytes`,
351
- retryable: false,
352
372
  durationMs
353
373
  });
354
374
  return;
@@ -356,7 +376,7 @@ function spawnIn(job, scratch) {
356
376
  if (code !== 0) {
357
377
  const said = `${stdout}
358
378
  ${stderr}`;
359
- const blocked = quotaBlock(said, Date.now());
379
+ const blocked = quotaBlock(said, (job.now ?? Date.now)(), job.corpus);
360
380
  const authFailed = blocked === void 0 && isAuthFailure(said);
361
381
  finish({
362
382
  ok: false,
@@ -378,7 +398,6 @@ ${stderr}`;
378
398
  goes to the site on success. */
379
399
  stdout.trim() !== "" ? `${displayName} failed: ${firstLine(stdout)}` : `${displayName} exited with status ${String(code)}`
380
400
  ),
381
- retryable: false,
382
401
  durationMs
383
402
  });
384
403
  return;
@@ -665,21 +684,20 @@ function failureMessage(event) {
665
684
  const error = record(event["error"]);
666
685
  return diagnostic(event["message"]) ?? diagnostic(error?.["message"]) ?? "codex reported an error";
667
686
  }
668
- function classifyFailure(message, now) {
669
- const blocked = quotaBlock(message, now);
687
+ function classifyFailure(message, now, corpus) {
688
+ const blocked = quotaBlock(message, now, corpus);
670
689
  if (blocked !== void 0) {
671
690
  return {
672
691
  code: "quota-exhausted",
673
- retryable: true,
674
692
  ...blocked.until === void 0 ? {} : { until: blocked.until }
675
693
  };
676
694
  }
677
695
  if (isAuthFailure(message)) {
678
- return { code: "unauthorized", retryable: false };
696
+ return { code: "unauthorized" };
679
697
  }
680
- return { code: "backend-error", retryable: false };
698
+ return { code: "backend-error" };
681
699
  }
682
- function parseCodexOutput(output, now = Date.now()) {
700
+ function parseCodexOutput(output, now = Date.now(), corpus) {
683
701
  const messages = [];
684
702
  for (const line of output.split(/\r?\n/u)) {
685
703
  if (line.trim() === "") continue;
@@ -701,7 +719,7 @@ function parseCodexOutput(output, now = Date.now()) {
701
719
  }
702
720
  if (type === "error" || type === "turn.failed") {
703
721
  const detail = failureMessage(event);
704
- const classified = classifyFailure(detail, now);
722
+ const classified = classifyFailure(detail, now, corpus);
705
723
  return {
706
724
  ok: false,
707
725
  ...classified,
@@ -715,8 +733,7 @@ function parseCodexOutput(output, now = Date.now()) {
715
733
  return {
716
734
  ok: false,
717
735
  code: "backend-error",
718
- message: "the codex CLI ended without a terminal event",
719
- retryable: false
736
+ message: "the codex CLI ended without a terminal event"
720
737
  };
721
738
  }
722
739
  var CodexCliBackend = class {
@@ -965,7 +982,6 @@ var OpenAiHttpBackend = class {
965
982
  ok: false,
966
983
  code: "backend-error",
967
984
  message: "no time limit was set for this call",
968
- retryable: false,
969
985
  durationMs: 0
970
986
  };
971
987
  }
@@ -990,7 +1006,6 @@ var OpenAiHttpBackend = class {
990
1006
  return this.#fail(
991
1007
  "unauthorized",
992
1008
  "the model server rejected our credentials",
993
- false,
994
1009
  started
995
1010
  );
996
1011
  }
@@ -998,7 +1013,6 @@ var OpenAiHttpBackend = class {
998
1013
  return this.#fail(
999
1014
  "model-not-found",
1000
1015
  `the model server does not know "${request.model}"`,
1001
- false,
1002
1016
  started
1003
1017
  );
1004
1018
  }
@@ -1006,7 +1020,6 @@ var OpenAiHttpBackend = class {
1006
1020
  return this.#fail(
1007
1021
  "backend-error",
1008
1022
  `the model server returned HTTP ${String(response.status)}`,
1009
- response.status >= 500,
1010
1023
  started
1011
1024
  );
1012
1025
  }
@@ -1015,7 +1028,6 @@ var OpenAiHttpBackend = class {
1015
1028
  return this.#fail(
1016
1029
  "output-too-large",
1017
1030
  `the model produced more than ${String(request.maxOutputBytes)} bytes`,
1018
- false,
1019
1031
  started
1020
1032
  );
1021
1033
  }
@@ -1024,37 +1036,43 @@ var OpenAiHttpBackend = class {
1024
1036
  return this.#fail(
1025
1037
  "backend-error",
1026
1038
  "the model server's response was not in OpenAI chat-completion shape",
1027
- false,
1028
1039
  started
1029
1040
  );
1030
1041
  }
1031
1042
  return { ok: true, text: content, durationMs: Date.now() - started };
1032
1043
  } catch (error) {
1033
1044
  if (request.signal.aborted) {
1034
- return this.#fail("canceled", "the job was canceled", false, started);
1045
+ return this.#fail("canceled", "the job was canceled", started);
1035
1046
  }
1036
1047
  if (isAbort(error)) {
1037
1048
  return this.#fail(
1038
1049
  "timeout",
1039
1050
  `the model did not answer within ${String(request.timeoutMs)}ms`,
1040
- true,
1041
1051
  started
1042
1052
  );
1043
1053
  }
1044
1054
  return this.#fail(
1045
1055
  "backend-unreachable",
1046
1056
  describeFetchError(error, this.#baseUrl.origin),
1047
- true,
1048
1057
  started
1049
1058
  );
1050
1059
  }
1051
1060
  }
1052
- #fail(code, message, retryable, started) {
1061
+ /*
1062
+ * No `retryable` argument any more — ruled 2026-09-04.
1063
+ *
1064
+ * This adapter used to decide it per failure, and it was the only one whose
1065
+ * rule was defensible: `true` for unreachable and for a 5xx. That is
1066
+ * exactly why removing it matters. The decision now lives once, in the
1067
+ * site-facing class table, and it was the *disagreement* between three
1068
+ * adapters that let a value only quota produced become readable as a fact
1069
+ * about somebody's account.
1070
+ */
1071
+ #fail(code, message, started) {
1053
1072
  return {
1054
1073
  ok: false,
1055
1074
  code,
1056
1075
  message,
1057
- retryable,
1058
1076
  durationMs: Date.now() - started
1059
1077
  };
1060
1078
  }
@@ -3486,27 +3504,27 @@ var FOR_SITE = Object.freeze({
3486
3504
  "backend-unreachable": {
3487
3505
  code: "service_unavailable",
3488
3506
  message: SERVICE_UNAVAILABLE,
3489
- retryable: false
3507
+ retryable: true
3490
3508
  },
3491
3509
  "backend-error": {
3492
3510
  code: "service_unavailable",
3493
3511
  message: SERVICE_UNAVAILABLE,
3494
- retryable: false
3512
+ retryable: true
3495
3513
  },
3496
3514
  "model-not-found": {
3497
3515
  code: "service_unavailable",
3498
3516
  message: SERVICE_UNAVAILABLE,
3499
- retryable: false
3517
+ retryable: true
3500
3518
  },
3501
3519
  "quota-exhausted": {
3502
3520
  code: "service_unavailable",
3503
3521
  message: SERVICE_UNAVAILABLE,
3504
- retryable: false
3522
+ retryable: true
3505
3523
  },
3506
3524
  unauthorized: {
3507
3525
  code: "service_unavailable",
3508
3526
  message: SERVICE_UNAVAILABLE,
3509
- retryable: false
3527
+ retryable: true
3510
3528
  },
3511
3529
  timeout: {
3512
3530
  code: "timeout",
@@ -3647,6 +3665,8 @@ var Runner = class {
3647
3665
  * service was blocked.
3648
3666
  */
3649
3667
  #blockedReport = /* @__PURE__ */ new Map();
3668
+ /** What was last handed to the writer, so an unchanged pass writes nothing. */
3669
+ #lastWritten;
3650
3670
  /**
3651
3671
  * The key grants are checked against — from the pairing, or adopted later.
3652
3672
  *
@@ -3769,6 +3789,7 @@ var Runner = class {
3769
3789
  }
3770
3790
  this.#blocked.delete(route.service);
3771
3791
  this.#blockedReport.delete(route.service);
3792
+ this.serviceStates.delete(route.service);
3772
3793
  }
3773
3794
  let usable = probed.get(route.service);
3774
3795
  if (usable === void 0) {
@@ -3831,6 +3852,7 @@ var Runner = class {
3831
3852
  });
3832
3853
  }
3833
3854
  this.#capabilities = capabilities;
3855
+ this.#writeServiceStates();
3834
3856
  return capabilities;
3835
3857
  }
3836
3858
  #backendFor(route) {
@@ -4166,6 +4188,9 @@ var Runner = class {
4166
4188
  outcome: "error",
4167
4189
  code: "no-capability",
4168
4190
  message: "this device has no route for that job kind",
4191
+ /* Not retryable, and not from the class table: this is the runner's
4192
+ own refusal rather than a backend failure. A device with no route
4193
+ for a kind will not grow one by being asked again. */
4169
4194
  retryable: false
4170
4195
  };
4171
4196
  }
@@ -4210,6 +4235,12 @@ var Runner = class {
4210
4235
  });
4211
4236
  if (!result.ok && result.code === "unauthorized") {
4212
4237
  this.#unauthenticated.add(route.service);
4238
+ this.#noteServiceState(route.service, {
4239
+ state: {
4240
+ kind: "signed-out",
4241
+ detail: result.message
4242
+ }
4243
+ });
4213
4244
  this.#options.onEvent?.({
4214
4245
  type: "service-not-signed-in",
4215
4246
  service: route.service,
@@ -4218,18 +4249,15 @@ var Runner = class {
4218
4249
  }
4219
4250
  if (!result.ok && result.code === "quota-exhausted") {
4220
4251
  this.#blocked.set(route.service, result.until);
4221
- const report2 = {
4252
+ const report2 = this.#noteServiceState(route.service, {
4222
4253
  ...this.serviceStates.get(route.service),
4223
4254
  state: {
4224
4255
  kind: "blocked",
4225
4256
  detail: result.message,
4226
4257
  ...result.until === void 0 ? {} : { until: result.until }
4227
4258
  }
4228
- };
4229
- this.serviceStates.set(route.service, report2);
4259
+ });
4230
4260
  this.#blockedReport.set(route.service, report2);
4231
- const noted = this.#options.onServiceStates?.(this.serviceStates);
4232
- if (noted !== void 0) void noted.catch(() => void 0);
4233
4261
  this.#options.onEvent?.({
4234
4262
  type: "service-out-of-quota",
4235
4263
  service: route.service,
@@ -4339,6 +4367,34 @@ var Runner = class {
4339
4367
  await sleep(heartbeatMs * (0.85 + Math.random() * 0.3), signal);
4340
4368
  }
4341
4369
  }
4370
+ /**
4371
+ * Record a service's state, and tell whoever writes the file — S-2.
4372
+ *
4373
+ * One door, because the bug was two doors and only one of them wrote. The
4374
+ * quota path wrote on block and not on lift; the sign-out path never wrote
4375
+ * at all. Both are changes `byollm status` would report differently, and
4376
+ * status is a different process whose only source is that file.
4377
+ */
4378
+ #noteServiceState(service, report2) {
4379
+ this.serviceStates.set(service, report2);
4380
+ this.#writeServiceStates();
4381
+ return report2;
4382
+ }
4383
+ /**
4384
+ * Hand the current map to whoever persists it.
4385
+ *
4386
+ * Not awaited, and the rejection is swallowed on purpose: a job must not
4387
+ * fail — nor the runner die on an unhandled rejection — because a status
4388
+ * note could not be written. The note is evidence about the job, not part
4389
+ * of it.
4390
+ */
4391
+ #writeServiceStates() {
4392
+ const now = JSON.stringify([...this.serviceStates]);
4393
+ if (now === this.#lastWritten) return;
4394
+ this.#lastWritten = now;
4395
+ const noted = this.#options.onServiceStates?.(this.serviceStates);
4396
+ if (noted !== void 0) void noted.catch(() => void 0);
4397
+ }
4342
4398
  /**
4343
4399
  * Note a refusal that means the relationship is over.
4344
4400
  *
@@ -5907,7 +5963,7 @@ Connecting to ${origin}
5907
5963
  `
5908
5964
  Your steps:
5909
5965
  1) Open: ${info.verificationUrl}
5910
- 2) Enter code: ${info.userCode} (expires in ${String(minutes)} minutes)
5966
+ 2) Enter code: ${emphasise(info.userCode, terminalContext())} (expires in ${String(minutes)} minutes)
5911
5967
  3) Check the screen shows this device's fingerprint, then approve:
5912
5968
 
5913
5969
  ${fingerprint3(deviceIdentity.identity)}
@@ -6973,7 +7029,7 @@ async function exists(path) {
6973
7029
  }
6974
7030
 
6975
7031
  // src/index.ts
6976
- var DAEMON_VERSION = "0.1.0-alpha.75";
7032
+ var DAEMON_VERSION = "0.1.0-alpha.76";
6977
7033
  function daemonVersion() {
6978
7034
  return {
6979
7035
  daemon: DAEMON_VERSION,
@@ -7025,4 +7081,4 @@ export {
7025
7081
  runCli,
7026
7082
  main
7027
7083
  };
7028
- //# sourceMappingURL=chunk-ID6QL5NR.js.map
7084
+ //# sourceMappingURL=chunk-MSRBBLRQ.js.map