tines 0.0.203 → 0.0.205

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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/index.js +1136 -374
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3407,8 +3407,8 @@ var STATE_CATEGORIES = [
3407
3407
  "awaiting_human",
3408
3408
  "done"
3409
3409
  ];
3410
- function runRefLabel(run) {
3411
- return run.issue_ref ? `run on ${run.issue_ref.project_name}/${run.issue_ref.number}` : `run ${run.run_id}`;
3410
+ function runRefLabel(run2) {
3411
+ return run2.issue_ref ? `run on ${run2.issue_ref.project_name}/${run2.issue_ref.number}` : `run ${run2.run_id}`;
3412
3412
  }
3413
3413
  function actorLabel(actor) {
3414
3414
  if (actor.run) {
@@ -3530,28 +3530,28 @@ function ageLabel(at, now = Date.now()) {
3530
3530
  function prUrlOf(v) {
3531
3531
  return v.pr_repo_url && v.pr_number !== null ? `${v.pr_repo_url}/pull/${v.pr_number}` : null;
3532
3532
  }
3533
- function runDurationLabel(run, now = Date.now()) {
3534
- if (!run.started_at) return "\u2014";
3535
- const seconds = Math.max(0, Math.round(((run.ended_at ?? now) - run.started_at) / 1e3));
3533
+ function runDurationLabel(run2, now = Date.now()) {
3534
+ if (!run2.started_at) return "\u2014";
3535
+ const seconds = Math.max(0, Math.round(((run2.ended_at ?? now) - run2.started_at) / 1e3));
3536
3536
  return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m`;
3537
3537
  }
3538
- function runCostLabel(run) {
3539
- const usage = run.usage;
3540
- if (!usage) return null;
3541
- if (usage.cost_usd !== void 0) {
3542
- const dollars = usage.cost_usd === 0 ? "$0" : usage.cost_usd < 0.01 ? "<$0.01" : `$${usage.cost_usd.toFixed(2)}`;
3543
- const source = usage.cost_source === "provider" ? "Reported" : usage.cost_source === "priced" ? "Estimated" : "Recorded";
3538
+ function runCostLabel(run2) {
3539
+ const usage2 = run2.usage;
3540
+ if (!usage2) return null;
3541
+ if (usage2.cost_usd !== void 0) {
3542
+ const dollars = usage2.cost_usd === 0 ? "$0" : usage2.cost_usd < 0.01 ? "<$0.01" : `$${usage2.cost_usd.toFixed(2)}`;
3543
+ const source = usage2.cost_source === "provider" ? "Reported" : usage2.cost_source === "priced" ? "Estimated" : "Recorded";
3544
3544
  return `${dollars} ${source}`;
3545
3545
  }
3546
- if (usage.pricing?.status === "unpriced") return "Unpriced";
3547
- if (usage.cost_source === "none") return "Unreported";
3546
+ if (usage2.pricing?.status === "unpriced") return "Unpriced";
3547
+ if (usage2.cost_source === "none") return "Unreported";
3548
3548
  const measured = [
3549
- usage.input_tokens,
3550
- usage.output_tokens,
3551
- usage.cache_read_tokens,
3552
- usage.cache_write_tokens
3549
+ usage2.input_tokens,
3550
+ usage2.output_tokens,
3551
+ usage2.cache_read_tokens,
3552
+ usage2.cache_write_tokens
3553
3553
  ].some((value) => value !== void 0);
3554
- const tokens = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0) + (usage.cache_read_tokens ?? 0) + (usage.cache_write_tokens ?? 0);
3554
+ const tokens = (usage2.input_tokens ?? 0) + (usage2.output_tokens ?? 0) + (usage2.cache_read_tokens ?? 0) + (usage2.cache_write_tokens ?? 0);
3555
3555
  return tokens > 0 ? `${tokens.toLocaleString()} tok` : measured ? "Unpriced" : null;
3556
3556
  }
3557
3557
  function utilizationLabel(quota, activeRuns, stateName = (id2) => id2) {
@@ -3559,13 +3559,13 @@ function utilizationLabel(quota, activeRuns, stateName = (id2) => id2) {
3559
3559
  return `${activeRuns.length}/${quota.limit} global slot${quota.limit === 1 ? "" : "s"} in use`;
3560
3560
  }
3561
3561
  const counts = /* @__PURE__ */ new Map();
3562
- for (const run of activeRuns) {
3563
- const entry = counts.get(run.state_id_at_start) ?? {
3564
- name: run.state_at_start_name ?? stateName(run.state_id_at_start),
3562
+ for (const run2 of activeRuns) {
3563
+ const entry = counts.get(run2.state_id_at_start) ?? {
3564
+ name: run2.state_at_start_name ?? stateName(run2.state_id_at_start),
3565
3565
  n: 0
3566
3566
  };
3567
3567
  entry.n += 1;
3568
- counts.set(run.state_id_at_start, entry);
3568
+ counts.set(run2.state_id_at_start, entry);
3569
3569
  }
3570
3570
  for (const stateId of Object.keys(quota.overrides)) {
3571
3571
  if (!counts.has(stateId)) counts.set(stateId, { name: stateName(stateId), n: 0 });
@@ -3587,8 +3587,8 @@ function str(v) {
3587
3587
  function isoTime(v) {
3588
3588
  return typeof v === "number" && Number.isFinite(v) ? new Date(v).toISOString() : "an unknown time";
3589
3589
  }
3590
- function joinChanged(v, sep2) {
3591
- return Array.isArray(v) ? v.join(sep2) : "";
3590
+ function joinChanged(v, sep4) {
3591
+ return Array.isArray(v) ? v.join(sep4) : "";
3592
3592
  }
3593
3593
  function action(type) {
3594
3594
  return type.split(".")[1] ?? type;
@@ -5354,30 +5354,30 @@ function structuralDocument(value, allowMissingDigest) {
5354
5354
  throw new LibraryValidationError([
5355
5355
  { path: "", code: "invalid_document", message: "Library document must be an object" }
5356
5356
  ]);
5357
- const object3 = value;
5358
- if (object3.format !== "tines.library")
5357
+ const object4 = value;
5358
+ if (object4.format !== "tines.library")
5359
5359
  throw new LibraryValidationError([
5360
5360
  { path: "/format", code: "invalid_format", message: "Expected tines.library" }
5361
5361
  ]);
5362
- if (object3.version !== 3)
5362
+ if (object4.version !== 3)
5363
5363
  throw new LibraryValidationError([
5364
5364
  {
5365
5365
  path: "/version",
5366
5366
  code: "unsupported_version",
5367
- message: `Unsupported library version ${String(object3.version)}`
5367
+ message: `Unsupported library version ${String(object4.version)}`
5368
5368
  }
5369
5369
  ]);
5370
- if (object3.profile !== "workflow" && object3.profile !== "library")
5370
+ if (object4.profile !== "workflow" && object4.profile !== "library")
5371
5371
  throw new LibraryValidationError([
5372
5372
  { path: "/profile", code: "invalid_profile", message: "Expected workflow or library profile" }
5373
5373
  ]);
5374
- const allowed = TOP_LEVEL[object3.profile];
5375
- const unknown = Object.keys(object3).find((key) => !allowed.includes(key));
5374
+ const allowed = TOP_LEVEL[object4.profile];
5375
+ const unknown = Object.keys(object4).find((key) => !allowed.includes(key));
5376
5376
  if (unknown)
5377
5377
  throw new LibraryValidationError([
5378
5378
  { path: `/${unknown}`, code: "unknown_field", message: `Unknown field ${unknown}` }
5379
5379
  ]);
5380
- if (!Number.isSafeInteger(object3.exported_at) || object3.exported_at < 0)
5380
+ if (!Number.isSafeInteger(object4.exported_at) || object4.exported_at < 0)
5381
5381
  throw new LibraryValidationError([
5382
5382
  {
5383
5383
  path: "/exported_at",
@@ -5385,7 +5385,7 @@ function structuralDocument(value, allowMissingDigest) {
5385
5385
  message: "exported_at must be a non-negative safe integer"
5386
5386
  }
5387
5387
  ]);
5388
- if (!(allowMissingDigest && object3.digest === void 0) && (typeof object3.digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(object3.digest)))
5388
+ if (!(allowMissingDigest && object4.digest === void 0) && (typeof object4.digest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(object4.digest)))
5389
5389
  throw new LibraryValidationError([
5390
5390
  {
5391
5391
  path: "/digest",
@@ -5395,14 +5395,14 @@ function structuralDocument(value, allowMissingDigest) {
5395
5395
  ]);
5396
5396
  for (const key of allowed) {
5397
5397
  if (key === "digest" && allowMissingDigest) continue;
5398
- if (!(key in object3))
5398
+ if (!(key in object4))
5399
5399
  throw new LibraryValidationError([
5400
5400
  { path: `/${key}`, code: "missing_field", message: `Missing required field ${key}` }
5401
5401
  ]);
5402
5402
  }
5403
5403
  return {
5404
- ...object3,
5405
- digest: typeof object3.digest === "string" ? object3.digest : ""
5404
+ ...object4,
5405
+ digest: typeof object4.digest === "string" ? object4.digest : ""
5406
5406
  };
5407
5407
  }
5408
5408
  async function parseLibraryV3Document(input2, options = {}) {
@@ -5623,7 +5623,7 @@ function roundLines(round, now = Date.now()) {
5623
5623
  if (latest.artifacts.length > 0) {
5624
5624
  lines2.push(` ${latest.artifacts.map(artifactChangeLabel).join(" \xB7 ")}`);
5625
5625
  }
5626
- for (const run of earlier) lines2.push(` ${earlierAttemptLine(run, now)}`);
5626
+ for (const run2 of earlier) lines2.push(` ${earlierAttemptLine(run2, now)}`);
5627
5627
  if (latest.summary_comment) {
5628
5628
  lines2.push(` summary (${latest.summary_comment.id}):`);
5629
5629
  for (const line of latest.summary_comment.body.split("\n")) lines2.push(` ${line}`);
@@ -5637,20 +5637,20 @@ function roundLines(round, now = Date.now()) {
5637
5637
  }
5638
5638
  return lines2;
5639
5639
  }
5640
- function runHeadline(run, now) {
5641
- const cost = runCostLabel(run);
5640
+ function runHeadline(run2, now) {
5641
+ const cost = runCostLabel(run2);
5642
5642
  return [
5643
- `${run.run_id} on ${run.runner_name}`,
5644
- runDurationLabel(run, now),
5645
- run.outcome ?? run.status,
5643
+ `${run2.run_id} on ${run2.runner_name}`,
5644
+ runDurationLabel(run2, now),
5645
+ run2.outcome ?? run2.status,
5646
5646
  ...cost ? [cost] : [],
5647
- run.transition ? `"${run.transition.action ?? "moved directly"}" \u2192 ${run.transition.to_state.name}` : "no transition"
5647
+ run2.transition ? `"${run2.transition.action ?? "moved directly"}" \u2192 ${run2.transition.to_state.name}` : "no transition"
5648
5648
  ].join(" \xB7 ");
5649
5649
  }
5650
- function earlierAttemptLine(run, now) {
5651
- const back = run.returned_via;
5650
+ function earlierAttemptLine(run2, now) {
5651
+ const back = run2.returned_via;
5652
5652
  const how = back ? back.action ? `sent back by ${back.from_state.name} ("${back.action}")` : "moved directly back" : "never came back";
5653
- return `${run.run_id} \xB7 ${runDurationLabel(run, now)} \xB7 ${run.outcome ?? run.status} \xB7 ${how}`;
5653
+ return `${run2.run_id} \xB7 ${runDurationLabel(run2, now)} \xB7 ${run2.outcome ?? run2.status} \xB7 ${how}`;
5654
5654
  }
5655
5655
  function artifactChangeLabel(a) {
5656
5656
  const versions = a.from_version === null ? `v${a.to_version}` : `v${a.from_version} \u2192 v${a.to_version}`;
@@ -5669,16 +5669,16 @@ function roundSummaryLabel(summary) {
5669
5669
  ];
5670
5670
  return parts.length > 0 ? parts.join(" \xB7 ") : "\u2014";
5671
5671
  }
5672
- function runRow(run) {
5672
+ function runRow(run2) {
5673
5673
  return [
5674
- run.id,
5675
- run.issue_ref ? issueRef(run.issue_ref) : run.issue_id,
5676
- run.runner_name,
5677
- `${run.tier}${run.model ? ` (${run.model})` : ""}`,
5678
- `${run.status}${run.resumed_from_run_id ? ` \xB7 resumed run ${run.resumed_from_run_id}` : ""}`,
5679
- runDurationLabel(run),
5680
- runCostLabel(run) ?? "\u2014",
5681
- timestamp(run.created_at)
5674
+ run2.id,
5675
+ run2.issue_ref ? issueRef(run2.issue_ref) : run2.issue_id,
5676
+ run2.runner_name,
5677
+ `${run2.tier}${run2.model ? ` (${run2.model})` : ""}`,
5678
+ `${run2.status}${run2.resumed_from_run_id ? ` \xB7 resumed run ${run2.resumed_from_run_id}` : ""}`,
5679
+ runDurationLabel(run2),
5680
+ runCostLabel(run2) ?? "\u2014",
5681
+ timestamp(run2.created_at)
5682
5682
  ];
5683
5683
  }
5684
5684
  function byteSize(bytes2) {
@@ -5751,11 +5751,11 @@ function assertNewStatesHavePrompts(states, prompts) {
5751
5751
  }
5752
5752
  }
5753
5753
  function parseScheduleRef(ref) {
5754
- const sep2 = ref.indexOf("/");
5755
- if (sep2 < 1 || sep2 === ref.length - 1) {
5754
+ const sep4 = ref.indexOf("/");
5755
+ if (sep4 < 1 || sep4 === ref.length - 1) {
5756
5756
  throw new CliError(`schedule reference must look like <project>/<name>, got "${ref}"`);
5757
5757
  }
5758
- return { project: ref.slice(0, sep2), name: ref.slice(sep2 + 1) };
5758
+ return { project: ref.slice(0, sep4), name: ref.slice(sep4 + 1) };
5759
5759
  }
5760
5760
  function parseIssueRef(ref) {
5761
5761
  const match = ref.match(/^(.+)\/(\d+)$/);
@@ -5763,12 +5763,12 @@ function parseIssueRef(ref) {
5763
5763
  return { project: match[1], number: Number.parseInt(match[2], 10) };
5764
5764
  }
5765
5765
  function parseFileSpec(spec) {
5766
- const sep2 = spec.indexOf("=");
5767
- if (sep2 < 1 || sep2 === spec.length - 1) {
5766
+ const sep4 = spec.indexOf("=");
5767
+ if (sep4 < 1 || sep4 === spec.length - 1) {
5768
5768
  throw new CliError(`--file must look like <path>=@<local-file>, got "${spec}"`);
5769
5769
  }
5770
- const path2 = spec.slice(0, sep2);
5771
- const source = spec.slice(sep2 + 1);
5770
+ const path2 = spec.slice(0, sep4);
5771
+ const source = spec.slice(sep4 + 1);
5772
5772
  if (!source.startsWith("@")) {
5773
5773
  throw new CliError(
5774
5774
  `skill file content always comes from a local file: --file ${path2}=@<local-file>`
@@ -5782,10 +5782,10 @@ function parseFileSpec(spec) {
5782
5782
  }
5783
5783
  }
5784
5784
  function parseTargetSpec(spec) {
5785
- const sep2 = spec.lastIndexOf(":");
5786
- if (sep2 === -1) return { name: spec };
5787
- const name2 = spec.slice(0, sep2);
5788
- const tier = spec.slice(sep2 + 1);
5785
+ const sep4 = spec.lastIndexOf(":");
5786
+ if (sep4 === -1) return { name: spec };
5787
+ const name2 = spec.slice(0, sep4);
5788
+ const tier = spec.slice(sep4 + 1);
5789
5789
  if (!name2) throw new CliError(`target must look like <runner>[:tier], got "${spec}"`);
5790
5790
  if (!MODEL_TIERS.includes(tier)) {
5791
5791
  throw new CliError(`unknown tier "${tier}" in "${spec}" (tiers: ${MODEL_TIERS.join(", ")})`);
@@ -5997,12 +5997,12 @@ async function resolveLabelFlag(api, ref) {
5997
5997
  return match.id;
5998
5998
  }
5999
5999
  async function resolveStateFlag(api, ref) {
6000
- const sep2 = ref.indexOf("/");
6001
- if (sep2 < 1 || sep2 === ref.length - 1) {
6000
+ const sep4 = ref.indexOf("/");
6001
+ if (sep4 < 1 || sep4 === ref.length - 1) {
6002
6002
  die(`--state must look like <workflow>/<state>, got "${ref}"`);
6003
6003
  }
6004
- const workflow2 = await resolveWorkflow(api, ref.slice(0, sep2));
6005
- const stateRef2 = ref.slice(sep2 + 1);
6004
+ const workflow2 = await resolveWorkflow(api, ref.slice(0, sep4));
6005
+ const stateRef2 = ref.slice(sep4 + 1);
6006
6006
  const state = workflow2.states.find((s) => s.name === stateRef2) ?? workflow2.states.find((s) => s.id === stateRef2);
6007
6007
  if (!state) {
6008
6008
  die(
@@ -8489,9 +8489,6 @@ function register7(program3) {
8489
8489
  });
8490
8490
  }
8491
8491
 
8492
- // src/commands/runners.ts
8493
- import { hostname as hostname2 } from "node:os";
8494
-
8495
8492
  // src/daemon/daemon.ts
8496
8493
  import { spawn as spawn2 } from "node:child_process";
8497
8494
  import {
@@ -8505,10 +8502,9 @@ import {
8505
8502
  unlinkSync as unlinkSync2,
8506
8503
  writeFileSync as writeFileSync3
8507
8504
  } from "node:fs";
8508
- import { hostname, platform, arch } from "node:os";
8509
8505
  import { randomUUID } from "node:crypto";
8510
- import { dirname as dirname4, join as join5 } from "node:path";
8511
- import { StringDecoder } from "node:string_decoder";
8506
+ import { dirname as dirname4, join as join6 } from "node:path";
8507
+ import { StringDecoder as StringDecoder2 } from "node:string_decoder";
8512
8508
 
8513
8509
  // src/version.ts
8514
8510
  import { readFileSync as readFileSync6 } from "node:fs";
@@ -8535,7 +8531,17 @@ function copySummary(value) {
8535
8531
  ...value.pricingEvidence ? {
8536
8532
  pricingEvidence: {
8537
8533
  ...value.pricingEvidence,
8538
- ...value.pricingEvidence.raw_usage ? { raw_usage: { ...value.pricingEvidence.raw_usage } } : {}
8534
+ ...value.pricingEvidence.raw_usage ? { raw_usage: { ...value.pricingEvidence.raw_usage } } : {},
8535
+ ...value.pricingEvidence.request_context ? {
8536
+ request_context: {
8537
+ ...value.pricingEvidence.request_context,
8538
+ ...value.pricingEvidence.request_context.status === "complete" ? {
8539
+ reconciled_usage: {
8540
+ ...value.pricingEvidence.request_context.reconciled_usage
8541
+ }
8542
+ } : {}
8543
+ }
8544
+ } : {}
8539
8545
  }
8540
8546
  } : {}
8541
8547
  };
@@ -8663,10 +8669,10 @@ var ClaudeStreamRenderer = class {
8663
8669
  }
8664
8670
  if (validMetric(event.num_turns)) this.collected.numTurns = event.num_turns;
8665
8671
  if (validMetric(event.duration_ms)) this.collected.durationMs = event.duration_ms;
8666
- const usage = { cost_source: "provider" };
8672
+ const usage2 = { cost_source: "provider" };
8667
8673
  let measured = false;
8668
8674
  if (validMetric(event.total_cost_usd)) {
8669
- usage.cost_usd = event.total_cost_usd;
8675
+ usage2.cost_usd = event.total_cost_usd;
8670
8676
  measured = true;
8671
8677
  }
8672
8678
  const raw = event.usage;
@@ -8678,12 +8684,12 @@ var ClaudeStreamRenderer = class {
8678
8684
  ["cache_creation_input_tokens", "cache_write_tokens"]
8679
8685
  ]) {
8680
8686
  if (validMetric(raw[source])) {
8681
- usage[destination] = raw[source];
8687
+ usage2[destination] = raw[source];
8682
8688
  measured = true;
8683
8689
  }
8684
8690
  }
8685
8691
  }
8686
- if (measured) this.collected.usage = usage;
8692
+ if (measured) this.collected.usage = usage2;
8687
8693
  }
8688
8694
  };
8689
8695
 
@@ -8840,12 +8846,12 @@ var CodexStreamRenderer = class {
8840
8846
  this.stickyStatus = "nonmonotonic";
8841
8847
  }
8842
8848
  status = this.stickyStatus ?? status;
8843
- const usage = {};
8844
- if (complete) usage.input_tokens = input2;
8845
- if (rawCached !== void 0) usage.cache_read_tokens = rawCached;
8846
- if (rawWrite !== void 0) usage.cache_write_tokens = rawWrite;
8847
- if (output !== void 0) usage.output_tokens = output;
8848
- this.collected.usage = usage;
8849
+ const usage2 = {};
8850
+ if (complete) usage2.input_tokens = input2;
8851
+ if (rawCached !== void 0) usage2.cache_read_tokens = rawCached;
8852
+ if (rawWrite !== void 0) usage2.cache_write_tokens = rawWrite;
8853
+ if (output !== void 0) usage2.output_tokens = output;
8854
+ this.collected.usage = usage2;
8849
8855
  this.updateEvidence(status, void 0, rawUsage);
8850
8856
  }
8851
8857
  updateEvidence(status, rerouted, rawUsage) {
@@ -8866,6 +8872,261 @@ var CodexStreamRenderer = class {
8866
8872
  }
8867
8873
  };
8868
8874
 
8875
+ // src/daemon/codex-rollout.ts
8876
+ import { open, opendir, realpath, stat } from "node:fs/promises";
8877
+ import { isAbsolute, join as join3, relative, resolve, sep } from "node:path";
8878
+ import { homedir as homedir2 } from "node:os";
8879
+ import { StringDecoder } from "node:string_decoder";
8880
+ var CODEX_ROLLOUT_MAX_BYTES = 128 * 1024 * 1024;
8881
+ var CODEX_ROLLOUT_MAX_LINE_BYTES = 8 * 1024 * 1024;
8882
+ var CODEX_ROLLOUT_MAX_LINES = 25e4;
8883
+ var CODEX_ROLLOUT_MAX_REQUESTS = 1e4;
8884
+ var CODEX_ROLLOUT_TIMEOUT_MS = 5e3;
8885
+ var SUPPORTED_VERSION = "0.153.4";
8886
+ var FIELDS = [
8887
+ "input_tokens",
8888
+ "cached_input_tokens",
8889
+ "cache_write_input_tokens",
8890
+ "output_tokens"
8891
+ ];
8892
+ var unavailable = (reason) => ({
8893
+ version: 1,
8894
+ normalization: "codex-rollout-delta-v1",
8895
+ status: "unavailable",
8896
+ reason
8897
+ });
8898
+ var invalid2 = (reason, harness_version) => ({
8899
+ version: 1,
8900
+ normalization: "codex-rollout-delta-v1",
8901
+ ...harness_version ? { harness_version: harness_version.slice(0, 100) } : {},
8902
+ status: "invalid",
8903
+ reason
8904
+ });
8905
+ function object3(value) {
8906
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
8907
+ }
8908
+ function usage(value, validateClasses = true) {
8909
+ const raw = object3(value);
8910
+ if (!raw) return;
8911
+ const out = {};
8912
+ for (const field of FIELDS) {
8913
+ const metric = raw[field];
8914
+ if (!Number.isSafeInteger(metric) || metric < 0) return;
8915
+ out[field] = metric;
8916
+ }
8917
+ if (validateClasses && out.cached_input_tokens + out.cache_write_input_tokens > out.input_tokens)
8918
+ return;
8919
+ return out;
8920
+ }
8921
+ function equal(a, b) {
8922
+ return FIELDS.every((field) => a[field] === b[field]);
8923
+ }
8924
+ function reconcileCodexRollout(records, input2) {
8925
+ let metadata = false;
8926
+ let harnessVersion;
8927
+ let observedModel;
8928
+ let previous = {
8929
+ input_tokens: 0,
8930
+ cached_input_tokens: 0,
8931
+ cache_write_input_tokens: 0,
8932
+ output_tokens: 0
8933
+ };
8934
+ let observed = false;
8935
+ let requestCount = 0;
8936
+ let maxRequestInput = 0;
8937
+ let previousLast = { ...previous };
8938
+ for (const value of records) {
8939
+ const record = object3(value);
8940
+ const payload = object3(record?.payload);
8941
+ if (record?.type === "session_meta") {
8942
+ if (metadata) return invalid2("metadata_mismatch", harnessVersion);
8943
+ harnessVersion = typeof payload?.cli_version === "string" ? payload.cli_version : void 0;
8944
+ if (payload?.id !== input2.threadId || payload?.source !== "exec" || payload?.originator !== "codex_exec")
8945
+ return invalid2("metadata_mismatch", harnessVersion);
8946
+ metadata = true;
8947
+ continue;
8948
+ }
8949
+ if (record?.type === "turn_context" && typeof payload?.model === "string") {
8950
+ observedModel ??= payload.model;
8951
+ if (observedModel !== payload.model || input2.model && payload.model !== input2.model)
8952
+ return invalid2("model_mismatch", harnessVersion);
8953
+ continue;
8954
+ }
8955
+ if (record?.type !== "event_msg" || payload?.type !== "token_count") continue;
8956
+ const info = object3(payload.info);
8957
+ if (!info) continue;
8958
+ const total = usage(info.total_token_usage);
8959
+ const last = usage(info.last_token_usage, false);
8960
+ if (!total || !last) return invalid2("missing_dimension", harnessVersion);
8961
+ observed = true;
8962
+ if (equal(total, previous)) {
8963
+ if (!equal(last, previousLast)) return invalid2("delta_mismatch", harnessVersion);
8964
+ continue;
8965
+ }
8966
+ if (FIELDS.some((field) => total[field] < previous[field]))
8967
+ return invalid2("nonmonotonic", harnessVersion);
8968
+ const delta = {};
8969
+ for (const field of FIELDS) delta[field] = total[field] - previous[field];
8970
+ if (delta.cached_input_tokens + delta.cache_write_input_tokens > delta.input_tokens)
8971
+ return invalid2("nonmonotonic", harnessVersion);
8972
+ if (!equal(delta, last)) return invalid2("delta_mismatch", harnessVersion);
8973
+ requestCount++;
8974
+ if (requestCount > CODEX_ROLLOUT_MAX_REQUESTS) return invalid2("limit_exceeded", harnessVersion);
8975
+ maxRequestInput = Math.max(maxRequestInput, delta.input_tokens);
8976
+ previous = total;
8977
+ previousLast = delta;
8978
+ }
8979
+ if (!metadata) return invalid2("metadata_mismatch", harnessVersion);
8980
+ if (harnessVersion !== SUPPORTED_VERSION) {
8981
+ return {
8982
+ version: 1,
8983
+ normalization: "codex-rollout-delta-v1",
8984
+ ...harnessVersion ? { harness_version: harnessVersion.slice(0, 100) } : {},
8985
+ status: "unsupported",
8986
+ reason: "unsupported_version"
8987
+ };
8988
+ }
8989
+ const terminalZero = FIELDS.every((field) => input2.terminalUsage[field] === 0);
8990
+ if (!observed && !terminalZero) return invalid2("missing_dimension", harnessVersion);
8991
+ if (!equal(previous, input2.terminalUsage)) return invalid2("terminal_mismatch", harnessVersion);
8992
+ return {
8993
+ version: 1,
8994
+ normalization: "codex-rollout-delta-v1",
8995
+ harness_version: SUPPORTED_VERSION,
8996
+ status: "complete",
8997
+ request_count: requestCount,
8998
+ max_request_input_tokens: maxRequestInput,
8999
+ reconciled_usage: { ...previous }
9000
+ };
9001
+ }
9002
+ function resolveCodexHome(env, workspace) {
9003
+ const configured = env.CODEX_HOME;
9004
+ return configured ? resolve(workspace, configured) : join3(homedir2(), ".codex");
9005
+ }
9006
+ function dateDirectories(startedAt, endedAt) {
9007
+ const first = new Date(startedAt);
9008
+ first.setUTCDate(first.getUTCDate() - 1);
9009
+ first.setUTCHours(0, 0, 0, 0);
9010
+ const last = new Date(endedAt);
9011
+ last.setUTCDate(last.getUTCDate() + 1);
9012
+ last.setUTCHours(0, 0, 0, 0);
9013
+ const result = [];
9014
+ for (let date = first; date <= last; date = new Date(date.getTime() + 864e5)) {
9015
+ result.push(
9016
+ join3(
9017
+ String(date.getUTCFullYear()),
9018
+ String(date.getUTCMonth() + 1).padStart(2, "0"),
9019
+ String(date.getUTCDate()).padStart(2, "0")
9020
+ )
9021
+ );
9022
+ if (result.length > 10) return null;
9023
+ }
9024
+ return result;
9025
+ }
9026
+ async function readCodexRolloutRecords(stream, timeoutMs) {
9027
+ let timedOut = false;
9028
+ const timer = setTimeout(
9029
+ () => {
9030
+ timedOut = true;
9031
+ stream.destroy(new Error("Codex rollout read deadline exceeded"));
9032
+ },
9033
+ Math.max(0, timeoutMs)
9034
+ );
9035
+ const decoder = new StringDecoder("utf8");
9036
+ let pending = "";
9037
+ let bytes2 = 0;
9038
+ let lines2 = 0;
9039
+ const records = [];
9040
+ const consume = (line) => {
9041
+ if (++lines2 > CODEX_ROLLOUT_MAX_LINES || Buffer.byteLength(line) > CODEX_ROLLOUT_MAX_LINE_BYTES)
9042
+ return { reason: "limit_exceeded" };
9043
+ if (!line.trim()) return;
9044
+ try {
9045
+ records.push(JSON.parse(line));
9046
+ } catch {
9047
+ return { reason: "malformed" };
9048
+ }
9049
+ };
9050
+ try {
9051
+ for await (const value of stream) {
9052
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
9053
+ bytes2 += chunk.length;
9054
+ if (bytes2 > CODEX_ROLLOUT_MAX_BYTES) return { reason: "limit_exceeded" };
9055
+ pending += decoder.write(chunk);
9056
+ let newline;
9057
+ while ((newline = pending.indexOf("\n")) >= 0) {
9058
+ const failure = consume(pending.slice(0, newline));
9059
+ if (failure) return failure;
9060
+ pending = pending.slice(newline + 1);
9061
+ }
9062
+ if (Buffer.byteLength(pending) > CODEX_ROLLOUT_MAX_LINE_BYTES)
9063
+ return { reason: "limit_exceeded" };
9064
+ }
9065
+ pending += decoder.end();
9066
+ if (pending.length) {
9067
+ const failure = consume(pending);
9068
+ if (failure) return failure;
9069
+ }
9070
+ return { records };
9071
+ } catch {
9072
+ return { reason: timedOut ? "limit_exceeded" : "read_failed" };
9073
+ } finally {
9074
+ clearTimeout(timer);
9075
+ stream.destroy();
9076
+ }
9077
+ }
9078
+ async function collectCodexRequestContext(input2) {
9079
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input2.threadId))
9080
+ return unavailable("thread_id_missing");
9081
+ const dates = dateDirectories(input2.startedAt, input2.endedAt);
9082
+ if (!dates) return unavailable("limit_exceeded");
9083
+ const sessions = join3(input2.codexHome, "sessions");
9084
+ let root;
9085
+ try {
9086
+ root = await realpath(sessions);
9087
+ } catch {
9088
+ return unavailable("rollout_missing");
9089
+ }
9090
+ const candidates = [];
9091
+ let entries = 0;
9092
+ const deadline = Date.now() + CODEX_ROLLOUT_TIMEOUT_MS;
9093
+ try {
9094
+ for (const date of dates) {
9095
+ const directory = join3(root, date);
9096
+ let stream;
9097
+ try {
9098
+ stream = await opendir(directory);
9099
+ } catch {
9100
+ continue;
9101
+ }
9102
+ for await (const entry of stream) {
9103
+ if (++entries > 2e4 || Date.now() > deadline) return unavailable("limit_exceeded");
9104
+ if ((entry.isFile() || entry.isSymbolicLink()) && entry.name.startsWith("rollout-") && entry.name.endsWith(`-${input2.threadId}.jsonl`))
9105
+ candidates.push(join3(directory, entry.name));
9106
+ }
9107
+ }
9108
+ if (candidates.length === 0) return unavailable("rollout_missing");
9109
+ if (candidates.length !== 1) return unavailable("rollout_ambiguous");
9110
+ const candidate = await realpath(candidates[0]);
9111
+ const inside = relative(root, candidate);
9112
+ if (inside.startsWith(`..${sep}`) || inside === ".." || isAbsolute(inside))
9113
+ return unavailable("unsafe_path");
9114
+ const before = await stat(candidate);
9115
+ if (!before.isFile() || before.size > CODEX_ROLLOUT_MAX_BYTES)
9116
+ return unavailable("limit_exceeded");
9117
+ const handle = await open(candidate, "r");
9118
+ const read = await readCodexRolloutRecords(handle.createReadStream(), deadline - Date.now());
9119
+ if ("reason" in read)
9120
+ return read.reason === "malformed" ? invalid2("malformed") : unavailable(read.reason);
9121
+ const after = await stat(candidate);
9122
+ if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs)
9123
+ return invalid2("read_failed");
9124
+ return reconcileCodexRollout(read.records, input2);
9125
+ } catch {
9126
+ return unavailable("read_failed");
9127
+ }
9128
+ }
9129
+
8869
9130
  // src/daemon/rate-limit.ts
8870
9131
  var USAGE_LIMIT_PREFIXES = [
8871
9132
  "You've hit your",
@@ -8969,11 +9230,11 @@ var DURATION = /(\d+)\s*([dhms])/g;
8969
9230
  function parseResetTime(text4, now) {
8970
9231
  const raw = text4.trim();
8971
9232
  if (!raw) return null;
8972
- const relative = raw.match(/^in\s+(.+)$/i);
8973
- if (relative) {
9233
+ const relative2 = raw.match(/^in\s+(.+)$/i);
9234
+ if (relative2) {
8974
9235
  let ms = 0;
8975
9236
  let matched = false;
8976
- for (const [, amount, unit] of relative[1].matchAll(DURATION)) {
9237
+ for (const [, amount, unit] of relative2[1].matchAll(DURATION)) {
8977
9238
  const n = Number(amount);
8978
9239
  ms += n * { d: 864e5, h: 36e5, m: 6e4, s: 1e3 }[unit];
8979
9240
  matched = true;
@@ -9056,10 +9317,10 @@ var RateLimitDetector = class {
9056
9317
  // src/daemon/cli-refresh.ts
9057
9318
  import { spawn } from "node:child_process";
9058
9319
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync7 } from "node:fs";
9059
- import { dirname as dirname3, join as join3 } from "node:path";
9320
+ import { dirname as dirname3, join as join4 } from "node:path";
9060
9321
 
9061
9322
  // src/daemon/support.ts
9062
- import { delimiter, sep } from "node:path";
9323
+ import { delimiter, sep as sep2 } from "node:path";
9063
9324
  var HARNESS_KINDS = ["claude_code", "codex", "custom"];
9064
9325
  var KEEP_WORKSPACES_MODES = ["never", "failed", "always"];
9065
9326
  function keepWorkspace(mode, outcome) {
@@ -9138,9 +9399,9 @@ function formatExitLine(exit) {
9138
9399
  return `# tines runner: exit ${parts.join(" ")} after ${formatDuration(exit.durationMs)}
9139
9400
  `;
9140
9401
  }
9141
- function exitLineForRun(run, exit) {
9142
- if (run.settled) return null;
9143
- return formatExitLine({ ...exit, timedOut: run.timedOut });
9402
+ function exitLineForRun(run2, exit) {
9403
+ if (run2.settled) return null;
9404
+ return formatExitLine({ ...exit, timedOut: run2.timedOut });
9144
9405
  }
9145
9406
  var RunTable = class {
9146
9407
  constructor(effects, opts = {}) {
@@ -9163,8 +9424,8 @@ var RunTable = class {
9163
9424
  has(runId) {
9164
9425
  return this.runs.has(runId);
9165
9426
  }
9166
- track(run) {
9167
- this.runs.set(run.runId, run);
9427
+ track(run2) {
9428
+ this.runs.set(run2.runId, run2);
9168
9429
  this.effects.persist();
9169
9430
  }
9170
9431
  /** Call after setting a run's pid so the state file reflects it. */
@@ -9180,10 +9441,10 @@ var RunTable = class {
9180
9441
  * already-settled run, a daemon shutdown. Guessing `failed` also errs the
9181
9442
  * safe way — it keeps a directory rather than destroying evidence.
9182
9443
  */
9183
- cleanup(run, outcome = "failed") {
9184
- this.runs.delete(run.runId);
9444
+ cleanup(run2, outcome = "failed") {
9445
+ this.runs.delete(run2.runId);
9185
9446
  this.effects.persist();
9186
- this.effects.release(run, { keep: this.keep(outcome, run), outcome });
9447
+ this.effects.release(run2, { keep: this.keep(outcome, run2), outcome });
9187
9448
  }
9188
9449
  /**
9189
9450
  * Ends a run: flush logs, finish-report, clean up. On a run someone
@@ -9199,26 +9460,26 @@ var RunTable = class {
9199
9460
  * `{ judgment: 'rate_limited' }` with its reset time, while transient 5xx
9200
9461
  * and transport outages use `interrupted` and the normal short backoff.
9201
9462
  */
9202
- async finishAndCleanup(run, status, error, judgment) {
9203
- if (run.settled) return this.cleanup(run);
9204
- run.settled = true;
9205
- run.endNote ??= error;
9206
- run.drain?.();
9207
- if (this.keep(status, run)) this.effects.noteKept?.(run);
9208
- await run.flush?.();
9209
- const keptBeforeFinish = this.keep(status, run);
9463
+ async finishAndCleanup(run2, status, error, judgment) {
9464
+ if (run2.settled) return this.cleanup(run2);
9465
+ run2.settled = true;
9466
+ run2.endNote ??= error;
9467
+ run2.drain?.();
9468
+ if (this.keep(status, run2)) this.effects.noteKept?.(run2);
9469
+ await run2.flush?.();
9470
+ const keptBeforeFinish = this.keep(status, run2);
9210
9471
  try {
9211
- await this.effects.finish(run, status, error, judgment);
9212
- this.effects.log(`run ${run.runId} finished: ${status}${error ? ` (${error})` : ""}`);
9213
- if (!keptBeforeFinish && this.keep(status, run)) {
9214
- this.effects.log(`run ${run.runId} workspace kept for resume at ${run.workspace}`);
9472
+ await this.effects.finish(run2, status, error, judgment);
9473
+ this.effects.log(`run ${run2.runId} finished: ${status}${error ? ` (${error})` : ""}`);
9474
+ if (!keptBeforeFinish && this.keep(status, run2)) {
9475
+ this.effects.log(`run ${run2.runId} workspace kept for resume at ${run2.workspace}`);
9215
9476
  }
9216
9477
  } catch (err) {
9217
9478
  this.effects.log(
9218
- `finish report for run ${run.runId} not accepted: ${err instanceof Error ? err.message : String(err)}`
9479
+ `finish report for run ${run2.runId} not accepted: ${err instanceof Error ? err.message : String(err)}`
9219
9480
  );
9220
9481
  }
9221
- this.cleanup(run, status);
9482
+ this.cleanup(run2, status);
9222
9483
  }
9223
9484
  /**
9224
9485
  * Marks a supervisor-settled run (`cancels`): kill, do NOT finish-report.
@@ -9227,11 +9488,11 @@ var RunTable = class {
9227
9488
  * is cleaned up by the launch path's next settled check.
9228
9489
  */
9229
9490
  markCanceled(runId) {
9230
- const run = this.runs.get(runId);
9231
- if (!run || run.settled) return null;
9232
- run.canceled = true;
9233
- run.settled = true;
9234
- return run;
9491
+ const run2 = this.runs.get(runId);
9492
+ if (!run2 || run2.settled) return null;
9493
+ run2.canceled = true;
9494
+ run2.settled = true;
9495
+ return run2;
9235
9496
  }
9236
9497
  };
9237
9498
  var LogBatcher = class {
@@ -9363,7 +9624,7 @@ function pendingSelfUpdate(cli, running) {
9363
9624
  return isNewerVersion(cli.version, running) ? cli.version : null;
9364
9625
  }
9365
9626
  function pathWithin(path2, dir) {
9366
- const root = dir.endsWith(sep) ? dir : dir + sep;
9627
+ const root = dir.endsWith(sep2) ? dir : dir + sep2;
9367
9628
  return path2.startsWith(root);
9368
9629
  }
9369
9630
  function buildSpawnEnv(base, opts) {
@@ -9383,14 +9644,14 @@ function buildSpawnEnv(base, opts) {
9383
9644
  var PACKAGE = "tines";
9384
9645
  var NPM_ARGS = ["--min-release-age=0", "--no-audit", "--no-fund", "--loglevel=error"];
9385
9646
  function agentCliPrefix(configDir) {
9386
- return join3(configDir, "cli");
9647
+ return join4(configDir, "cli");
9387
9648
  }
9388
9649
  function binDirOf(prefix) {
9389
- return join3(prefix, "node_modules", ".bin");
9650
+ return join4(prefix, "node_modules", ".bin");
9390
9651
  }
9391
9652
  function installedVersion(prefix) {
9392
9653
  try {
9393
- const pkg = readFileSync7(join3(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
9654
+ const pkg = readFileSync7(join4(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
9394
9655
  const version = JSON.parse(pkg).version;
9395
9656
  return typeof version === "string" ? version : null;
9396
9657
  } catch {
@@ -9399,11 +9660,11 @@ function installedVersion(prefix) {
9399
9660
  }
9400
9661
  function lastGood(prefix) {
9401
9662
  const binDir = binDirOf(prefix);
9402
- if (!existsSync4(join3(binDir, PACKAGE))) return AMBIENT_CLI;
9663
+ if (!existsSync4(join4(binDir, PACKAGE))) return AMBIENT_CLI;
9403
9664
  return { binDir, version: installedVersion(prefix), source: "stale" };
9404
9665
  }
9405
9666
  function runNpm(file, args, cwd, timeoutMs) {
9406
- return new Promise((resolve) => {
9667
+ return new Promise((resolve2) => {
9407
9668
  let output = "";
9408
9669
  let done = false;
9409
9670
  let timedOut = false;
@@ -9418,7 +9679,7 @@ function runNpm(file, args, cwd, timeoutMs) {
9418
9679
  if (done) return;
9419
9680
  done = true;
9420
9681
  clearTimeout(timer);
9421
- resolve({ ...result, timedOut, output });
9682
+ resolve2({ ...result, timedOut, output });
9422
9683
  };
9423
9684
  child.stdout?.on("data", (data) => output += data.toString("utf8"));
9424
9685
  child.stderr?.on("data", (data) => output += data.toString("utf8"));
@@ -9444,7 +9705,7 @@ async function installAgentCli(opts) {
9444
9705
  const args = ["install", "--prefix", prefix, `${PACKAGE}@latest`, ...NPM_ARGS];
9445
9706
  let result = await runNpm("npm", args, prefix, timeoutMs);
9446
9707
  if (result.spawnError?.code === "ENOENT") {
9447
- const sibling = join3(dirname3(process.execPath), "npm");
9708
+ const sibling = join4(dirname3(process.execPath), "npm");
9448
9709
  if (existsSync4(sibling)) result = await runNpm(sibling, args, prefix, timeoutMs);
9449
9710
  }
9450
9711
  if (result.code !== 0) {
@@ -9466,14 +9727,17 @@ function message(err) {
9466
9727
  return err instanceof Error ? err.message : String(err);
9467
9728
  }
9468
9729
 
9730
+ // src/daemon/register.ts
9731
+ import { arch, hostname, platform } from "node:os";
9732
+
9469
9733
  // src/daemon/store.ts
9470
9734
  import { readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
9471
- import { join as join4 } from "node:path";
9735
+ import { join as join5 } from "node:path";
9472
9736
  function credentialsKey(url, name2) {
9473
9737
  return `${url.replace(/\/+$/, "")}#${name2}`;
9474
9738
  }
9475
9739
  function credentialsPath(dir) {
9476
- return join4(dir, "runners.json");
9740
+ return join5(dir, "runners.json");
9477
9741
  }
9478
9742
  function loadRunnerCredentials(dir, url, name2) {
9479
9743
  const all = readJsonFile(credentialsPath(dir));
@@ -9496,7 +9760,7 @@ function clearRunnerCredentials(dir, url, name2) {
9496
9760
  writeJsonFile(path2, all, { secret: true });
9497
9761
  }
9498
9762
  function daemonStatePath(dir, runnerId) {
9499
- return join4(dir, `daemon-state-${runnerId}.json`);
9763
+ return join5(dir, `daemon-state-${runnerId}.json`);
9500
9764
  }
9501
9765
  function loadDaemonState(path2) {
9502
9766
  const state = readJsonFile(path2);
@@ -9510,8 +9774,8 @@ function saveDaemonState(path2, runs) {
9510
9774
  }
9511
9775
  function processStartTimeMs(pid) {
9512
9776
  try {
9513
- const stat = readFileSync8(`/proc/${pid}/stat`, "utf8");
9514
- const afterComm = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
9777
+ const stat2 = readFileSync8(`/proc/${pid}/stat`, "utf8");
9778
+ const afterComm = stat2.slice(stat2.lastIndexOf(")") + 2).split(" ");
9515
9779
  const startTicks = Number(afterComm[19]);
9516
9780
  const btimeLine = readFileSync8("/proc/stat", "utf8").split("\n").find((line) => line.startsWith("btime "));
9517
9781
  const btime = Number(btimeLine?.slice("btime ".length));
@@ -9522,10 +9786,10 @@ function processStartTimeMs(pid) {
9522
9786
  }
9523
9787
  }
9524
9788
  function workspacesDir(configDir) {
9525
- return join4(configDir, "workspaces");
9789
+ return join5(configDir, "workspaces");
9526
9790
  }
9527
9791
  function keptMarkerPath(workspace) {
9528
- return join4(workspace, "kept.json");
9792
+ return join5(workspace, "kept.json");
9529
9793
  }
9530
9794
  function writeKeptMarker(workspace, marker) {
9531
9795
  writeJsonFile(keptMarkerPath(workspace), marker);
@@ -9547,7 +9811,7 @@ function listKeptWorkspaces(configDir) {
9547
9811
  }
9548
9812
  const kept = [];
9549
9813
  for (const name2 of names) {
9550
- const path2 = join4(root, name2);
9814
+ const path2 = join5(root, name2);
9551
9815
  const marker = readKeptMarker(path2);
9552
9816
  if (marker) kept.push({ ...marker, path: path2 });
9553
9817
  }
@@ -9584,7 +9848,7 @@ function directorySizeBytes(path2) {
9584
9848
  return 0;
9585
9849
  }
9586
9850
  for (const entry of entries) {
9587
- const child = join4(path2, entry.name);
9851
+ const child = join5(path2, entry.name);
9588
9852
  if (entry.isDirectory()) total += directorySizeBytes(child);
9589
9853
  else if (entry.isFile()) {
9590
9854
  try {
@@ -9596,15 +9860,48 @@ function directorySizeBytes(path2) {
9596
9860
  return total;
9597
9861
  }
9598
9862
 
9863
+ // src/daemon/register.ts
9864
+ function missingApiKeyMessage(name2, baseUrl) {
9865
+ return `no stored runner token for "${name2}" at ${baseUrl} \u2014 set TINES_API_KEY (a user API key) to register`;
9866
+ }
9867
+ async function ensureRunnerCredentials(opts) {
9868
+ const stored = loadRunnerCredentials(opts.configDir, opts.baseUrl, opts.name);
9869
+ if (stored) {
9870
+ opts.log(
9871
+ `reconnecting as runner "${opts.name}" (${stored.runner_id}) \u2014 token from ${opts.configDir}`
9872
+ );
9873
+ return { creds: stored, registered: false };
9874
+ }
9875
+ if (!opts.apiKey) throw new Error(missingApiKeyMessage(opts.name, opts.baseUrl));
9876
+ const userClient = createApiClient({ baseUrl: opts.baseUrl, apiKey: opts.apiKey });
9877
+ const registered = await userClient.registerRunner({
9878
+ name: opts.name,
9879
+ harness: opts.harness,
9880
+ ...opts.command !== void 0 ? { command: opts.command } : {},
9881
+ max_concurrent: opts.maxConcurrent,
9882
+ hostname: hostname(),
9883
+ platform: `${platform()} ${arch()}`
9884
+ });
9885
+ const creds = { runner_id: registered.runner.id, token: registered.runner_token };
9886
+ saveRunnerCredentials(opts.configDir, opts.baseUrl, opts.name, creds);
9887
+ opts.log(
9888
+ `registered runner "${opts.name}" (${creds.runner_id}); token stored in ${opts.configDir}`
9889
+ );
9890
+ return { creds, registered: true };
9891
+ }
9892
+ function nextStepsMessage(name2, baseUrl) {
9893
+ return `next: route work to "${name2}" \u2014 ${baseUrl}/agents (or: tines routing set ${name2}). Eligible work starts when this runner is available and routing matches. If automation is stopped, resume it in Agents or with tines supervisor enable.`;
9894
+ }
9895
+
9599
9896
  // src/daemon/daemon.ts
9600
9897
  var CLI_REFRESH_TTL_MS = 10 * 6e4;
9601
9898
  var DAEMON_VERSION = cliVersion();
9602
- async function uploadRawLog(run) {
9603
- const path2 = run.rawSpoolPath;
9604
- if (!path2 || !run.rawUpload) return;
9605
- run.rawSpoolPath = void 0;
9899
+ async function uploadRawLog(run2) {
9900
+ const path2 = run2.rawSpoolPath;
9901
+ if (!path2 || !run2.rawUpload) return;
9902
+ run2.rawSpoolPath = void 0;
9606
9903
  try {
9607
- await new Promise((resolve) => run.rawSpool?.end(resolve) ?? resolve());
9904
+ await new Promise((resolve2) => run2.rawSpool?.end(resolve2) ?? resolve2());
9608
9905
  const size = statSync4(path2).size;
9609
9906
  if (size > 0) {
9610
9907
  let body = readFileSync9(path2);
@@ -9618,7 +9915,7 @@ async function uploadRawLog(run) {
9618
9915
  body.subarray(body.byteLength - RUN_LOG_RAW_MAX_BYTES + marker.byteLength)
9619
9916
  ]);
9620
9917
  }
9621
- await run.rawUpload(body);
9918
+ await run2.rawUpload(body);
9622
9919
  }
9623
9920
  } catch {
9624
9921
  } finally {
@@ -9629,7 +9926,7 @@ async function uploadRawLog(run) {
9629
9926
  }
9630
9927
  }
9631
9928
  var log = (message3) => console.log(`[${(/* @__PURE__ */ new Date()).toISOString().slice(11, 19)}] ${message3}`);
9632
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
9929
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
9633
9930
  function killTree(pid, signal) {
9634
9931
  try {
9635
9932
  process.kill(-pid, signal);
@@ -9680,31 +9977,21 @@ async function runDaemon(opts) {
9680
9977
  );
9681
9978
  }
9682
9979
  };
9683
- let creds = loadRunnerCredentials(opts.configDir, baseUrl, opts.name);
9684
- if (creds) {
9685
- log(
9686
- `reconnecting as runner "${opts.name}" (${creds.runner_id}) \u2014 token from ${opts.configDir}`
9687
- );
9688
- } else {
9689
- if (!opts.apiKey) {
9690
- throw new Error(
9691
- `no stored runner token for "${opts.name}" at ${baseUrl} \u2014 set TINES_API_KEY (a user API key) to register`
9692
- );
9693
- }
9694
- const userClient = createApiClient({ baseUrl, apiKey: opts.apiKey });
9695
- const registered = await userClient.registerRunner({
9696
- name: opts.name,
9697
- harness: opts.harness,
9698
- ...opts.command !== void 0 ? { command: opts.command } : {},
9699
- max_concurrent: opts.maxConcurrent,
9700
- hostname: hostname(),
9701
- platform: `${platform()} ${arch()}`
9702
- });
9703
- creds = { runner_id: registered.runner.id, token: registered.runner_token };
9704
- saveRunnerCredentials(opts.configDir, baseUrl, opts.name, creds);
9705
- log(`registered runner "${opts.name}" (${creds.runner_id}); token stored in ${opts.configDir}`);
9980
+ const ensured = await ensureRunnerCredentials({
9981
+ configDir: opts.configDir,
9982
+ baseUrl,
9983
+ name: opts.name,
9984
+ harness: opts.harness,
9985
+ ...opts.command !== void 0 ? { command: opts.command } : {},
9986
+ maxConcurrent: opts.maxConcurrent,
9987
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
9988
+ log
9989
+ });
9990
+ const creds = ensured.creds;
9991
+ if (ensured.registered) {
9992
+ log(nextStepsMessage(opts.name, baseUrl));
9706
9993
  log(
9707
- `next: route work to "${opts.name}" \u2014 ${baseUrl}/agents (or: tines routing set ${opts.name}). Eligible work starts when this runner is available and routing matches. If automation is stopped, resume it in Agents or with tines supervisor enable.`
9994
+ `keep it running: \`tines runner install --name ${opts.name} --harness ${opts.harness.replaceAll("_", "-")}\` installs this runner as a launchd/systemd service that keeps itself updated (docs/runner-daemon.md)`
9708
9995
  );
9709
9996
  }
9710
9997
  const client2 = createApiClient({ baseUrl, apiKey: creds.token });
@@ -9719,7 +10006,7 @@ async function runDaemon(opts) {
9719
10006
  const selfUpdate = opts.selfUpdate && opts.cliRefresh && managedInstall;
9720
10007
  if (opts.selfUpdate && opts.cliRefresh) {
9721
10008
  log(
9722
- managedInstall ? `self-update: on \u2014 this daemon (tines ${DAEMON_VERSION}) runs from ${prefix} and will restart once idle after a newer release is installed there` : `self-update: off \u2014 this daemon (tines ${DAEMON_VERSION}) runs from ${selfPath ?? process.argv[1] ?? "?"}, not ${prefix}; launch it from ${join5(prefix, "node_modules", ".bin", "tines")} to have refreshes apply on restart (docs/runner-daemon.md)`
10009
+ managedInstall ? `self-update: on \u2014 this daemon (tines ${DAEMON_VERSION}) runs from ${prefix} and will restart once idle after a newer release is installed there` : `self-update: off \u2014 this daemon (tines ${DAEMON_VERSION}) runs from ${selfPath ?? process.argv[1] ?? "?"}, not ${prefix}; launch it from ${join6(prefix, "node_modules", ".bin", "tines")} to have refreshes apply on restart \u2014 \`tines runner install --name ${opts.name} --harness ${opts.harness.replaceAll("_", "-")}\` sets that up as a service (docs/runner-daemon.md)`
9723
10010
  );
9724
10011
  }
9725
10012
  let pendingUpdate = null;
@@ -9739,18 +10026,56 @@ async function runDaemon(opts) {
9739
10026
  log(`agent CLI: ${cliLabel(await ensureCli())}`);
9740
10027
  const table2 = new RunTable(
9741
10028
  {
9742
- finish: async (run, status, error, judgment) => {
9743
- const summary = run.renderer?.summary();
9744
- const ended = await client2.finishRun(run.runId, {
10029
+ finish: async (run2, status, error, judgment) => {
10030
+ const summary = run2.renderer?.summary();
10031
+ if (summary?.pricingEvidence && opts.harness === "codex") {
10032
+ const raw = summary.pricingEvidence.raw_usage;
10033
+ const completeRaw = raw && [
10034
+ "input_tokens",
10035
+ "cached_input_tokens",
10036
+ "cache_write_input_tokens",
10037
+ "output_tokens"
10038
+ ].every((field) => raw[field] !== void 0);
10039
+ if (status === "completed" && run2.pricingSessionMode === "cold" && summary.pricingEvidence.measurement_status === "complete" && completeRaw && summary.providerSessionId && run2.codexHome && run2.spawnedAt) {
10040
+ summary.pricingEvidence.request_context = await collectCodexRequestContext({
10041
+ codexHome: run2.codexHome,
10042
+ threadId: summary.providerSessionId,
10043
+ model: run2.pricingModel ?? null,
10044
+ startedAt: run2.spawnedAt,
10045
+ endedAt: Date.now(),
10046
+ terminalUsage: raw
10047
+ });
10048
+ if ("reason" in summary.pricingEvidence.request_context && summary.pricingEvidence.request_context.reason === "model_mismatch")
10049
+ summary.pricingEvidence.model_rerouted = true;
10050
+ } else {
10051
+ summary.pricingEvidence.request_context = {
10052
+ version: 1,
10053
+ normalization: "codex-rollout-delta-v1",
10054
+ status: "unavailable",
10055
+ reason: "not_applicable"
10056
+ };
10057
+ }
10058
+ }
10059
+ const accounting = {
10060
+ usage: summary?.usage ?? { cost_source: "none" },
10061
+ ...summary?.pricingEvidence ? { pricing_evidence: summary.pricingEvidence } : {},
10062
+ ...summary?.providerSessionId ? { provider_session_id: summary.providerSessionId } : {},
10063
+ daemon_version: DAEMON_VERSION,
10064
+ status
10065
+ };
10066
+ run2.batcher.append(`[usage] ${JSON.stringify(accounting)}
10067
+ `);
10068
+ await run2.batcher.flush();
10069
+ const ended = await client2.finishRun(run2.runId, {
9745
10070
  status,
9746
10071
  ...error ? { error } : {},
9747
10072
  ...judgment,
9748
- usage: summary?.usage ?? { cost_source: "none" },
10073
+ usage: accounting.usage,
9749
10074
  ...summary?.pricingEvidence ? {
9750
10075
  pricing_evidence: {
9751
10076
  ...summary.pricingEvidence,
9752
- model: run.pricingModel ?? null,
9753
- session_mode: run.pricingSessionMode ?? "cold",
10077
+ model: run2.pricingModel ?? null,
10078
+ session_mode: run2.pricingSessionMode ?? "cold",
9754
10079
  daemon_version: DAEMON_VERSION
9755
10080
  }
9756
10081
  } : {},
@@ -9758,33 +10083,33 @@ async function runDaemon(opts) {
9758
10083
  ...summary?.numTurns !== void 0 ? { turn_count: summary.numTurns } : {},
9759
10084
  // The whole conversation's turns, which for a resumed run is
9760
10085
  // more than this run's own: the size guard reads this one.
9761
- ...summary?.numTurns !== void 0 ? { conversation_turn_count: run.priorTurns + summary.numTurns } : {},
9762
- workspace_path: run.workspace
10086
+ ...summary?.numTurns !== void 0 ? { conversation_turn_count: run2.priorTurns + summary.numTurns } : {},
10087
+ workspace_path: run2.workspace
9763
10088
  });
9764
- if (ended?.resume_expires_at) run.keepForResume = true;
10089
+ if (ended?.resume_expires_at) run2.keepForResume = true;
9765
10090
  },
9766
- release: (run, { keep, outcome }) => {
9767
- if (run.timeout) clearTimeout(run.timeout);
9768
- run.renderer?.finish();
9769
- settleWorkspace(run.workspace, keep, {
9770
- run_id: run.runId,
9771
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {},
10091
+ release: (run2, { keep, outcome }) => {
10092
+ if (run2.timeout) clearTimeout(run2.timeout);
10093
+ run2.renderer?.finish();
10094
+ settleWorkspace(run2.workspace, keep, {
10095
+ run_id: run2.runId,
10096
+ ...run2.issueLabel ? { issue_ref: run2.issueLabel } : {},
9772
10097
  status: outcome,
9773
- ...run.endNote ? { error: run.endNote } : {}
10098
+ ...run2.endNote ? { error: run2.endNote } : {}
9774
10099
  });
9775
- void uploadRawLog(run);
10100
+ void uploadRawLog(run2);
9776
10101
  sweepKeptWorkspaces();
9777
10102
  },
9778
- noteKept: (run) => run.batcher.append(`workspace kept at ${run.workspace}
10103
+ noteKept: (run2) => run2.batcher.append(`workspace kept at ${run2.workspace}
9779
10104
  `),
9780
10105
  persist: () => {
9781
- const entries = table2.values().filter((run) => run.child?.pid !== void 0).map((run) => ({
9782
- run_id: run.runId,
9783
- pid: run.child.pid,
9784
- workspace: run.workspace,
9785
- key_fingerprint: run.keyFingerprint,
9786
- started_at: run.spawnedAt,
9787
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {}
10106
+ const entries = table2.values().filter((run2) => run2.child?.pid !== void 0).map((run2) => ({
10107
+ run_id: run2.runId,
10108
+ pid: run2.child.pid,
10109
+ workspace: run2.workspace,
10110
+ key_fingerprint: run2.keyFingerprint,
10111
+ started_at: run2.spawnedAt,
10112
+ ...run2.issueLabel ? { issue_ref: run2.issueLabel } : {}
9788
10113
  }));
9789
10114
  saveDaemonState(statePath, entries);
9790
10115
  },
@@ -9793,7 +10118,7 @@ async function runDaemon(opts) {
9793
10118
  {
9794
10119
  // A workspace the server retained for a resume is kept whatever the
9795
10120
  // --keep-workspaces mode says; pruning still bounds it by age/count.
9796
- keep: (outcome, run) => run.keepForResume || keepWorkspace(opts.keepWorkspaces, outcome)
10121
+ keep: (outcome, run2) => run2.keepForResume || keepWorkspace(opts.keepWorkspaces, outcome)
9797
10122
  }
9798
10123
  );
9799
10124
  for (const orphan of loadDaemonState(statePath)) {
@@ -9827,12 +10152,12 @@ async function runDaemon(opts) {
9827
10152
  }
9828
10153
  saveDaemonState(statePath, []);
9829
10154
  const killWithoutFinish = (runId) => {
9830
- const run = table2.markCanceled(runId);
9831
- if (!run) return;
9832
- run.endNote ??= "canceled by supervisor";
10155
+ const run2 = table2.markCanceled(runId);
10156
+ if (!run2) return;
10157
+ run2.endNote ??= "canceled by supervisor";
9833
10158
  log(`supervisor canceled run ${runId}; killing without finish-reporting`);
9834
- if (run.child?.pid) {
9835
- const pid = run.child.pid;
10159
+ if (run2.child?.pid) {
10160
+ const pid = run2.child.pid;
9836
10161
  killTree(pid, "SIGTERM");
9837
10162
  setTimeout(() => killTree(pid, "SIGKILL"), 5e3).unref?.();
9838
10163
  }
@@ -9846,9 +10171,9 @@ async function runDaemon(opts) {
9846
10171
  `run ${runId}: resume workspace ${assignment.resume.workspace_path} is gone; launching fresh`
9847
10172
  );
9848
10173
  }
9849
- const workspace = resume ? resume.workspace_path : join5(workspacesDir(opts.configDir), runId);
10174
+ const workspace = resume ? resume.workspace_path : join6(workspacesDir(opts.configDir), runId);
9850
10175
  const issueLabel = assignment.run.issue_ref ? `${assignment.run.issue_ref.project_name}/${assignment.run.issue_ref.number}` : void 0;
9851
- const run = {
10176
+ const run2 = {
9852
10177
  runId,
9853
10178
  workspace,
9854
10179
  ...issueLabel ? { issueLabel } : {},
@@ -9864,8 +10189,8 @@ async function runDaemon(opts) {
9864
10189
  { onError: (err) => log(`log append for run ${runId} failed: ${message2(err)}`) }
9865
10190
  )
9866
10191
  };
9867
- run.flush = () => run.batcher.flush();
9868
- table2.track(run);
10192
+ run2.flush = () => run2.batcher.flush();
10193
+ table2.track(run2);
9869
10194
  log(
9870
10195
  resume ? `run ${runId} assigned (issue ${issueLabel ?? assignment.run.issue_id}); resuming run ${resume.previous_run_id} in ${workspace}` : `run ${runId} assigned (issue ${issueLabel ?? assignment.run.issue_id}); materializing workspace`
9871
10196
  );
@@ -9874,47 +10199,47 @@ async function runDaemon(opts) {
9874
10199
  rmSync3(workspace, { recursive: true, force: true });
9875
10200
  mkdirSync4(workspace, { recursive: true });
9876
10201
  } else {
9877
- rmSync3(join5(workspace, "kept.json"), { force: true });
10202
+ rmSync3(join6(workspace, "kept.json"), { force: true });
9878
10203
  }
9879
- writeFileSync3(join5(workspace, "prompt.md"), `${assignment.prompt}
10204
+ writeFileSync3(join6(workspace, "prompt.md"), `${assignment.prompt}
9880
10205
  `);
9881
10206
  if (!resume)
9882
10207
  for (const skill of assignment.bundle.skills) {
9883
10208
  for (const file of skill.files) {
9884
- const target = join5(workspace, "skills", skill.name, file.path);
10209
+ const target = join6(workspace, "skills", skill.name, file.path);
9885
10210
  mkdirSync4(dirname4(target), { recursive: true });
9886
10211
  writeFileSync3(target, file.content);
9887
10212
  }
9888
10213
  }
9889
10214
  if (!resume)
9890
10215
  writeFileSync3(
9891
- join5(workspace, "repos.json"),
10216
+ join6(workspace, "repos.json"),
9892
10217
  `${JSON.stringify(assignment.bundle.repos, null, 2)}
9893
10218
  `
9894
10219
  );
9895
10220
  for (const repo of resume ? [] : assignment.bundle.repos) {
9896
- if (run.settled) return table2.cleanup(run);
10221
+ if (run2.settled) return table2.cleanup(run2);
9897
10222
  const args = [
9898
10223
  "clone",
9899
10224
  ...repo.branch ? ["--branch", repo.branch] : [],
9900
10225
  repo.url,
9901
10226
  repo.dir
9902
10227
  ];
9903
- run.batcher.append(`$ git ${args.join(" ")}
10228
+ run2.batcher.append(`$ git ${args.join(" ")}
9904
10229
  `);
9905
- const result = await runGit(args, workspace, run.batcher);
10230
+ const result = await runGit(args, workspace, run2.batcher);
9906
10231
  if (result !== 0) {
9907
10232
  return table2.finishAndCleanup(
9908
- run,
10233
+ run2,
9909
10234
  "failed",
9910
10235
  `git clone failed for ${repo.url} (exit ${result})`
9911
10236
  );
9912
10237
  }
9913
10238
  }
9914
- if (run.settled) return table2.cleanup(run);
10239
+ if (run2.settled) return table2.cleanup(run2);
9915
10240
  const cli = await ensureCli();
9916
- if (run.settled) return table2.cleanup(run);
9917
- run.batcher.append(
10241
+ if (run2.settled) return table2.cleanup(run2);
10242
+ run2.batcher.append(
9918
10243
  cli.source === "fresh" ? `tines CLI: ${cli.version ?? "unknown"} (daemon-managed)
9919
10244
  ` : cli.source === "stale" ? `warning: agent CLI refresh failed; using last-good tines ${cli.version ?? "unknown"} from ${opts.configDir}/cli
9920
10245
  ` : `warning: no daemon-managed tines CLI; using whatever \`tines\` is on this machine's PATH
@@ -9922,7 +10247,7 @@ async function runDaemon(opts) {
9922
10247
  );
9923
10248
  const harnessInput = {
9924
10249
  workspace,
9925
- promptFile: join5(workspace, "prompt.md"),
10250
+ promptFile: join6(workspace, "prompt.md"),
9926
10251
  prompt: assignment.prompt,
9927
10252
  model: assignment.run.model,
9928
10253
  ...resume ? { resumeSessionId: resume.provider_session_id } : {}
@@ -9932,10 +10257,10 @@ async function runDaemon(opts) {
9932
10257
  harnessInput
9933
10258
  );
9934
10259
  if (opts.harness === "codex") {
9935
- run.pricingModel = harnessInput.model;
9936
- run.pricingSessionMode = resume ? "resumed" : "cold";
10260
+ run2.pricingModel = harnessInput.model;
10261
+ run2.pricingSessionMode = resume ? "resumed" : "cold";
9937
10262
  }
9938
- run.batcher.append(
10263
+ run2.batcher.append(
9939
10264
  formatLaunchBanner(invocation, harnessInput, {
9940
10265
  harness: opts.harness,
9941
10266
  timeoutMinutes: assignment.timeout_minutes,
@@ -9943,96 +10268,98 @@ async function runDaemon(opts) {
9943
10268
  ...resume ? { resumedFromRunId: resume.previous_run_id } : {}
9944
10269
  })
9945
10270
  );
10271
+ const spawnEnv = buildSpawnEnv(process.env, {
10272
+ binDir: cli.binDir,
10273
+ apiKey: assignment.run_key,
10274
+ apiUrl: baseUrl
10275
+ });
10276
+ if (opts.harness === "codex") run2.codexHome = resolveCodexHome(spawnEnv, workspace);
9946
10277
  const child = spawn2(invocation.file, invocation.args, {
9947
10278
  cwd: workspace,
9948
- env: buildSpawnEnv(process.env, {
9949
- binDir: cli.binDir,
9950
- apiKey: assignment.run_key,
9951
- apiUrl: baseUrl
9952
- }),
10279
+ env: spawnEnv,
9953
10280
  stdio: ["ignore", "pipe", "pipe"],
9954
10281
  detached: true
9955
10282
  });
9956
- run.child = child;
9957
- run.spawnedAt = Date.now();
10283
+ run2.child = child;
10284
+ run2.spawnedAt = Date.now();
9958
10285
  table2.persist();
9959
10286
  log(`run ${runId}: launched ${invocation.file} (pid ${child.pid})`);
9960
10287
  if (opts.harness === "claude_code") {
9961
10288
  const limiter = new RateLimitDetector();
9962
- run.limiter = limiter;
10289
+ run2.limiter = limiter;
9963
10290
  const renderer = new ClaudeStreamRenderer(
9964
- (line) => run.batcher.append(line),
10291
+ (line) => run2.batcher.append(line),
9965
10292
  (event) => limiter.noteStreamEvent(event)
9966
10293
  );
9967
- run.renderer = renderer;
9968
- const spoolPath = join5(opts.configDir, "rawlogs", `${runId}.ndjson`);
10294
+ run2.renderer = renderer;
10295
+ const spoolPath = join6(opts.configDir, "rawlogs", `${runId}.ndjson`);
9969
10296
  mkdirSync4(dirname4(spoolPath), { recursive: true });
9970
- run.rawSpoolPath = spoolPath;
9971
- run.rawSpool = createWriteStream(spoolPath);
9972
- run.rawUpload = (body) => client2.putRunLogRaw(runId, body);
9973
- const decoder = new StringDecoder("utf8");
9974
- run.drain = () => {
10297
+ run2.rawSpoolPath = spoolPath;
10298
+ run2.rawSpool = createWriteStream(spoolPath);
10299
+ run2.rawUpload = (body) => client2.putRunLogRaw(runId, body);
10300
+ const decoder = new StringDecoder2("utf8");
10301
+ run2.drain = () => {
9975
10302
  const trailing = decoder.end();
9976
10303
  if (trailing) renderer.write(trailing);
9977
10304
  renderer.finish();
9978
10305
  };
9979
10306
  child.stdout?.on("data", (data) => {
9980
- run.rawSpool?.write(data);
10307
+ run2.rawSpool?.write(data);
9981
10308
  renderer.write(decoder.write(data));
9982
10309
  });
9983
10310
  } else if (opts.harness === "codex") {
9984
- const renderer = new CodexStreamRenderer((line) => run.batcher.append(line));
9985
- const decoder = new StringDecoder("utf8");
9986
- run.renderer = renderer;
9987
- run.drain = () => {
10311
+ const renderer = new CodexStreamRenderer((line) => run2.batcher.append(line));
10312
+ const decoder = new StringDecoder2("utf8");
10313
+ run2.renderer = renderer;
10314
+ run2.drain = () => {
9988
10315
  const trailing = decoder.end();
9989
10316
  if (trailing) renderer.write(trailing);
9990
10317
  renderer.finish();
9991
10318
  };
9992
10319
  child.stdout?.on("data", (data) => renderer.write(decoder.write(data)));
9993
10320
  } else {
9994
- child.stdout?.on("data", (data) => run.batcher.append(data.toString("utf8")));
10321
+ child.stdout?.on("data", (data) => run2.batcher.append(data.toString("utf8")));
9995
10322
  }
9996
10323
  child.stderr?.on("data", (data) => {
9997
10324
  const text4 = data.toString("utf8");
9998
- run.batcher.append(text4);
9999
- run.limiter?.noteStderr(text4);
10325
+ run2.batcher.append(text4);
10326
+ run2.limiter?.noteStderr(text4);
10000
10327
  });
10001
- run.timeout = setTimeout(() => {
10002
- if (run.settled) return;
10328
+ run2.timeout = setTimeout(() => {
10329
+ if (run2.settled) return;
10003
10330
  log(`run ${runId} hit its ${assignment.timeout_minutes}m timeout; killing`);
10004
- run.timedOut = true;
10331
+ run2.timedOut = true;
10005
10332
  if (child.pid) killTree(child.pid, "SIGTERM");
10006
10333
  if (child.pid) setTimeout(() => killTree(child.pid, "SIGKILL"), 5e3).unref?.();
10007
10334
  }, assignment.timeout_minutes * 6e4);
10008
10335
  child.on("error", (err) => {
10009
- void table2.finishAndCleanup(run, "failed", `failed to launch harness: ${message2(err)}`);
10336
+ void table2.finishAndCleanup(run2, "failed", `failed to launch harness: ${message2(err)}`);
10010
10337
  });
10011
10338
  child.on("close", (code, signal) => {
10012
- run.drain?.();
10013
- run.limiter?.finish();
10014
- const closing = exitLineForRun(run, {
10339
+ run2.drain?.();
10340
+ run2.limiter?.finish();
10341
+ const closing = exitLineForRun(run2, {
10015
10342
  code,
10016
10343
  signal,
10017
- durationMs: Date.now() - (run.spawnedAt ?? Date.now())
10344
+ durationMs: Date.now() - (run2.spawnedAt ?? Date.now())
10018
10345
  });
10019
- if (closing) run.batcher.append(closing);
10020
- if (run.timedOut) {
10346
+ if (closing) run2.batcher.append(closing);
10347
+ if (run2.timedOut) {
10021
10348
  void table2.finishAndCleanup(
10022
- run,
10349
+ run2,
10023
10350
  "failed",
10024
10351
  `run exceeded the ${assignment.timeout_minutes}m timeout; harness killed`
10025
10352
  );
10026
10353
  } else if (code === 0) {
10027
- void table2.finishAndCleanup(run, "completed");
10354
+ void table2.finishAndCleanup(run2, "completed");
10028
10355
  } else {
10029
- const limited = signal ? null : run.limiter?.signal() ?? null;
10030
- const providerError = signal ? null : run.limiter?.providerError() ?? null;
10356
+ const limited = signal ? null : run2.limiter?.signal() ?? null;
10357
+ const providerError = signal ? null : run2.limiter?.providerError() ?? null;
10031
10358
  if (limited) {
10032
10359
  log(
10033
10360
  `run ${runId}: harness rate limited (${limited.detail}); reporting without a strike`
10034
10361
  );
10035
- void table2.finishAndCleanup(run, "failed", `rate limited: ${limited.detail}`, {
10362
+ void table2.finishAndCleanup(run2, "failed", `rate limited: ${limited.detail}`, {
10036
10363
  judgment: "rate_limited",
10037
10364
  ...limited.resumeAt !== null ? { resume_at: limited.resumeAt } : {}
10038
10365
  });
@@ -10040,12 +10367,12 @@ async function runDaemon(opts) {
10040
10367
  log(
10041
10368
  `run ${runId}: transient provider error (${providerError.detail}); reporting without a strike`
10042
10369
  );
10043
- void table2.finishAndCleanup(run, "failed", `provider error: ${providerError.detail}`, {
10370
+ void table2.finishAndCleanup(run2, "failed", `provider error: ${providerError.detail}`, {
10044
10371
  judgment: "interrupted"
10045
10372
  });
10046
10373
  } else {
10047
10374
  void table2.finishAndCleanup(
10048
- run,
10375
+ run2,
10049
10376
  "failed",
10050
10377
  signal ? `harness killed by ${signal}` : `harness exited with code ${code}`
10051
10378
  );
@@ -10053,7 +10380,7 @@ async function runDaemon(opts) {
10053
10380
  }
10054
10381
  });
10055
10382
  } catch (err) {
10056
- void table2.finishAndCleanup(run, "failed", `workspace setup failed: ${message2(err)}`);
10383
+ void table2.finishAndCleanup(run2, "failed", `workspace setup failed: ${message2(err)}`);
10057
10384
  }
10058
10385
  };
10059
10386
  const shutdown = async () => {
@@ -10061,9 +10388,9 @@ async function runDaemon(opts) {
10061
10388
  shuttingDown = true;
10062
10389
  log("shutting down; failing in-flight runs");
10063
10390
  await Promise.all(
10064
- table2.values().map(async (run) => {
10065
- if (run.child?.pid) killTree(run.child.pid, "SIGTERM");
10066
- await table2.finishAndCleanup(run, "failed", "daemon shut down", {
10391
+ table2.values().map(async (run2) => {
10392
+ if (run2.child?.pid) killTree(run2.child.pid, "SIGTERM");
10393
+ await table2.finishAndCleanup(run2, "failed", "daemon shut down", {
10067
10394
  judgment: "interrupted"
10068
10395
  });
10069
10396
  })
@@ -10114,11 +10441,11 @@ async function runDaemon(opts) {
10114
10441
  return;
10115
10442
  }
10116
10443
  if (err instanceof ApiError && err.status === 401) {
10117
- for (const run of table2.values()) {
10118
- if (run.child?.pid) killTree(run.child.pid, "SIGKILL");
10119
- settleWorkspace(run.workspace, keepWorkspace(opts.keepWorkspaces, "failed"), {
10120
- run_id: run.runId,
10121
- ...run.issueLabel ? { issue_ref: run.issueLabel } : {},
10444
+ for (const run2 of table2.values()) {
10445
+ if (run2.child?.pid) killTree(run2.child.pid, "SIGKILL");
10446
+ settleWorkspace(run2.workspace, keepWorkspace(opts.keepWorkspaces, "failed"), {
10447
+ run_id: run2.runId,
10448
+ ...run2.issueLabel ? { issue_ref: run2.issueLabel } : {},
10122
10449
  status: "failed",
10123
10450
  error: "daemon token rejected"
10124
10451
  });
@@ -10151,12 +10478,486 @@ function realpathOrNull(path2) {
10151
10478
  }
10152
10479
  }
10153
10480
  function runGit(args, cwd, batcher) {
10154
- return new Promise((resolve) => {
10481
+ return new Promise((resolve2) => {
10155
10482
  const child = spawn2("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
10156
10483
  child.stdout?.on("data", (data) => batcher.append(data.toString("utf8")));
10157
10484
  child.stderr?.on("data", (data) => batcher.append(data.toString("utf8")));
10158
- child.on("error", () => resolve(127));
10159
- child.on("exit", (code) => resolve(code ?? 1));
10485
+ child.on("error", () => resolve2(127));
10486
+ child.on("exit", (code) => resolve2(code ?? 1));
10487
+ });
10488
+ }
10489
+
10490
+ // src/commands/daemon-flags.ts
10491
+ import { hostname as hostname2 } from "node:os";
10492
+ function withDaemonFlags(cmd) {
10493
+ return cmd.option(
10494
+ "--name <name>",
10495
+ "runner name, unique per user; name it machine-plus-harness, e.g. macbook-claude (default: this hostname)"
10496
+ ).option("--harness <harness>", "claude-code | codex | custom", "claude-code").option(
10497
+ "--command <template>",
10498
+ "custom harness command template ({prompt_file}, {workspace}, {model})"
10499
+ ).option("--max-concurrent <n>", "maximum simultaneous runs", (v) => Number.parseInt(v, 10), 1).option("--poll-interval <seconds>", "seconds between polls", (v) => Number.parseInt(v, 10), 15).option(
10500
+ "--keep-workspaces <mode>",
10501
+ "keep settled runs' workspaces for debugging: never | failed | always",
10502
+ "never"
10503
+ ).option(
10504
+ "--keep-workspaces-for <hours>",
10505
+ "delete kept workspaces older than this",
10506
+ (v) => Number(v),
10507
+ 72
10508
+ ).option(
10509
+ "--keep-workspaces-max <n>",
10510
+ "keep at most this many workspaces (oldest removed first)",
10511
+ (v) => Number.parseInt(v, 10),
10512
+ 20
10513
+ );
10514
+ }
10515
+ function parseDaemonFlags(opts) {
10516
+ const harness = opts.harness.replaceAll("-", "_");
10517
+ if (!HARNESS_KINDS.includes(harness)) {
10518
+ die(`--harness must be claude-code, codex, or custom, got "${opts.harness}"`);
10519
+ }
10520
+ if (harness === "custom" && !opts.command) {
10521
+ die('the custom harness needs --command "<template>" ({prompt_file}, {workspace}, {model})');
10522
+ }
10523
+ if (harness !== "custom" && opts.command) die("--command only applies to --harness custom");
10524
+ if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent < 1 || opts.maxConcurrent > 100) {
10525
+ die("--max-concurrent must be an integer between 1 and 100");
10526
+ }
10527
+ if (!Number.isInteger(opts.pollInterval) || opts.pollInterval < 1) {
10528
+ die("--poll-interval must be a positive number of seconds");
10529
+ }
10530
+ const keepWorkspaces = opts.keepWorkspaces;
10531
+ if (!KEEP_WORKSPACES_MODES.includes(keepWorkspaces)) {
10532
+ die(
10533
+ `--keep-workspaces must be ${KEEP_WORKSPACES_MODES.join(", ")}, got "${opts.keepWorkspaces}"`
10534
+ );
10535
+ }
10536
+ if (!Number.isFinite(opts.keepWorkspacesFor) || opts.keepWorkspacesFor <= 0) {
10537
+ die("--keep-workspaces-for must be a positive number of hours");
10538
+ }
10539
+ if (!Number.isInteger(opts.keepWorkspacesMax) || opts.keepWorkspacesMax < 1) {
10540
+ die("--keep-workspaces-max must be a positive integer");
10541
+ }
10542
+ return {
10543
+ name: opts.name ?? hostname2(),
10544
+ harness,
10545
+ ...opts.command !== void 0 ? { command: opts.command } : {},
10546
+ maxConcurrent: opts.maxConcurrent,
10547
+ pollIntervalSeconds: opts.pollInterval,
10548
+ keepWorkspaces,
10549
+ keepWorkspacesForHours: opts.keepWorkspacesFor,
10550
+ keepWorkspacesMax: opts.keepWorkspacesMax
10551
+ };
10552
+ }
10553
+ function harnessFlag(harness) {
10554
+ return harness.replaceAll("_", "-");
10555
+ }
10556
+
10557
+ // src/commands/runner-service.ts
10558
+ import { execFile } from "node:child_process";
10559
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync10, rmSync as rmSync4, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
10560
+ import { homedir as homedir3, hostname as hostname3, userInfo } from "node:os";
10561
+ import { dirname as dirname6 } from "node:path";
10562
+ import { promisify } from "node:util";
10563
+
10564
+ // src/daemon/service.ts
10565
+ import { existsSync as existsSync6, statSync as statSync5 } from "node:fs";
10566
+ import { delimiter as delimiter2, dirname as dirname5, join as join7, sep as sep3 } from "node:path";
10567
+ function defaultServiceManager(platform2 = process.platform) {
10568
+ if (platform2 === "darwin") return "launchd";
10569
+ if (platform2 === "linux") return "systemd";
10570
+ return null;
10571
+ }
10572
+ function serviceLabel(name2, kind) {
10573
+ return kind === "launchd" ? `dev.tines.runner.${name2}` : `tines-runner-${name2}`;
10574
+ }
10575
+ function unitPath(name2, kind, home) {
10576
+ const label = serviceLabel(name2, kind);
10577
+ return kind === "launchd" ? join7(home, "Library", "LaunchAgents", `${label}.plist`) : join7(home, ".config", "systemd", "user", `${label}.service`);
10578
+ }
10579
+ function serviceLogPath(configDir, name2) {
10580
+ return join7(configDir, "logs", `runner-${name2}.log`);
10581
+ }
10582
+ function managedBinary(prefix) {
10583
+ return join7(prefix, "node_modules", ".bin", "tines");
10584
+ }
10585
+ function daemonArgs(input2) {
10586
+ const args = [
10587
+ "runner",
10588
+ "daemon",
10589
+ "--url",
10590
+ input2.url,
10591
+ "--name",
10592
+ input2.name,
10593
+ "--harness",
10594
+ input2.harness
10595
+ ];
10596
+ if (input2.command !== void 0) args.push("--command", input2.command);
10597
+ if (input2.maxConcurrent !== 1) args.push("--max-concurrent", String(input2.maxConcurrent));
10598
+ if (input2.pollIntervalSeconds !== void 0 && input2.pollIntervalSeconds !== 15)
10599
+ args.push("--poll-interval", String(input2.pollIntervalSeconds));
10600
+ if (input2.keepWorkspaces !== void 0 && input2.keepWorkspaces !== "never")
10601
+ args.push("--keep-workspaces", input2.keepWorkspaces);
10602
+ if (input2.keepWorkspacesForHours !== void 0 && input2.keepWorkspacesForHours !== 72)
10603
+ args.push("--keep-workspaces-for", String(input2.keepWorkspacesForHours));
10604
+ if (input2.keepWorkspacesMax !== void 0 && input2.keepWorkspacesMax !== 20)
10605
+ args.push("--keep-workspaces-max", String(input2.keepWorkspacesMax));
10606
+ return args;
10607
+ }
10608
+ function findOnPath(name2, envPath) {
10609
+ for (const dir of (envPath ?? "").split(delimiter2)) {
10610
+ if (!dir) continue;
10611
+ const candidate = join7(dir, name2);
10612
+ try {
10613
+ if (statSync5(candidate).isFile()) return candidate;
10614
+ } catch {
10615
+ }
10616
+ }
10617
+ return null;
10618
+ }
10619
+ function servicePath(input2) {
10620
+ const dirs = [dirname5(input2.execPath)];
10621
+ const missing = [];
10622
+ for (const name2 of input2.required) {
10623
+ const found = findOnPath(name2, input2.envPath);
10624
+ if (found) dirs.push(dirname5(found));
10625
+ else missing.push(name2);
10626
+ }
10627
+ dirs.push(
10628
+ ...input2.platform === "darwin" ? ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"] : ["/usr/local/bin", "/usr/bin", "/bin"]
10629
+ );
10630
+ return { path: [...new Set(dirs)].join(delimiter2), missing };
10631
+ }
10632
+ function xml(value) {
10633
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
10634
+ }
10635
+ function renderLaunchdPlist(spec) {
10636
+ const label = serviceLabel(spec.name, "launchd");
10637
+ const env = { PATH: spec.path, ...spec.env };
10638
+ const lines2 = [
10639
+ '<?xml version="1.0" encoding="UTF-8"?>',
10640
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
10641
+ '<plist version="1.0">',
10642
+ "<dict>",
10643
+ ` <key>Label</key><string>${xml(label)}</string>`,
10644
+ " <key>ProgramArguments</key>",
10645
+ " <array>",
10646
+ ...[spec.binary, ...spec.args].map((arg) => ` <string>${xml(arg)}</string>`),
10647
+ " </array>",
10648
+ " <key>EnvironmentVariables</key>",
10649
+ " <dict>",
10650
+ ...Object.entries(env).map(
10651
+ ([key, value]) => ` <key>${xml(key)}</key><string>${xml(value)}</string>`
10652
+ ),
10653
+ " </dict>",
10654
+ " <key>RunAtLoad</key><true/>",
10655
+ " <!-- Relaunches after any exit: a crash, and the daemon's own exit for a self-update. -->",
10656
+ " <key>KeepAlive</key><true/>",
10657
+ ` <key>StandardOutPath</key><string>${xml(spec.logPath)}</string>`,
10658
+ ` <key>StandardErrorPath</key><string>${xml(spec.logPath)}</string>`,
10659
+ "</dict>",
10660
+ "</plist>",
10661
+ ""
10662
+ ];
10663
+ return lines2.join("\n");
10664
+ }
10665
+ function unitQuote(value) {
10666
+ return /^[A-Za-z0-9_./:=@%+-]+$/.test(value) ? value : `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
10667
+ }
10668
+ function renderSystemdUnit(spec) {
10669
+ const env = { PATH: spec.path, ...spec.env };
10670
+ const lines2 = [
10671
+ "[Unit]",
10672
+ `Description=Tines local runner daemon (${spec.name})`,
10673
+ "After=network-online.target",
10674
+ "",
10675
+ "[Service]",
10676
+ `ExecStart=${[spec.binary, ...spec.args].map(unitQuote).join(" ")}`,
10677
+ ...Object.entries(env).map(([key, value]) => `Environment=${unitQuote(`${key}=${value}`)}`),
10678
+ `StandardOutput=append:${spec.logPath}`,
10679
+ `StandardError=append:${spec.logPath}`,
10680
+ "# always, not on-failure: the daemon exits 0 on purpose to pick up a self-update.",
10681
+ "Restart=always",
10682
+ "RestartSec=10",
10683
+ "",
10684
+ "[Install]",
10685
+ "WantedBy=default.target",
10686
+ ""
10687
+ ];
10688
+ return lines2.join("\n");
10689
+ }
10690
+ function renderUnit(spec) {
10691
+ return spec.kind === "launchd" ? renderLaunchdPlist(spec) : renderSystemdUnit(spec);
10692
+ }
10693
+ function serviceCommands(target) {
10694
+ const label = serviceLabel(target.name, target.kind);
10695
+ if (target.kind === "launchd") {
10696
+ const domain = `gui/${target.uid}`;
10697
+ return {
10698
+ // bootout first so a re-install replaces the loaded definition
10699
+ // instead of failing with "service already loaded".
10700
+ load: [
10701
+ { argv: ["launchctl", "bootout", `${domain}/${label}`], optional: true },
10702
+ { argv: ["launchctl", "bootstrap", domain, target.unitPath] }
10703
+ ],
10704
+ restart: [{ argv: ["launchctl", "kickstart", "-k", `${domain}/${label}`] }],
10705
+ unload: [{ argv: ["launchctl", "bootout", `${domain}/${label}`], optional: true }]
10706
+ };
10707
+ }
10708
+ return {
10709
+ load: [
10710
+ { argv: ["systemctl", "--user", "daemon-reload"] },
10711
+ { argv: ["systemctl", "--user", "enable", "--now", label] },
10712
+ // So the runner keeps working while nobody is logged in.
10713
+ { argv: ["loginctl", "enable-linger"], optional: true }
10714
+ ],
10715
+ restart: [{ argv: ["systemctl", "--user", "restart", label] }],
10716
+ unload: [
10717
+ { argv: ["systemctl", "--user", "disable", "--now", label], optional: true },
10718
+ { argv: ["systemctl", "--user", "daemon-reload"], optional: true }
10719
+ ]
10720
+ };
10721
+ }
10722
+ function matchDaemonProcesses(psOutput, query2) {
10723
+ const matches = [];
10724
+ for (const raw of psOutput.split("\n")) {
10725
+ const line = raw.trim();
10726
+ const m = /^(\d+)\s+(.*)$/.exec(line);
10727
+ if (!m) continue;
10728
+ const pid = Number(m[1]);
10729
+ const args = m[2];
10730
+ if (pid === query2.selfPid) continue;
10731
+ const words = args.split(/\s+/);
10732
+ const daemonAt = words.findIndex((w, i) => w === "runner" && words[i + 1] === "daemon");
10733
+ if (daemonAt < 0) continue;
10734
+ const binary = words[daemonAt - 1] ?? "";
10735
+ if (!/(^|\/)tines(\.js|\.cjs|\.mjs)?$/.test(binary) && !/(^|\/)tines$/.test(binary)) continue;
10736
+ let name2 = null;
10737
+ for (let i = daemonAt + 2; i < words.length; i++) {
10738
+ const w = words[i];
10739
+ if (w === "--name") name2 = words[i + 1] ?? null;
10740
+ else if (w.startsWith("--name=")) name2 = w.slice("--name=".length);
10741
+ }
10742
+ if ((name2 ?? query2.hostname) !== query2.name) continue;
10743
+ const root = query2.prefix.endsWith(sep3) ? query2.prefix : query2.prefix + sep3;
10744
+ matches.push({ pid, args, managed: args.includes(root) });
10745
+ }
10746
+ return matches;
10747
+ }
10748
+ function daemonStartedLine(name2) {
10749
+ const escaped = name2.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
10750
+ return new RegExp(`(reconnecting as runner|registered runner) "${escaped}"`);
10751
+ }
10752
+ function managedBinaryPresent(prefix) {
10753
+ return existsSync6(managedBinary(prefix));
10754
+ }
10755
+
10756
+ // src/commands/runner-service.ts
10757
+ var run = promisify(execFile);
10758
+ var STARTUP_TIMEOUT_MS = 3e4;
10759
+ var log2 = (message3) => console.log(message3);
10760
+ function resolveServiceManager(flag) {
10761
+ if (flag !== void 0) {
10762
+ if (flag !== "launchd" && flag !== "systemd")
10763
+ die(`--service-manager must be launchd or systemd, got "${flag}"`);
10764
+ return flag;
10765
+ }
10766
+ const kind = defaultServiceManager();
10767
+ if (!kind) {
10768
+ die(
10769
+ `no service manager is known for ${process.platform}; run \`tines runner daemon\` under your own supervisor instead`
10770
+ );
10771
+ }
10772
+ return kind;
10773
+ }
10774
+ async function runServiceCommands(steps) {
10775
+ for (const step of steps) {
10776
+ try {
10777
+ await run(step.argv[0], step.argv.slice(1), { timeout: 3e4 });
10778
+ } catch (err) {
10779
+ if (step.optional) continue;
10780
+ const detail = err && typeof err === "object" && "stderr" in err && String(err.stderr).trim() ? `: ${String(err.stderr).trim()}` : err instanceof Error ? `: ${err.message}` : "";
10781
+ die(`\`${step.argv.join(" ")}\` failed${detail}`);
10782
+ }
10783
+ }
10784
+ }
10785
+ async function listProcesses() {
10786
+ try {
10787
+ const { stdout } = await run("ps", ["-axo", "pid=,args="], { maxBuffer: 16 * 1024 * 1024 });
10788
+ return stdout;
10789
+ } catch {
10790
+ return null;
10791
+ }
10792
+ }
10793
+ function fileSize(path2) {
10794
+ try {
10795
+ return statSync6(path2).size;
10796
+ } catch {
10797
+ return 0;
10798
+ }
10799
+ }
10800
+ async function waitForStart(logPath, offset, name2) {
10801
+ const pattern = daemonStartedLine(name2);
10802
+ const deadline = Date.now() + STARTUP_TIMEOUT_MS;
10803
+ while (Date.now() < deadline) {
10804
+ if (existsSync7(logPath)) {
10805
+ const content = readFileSync10(logPath, "utf8").slice(offset);
10806
+ if (pattern.test(content)) return true;
10807
+ }
10808
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
10809
+ }
10810
+ return false;
10811
+ }
10812
+ function serviceTarget(name2, kind) {
10813
+ return { kind, name: name2, unitPath: unitPath(name2, kind, homedir3()), uid: userInfo().uid };
10814
+ }
10815
+ function registerServiceCommands(runnerCmd) {
10816
+ withCommon(
10817
+ withDaemonFlags(
10818
+ runnerCmd.command("install").description(
10819
+ "Register this machine as a runner and install the daemon as a launchd/systemd service that keeps itself updated"
10820
+ )
10821
+ ).option("--service-manager <kind>", "launchd | systemd (default: the one this OS ships)")
10822
+ ).action(async (opts) => {
10823
+ const settings = parseDaemonFlags(opts);
10824
+ const kind = resolveServiceManager(opts.serviceManager);
10825
+ const baseUrl = resolveUrl(opts).replace(/\/+$/, "");
10826
+ const configDir = defaultConfigDir();
10827
+ mkdirSync5(configDir, { recursive: true });
10828
+ const prefix = agentCliPrefix(configDir);
10829
+ const ps = await listProcesses();
10830
+ const running = ps ? matchDaemonProcesses(ps, {
10831
+ name: settings.name,
10832
+ hostname: hostname3(),
10833
+ prefix,
10834
+ selfPid: process.pid
10835
+ }) : [];
10836
+ if (running.length > 0) {
10837
+ const label = serviceLabel(settings.name, kind);
10838
+ const lines2 = running.map((p) => ` pid ${p.pid}: ${p.args}`);
10839
+ die(
10840
+ running.every((p) => p.managed) ? `"${settings.name}" is already running under its service (${label}):
10841
+ ${lines2.join("\n")}
10842
+ to change its flags: \`tines runners pause ${settings.name}\`, wait for 0 active runs, \`tines runner uninstall ${settings.name}\`, then install again \u2014 or \`tines runner restart ${settings.name}\` to relaunch it as is` : `a daemon for "${settings.name}" is already running outside the service:
10843
+ ${lines2.join("\n")}
10844
+ stop it first \u2014 \`tines runners pause ${settings.name}\`, wait for 0 active runs, kill that process \u2014 then run install again; it reconnects with the stored token, so no key is needed`
10845
+ );
10846
+ }
10847
+ if (!managedBinaryPresent(prefix)) {
10848
+ log2(`installing the daemon-managed tines into ${prefix} \u2026`);
10849
+ await installAgentCli({ configDir, log: log2 });
10850
+ if (!managedBinaryPresent(prefix)) {
10851
+ die(
10852
+ `could not install tines into ${prefix}; the service needs that copy (npm and network access required)`
10853
+ );
10854
+ }
10855
+ }
10856
+ let ensured;
10857
+ try {
10858
+ ensured = await ensureRunnerCredentials({
10859
+ configDir,
10860
+ baseUrl,
10861
+ name: settings.name,
10862
+ harness: settings.harness,
10863
+ ...settings.command !== void 0 ? { command: settings.command } : {},
10864
+ maxConcurrent: settings.maxConcurrent,
10865
+ ...resolveApiKey(opts) !== void 0 ? { apiKey: resolveApiKey(opts) } : {},
10866
+ log: log2
10867
+ });
10868
+ } catch (err) {
10869
+ die(err instanceof Error ? err.message : String(err));
10870
+ }
10871
+ const harnessBinary = settings.harness === "claude_code" ? "claude" : settings.harness === "codex" ? "codex" : null;
10872
+ const { path: path2, missing } = servicePath({
10873
+ execPath: process.execPath,
10874
+ required: [...harnessBinary ? [harnessBinary] : [], "git"],
10875
+ envPath: process.env.PATH,
10876
+ platform: process.platform
10877
+ });
10878
+ for (const name2 of missing) {
10879
+ log2(
10880
+ `warning: \`${name2}\` is not on this shell's PATH, so it is not on the service's either \u2014 install it, then \`tines runner install\` again`
10881
+ );
10882
+ }
10883
+ const target = serviceTarget(settings.name, kind);
10884
+ const logPath = serviceLogPath(configDir, settings.name);
10885
+ const spec = {
10886
+ name: settings.name,
10887
+ kind,
10888
+ binary: managedBinary(prefix),
10889
+ args: daemonArgs({
10890
+ url: baseUrl,
10891
+ name: settings.name,
10892
+ harness: harnessFlag(settings.harness),
10893
+ ...settings.command !== void 0 ? { command: settings.command } : {},
10894
+ maxConcurrent: settings.maxConcurrent,
10895
+ pollIntervalSeconds: settings.pollIntervalSeconds,
10896
+ keepWorkspaces: settings.keepWorkspaces,
10897
+ keepWorkspacesForHours: settings.keepWorkspacesForHours,
10898
+ keepWorkspacesMax: settings.keepWorkspacesMax
10899
+ }),
10900
+ path: path2,
10901
+ logPath,
10902
+ env: process.env.TINES_CONFIG_DIR ? { TINES_CONFIG_DIR: process.env.TINES_CONFIG_DIR } : {}
10903
+ };
10904
+ mkdirSync5(dirname6(target.unitPath), { recursive: true });
10905
+ mkdirSync5(dirname6(logPath), { recursive: true });
10906
+ writeFileSync4(target.unitPath, renderUnit(spec), { mode: 420 });
10907
+ log2(`wrote ${target.unitPath}`);
10908
+ const offset = fileSize(logPath);
10909
+ await runServiceCommands(serviceCommands(target).load);
10910
+ log2(`loaded ${serviceLabel(settings.name, kind)}; waiting for the daemon to report in \u2026`);
10911
+ const started = await waitForStart(logPath, offset, settings.name);
10912
+ const summary = {
10913
+ name: settings.name,
10914
+ runner_id: ensured.creds.runner_id,
10915
+ registered: ensured.registered,
10916
+ service_manager: kind,
10917
+ unit_path: target.unitPath,
10918
+ log_path: logPath,
10919
+ started
10920
+ };
10921
+ if (!started) {
10922
+ if (opts.json) printJson(summary);
10923
+ die(
10924
+ `the service is loaded but the daemon has not reported in after ${STARTUP_TIMEOUT_MS / 1e3}s \u2014 read ${logPath} (a missing \`node\` on the service PATH, or a rejected token, shows there)`
10925
+ );
10926
+ }
10927
+ if (opts.json) return printJson(summary);
10928
+ log2(
10929
+ `runner "${settings.name}" is up under ${kind} and updates itself: log at ${logPath}; \`tines runner restart ${settings.name}\` relaunches it, \`tines runner uninstall ${settings.name}\` removes the service`
10930
+ );
10931
+ if (ensured.registered) log2(nextStepsMessage(settings.name, baseUrl));
10932
+ });
10933
+ runnerCmd.command("restart [name]").description(
10934
+ "Relaunch an installed runner service (interrupts runs in flight: pause the runner and wait for 0 active runs first if that matters)"
10935
+ ).option("--service-manager <kind>", "launchd | systemd (default: the one this OS ships)").option("--json", "print JSON instead of a message").action(async (name2, opts) => {
10936
+ const kind = resolveServiceManager(opts.serviceManager);
10937
+ const target = serviceTarget(name2 ?? hostname3(), kind);
10938
+ if (!existsSync7(target.unitPath)) {
10939
+ die(
10940
+ `"${target.name}" is not installed as a service (no ${target.unitPath}); run \`tines runner install --name ${target.name} \u2026\` first`
10941
+ );
10942
+ }
10943
+ await runServiceCommands(serviceCommands(target).restart);
10944
+ if (opts.json)
10945
+ return printJson({ name: target.name, service_manager: kind, restarted: true });
10946
+ log2(`restarted ${serviceLabel(target.name, kind)}`);
10947
+ });
10948
+ runnerCmd.command("uninstall [name]").description(
10949
+ "Stop and remove a runner's service unit (the stored runner token is kept, so a later install reconnects)"
10950
+ ).option("--service-manager <kind>", "launchd | systemd (default: the one this OS ships)").option("--json", "print JSON instead of a message").action(async (name2, opts) => {
10951
+ const kind = resolveServiceManager(opts.serviceManager);
10952
+ const target = serviceTarget(name2 ?? hostname3(), kind);
10953
+ const existed = existsSync7(target.unitPath);
10954
+ await runServiceCommands(serviceCommands(target).unload);
10955
+ rmSync4(target.unitPath, { force: true });
10956
+ if (opts.json)
10957
+ return printJson({ name: target.name, service_manager: kind, removed: existed });
10958
+ log2(
10959
+ existed ? `removed ${serviceLabel(target.name, kind)} (${target.unitPath}); the runner token is still stored, so \`tines runner install\` reconnects without a key` : `no service unit for "${target.name}" at ${target.unitPath}; nothing to remove`
10960
+ );
10160
10961
  });
10161
10962
  }
10162
10963
 
@@ -10455,87 +11256,38 @@ function register8(program3) {
10455
11256
  });
10456
11257
  const runnerCmd = program3.command("runner").description("The local runner daemon");
10457
11258
  withCommon(
10458
- runnerCmd.command("daemon").description(
10459
- "Run the local runner daemon: register/reconnect, poll for assigned runs, execute them"
10460
- ).option(
10461
- "--name <name>",
10462
- "runner name, unique per user; name it machine-plus-harness, e.g. macbook-claude (default: this hostname)"
10463
- ).option("--harness <harness>", "claude-code | codex | custom", "claude-code").option(
10464
- "--command <template>",
10465
- "custom harness command template ({prompt_file}, {workspace}, {model})"
10466
- ).option("--max-concurrent <n>", "maximum simultaneous runs", (v) => Number.parseInt(v, 10), 1).option(
10467
- "--poll-interval <seconds>",
10468
- "seconds between polls",
10469
- (v) => Number.parseInt(v, 10),
10470
- 15
11259
+ withDaemonFlags(
11260
+ runnerCmd.command("daemon").description(
11261
+ "Run the local runner daemon in the foreground: register/reconnect, poll for assigned runs, execute them (`tines runner install` runs it as a service instead)"
11262
+ )
10471
11263
  ).option(
10472
11264
  "--no-cli-refresh",
10473
11265
  "do not install/refresh the agent-facing tines CLI from npm (harnesses use the ambient PATH)"
10474
11266
  ).option(
10475
11267
  "--no-self-update",
10476
11268
  "do not exit for the service manager to relaunch a newer daemon (only applies when launched from the daemon-managed prefix)"
10477
- ).option(
10478
- "--keep-workspaces <mode>",
10479
- "keep settled runs' workspaces for debugging: never | failed | always",
10480
- "never"
10481
- ).option(
10482
- "--keep-workspaces-for <hours>",
10483
- "delete kept workspaces older than this",
10484
- (v) => Number(v),
10485
- 72
10486
- ).option(
10487
- "--keep-workspaces-max <n>",
10488
- "keep at most this many workspaces (oldest removed first)",
10489
- (v) => Number.parseInt(v, 10),
10490
- 20
10491
11269
  )
10492
11270
  ).action(
10493
11271
  async (opts) => {
10494
- const harness = opts.harness.replaceAll("-", "_");
10495
- if (!HARNESS_KINDS.includes(harness)) {
10496
- die(`--harness must be claude-code, codex, or custom, got "${opts.harness}"`);
10497
- }
10498
- if (harness === "custom" && !opts.command) {
10499
- die(
10500
- 'the custom harness needs --command "<template>" ({prompt_file}, {workspace}, {model})'
10501
- );
10502
- }
10503
- if (harness !== "custom" && opts.command) die("--command only applies to --harness custom");
10504
- if (!Number.isInteger(opts.maxConcurrent) || opts.maxConcurrent < 1 || opts.maxConcurrent > 100) {
10505
- die("--max-concurrent must be an integer between 1 and 100");
10506
- }
10507
- if (!Number.isInteger(opts.pollInterval) || opts.pollInterval < 1) {
10508
- die("--poll-interval must be a positive number of seconds");
10509
- }
10510
- const keepWorkspaces = opts.keepWorkspaces;
10511
- if (!KEEP_WORKSPACES_MODES.includes(keepWorkspaces)) {
10512
- die(
10513
- `--keep-workspaces must be ${KEEP_WORKSPACES_MODES.join(", ")}, got "${opts.keepWorkspaces}"`
10514
- );
10515
- }
10516
- if (!Number.isFinite(opts.keepWorkspacesFor) || opts.keepWorkspacesFor <= 0) {
10517
- die("--keep-workspaces-for must be a positive number of hours");
10518
- }
10519
- if (!Number.isInteger(opts.keepWorkspacesMax) || opts.keepWorkspacesMax < 1) {
10520
- die("--keep-workspaces-max must be a positive integer");
10521
- }
11272
+ const settings = parseDaemonFlags(opts);
10522
11273
  await runDaemon({
10523
11274
  url: resolveUrl(opts).replace(/\/+$/, ""),
10524
11275
  apiKey: resolveApiKey(opts),
10525
- name: opts.name ?? hostname2(),
10526
- harness,
10527
- command: opts.command,
10528
- maxConcurrent: opts.maxConcurrent,
10529
- pollIntervalMs: opts.pollInterval * 1e3,
11276
+ name: settings.name,
11277
+ harness: settings.harness,
11278
+ command: settings.command,
11279
+ maxConcurrent: settings.maxConcurrent,
11280
+ pollIntervalMs: settings.pollIntervalSeconds * 1e3,
10530
11281
  configDir: defaultConfigDir(),
10531
11282
  cliRefresh: opts.cliRefresh,
10532
11283
  selfUpdate: opts.selfUpdate,
10533
- keepWorkspaces,
10534
- keepWorkspacesForHours: opts.keepWorkspacesFor,
10535
- keepWorkspacesMax: opts.keepWorkspacesMax
11284
+ keepWorkspaces: settings.keepWorkspaces,
11285
+ keepWorkspacesForHours: settings.keepWorkspacesForHours,
11286
+ keepWorkspacesMax: settings.keepWorkspacesMax
10536
11287
  });
10537
11288
  }
10538
11289
  );
11290
+ registerServiceCommands(runnerCmd);
10539
11291
  const workspacesCmd = runnerCmd.command("workspaces").description("List run workspaces the daemon kept for debugging (--keep-workspaces)").option("--json", "print JSON instead of a table").action((opts) => {
10540
11292
  const configDir = defaultConfigDir();
10541
11293
  const kept = listKeptWorkspaces(configDir);
@@ -10622,14 +11374,14 @@ function register8(program3) {
10622
11374
  if (evidence.population === "pending") {
10623
11375
  table([
10624
11376
  ["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "COST", "CREATED"],
10625
- ...items.map((run) => [
10626
- run.id,
10627
- run.issue_ref ? `${run.issue_ref.project_name}/${run.issue_ref.number}` : run.issue_id,
10628
- run.runner_name,
10629
- run.tier,
11377
+ ...items.map((run2) => [
11378
+ run2.id,
11379
+ run2.issue_ref ? `${run2.issue_ref.project_name}/${run2.issue_ref.number}` : run2.issue_id,
11380
+ run2.runner_name,
11381
+ run2.tier,
10630
11382
  "Pending at cutoff",
10631
11383
  "\u2014",
10632
- timestamp(run.created_at)
11384
+ timestamp(run2.created_at)
10633
11385
  ])
10634
11386
  ]);
10635
11387
  return;
@@ -10645,29 +11397,29 @@ function register8(program3) {
10645
11397
  ).action(
10646
11398
  async (id2, opts) => {
10647
11399
  const api = client(opts);
10648
- const run = await api.getRun(id2);
10649
- if (opts.json) return printJson(run);
11400
+ const run2 = await api.getRun(id2);
11401
+ if (opts.json) return printJson(run2);
10650
11402
  console.log(
10651
- `${run.id} ${run.status} on ${run.runner_name}${run.resumed_from_run_id ? ` \xB7 resumed run ${run.resumed_from_run_id}` : ""}`
11403
+ `${run2.id} ${run2.status} on ${run2.runner_name}${run2.resumed_from_run_id ? ` \xB7 resumed run ${run2.resumed_from_run_id}` : ""}`
10652
11404
  );
10653
- if (run.issue_ref) console.log(`issue: ${issueRef(run.issue_ref)} \u2014 ${run.issue_ref.title}`);
10654
- console.log(`tier: ${run.tier} model: ${run.model ?? "(n/a)"}`);
11405
+ if (run2.issue_ref) console.log(`issue: ${issueRef(run2.issue_ref)} \u2014 ${run2.issue_ref.title}`);
11406
+ console.log(`tier: ${run2.tier} model: ${run2.model ?? "(n/a)"}`);
10655
11407
  console.log(
10656
- `states: ${run.state_at_start_name ?? run.state_id_at_start} \u2192 ${run.state_at_end_name ?? run.state_id_at_end ?? "\u2026"}`
11408
+ `states: ${run2.state_at_start_name ?? run2.state_id_at_start} \u2192 ${run2.state_at_end_name ?? run2.state_id_at_end ?? "\u2026"}`
10657
11409
  );
10658
11410
  console.log(
10659
- `created: ${timestamp(run.created_at)} started: ${run.started_at ? timestamp(run.started_at) : "\u2014"} ended: ${run.ended_at ? timestamp(run.ended_at) : "\u2014"} duration: ${runDurationLabel(run)}`
11411
+ `created: ${timestamp(run2.created_at)} started: ${run2.started_at ? timestamp(run2.started_at) : "\u2014"} ended: ${run2.ended_at ? timestamp(run2.ended_at) : "\u2014"} duration: ${runDurationLabel(run2)}`
10660
11412
  );
10661
- if (run.usage) {
10662
- const u = run.usage;
10663
- const costLabel = runCostLabel(run);
11413
+ if (run2.usage) {
11414
+ const u = run2.usage;
11415
+ const costLabel = runCostLabel(run2);
10664
11416
  const metric = (value) => value === void 0 ? "unknown" : value.toLocaleString();
10665
11417
  console.log(
10666
11418
  `usage: input ${metric(u.input_tokens)} cache-read ${metric(u.cache_read_tokens)} cache-write ${metric(u.cache_write_tokens)} output ${metric(u.output_tokens)}`
10667
11419
  );
10668
11420
  if (u.cost_usd !== void 0)
10669
11421
  console.log(
10670
- `cost: ${runCostLabel(run)}${u.cost_source === void 0 ? " \xB7 source unavailable" : ""}`
11422
+ `cost: ${runCostLabel(run2)}${u.cost_source === void 0 ? " \xB7 source unavailable" : ""}`
10671
11423
  );
10672
11424
  const pricing = u.pricing;
10673
11425
  if (pricing?.status === "provider_authoritative")
@@ -10691,10 +11443,20 @@ function register8(program3) {
10691
11443
  console.log(` exact estimated USD: ${b.cost_usd_exact}`);
10692
11444
  console.log(" Standard API list-price estimate; not an invoice or subscription usage.");
10693
11445
  }
11446
+ const proof = pricing?.evidence?.request_context;
11447
+ if (proof?.status === "complete") {
11448
+ console.log(
11449
+ `request context: ${proof.request_count.toLocaleString()} verified \xB7 largest input ${proof.max_request_input_tokens.toLocaleString()} \xB7 Codex ${proof.harness_version} \xB7 ${proof.normalization}`
11450
+ );
11451
+ } else if (proof) {
11452
+ console.log(
11453
+ `request context: ${proof.status} (${proof.reason})${proof.harness_version ? ` \xB7 Codex ${proof.harness_version}` : ""} \xB7 ${proof.normalization}`
11454
+ );
11455
+ }
10694
11456
  }
10695
- if (run.provider_session_id) console.log(`provider session: ${run.provider_session_id}`);
10696
- if (run.provider_url) console.log(`provider console: ${run.provider_url}`);
10697
- if (run.error) console.log(`error: ${run.error}`);
11457
+ if (run2.provider_session_id) console.log(`provider session: ${run2.provider_session_id}`);
11458
+ if (run2.provider_url) console.log(`provider console: ${run2.provider_url}`);
11459
+ if (run2.error) console.log(`error: ${run2.error}`);
10698
11460
  if (opts.logs) {
10699
11461
  console.log("");
10700
11462
  if (opts.full || opts.raw) {
@@ -10711,12 +11473,12 @@ function register8(program3) {
10711
11473
  process.stdout.write(decoder.decode());
10712
11474
  return;
10713
11475
  }
10714
- if (run.log_bytes_dropped > 0) {
11476
+ if (run2.log_bytes_dropped > 0) {
10715
11477
  console.log(
10716
- `[${Math.round(run.log_bytes_dropped / 1024)} KB truncated from the head \u2014 ` + (run.log_expired ? "past its retention window; only this tail remains]" : `run \`tines runs show ${run.id} --logs --full\` for the complete ${Math.round(run.log_full_bytes / 1024)} KB log]`)
11478
+ `[${Math.round(run2.log_bytes_dropped / 1024)} KB truncated from the head \u2014 ` + (run2.log_expired ? "past its retention window; only this tail remains]" : `run \`tines runs show ${run2.id} --logs --full\` for the complete ${Math.round(run2.log_full_bytes / 1024)} KB log]`)
10717
11479
  );
10718
11480
  }
10719
- console.log(run.log || "(no log output captured)");
11481
+ console.log(run2.log || "(no log output captured)");
10720
11482
  }
10721
11483
  }
10722
11484
  );
@@ -10724,10 +11486,10 @@ function register8(program3) {
10724
11486
  runsCmd.command("cancel <id>").description("Cancel a run (judged like any other end: usually a strike)")
10725
11487
  ).action(async (id2, opts) => {
10726
11488
  const api = client(opts);
10727
- const run = await api.cancelRun(id2);
10728
- if (opts.json) return printJson(run);
11489
+ const run2 = await api.cancelRun(id2);
11490
+ if (opts.json) return printJson(run2);
10729
11491
  console.log(
10730
- `canceled ${run.id}${run.issue_ref ? ` on ${issueRef(run.issue_ref)}` : ""} (was on ${run.runner_name})`
11492
+ `canceled ${run2.id}${run2.issue_ref ? ` on ${issueRef(run2.issue_ref)}` : ""} (was on ${run2.runner_name})`
10731
11493
  );
10732
11494
  });
10733
11495
  }
@@ -11104,12 +11866,12 @@ function register10(program3) {
11104
11866
  const api = client(opts);
11105
11867
  const overrides = {};
11106
11868
  for (const spec of opts.state) {
11107
- const sep2 = spec.lastIndexOf("=");
11108
- if (sep2 < 1 || sep2 === spec.length - 1) {
11869
+ const sep4 = spec.lastIndexOf("=");
11870
+ if (sep4 < 1 || sep4 === spec.length - 1) {
11109
11871
  die(`--state must look like <workflow>/<state>=<n>, got "${spec}"`);
11110
11872
  }
11111
- const limit = Number.parseInt(spec.slice(sep2 + 1), 10);
11112
- const { state } = await resolveStateFlag(api, spec.slice(0, sep2));
11873
+ const limit = Number.parseInt(spec.slice(sep4 + 1), 10);
11874
+ const { state } = await resolveStateFlag(api, spec.slice(0, sep4));
11113
11875
  overrides[state.id] = limit;
11114
11876
  }
11115
11877
  const settings = await api.updateSupervisorSettings({
@@ -11126,17 +11888,17 @@ function register10(program3) {
11126
11888
  }
11127
11889
 
11128
11890
  // src/commands/workflows.ts
11129
- import { readFileSync as readFileSync11 } from "node:fs";
11891
+ import { readFileSync as readFileSync12 } from "node:fs";
11130
11892
 
11131
11893
  // src/workflow-packages.ts
11132
11894
  import { createInterface as createInterface4 } from "node:readline/promises";
11133
11895
  import { createHash } from "node:crypto";
11134
- import { readFileSync as readFileSync10 } from "node:fs";
11896
+ import { readFileSync as readFileSync11 } from "node:fs";
11135
11897
  var PACKAGE_STDIN = "-";
11136
11898
  var MAX_LOCAL_JSON_BYTES = 32 * 1024 * 1024;
11137
11899
  function readPackageSource(path2) {
11138
11900
  try {
11139
- const bytes2 = readFileSync10(path2 === PACKAGE_STDIN ? 0 : path2);
11901
+ const bytes2 = readFileSync11(path2 === PACKAGE_STDIN ? 0 : path2);
11140
11902
  if (bytes2.byteLength > MAX_LOCAL_JSON_BYTES)
11141
11903
  throw new Error(`input exceeds ${MAX_LOCAL_JSON_BYTES} bytes`);
11142
11904
  return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2);
@@ -11480,14 +12242,14 @@ function readJsonBody(inline, file) {
11480
12242
  if (file !== void 0 && file !== "-") {
11481
12243
  let raw;
11482
12244
  try {
11483
- raw = readFileSync11(file, "utf8");
12245
+ raw = readFileSync12(file, "utf8");
11484
12246
  } catch (err) {
11485
12247
  die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
11486
12248
  }
11487
12249
  return parseJsonObject(raw, file);
11488
12250
  }
11489
12251
  if (file === "-" || !process.stdin.isTTY) {
11490
- const raw = readFileSync11(0, "utf8");
12252
+ const raw = readFileSync12(0, "utf8");
11491
12253
  if (raw.trim() === "") {
11492
12254
  if (file === "-") die("no JSON on stdin");
11493
12255
  return void 0;
@@ -11596,9 +12358,9 @@ async function resolveStateBases(states, library) {
11596
12358
  }
11597
12359
  }
11598
12360
  function resolveBasePair(lib, pair, stateName) {
11599
- const sep2 = pair.indexOf("/");
11600
- const workflowRef = pair.slice(0, sep2).trim();
11601
- const stateRef2 = pair.slice(sep2 + 1).trim();
12361
+ const sep4 = pair.indexOf("/");
12362
+ const workflowRef = pair.slice(0, sep4).trim();
12363
+ const stateRef2 = pair.slice(sep4 + 1).trim();
11602
12364
  const where = `state "${stateName}" inherits from "${pair}"`;
11603
12365
  if (!workflowRef || !stateRef2) {
11604
12366
  die(`${where}, which is not a <workflow>/<state> pair`);
@@ -11892,8 +12654,8 @@ function registerPackageCommands(workflows) {
11892
12654
  else {
11893
12655
  console.log(`installed workflow package (${receipt.id})`);
11894
12656
  console.log(`plan: ${receipt.plan_digest}`);
11895
- for (const object3 of receipt.objects)
11896
- console.log(` ${object3.kind}: ${object3.name} \u2014 ${object3.href}`);
12657
+ for (const object4 of receipt.objects)
12658
+ console.log(` ${object4.kind}: ${object4.name} \u2014 ${object4.href}`);
11897
12659
  }
11898
12660
  }
11899
12661
  );