@azure-id/orc 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -220,6 +220,16 @@ function installGuards(claudeDir) {
220
220
  JSON.stringify({ version: currentVersion() }) + "\n"
221
221
  );
222
222
  } catch (_) {}
223
+ // The statusline's phase rail (v1.2.1). Regenerated every install from the
224
+ // CLI's own registries, so the hook renders a phase list it never derives.
225
+ // Same fail-silent contract: a missing manifest hides the `status:` segment,
226
+ // it never breaks a render.
227
+ try {
228
+ fs.writeFileSync(
229
+ path.join(hooksDest, "orc-lane-rails.json"),
230
+ JSON.stringify(laneRailsManifest()) + "\n"
231
+ );
232
+ } catch (_) {}
223
233
 
224
234
  const guardCmd = nodeCmd(path.join(hooksDest, "orc-effort-guard.js"));
225
235
  const statusCmd = nodeCmd(path.join(hooksDest, "orc-statusline.js"));
@@ -346,6 +356,7 @@ function shippedFootprint() {
346
356
  for (const rel of listSrcRel(SRC_AGENTS)) files.push("agents/" + rel);
347
357
  for (const rel of listSrcRel(SRC_HOOKS)) files.push("hooks/" + rel);
348
358
  files.push("hooks/orc-version.json"); // generated by installGuards, ORC-owned
359
+ files.push("hooks/orc-lane-rails.json"); // generated by installGuards, ORC-owned
349
360
  return files;
350
361
  }
351
362
 
@@ -3091,6 +3102,18 @@ const LANE_CALLS = {
3091
3102
  never: "never merge `.claude/orc.config.yaml` yourself, and never re-derive a precedence — the answer already carries it",
3092
3103
  lanes: ["context-combiner", "orc", "orc-aftermath", "orc-analyze", "orc-analyze-mini", "orc-boundary", "orc-brainstorm", "orc-budget", "orc-challenge", "orc-claude", "orc-diy", "orc-doc", "orc-explain", "orc-export", "orc-fast", "orc-grill", "orc-handoff", "orc-learn", "orc-mini", "orc-pact", "orc-pattern", "orc-poly", "orc-pr-driver", "orc-pr-setup", "orc-quick", "orc-retro", "orc-route", "orc-verify", "orc-wiki"],
3093
3104
  },
3105
+ "run-inflight": {
3106
+ cmd: "orc run inflight [--json]",
3107
+ what: "is a dispatch from this run still alive — the ONE reader of the trace's pending sidecar",
3108
+ exits: { 0: "clear — provably nothing in flight", 1: "in-flight — at least one dispatch has not returned", 2: "unknown — cannot prove either way" },
3109
+ states: ["clear", "in-flight", "unknown"],
3110
+ cost: "free",
3111
+ when: "before ANY re-dispatch, requeue or repair round, and before the first dispatch of a resumed run",
3112
+ on_absent: "exit 2 REFUSES by default — the one place an absent reading blocks, because a wrongly-refused dispatch costs a question and a wrongly-issued one costs a second Opus agent for an hour",
3113
+ canonical: "_shared/return-validation.md",
3114
+ never: "never read `clear` as proof that an AD-HOC dispatch finished — the hook writes no SPAWN for one, so no record exists",
3115
+ lanes: ["orc", "orc-doc", "orc-fast", "orc-mini", "orc-quick", "orc-wiki"],
3116
+ },
3094
3117
  "lane-phases": {
3095
3118
  cmd: "orc lane phases <lane> [--json]",
3096
3119
  what: "which SHARED phases this lane runs, in order — the file, the layers to read, and when",
@@ -4077,6 +4100,150 @@ function lanePhaseRows(lane) {
4077
4100
  return rows;
4078
4101
  }
4079
4102
 
4103
+ // ---------------------------------------------------------------------------
4104
+ // The STATUSLINE RAIL MANIFEST (v1.2.1)
4105
+ //
4106
+ // `templates/hooks/orc-statusline.js` shows which phase an ORC run is in. It
4107
+ // cannot call this CLI: a statusline re-renders on every keystroke, so a
4108
+ // subprocess per render is not on the table. It also must never hold its own
4109
+ // idea of what the phases are — that is the Flow-stepper failure (`orc ui`
4110
+ // v0.43.7) applied to a second surface, and a second phase table is drift no
4111
+ // lint could see.
4112
+ //
4113
+ // So the CLI COMPUTES and the hook RENDERS: `orc init` / `orc update` writes
4114
+ // `hooks/orc-lane-rails.json` from the registries below, exactly the way
4115
+ // `hooks/orc-version.json` is stamped at install time.
4116
+ //
4117
+ // FRAMES ARE NOT IN HERE. The glyphs live in the hook, because they are
4118
+ // presentation: a motif change must not need a reinstall. What ships is the
4119
+ // KIND — a closed word the hook maps to a frame set.
4120
+ // ---------------------------------------------------------------------------
4121
+
4122
+ // A phase's motif kind. The set is CLOSED, and a phase this table does not name
4123
+ // gets `generic` — the honest sweep, not a guess. Ported in spirit from
4124
+ // orc-cli's motifs.js, keyed on the PHASE rather than the lane: the lane name is
4125
+ // already the first word of the statusline's `status:` segment, so the glyph is
4126
+ // free to say what ORC is DOING.
4127
+ const PHASE_KINDS = ["look", "ask", "plan", "do", "check", "ship", "wait", "generic"];
4128
+ const PHASE_KIND = {
4129
+ // reading, scanning, gathering evidence
4130
+ preflight: "look",
4131
+ trace: "look",
4132
+ "wiki-consult": "look",
4133
+ "analyst-gates": "look",
4134
+ "house-rules": "look",
4135
+ analysis: "look",
4136
+ combine: "look",
4137
+ // waiting on the user
4138
+ intake: "ask",
4139
+ // deciding what to do and in what order
4140
+ planning: "plan",
4141
+ "plan-handoff": "plan",
4142
+ "wave-grouping": "plan",
4143
+ scoring: "plan",
4144
+ // executors running
4145
+ execution: "do",
4146
+ // grading what came back
4147
+ review: "check",
4148
+ security: "check",
4149
+ "security-checklist": "check",
4150
+ verify: "check",
4151
+ testgen: "check",
4152
+ "mock-example": "check",
4153
+ "ultra-gate": "check",
4154
+ // handing it over
4155
+ ship: "ship",
4156
+ summary: "ship",
4157
+ // stopped on purpose
4158
+ "stop-resume": "wait",
4159
+ };
4160
+
4161
+ // A word in an own-phase heading that decides its kind. Checked in order, so
4162
+ // the first match wins; no match falls through to `generic`. This is how a
4163
+ // lane's own pipeline (`Q1 LOOK`, `D4 DRAFT`, `C6 ADVISE`) gets a motif without
4164
+ // a hand-maintained row per phase per lane.
4165
+ const OWN_PHASE_KIND_WORDS = [
4166
+ [/\b(ask|question|intake|interview|frame|scope|choose|pick|gate)\b/i, "ask"],
4167
+ [/\b(look|preflight|read|scan|ingest|evidence|load|context|recon|harvest)\b/i, "look"],
4168
+ [/\b(plan|outline|cluster|shape|diverge|converge|order|batch|wave)\b/i, "plan"],
4169
+ [/\b(do|write|build|execute|implement|draft|generate|dispatch|compile)\b/i, "do"],
4170
+ [/\b(check|review|verify|judge|challenge|stress|grade|lint|audit|conserv)\b/i, "check"],
4171
+ [/\b(ship|finish|exit|deliver|hand|summar|report|card|record)\b/i, "ship"],
4172
+ [/\b(wait|stop|resume|pause|suspend)\b/i, "wait"],
4173
+ ];
4174
+
4175
+ // The display label for one phase. 18 visible chars, because line 2 of the
4176
+ // statusline already carries five other segments.
4177
+ //
4178
+ // A shared phase is its own id — those ids ARE the words ORC uses everywhere
4179
+ // else, and renaming them for a status bar would invent a second vocabulary.
4180
+ // An own phase is its heading up to the first bracket or dash, which is what
4181
+ // turns `## Q1 — LOOK (silent — no questions here)` into `Q1 LOOK`.
4182
+ function railLabel(row) {
4183
+ if (!row.heading) return String(row.id).slice(0, 18);
4184
+ let s = String(row.heading).replace(/^#+\s*/, "");
4185
+ s = s.split(/\s+[—–-]\s+|\s*\(/)[0].trim();
4186
+ // A heading whose whole first clause is just the code (`## C6 — Advise`)
4187
+ // loses the only interesting word, so keep the second clause too when the
4188
+ // first is a bare code.
4189
+ if (/^[A-Za-z]{1,2}\d+[a-z]?$/.test(s)) {
4190
+ const rest = String(row.heading)
4191
+ .replace(/^#+\s*/, "")
4192
+ .slice(s.length)
4193
+ .replace(/^\s*[—–-]\s*/, "")
4194
+ .split(/\s*\(/)[0]
4195
+ .trim();
4196
+ if (rest) s = s + " " + rest;
4197
+ }
4198
+ s = s.replace(/\s+/g, " ").trim();
4199
+ if (s.length <= 18) return s;
4200
+ // Cut at a word boundary. `D2 Supporting docu` is a typo the reader has to
4201
+ // decode; `D2 Supporting` is a label.
4202
+ const cut = s.slice(0, 18);
4203
+ const sp = cut.lastIndexOf(" ");
4204
+ return (sp > 3 ? cut.slice(0, sp) : cut).trim();
4205
+ }
4206
+
4207
+ function railKind(row) {
4208
+ if (PHASE_KIND[row.id]) return PHASE_KIND[row.id];
4209
+ const hay = `${row.id} ${row.heading || ""}`;
4210
+ for (const [re, kind] of OWN_PHASE_KIND_WORDS) if (re.test(hay)) return kind;
4211
+ return "generic";
4212
+ }
4213
+
4214
+ // The role families `templates/hooks/orc-trace.js` writes as `PHASE-EDGE <fam>`.
4215
+ // This is a MIRROR of that hook's `roleFamily()`, and it is the one new drift
4216
+ // surface this feature adds — a golden test compares the two by source text,
4217
+ // the way the `OPUS5_BANDS` alias is asserted.
4218
+ const TRACE_FAMILIES = ["combine", "analysis", "planning", "execution", "review", "verify", "testgen", "ultra-gate"];
4219
+
4220
+ // The whole object, deterministic from the registries above. `--json is not a
4221
+ // summary`: this is what the file holds and what `orc lane rails` prints.
4222
+ function laneRailsManifest() {
4223
+ const lanes = {};
4224
+ for (const lane of LANE_NAMES) {
4225
+ const t = LANE_TRACE[lane];
4226
+ // A lane that opens no run writes no trace file, so no `run-<lane>-…` name
4227
+ // can ever name it and the resolver could never reach the row. Omitting it
4228
+ // is an answer, not a gap.
4229
+ if (!t || !t.token) continue;
4230
+ const rows = [];
4231
+ for (const p of lanePhaseRows(lane))
4232
+ rows.push({ id: p.id, label: railLabel(p), kind: railKind(p), verbs: [] });
4233
+ for (const p of LANE_OWN_PHASES[lane] || [])
4234
+ rows.push({
4235
+ id: p.id,
4236
+ label: railLabel(p),
4237
+ kind: railKind(p),
4238
+ verbs: Array.isArray(p.trace_verbs) ? p.trace_verbs.slice() : [],
4239
+ });
4240
+ lanes[t.token] = { lane, tier: t.tier, phases: rows };
4241
+ }
4242
+ const families = {};
4243
+ for (const f of TRACE_FAMILIES) families[f] = { label: f, kind: PHASE_KIND[f] || "generic" };
4244
+ return { version: currentVersion(), kinds: PHASE_KINDS, families, lanes };
4245
+ }
4246
+
4080
4247
  // \`orc lane phases <lane>\` / \`--all\`. Exit 0 in every state; 2 for an unknown
4081
4248
  // lane or no argument — the \`orc lane calls\` contract, unchanged.
4082
4249
  //
@@ -4272,6 +4439,33 @@ function laneConfigCmd(lane, claudeDir) {
4272
4439
  }
4273
4440
 
4274
4441
 
4442
+ // `orc lane rails [--json]` — the statusline's phase rail, exactly as it is
4443
+ // written to `hooks/orc-lane-rails.json` at install. It exists so the manifest
4444
+ // is inspectable without opening a hook directory, and so a golden test reads
4445
+ // the same object the hook does. Exit 0 in every state.
4446
+ //
4447
+ // `--json is not a summary`: this prints the WHOLE object, not a count of it.
4448
+ function laneRailsCmd() {
4449
+ const m = laneRailsManifest();
4450
+ if (wantsJson()) {
4451
+ emitJson(Object.assign({ ok: true }, m));
4452
+ return 0;
4453
+ }
4454
+ console.log(`\nORC statusline rails ${ui.color.gray("v" + m.version)}`);
4455
+ console.log(ui.color.gray(` kinds ${m.kinds.join(", ")}`));
4456
+ console.log(ui.color.gray(` families ${Object.keys(m.families).join(", ")}`));
4457
+ for (const [token, row] of Object.entries(m.lanes)) {
4458
+ console.log(`\n ${ui.color.bold(token)} ${ui.color.gray(row.lane + " · " + row.tier)}`);
4459
+ for (const p of row.phases)
4460
+ console.log(
4461
+ " " + String(p.kind).padEnd(8) + " " + String(p.label).padEnd(20) +
4462
+ ui.color.gray(p.verbs.length ? "verbs: " + p.verbs.join(", ") : "")
4463
+ );
4464
+ }
4465
+ console.log("");
4466
+ return 0;
4467
+ }
4468
+
4275
4469
  // `orc lane` — the noun. `list` and `config` today; `phases` lands in W12 and
4276
4470
  // `calls` in W10.
4277
4471
  function lane() {
@@ -4287,13 +4481,16 @@ function lane() {
4287
4481
  process.exit(laneCallsCmd(pos[2], claudeDir));
4288
4482
  case "phases":
4289
4483
  process.exit(lanePhasesCmd(pos[2], claudeDir));
4484
+ case "rails":
4485
+ process.exit(laneRailsCmd());
4290
4486
  default:
4291
4487
  console.error(
4292
4488
  `Unknown: orc lane ${pos[1]}\n` +
4293
4489
  "Usage: orc lane list [--json]\n" +
4294
4490
  " orc lane config <lane> [--json]\n" +
4295
4491
  " orc lane calls <lane> [--json] | orc lane calls --all [--json]\n" +
4296
- " orc lane phases <lane> [--json] | orc lane phases --all [--json]"
4492
+ " orc lane phases <lane> [--json] | orc lane phases --all [--json]" +
4493
+ "\n orc lane rails [--json]"
4297
4494
  );
4298
4495
  process.exit(2);
4299
4496
  }
@@ -9006,6 +9203,149 @@ function resume() {
9006
9203
  }
9007
9204
 
9008
9205
  // `orc run list` / `orc run show <slug|n>`
9206
+ // ── `orc run inflight` — is a previous dispatch still alive? (v1.2.0) ───────
9207
+ //
9208
+ // WHY THIS EXISTS. Claude Code's Task tool returning an error does NOT kill the
9209
+ // subagent behind it. The agent keeps running and keeps writing files. Every
9210
+ // lane's retry rule ("a broken return = a failure, re-dispatch") silently
9211
+ // assumed the opposite, so an interrupted turn produced a SECOND agent on the
9212
+ // same task while the first was still working. A graded run put THREE
9213
+ // `orc-executor-opus-5-low` agents on one task for 50m19s + 115m22s + 100m53s
9214
+ // — 266 minutes of Opus 5 for one authorised dispatch, all editing the same
9215
+ // files. The hook already recorded every one of them; nothing ever READ it.
9216
+ //
9217
+ // The pending sidecar (`<trace>.pending.json`, written by orc-trace.js on every
9218
+ // SPAWN) is the evidence. This command makes it authoritative:
9219
+ // `a lane that re-dispatches over a live attempt` has broken the contract in
9220
+ // `_shared/return-validation.md` §0.
9221
+ //
9222
+ // UNKNOWN IS NOT ZERO. A missing pointer, an unreadable sidecar or a record too
9223
+ // old to trust all exit 2 — never 0. "I cannot prove anything is running" and
9224
+ // "I proved nothing is running" are different facts, and re-dispatching on the
9225
+ // first one is exactly the bug. Only a readable sidecar that is EMPTY *and* a
9226
+ // trace whose SPAWN/RETURN counts agree earns exit 0.
9227
+ //
9228
+ // exit 0 clear — provably nothing in flight
9229
+ // exit 1 in-flight — >=1 dispatch has not returned
9230
+ // exit 2 unknown — cannot prove either way
9231
+ const INFLIGHT_STALE_MS = 6 * 60 * 60 * 1000;
9232
+
9233
+ function runInflightCmd(claudeDir) {
9234
+ const dir = resolveLogDir(claudeDir);
9235
+ const out = {
9236
+ ok: true,
9237
+ state: "unknown",
9238
+ reason: null,
9239
+ count: 0,
9240
+ entries: [],
9241
+ trace: null,
9242
+ lane: null,
9243
+ slug: null,
9244
+ log_dir: dir,
9245
+ sidecar: null,
9246
+ sidecar_readable: false,
9247
+ spawns: null,
9248
+ returns: null,
9249
+ balance_agrees: null,
9250
+ stale_entries: 0,
9251
+ };
9252
+
9253
+ const finish = (state, reason, code) => {
9254
+ out.state = state;
9255
+ out.reason = reason;
9256
+ if (wantsJson()) emitJson(out);
9257
+ else renderInflight(out);
9258
+ process.exit(code);
9259
+ };
9260
+
9261
+ // 1. The run pointer. No pointer = no open run we can reason about.
9262
+ let cur = null;
9263
+ try {
9264
+ cur = fs.readFileSync(path.join(dir, ".current"), "utf8").trim();
9265
+ } catch (_) {}
9266
+ if (!cur) return finish("unknown", "no trace pointer — no run is open, or the pointer was never written", 2);
9267
+ out.trace = cur;
9268
+ const m = TRACE_NAME.exec(cur);
9269
+ if (m) { out.lane = m[1]; out.slug = m[2]; }
9270
+
9271
+ // 2. The trace's own SPAWN/RETURN balance — an independent second opinion.
9272
+ // Counted from the hook's own skeleton lines only.
9273
+ const tracePath = path.join(dir, cur);
9274
+ try {
9275
+ const text = fs.readFileSync(tracePath, "utf8");
9276
+ out.spawns = (text.match(/\] hook\s+SPAWN /g) || []).length;
9277
+ const all = (text.match(/\] hook\s+RETURN /g) || []).length;
9278
+ const loose = (text.match(/\] hook\s+RETURN ~agent :: unattributed/g) || []).length;
9279
+ out.returns = all - loose;
9280
+ } catch (_) {
9281
+ return finish("unknown", `trace pointer names "${cur}" but it cannot be read`, 2);
9282
+ }
9283
+
9284
+ // 3. The pending sidecar — the record of what was dispatched and never closed.
9285
+ const side = path.join(dir, cur + ".pending.json");
9286
+ out.sidecar = side;
9287
+ let pend = null;
9288
+ try {
9289
+ const raw = JSON.parse(fs.readFileSync(side, "utf8"));
9290
+ if (Array.isArray(raw)) { pend = raw; out.sidecar_readable = true; }
9291
+ } catch (_) {}
9292
+
9293
+ const now = Date.now();
9294
+ if (pend) {
9295
+ out.entries = pend.map((r) => {
9296
+ const ts = typeof r.ts === "number" ? r.ts : null;
9297
+ const age = ts == null ? null : Math.round((now - ts) / 1000);
9298
+ return {
9299
+ agent: r.agent == null ? null : String(r.agent),
9300
+ desc: r.desc == null ? null : String(r.desc),
9301
+ started_ms: ts,
9302
+ age_s: age,
9303
+ stale: ts != null && now - ts > INFLIGHT_STALE_MS,
9304
+ };
9305
+ });
9306
+ out.count = out.entries.length;
9307
+ out.stale_entries = out.entries.filter((e) => e.stale).length;
9308
+ }
9309
+
9310
+ const balance = out.spawns != null && out.returns != null ? out.spawns - out.returns : null;
9311
+ out.balance_agrees = balance == null || pend == null ? null : balance === out.count;
9312
+
9313
+ // 4. Verdict. Conservative in every direction.
9314
+ if (!out.sidecar_readable) {
9315
+ if (balance != null && balance > 0)
9316
+ return finish("in-flight", `sidecar unreadable, but the trace shows ${balance} SPAWN(s) with no RETURN`, 1);
9317
+ return finish("unknown", "the pending sidecar is missing or unreadable — cannot prove a dispatch finished", 2);
9318
+ }
9319
+ if (out.count > 0) {
9320
+ if (out.stale_entries === out.count)
9321
+ return finish(
9322
+ "unknown",
9323
+ `${out.count} record(s), all older than 6h — the run probably died without closing them`,
9324
+ 2
9325
+ );
9326
+ return finish("in-flight", `${out.count} dispatch(es) have not returned`, 1);
9327
+ }
9328
+ // Sidecar empty. Only trust it when the trace agrees.
9329
+ if (balance != null && balance > 0)
9330
+ return finish("unknown", `sidecar is empty but the trace shows ${balance} unmatched SPAWN(s) — they disagree`, 2);
9331
+ return finish("clear", "no dispatch is in flight", 0);
9332
+ }
9333
+
9334
+ function renderInflight(o) {
9335
+ const head =
9336
+ o.state === "clear" ? "clear" : o.state === "in-flight" ? `IN FLIGHT (${o.count})` : "unknown";
9337
+ console.log(`${ui.color.bold("dispatch:")} ${head}`);
9338
+ console.log(` ${o.reason}`);
9339
+ if (o.trace) console.log(` trace ${o.trace}`);
9340
+ for (const e of o.entries) {
9341
+ const age = e.age_s == null ? "age unknown" : `${Math.floor(e.age_s / 60)}m${e.age_s % 60}s ago`;
9342
+ console.log(` - ${e.agent || "(unnamed)"} ${age}${e.stale ? " [stale]" : ""}`);
9343
+ if (e.desc) console.log(` ${e.desc}`);
9344
+ }
9345
+ if (o.state === "in-flight")
9346
+ console.log("\n Do NOT re-dispatch these tasks. A Task error does not kill the agent behind it.");
9347
+ }
9348
+
9009
9349
  function runCmd() {
9010
9350
  const claudeDir = resolveClaudeDir();
9011
9351
  const pos = positionals(); // ["run", <sub?>, <arg?>]
@@ -9016,6 +9356,9 @@ function runCmd() {
9016
9356
  // on disk. Neither deletes anything.
9017
9357
  if (sub === "close" || sub === "reopen") return runCloseCmd(claudeDir, runs, sub, pos[2]);
9018
9358
 
9359
+ // Read-only, and the ONE reader of the pending sidecar. 0 clear / 1 in-flight / 2 unknown.
9360
+ if (sub === "inflight") return runInflightCmd(claudeDir);
9361
+
9019
9362
  if (sub === "show") {
9020
9363
  const arg = pos[2];
9021
9364
  const pick = /^\d+$/.test(String(arg)) ? runs[Number(arg) - 1] : runs.find((r) => r.slug === arg);
@@ -33210,6 +33553,318 @@ function usageResetMs(v) {
33210
33553
  return Number.isFinite(p) ? p : NaN;
33211
33554
  }
33212
33555
 
33556
+ // ── `orc usage report` — where the window went (v1.2.0) ─────────────────────
33557
+ //
33558
+ // `orc usage check` answers "is there room". This answers the question a user
33559
+ // actually asks mid-run: "how much has THIS session eaten, and what ate it".
33560
+ //
33561
+ // THE HONEST PART, and it decides the whole shape of this command. Claude Code
33562
+ // records NO token usage for a dispatched subagent — `isSidechain` is never set
33563
+ // and no sidechain message carries a usage block, in any transcript on disk. So
33564
+ // a per-executor TOKEN figure cannot be measured, and inventing one would be
33565
+ // the same class of bug as a fake validator. What IS measured, exactly, is WALL
33566
+ // TIME, from the trace hook's own SPAWN/RETURN lines. Foreign workers are the
33567
+ // one exception: `orc extra` records real four-kind vectors, so those rows
33568
+ // carry tokens and say so. Every other row reports `tokens: null` plus the
33569
+ // reason — never 0. Unknown is not zero.
33570
+ const USAGE_TOP_N = 5;
33571
+
33572
+ // "12m43s" | "1m7s" | "45s" → seconds. Anything else → null, never 0.
33573
+ function usageDurSeconds(s) {
33574
+ if (!s) return null;
33575
+ const m = /^(?:(\d+)m)?(\d+)s$/.exec(String(s).trim());
33576
+ if (!m) return null;
33577
+ return (m[1] ? Number(m[1]) * 60 : 0) + Number(m[2]);
33578
+ }
33579
+
33580
+ // Per-agent wall time for the run the trace pointer names. Returns null when
33581
+ // there is no open run — an absent trace is an absent measurement, not zero.
33582
+ function usageRunConsumers(claudeDir) {
33583
+ const dir = resolveLogDir(claudeDir);
33584
+ let cur = null;
33585
+ try {
33586
+ cur = fs.readFileSync(path.join(dir, ".current"), "utf8").trim();
33587
+ } catch (_) {}
33588
+ if (!cur) return null;
33589
+ let text = "";
33590
+ try {
33591
+ text = fs.readFileSync(path.join(dir, cur), "utf8");
33592
+ } catch (_) {
33593
+ return null;
33594
+ }
33595
+ const nameMatch = TRACE_NAME.exec(cur);
33596
+ const by = new Map();
33597
+ const bump = (agent, secs, running) => {
33598
+ const k = agent || "(unnamed)";
33599
+ const r = by.get(k) || { agent: k, dispatches: 0, wall_seconds: 0, running: 0, unmeasured: 0 };
33600
+ r.dispatches += 1;
33601
+ if (running) r.running += 1;
33602
+ if (secs == null) r.unmeasured += 1;
33603
+ else r.wall_seconds += secs;
33604
+ by.set(k, r);
33605
+ };
33606
+ for (const line of text.split("\n")) {
33607
+ // Only the hook's own skeleton lines. `~agent :: unattributed` is a
33608
+ // bookkeeping artefact of >=2 in flight and is never a dispatch.
33609
+ const r = /\] hook\s+RETURN ~?([^\s:]+) :: /.exec(line);
33610
+ if (!r) continue;
33611
+ if (r[1] === "agent") continue;
33612
+ const d = /\bdur=(\S+)/.exec(line);
33613
+ bump(r[1], usageDurSeconds(d && d[1]), false);
33614
+ }
33615
+ // Anything still open is real spend happening RIGHT NOW — the exact case the
33616
+ // v1.2.0 in-flight guard exists for, and the one a user most wants to see.
33617
+ let pending = [];
33618
+ try {
33619
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, cur + ".pending.json"), "utf8"));
33620
+ if (Array.isArray(raw)) pending = raw;
33621
+ } catch (_) {}
33622
+ const now = Date.now();
33623
+ for (const p of pending) {
33624
+ const secs = typeof p.ts === "number" ? Math.round((now - p.ts) / 1000) : null;
33625
+ bump(p.agent, secs, true);
33626
+ }
33627
+ return {
33628
+ trace: cur,
33629
+ lane: nameMatch ? nameMatch[1] : null,
33630
+ slug: nameMatch ? nameMatch[2] : null,
33631
+ agents: [...by.values()],
33632
+ in_flight: pending.length,
33633
+ };
33634
+ }
33635
+
33636
+ // Foreign dispatches DO carry measured tokens. Same file the spend report reads.
33637
+ function usageForeignSpend(claudeDir) {
33638
+ const f = path.join(claudeDir, "orc", "extra-spend.jsonl");
33639
+ let lines = [];
33640
+ try {
33641
+ lines = fs.readFileSync(f, "utf8").split("\n").filter(Boolean);
33642
+ } catch (_) {
33643
+ return { rows: [], unreadable: 0 };
33644
+ }
33645
+ const rows = [];
33646
+ let unreadable = 0;
33647
+ for (const l of lines) {
33648
+ try {
33649
+ rows.push(JSON.parse(l));
33650
+ } catch (_) {
33651
+ unreadable += 1;
33652
+ }
33653
+ }
33654
+ return { rows, unreadable };
33655
+ }
33656
+
33657
+ function usageReportCmd(claudeDir) {
33658
+ const asJson = wantsJson();
33659
+ const now = Date.now();
33660
+ const cfg = resolvedConfig(claudeDir);
33661
+ const stopPct = Math.min(50, Math.max(1, Number(cfg.usage_stop_pct) || 10));
33662
+
33663
+ // Read the bridge RAW here rather than through readUsageBridge: a stale
33664
+ // reading is still worth SHOWING, with its age, where a gate would rightly
33665
+ // discard it. The state word still comes from freshness — age is displayed,
33666
+ // never ignored.
33667
+ let raw = null;
33668
+ try {
33669
+ raw = JSON.parse(fs.readFileSync(path.join(claudeDir, "orc", "usage.json"), "utf8"));
33670
+ } catch (_) {}
33671
+ const ageMin =
33672
+ raw && typeof raw.written_at === "number" ? Math.round((now - raw.written_at) / 60000) : null;
33673
+ const stale = ageMin == null ? true : now - raw.written_at > WAIT_BRIDGE_MAX_AGE_MS;
33674
+
33675
+ const view = (o, label) => {
33676
+ if (!o || typeof o.used_percentage !== "number") return null;
33677
+ const used = Math.round(o.used_percentage);
33678
+ const at = usageResetMs(o.resets_at);
33679
+ return {
33680
+ window: label,
33681
+ used_percentage: used,
33682
+ remaining_percentage: Math.max(0, 100 - used),
33683
+ resets_at: Number.isFinite(at) ? new Date(at).toISOString() : null,
33684
+ resets_in_minutes: Number.isFinite(at) && at > now ? Math.round((at - now) / 60000) : null,
33685
+ low: 100 - used <= stopPct,
33686
+ };
33687
+ };
33688
+ const fh = raw ? view(raw.five_hour, "5h") : null;
33689
+ const sd = raw ? view(raw.seven_day, "wk") : null;
33690
+
33691
+ // Session consumption — the ledger the statusline keeps.
33692
+ let led = null;
33693
+ try {
33694
+ led = JSON.parse(fs.readFileSync(path.join(claudeDir, "orc", "usage-session.json"), "utf8"));
33695
+ } catch (_) {}
33696
+ const consumed = (w) =>
33697
+ !w
33698
+ ? null
33699
+ : {
33700
+ baseline_percentage: w.baseline,
33701
+ now_percentage: w.last,
33702
+ consumed_percentage: Math.max(0, w.accumulated + Math.max(0, w.last - w.baseline)),
33703
+ window_resets: w.resets,
33704
+ };
33705
+ const session = !led
33706
+ ? null
33707
+ : {
33708
+ session_id: led.session_id || null,
33709
+ started_at: led.started_at ? new Date(led.started_at).toISOString() : null,
33710
+ running_minutes: led.started_at ? Math.round((now - led.started_at) / 60000) : null,
33711
+ five_hour: consumed(led.five_hour),
33712
+ seven_day: consumed(led.seven_day),
33713
+ still_counting: true,
33714
+ // Never overclaim. The window is per ACCOUNT: a second Claude Code
33715
+ // window, a cloud session, or anyone else on the same key moves it too.
33716
+ caveat:
33717
+ "this is how far the window moved while this session ran — other sessions on the same account share it",
33718
+ };
33719
+
33720
+ const run = usageRunConsumers(claudeDir);
33721
+ const foreign = usageForeignSpend(claudeDir);
33722
+
33723
+ // Rank by measured wall time. A row with nothing measured keeps its slot.
33724
+ const top = [];
33725
+ if (run) {
33726
+ const sorted = run.agents.slice().sort((a, b) => b.wall_seconds - a.wall_seconds);
33727
+ for (const a of sorted.slice(0, USAGE_TOP_N))
33728
+ top.push({
33729
+ agent: a.agent,
33730
+ dispatches: a.dispatches,
33731
+ running: a.running,
33732
+ wall_seconds: a.wall_seconds,
33733
+ unmeasured_dispatches: a.unmeasured,
33734
+ tokens: null,
33735
+ tokens_source: "unavailable",
33736
+ });
33737
+ }
33738
+ for (const r of foreign.rows.slice(-USAGE_TOP_N)) {
33739
+ top.push({
33740
+ agent: "extra:" + (r.profile || "?") + "/" + (r.model || "?"),
33741
+ dispatches: 1,
33742
+ running: 0,
33743
+ wall_seconds: usageDurSeconds(r.dur),
33744
+ unmeasured_dispatches: 0,
33745
+ tokens: r.usage || null,
33746
+ tokens_source: r.usage ? "measured" : "not reported by the worker",
33747
+ });
33748
+ }
33749
+
33750
+ const windows = [fh, sd].filter(Boolean);
33751
+ const state = !windows.length || stale ? "unknown" : windows.some((w) => w.low) ? "low" : "ok";
33752
+ const code = state === "low" ? 1 : state === "unknown" ? 2 : 0;
33753
+ const out = {
33754
+ ok: true,
33755
+ state,
33756
+ five_hour: fh,
33757
+ seven_day: sd,
33758
+ context_used_percentage:
33759
+ raw && typeof raw.context_used_percentage === "number" ? raw.context_used_percentage : null,
33760
+ reading_age_minutes: ageMin,
33761
+ reading_stale: stale,
33762
+ stop_pct: stopPct,
33763
+ gate: String(cfg.usage_gate || "off"),
33764
+ session,
33765
+ run: run
33766
+ ? {
33767
+ trace: run.trace,
33768
+ lane: run.lane,
33769
+ slug: run.slug,
33770
+ dispatches: run.agents.reduce((n, a) => n + a.dispatches, 0),
33771
+ in_flight: run.in_flight,
33772
+ wall_seconds: run.agents.reduce((n, a) => n + a.wall_seconds, 0),
33773
+ }
33774
+ : null,
33775
+ top,
33776
+ foreign_spend_rows: foreign.rows.length,
33777
+ unreadable_spend_lines: foreign.unreadable,
33778
+ // Say it on every emission. A reader who does not know this reads a
33779
+ // wall-time ranking as a token ranking.
33780
+ tokens_note:
33781
+ "Claude Code records no token usage for a dispatched subagent, so a Claude agent's tokens are null and never 0. Rows are ranked by MEASURED WALL TIME. Only `orc extra` foreign workers report real token vectors.",
33782
+ };
33783
+ if (asJson) emitJson(out, code);
33784
+
33785
+ console.log(ui.header("ORC · usage"));
33786
+ console.log("");
33787
+ const pctLine = (w) =>
33788
+ !w
33789
+ ? ui.color.gray(" — (no reading)")
33790
+ : " " +
33791
+ String(w.used_percentage).padStart(3) +
33792
+ "% used · " +
33793
+ w.remaining_percentage +
33794
+ "% left" +
33795
+ (w.resets_in_minutes != null ? " · resets in " + w.resets_in_minutes + "m" : "") +
33796
+ (w.low ? " " + ui.color.yellow("LOW") : "");
33797
+ console.log(" 5 hours");
33798
+ console.log(pctLine(fh));
33799
+ console.log(" 7 days");
33800
+ console.log(pctLine(sd));
33801
+ if (out.context_used_percentage != null)
33802
+ console.log(" context " + out.context_used_percentage + "% of the window");
33803
+ if (stale)
33804
+ console.log(
33805
+ ui.color.gray(
33806
+ " reading " +
33807
+ (ageMin == null ? "none" : ageMin + "m old") +
33808
+ " — treated as unknown, and a run is never stopped on it"
33809
+ )
33810
+ );
33811
+ console.log("");
33812
+ if (session && session.five_hour) {
33813
+ const c = session.five_hour;
33814
+ console.log(
33815
+ " " +
33816
+ ui.color.bold(
33817
+ "This session has consumed " +
33818
+ c.consumed_percentage +
33819
+ "% of the 5-hour window and is still counting"
33820
+ )
33821
+ );
33822
+ console.log(
33823
+ ui.color.gray(
33824
+ " " +
33825
+ c.baseline_percentage +
33826
+ "% → " +
33827
+ c.now_percentage +
33828
+ "%" +
33829
+ (c.window_resets ? " across " + c.window_resets + " window reset(s)" : "") +
33830
+ (session.running_minutes != null ? " · " + session.running_minutes + "m in session" : "")
33831
+ )
33832
+ );
33833
+ console.log(ui.color.gray(" " + session.caveat));
33834
+ } else {
33835
+ console.log(ui.color.gray(" session consumption: no reading yet (the statusline writes it)"));
33836
+ }
33837
+ console.log("");
33838
+ if (top.length) {
33839
+ console.log(" " + ui.color.bold("Top " + USAGE_TOP_N + " by measured wall time"));
33840
+ for (const t of top.slice(0, USAGE_TOP_N)) {
33841
+ const secs = t.wall_seconds;
33842
+ const w =
33843
+ secs == null
33844
+ ? "—"
33845
+ : Math.floor(secs / 60) + "m" + String(secs % 60).padStart(2, "0") + "s";
33846
+ const tok = t.tokens ? "tokens measured" : ui.color.gray("tokens —");
33847
+ console.log(
33848
+ " " +
33849
+ w.padStart(8) +
33850
+ " " +
33851
+ t.agent +
33852
+ " " +
33853
+ ui.color.gray("x" + t.dispatches) +
33854
+ (t.running ? " " + ui.color.yellow(t.running + " RUNNING") : "") +
33855
+ " " +
33856
+ tok
33857
+ );
33858
+ }
33859
+ console.log(
33860
+ ui.color.gray(" wall time, not tokens — Claude Code does not record a subagent's tokens")
33861
+ );
33862
+ } else {
33863
+ console.log(ui.color.gray(" no dispatches in the open run (or no run is open)"));
33864
+ }
33865
+ process.exit(code);
33866
+ }
33867
+
33213
33868
  function usageCheckCmd(claudeDir) {
33214
33869
  const asJson = wantsJson();
33215
33870
  const now = Date.now();
@@ -33641,8 +34296,9 @@ function jsonCrash(err) {
33641
34296
  // v1.1.0 W4 — the ONE reader of the statusline's usage bridge.
33642
34297
  case "usage":
33643
34298
  if (positionals()[1] === "check") usageCheckCmd(resolveClaudeDir());
34299
+ else if (positionals()[1] === "report") usageReportCmd(resolveClaudeDir());
33644
34300
  else {
33645
- console.error("usage: orc usage check [--json]");
34301
+ console.error("usage: orc usage check|report [--json]");
33646
34302
  process.exit(1);
33647
34303
  }
33648
34304
  break;