@azure-id/orc 1.2.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/CHANGELOG.md +145 -0
- package/README.md +50 -66
- package/bin/cli.js +186 -1
- package/bin/verify-contracts.js +25 -0
- package/bin/verify-package.js +573 -568
- package/package.json +1 -1
- package/templates/hooks/README.md +202 -0
- package/templates/hooks/orc-statusline.js +377 -21
|
@@ -20,10 +20,34 @@
|
|
|
20
20
|
* `rate_limits.{five_hour,seven_day}` (Anthropic API headers, not estimated).
|
|
21
21
|
* A window ≥90% folds into the DEGRADE verdict; fail-silent when absent.
|
|
22
22
|
*
|
|
23
|
-
* Three-tier verdict (the "ORC-ready" acceptance matrix)
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* Three-tier verdict (the "ORC-ready" acceptance matrix). Since v1.2.1 the
|
|
24
|
+
* ICON carries the verdict and the words carry the installed ORC version — but
|
|
25
|
+
* the degrade branch still names every reason, because a warning with no reason
|
|
26
|
+
* is an emoji:
|
|
27
|
+
* ✅ ORC v1.2.1 - Opus 5/high Opus 4.8 high (the baseline)
|
|
28
|
+
* 🚀 ORC v1.2.1 - Opus 5/high Opus 4.8 xhigh/max, or Opus 5 / Fable 5 medium…max
|
|
29
|
+
* ⛔ ORC v1.2.1 - Sonnet 5/high (why) everything below (wrong model, sub-baseline effort, quota)
|
|
30
|
+
*
|
|
31
|
+
* The two lines, in full (v1.2.1):
|
|
32
|
+
*
|
|
33
|
+
* {icon} ORC v{version} - {model}/{effort} · context (N%) · 5h N% (reset) ↔ wk N%
|
|
34
|
+
* · ucs N% · wiki: … · diy:… · orc N.N.N available
|
|
35
|
+
* {glyph} status: {lane} · {phase} · agents N (M running) · orc-extra: on|off
|
|
36
|
+
* · Dur Nm · MTok NNNK · {branch}
|
|
37
|
+
*
|
|
38
|
+
* Line 1 answers "what tier am I on, and how full is the window". Line 2
|
|
39
|
+
* answers "what is this session DOING". Every segment on both is read from
|
|
40
|
+
* disk or from the payload; none of it costs a model call.
|
|
41
|
+
*
|
|
42
|
+
* Three segments on line 2 carry a rule worth stating here, because each is a
|
|
43
|
+
* place where the easy version would lie:
|
|
44
|
+
* - `status:` is the ONLY segment allowed to vanish. A phase the disk cannot
|
|
45
|
+
* prove is HIDDEN, never guessed — see the phase-rail block below for what
|
|
46
|
+
* that costs and why it is still the right trade.
|
|
47
|
+
* - `MTok` is MAIN TOKEN: this session's own turns. Claude Code records no
|
|
48
|
+
* token usage for a dispatched subagent, so an hour of Opus executors adds
|
|
49
|
+
* almost nothing. An em dash means not measured; `0` would mean free.
|
|
50
|
+
* - `ucs` is a delta of an ACCOUNT-WIDE window, not a private meter.
|
|
27
51
|
*
|
|
28
52
|
* This is the ONLY place Claude Code exposes the live model id, so it also
|
|
29
53
|
* writes a fail-silent session-model bridge (.claude/orc/session-model.json)
|
|
@@ -34,6 +58,12 @@
|
|
|
34
58
|
* Also appends a "newer orc version available" hint from the 24h update cache
|
|
35
59
|
* (cache-only here — never a network call in the statusline hot path; the
|
|
36
60
|
* PreToolUse guard refreshes the cache when /orc is invoked).
|
|
61
|
+
*
|
|
62
|
+
* Everything on line 2 rides in ONE scan, throttled to 5s and cached in the
|
|
63
|
+
* per-session ledger, because a statusline re-renders on every keystroke and
|
|
64
|
+
* anything unthrottled here is a per-keystroke disk scan. `MTok` additionally
|
|
65
|
+
* reads only the bytes the transcript has GROWN by. There is exactly one seam
|
|
66
|
+
* over that budget — ORC_STATUSLINE_SCAN_MS — and nothing in ORC ever sets it.
|
|
37
67
|
*/
|
|
38
68
|
|
|
39
69
|
// Opus 4.8 / Opus 5 / Fable 5 are matched by tolerant regexes below (accept
|
|
@@ -49,6 +79,266 @@ try {
|
|
|
49
79
|
updater = null;
|
|
50
80
|
}
|
|
51
81
|
|
|
82
|
+
// ── The phase rail, the motifs, and the two seams (v1.2.1) ──────────────────
|
|
83
|
+
// `status:` says which phase an ORC run is in. Three rules hold it up.
|
|
84
|
+
//
|
|
85
|
+
// 1. THE CLI COMPUTES, THIS FILE RENDERS. The phase ids, their order, their
|
|
86
|
+
// labels and their motif kind all come from `orc-lane-rails.json`, which
|
|
87
|
+
// `orc init` / `orc update` generates from the CLI's own registries. This
|
|
88
|
+
// hook holds no idea of what ORC's phases are. A second phase table here
|
|
89
|
+
// would be the Flow-stepper failure on a second surface, and no lint could
|
|
90
|
+
// see it. Frames are the exception and belong here: they are presentation,
|
|
91
|
+
// and a motif change must not need a reinstall.
|
|
92
|
+
//
|
|
93
|
+
// 2. A PHASE THE DISK CANNOT PROVE IS HIDDEN. Never guessed, never carried
|
|
94
|
+
// over from a minute ago. A stale phase word gets believed — the same
|
|
95
|
+
// reasoning as `unknown is not low` and `unknown is not zero`.
|
|
96
|
+
//
|
|
97
|
+
// 3. THE FLOOR IS HOOK-WRITTEN. `PHASE-EDGE <family>` and `SPAWN <agent>` are
|
|
98
|
+
// written by orc-trace.js with zero model cooperation. A trace verb the
|
|
99
|
+
// orchestrator narrated is allowed to REFINE that (it is more specific:
|
|
100
|
+
// `Q3 DO` rather than `execution`) but only when it is later in the file,
|
|
101
|
+
// and only when that lane's own rail publishes the verb — so it can sharpen
|
|
102
|
+
// the answer and can never invent one.
|
|
103
|
+
//
|
|
104
|
+
// What this cannot see, stated so nobody reads a blank as a bug:
|
|
105
|
+
// - a phase that dispatches nothing AND narrates nothing is INVISIBLE
|
|
106
|
+
// (/orc-quick Q1 LOOK and Q2 ASK, and every ask-the-user gate);
|
|
107
|
+
// - a CONTINUED agent emits no PreToolUse/SubagentStop pair, so the skeleton
|
|
108
|
+
// is a floor, never a census (orc-trace.js documents this);
|
|
109
|
+
// - `orc extra` runs a worker through Bash, so a foreign wave writes no
|
|
110
|
+
// SPAWN and resolves only through its narrated `EXTRA` verb, or hides.
|
|
111
|
+
//
|
|
112
|
+
// The animation is a LIVENESS TELL, not a driven animation. A statusline is a
|
|
113
|
+
// pull surface — Claude Code re-renders it, ORC cannot — so the frame is picked
|
|
114
|
+
// off the wall clock. It advances while you type and while turns land, and it
|
|
115
|
+
// FREEZES when the session is idle, which is true and is the point.
|
|
116
|
+
//
|
|
117
|
+
// Two env seams, for tests and for terminals, and nothing in ORC ever sets
|
|
118
|
+
// either: ORC_STATUSLINE_ASCII=1 swaps the glyph set, ORC_STATUSLINE_MOTION=0
|
|
119
|
+
// REMOVES motion rather than slowing it (a frozen frame of a cycling animation
|
|
120
|
+
// is a bug that looks like a hang — the web panel learned this at v0.44.0, and
|
|
121
|
+
// so the still frame is designed as a still frame: frame 0 of each set).
|
|
122
|
+
|
|
123
|
+
const PHASE_STALE_MS = 10 * 60 * 1000;
|
|
124
|
+
|
|
125
|
+
const MOTIFS = {
|
|
126
|
+
look: { u: ["◔", "◑", "◕", "●"], a: [".", "o", "O", "0"], ms: 260 },
|
|
127
|
+
ask: { u: ["?", "¿", "?", "·"], a: ["?", "?", "?", "."], ms: 500 },
|
|
128
|
+
plan: { u: ["▁", "▃", "▅", "▇"], a: ["_", "-", "=", "#"], ms: 220 },
|
|
129
|
+
do: { u: ["▰", "▱", "▰", "▱"], a: ["=", "-", "=", "-"], ms: 180 },
|
|
130
|
+
check: { u: ["◇", "◈", "◆", "◈"], a: ["<", "=", ">", "="], ms: 240 },
|
|
131
|
+
ship: { u: ["›", "»", "≫", "»"], a: [">", ">", "=", ">"], ms: 200 },
|
|
132
|
+
wait: { u: ["·", "˙", "·", "˙"], a: [".", "'", ".", "'"], ms: 700 },
|
|
133
|
+
generic: {
|
|
134
|
+
u: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
|
|
135
|
+
a: ["-", "\\", "|", "/"],
|
|
136
|
+
ms: 90,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function motifFrame(kind) {
|
|
141
|
+
const m = MOTIFS[kind] || MOTIFS.generic;
|
|
142
|
+
const frames = process.env.ORC_STATUSLINE_ASCII === "1" ? m.a : m.u;
|
|
143
|
+
if (process.env.ORC_STATUSLINE_MOTION === "0") return frames[0];
|
|
144
|
+
return frames[Math.floor(Date.now() / m.ms) % frames.length];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// The rail manifest, read once per process. Absent (a pre-1.2.1 install, or a
|
|
148
|
+
// write that failed) → null → `status:` hides. Never throws.
|
|
149
|
+
let RAILS = undefined;
|
|
150
|
+
function rails() {
|
|
151
|
+
if (RAILS !== undefined) return RAILS;
|
|
152
|
+
RAILS = null;
|
|
153
|
+
try {
|
|
154
|
+
const j = JSON.parse(
|
|
155
|
+
require("fs").readFileSync(
|
|
156
|
+
require("path").join(__dirname, "orc-lane-rails.json"),
|
|
157
|
+
"utf8"
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
if (j && j.lanes) RAILS = j;
|
|
161
|
+
} catch (_) {}
|
|
162
|
+
return RAILS;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Which phase is the run in? Called once per throttled scan, over text the scan
|
|
166
|
+
// has already read, so it costs one regex pass and no extra I/O.
|
|
167
|
+
//
|
|
168
|
+
// `text` is the active trace, `laneToken` its filename's lane. Returns
|
|
169
|
+
// {lane, label, kind} or null — and null is a real answer.
|
|
170
|
+
function resolvePhase(laneToken, text) {
|
|
171
|
+
const r = rails();
|
|
172
|
+
if (!r || !laneToken) return null;
|
|
173
|
+
const row = r.lanes[laneToken];
|
|
174
|
+
if (!row) return null;
|
|
175
|
+
|
|
176
|
+
// Every line carries its own timestamp; the newest one dates the run. A trace
|
|
177
|
+
// whose last line is old is not a run in progress, whatever it says.
|
|
178
|
+
const at = (l) => {
|
|
179
|
+
const t = /^\[(\d{2})(\d{2})(\d{2}) (\d{2}):(\d{2}):(\d{2})/.exec(l);
|
|
180
|
+
if (!t) return 0;
|
|
181
|
+
return new Date(
|
|
182
|
+
2000 + Number(t[3]), Number(t[2]) - 1, Number(t[1]),
|
|
183
|
+
Number(t[4]), Number(t[5]), Number(t[6])
|
|
184
|
+
).getTime();
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const lines = text.split("\n");
|
|
188
|
+
let newest = 0;
|
|
189
|
+
let edgeIdx = -1;
|
|
190
|
+
let edgeFam = null;
|
|
191
|
+
let verbIdx = -1;
|
|
192
|
+
let verbPhase = null;
|
|
193
|
+
for (let i = 0; i < lines.length; i++) {
|
|
194
|
+
const l = lines[i];
|
|
195
|
+
if (!l) continue;
|
|
196
|
+
const ts = at(l);
|
|
197
|
+
if (ts > newest) newest = ts;
|
|
198
|
+
const e = /PHASE-EDGE ([a-z-]+)/.exec(l);
|
|
199
|
+
if (e && r.families[e[1]]) {
|
|
200
|
+
edgeIdx = i;
|
|
201
|
+
edgeFam = e[1];
|
|
202
|
+
}
|
|
203
|
+
// A verb line is the orchestrator's. It counts only when this lane's own
|
|
204
|
+
// rail published that verb — the rail is what stops a narrated word from
|
|
205
|
+
// inventing a phase the lane does not have.
|
|
206
|
+
const body = l.replace(/^\[[^\]]*\]\s*\S+\s*/, "");
|
|
207
|
+
for (const p of row.phases) {
|
|
208
|
+
for (const v of p.verbs) {
|
|
209
|
+
if (body.indexOf(v) === 0) {
|
|
210
|
+
verbIdx = i;
|
|
211
|
+
verbPhase = p;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!newest || Date.now() - newest > PHASE_STALE_MS) return null;
|
|
218
|
+
|
|
219
|
+
if (verbPhase && verbIdx > edgeIdx)
|
|
220
|
+
return { lane: laneToken, label: verbPhase.label, kind: verbPhase.kind };
|
|
221
|
+
if (edgeFam) {
|
|
222
|
+
const fam = r.families[edgeFam];
|
|
223
|
+
return { lane: laneToken, label: fam.label, kind: fam.kind };
|
|
224
|
+
}
|
|
225
|
+
if (verbPhase) return { lane: laneToken, label: verbPhase.label, kind: verbPhase.kind };
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// The current branch, without a subprocess. A statusline re-renders on every
|
|
230
|
+
// keystroke, so `git rev-parse` here would be one process per keystroke.
|
|
231
|
+
// Anything unrecognised returns null and the segment is simply absent.
|
|
232
|
+
function gitBranch(projectDir) {
|
|
233
|
+
const fs = require("fs");
|
|
234
|
+
const path = require("path");
|
|
235
|
+
try {
|
|
236
|
+
const dot = path.join(projectDir, ".git");
|
|
237
|
+
let gitDir = dot;
|
|
238
|
+
if (fs.statSync(dot).isFile()) {
|
|
239
|
+
// A worktree or a submodule: `.git` is a pointer file.
|
|
240
|
+
const m = /gitdir:\s*(.+)/.exec(fs.readFileSync(dot, "utf8"));
|
|
241
|
+
if (!m) return null;
|
|
242
|
+
const g = m[1].trim();
|
|
243
|
+
gitDir = path.isAbsolute(g) ? g : path.join(projectDir, g);
|
|
244
|
+
}
|
|
245
|
+
const head = fs.readFileSync(path.join(gitDir, "HEAD"), "utf8").trim();
|
|
246
|
+
const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
|
|
247
|
+
const name = ref
|
|
248
|
+
? ref[1]
|
|
249
|
+
: /^[0-9a-f]{40}$/.test(head)
|
|
250
|
+
? "@" + head.slice(0, 7)
|
|
251
|
+
: null;
|
|
252
|
+
if (!name) return null;
|
|
253
|
+
return name.length > 24 ? name.slice(0, 23) + "…" : name;
|
|
254
|
+
} catch (_) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// MTok — MAIN TOKEN. The tokens THIS session's own turns consumed, summed from
|
|
260
|
+
// the session transcript's `usage` blocks.
|
|
261
|
+
//
|
|
262
|
+
// Two honesty rules ship with it.
|
|
263
|
+
//
|
|
264
|
+
// It is the MAIN session only. Claude Code records NO token usage for a
|
|
265
|
+
// dispatched subagent (v1.2.0 verified that across every transcript on two
|
|
266
|
+
// machines), so a wave of three Opus executors adds almost nothing here. That
|
|
267
|
+
// is why an unreadable transcript renders an em dash and NEVER `0` — a zero
|
|
268
|
+
// would say the session was free.
|
|
269
|
+
//
|
|
270
|
+
// It is all four kinds summed. `/orc-budget`'s rule is four kinds never
|
|
271
|
+
// blended, and that rule is about REPORTS; this is one cell on a status bar.
|
|
272
|
+
// Any subset ORC picked would be a weighting ORC invented, which is worse. The
|
|
273
|
+
// vector stays authoritative in `orc usage report`, and the ledger below keeps
|
|
274
|
+
// all four kinds so it always can be.
|
|
275
|
+
//
|
|
276
|
+
// The read is INCREMENTAL. A transcript is append-only and reaches tens of
|
|
277
|
+
// megabytes; re-reading it inside a 5-second loop is the per-keystroke disk
|
|
278
|
+
// hazard the throttle exists to prevent. Only bytes past the stored offset are
|
|
279
|
+
// read, and the offset resets when the file shrinks or the path changes.
|
|
280
|
+
function scanTokens(led, transcriptPath) {
|
|
281
|
+
const fs = require("fs");
|
|
282
|
+
const fresh = (p) => ({
|
|
283
|
+
path: p, offset: 0, size: 0, input: 0, cache_write: 0, cache_read: 0, output: 0,
|
|
284
|
+
});
|
|
285
|
+
if (!transcriptPath) return led.tok || null;
|
|
286
|
+
let prev = led.tok && led.tok.path === transcriptPath ? led.tok : fresh(transcriptPath);
|
|
287
|
+
let st;
|
|
288
|
+
try {
|
|
289
|
+
st = fs.statSync(transcriptPath);
|
|
290
|
+
} catch (_) {
|
|
291
|
+
return prev.offset ? prev : null; // never read it → em dash, not 0
|
|
292
|
+
}
|
|
293
|
+
// Truncated or rotated under us: everything counted so far is unprovable.
|
|
294
|
+
if (st.size < prev.size) prev = fresh(transcriptPath);
|
|
295
|
+
if (st.size > prev.offset) {
|
|
296
|
+
let chunk = "";
|
|
297
|
+
try {
|
|
298
|
+
const fd = fs.openSync(transcriptPath, "r");
|
|
299
|
+
const len = st.size - prev.offset;
|
|
300
|
+
const buf = Buffer.alloc(len);
|
|
301
|
+
fs.readSync(fd, buf, 0, len, prev.offset);
|
|
302
|
+
fs.closeSync(fd);
|
|
303
|
+
chunk = buf.toString("utf8");
|
|
304
|
+
} catch (_) {
|
|
305
|
+
return prev.offset ? prev : null;
|
|
306
|
+
}
|
|
307
|
+
// The final line may be half-written. Stop at the last newline and leave
|
|
308
|
+
// the remainder for the next scan.
|
|
309
|
+
const cut = chunk.lastIndexOf("\n");
|
|
310
|
+
if (cut >= 0) {
|
|
311
|
+
const whole = chunk.slice(0, cut);
|
|
312
|
+
prev.offset += Buffer.byteLength(whole, "utf8") + 1;
|
|
313
|
+
for (const l of whole.split("\n")) {
|
|
314
|
+
if (!l || l.indexOf('"usage"') === -1) continue;
|
|
315
|
+
let u = null;
|
|
316
|
+
try {
|
|
317
|
+
const j = JSON.parse(l);
|
|
318
|
+
u = (j && j.message && j.message.usage) || (j && j.usage) || null;
|
|
319
|
+
} catch (_) {}
|
|
320
|
+
if (!u) continue;
|
|
321
|
+
prev.input += Number(u.input_tokens) || 0;
|
|
322
|
+
prev.cache_write += Number(u.cache_creation_input_tokens) || 0;
|
|
323
|
+
prev.cache_read += Number(u.cache_read_input_tokens) || 0;
|
|
324
|
+
prev.output += Number(u.output_tokens) || 0;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
prev.size = st.size;
|
|
329
|
+
return prev;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function fmtTokens(tok) {
|
|
333
|
+
if (!tok) return null;
|
|
334
|
+
const n =
|
|
335
|
+
(tok.input || 0) + (tok.cache_write || 0) + (tok.cache_read || 0) + (tok.output || 0);
|
|
336
|
+
if (n <= 0) return null;
|
|
337
|
+
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, "") + "M";
|
|
338
|
+
if (n >= 1000) return Math.round(n / 1000) + "K";
|
|
339
|
+
return String(n);
|
|
340
|
+
}
|
|
341
|
+
|
|
52
342
|
let raw = "";
|
|
53
343
|
process.stdin.on("data", (c) => (raw += c));
|
|
54
344
|
process.stdin.on("end", () => {
|
|
@@ -64,7 +354,7 @@ process.stdin.on("end", () => {
|
|
|
64
354
|
const effort = String((d.effort && d.effort.level) || "").toLowerCase();
|
|
65
355
|
const pct =
|
|
66
356
|
d.context_window && typeof d.context_window.used_percentage === "number"
|
|
67
|
-
?
|
|
357
|
+
? `context (${d.context_window.used_percentage}%)`
|
|
68
358
|
: "";
|
|
69
359
|
|
|
70
360
|
// ── Session-model bridge (fail-silent) ─────────────────────────────────────
|
|
@@ -262,22 +552,50 @@ process.stdin.on("end", () => {
|
|
|
262
552
|
for (const u of usageBad) reasons.push(u);
|
|
263
553
|
}
|
|
264
554
|
|
|
555
|
+
// The installed ORC version (v1.2.1). It replaces the verdict WORD, not the
|
|
556
|
+
// verdict: the icon still carries that, and the degrade branch still names
|
|
557
|
+
// every reason — dropping those would turn the loudest safety segment in ORC
|
|
558
|
+
// into an emoji. A version we cannot read renders as plain `ORC`, never
|
|
559
|
+
// `ORC vnull`: the statusline never prints a word for a thing it does not know.
|
|
560
|
+
let ver = null;
|
|
561
|
+
try {
|
|
562
|
+
ver = updater ? updater.installedVersion(__dirname) : null;
|
|
563
|
+
} catch (_) {}
|
|
564
|
+
if (!ver) {
|
|
565
|
+
try {
|
|
566
|
+
ver = JSON.parse(
|
|
567
|
+
require("fs").readFileSync(
|
|
568
|
+
require("path").join(__dirname, "orc-version.json"),
|
|
569
|
+
"utf8"
|
|
570
|
+
)
|
|
571
|
+
).version || null;
|
|
572
|
+
} catch (_) {}
|
|
573
|
+
}
|
|
574
|
+
const brand = "ORC" + (ver ? " v" + ver : "");
|
|
575
|
+
|
|
265
576
|
let line;
|
|
266
577
|
if (verdict === "ready") {
|
|
267
|
-
line = `✅
|
|
578
|
+
line = `✅ ${brand} - ${tier}${pct ? " · " + pct : ""}`;
|
|
268
579
|
} else if (verdict === "boosted") {
|
|
269
|
-
line = `🚀
|
|
580
|
+
line = `🚀 ${brand} - ${tier}${pct ? " · " + pct : ""}`;
|
|
270
581
|
} else {
|
|
271
|
-
line = `⛔
|
|
582
|
+
line = `⛔ ${brand} - ${tier} (${reasons.join(", ")})${pct ? " · " + pct : ""}`;
|
|
272
583
|
}
|
|
273
584
|
|
|
274
585
|
// Subscription-usage segment (rendered after ctx, before wiki). Empty on
|
|
275
586
|
// older Claude Code that doesn't surface `rate_limits`.
|
|
276
587
|
if (rlSeg) line += " · " + rlSeg;
|
|
277
588
|
|
|
278
|
-
// How far the window moved while THIS
|
|
279
|
-
//
|
|
280
|
-
//
|
|
589
|
+
// ucs — USAGE, CURRENT SESSION. How far the 5-hour window moved while THIS
|
|
590
|
+
// session ran (v1.2.0; renamed v1.2.1). The ledger below keeps the raw
|
|
591
|
+
// numbers; this renders the delta.
|
|
592
|
+
//
|
|
593
|
+
// It KEEPS ITS SLOT at zero. "This session has consumed nothing measurable
|
|
594
|
+
// yet" and "this build has no ucs segment" are different facts and must not
|
|
595
|
+
// look the same — the same rule as `lanes: none yet` and `used 0/20`.
|
|
596
|
+
//
|
|
597
|
+
// And it is still a delta of an ACCOUNT-WIDE window, not a private meter: a
|
|
598
|
+
// second terminal moves it too.
|
|
281
599
|
try {
|
|
282
600
|
const fs = require("fs");
|
|
283
601
|
const path = require("path");
|
|
@@ -289,7 +607,7 @@ process.stdin.on("end", () => {
|
|
|
289
607
|
const w = led && led.five_hour;
|
|
290
608
|
if (w && typeof w.last === "number" && typeof w.baseline === "number") {
|
|
291
609
|
const used = Math.max(0, (w.accumulated || 0) + Math.max(0, w.last - w.baseline));
|
|
292
|
-
|
|
610
|
+
line += " · ucs " + used + "%";
|
|
293
611
|
}
|
|
294
612
|
} catch (_) {}
|
|
295
613
|
|
|
@@ -456,15 +774,32 @@ process.stdin.on("end", () => {
|
|
|
456
774
|
let spawns = 0;
|
|
457
775
|
let running = 0;
|
|
458
776
|
const lanes = [];
|
|
777
|
+
// The ACTIVE run, for `status:` (v1.2.1). `.current` is the pointer the
|
|
778
|
+
// lanes and the trace hook both write, so it — not "the newest file" — is
|
|
779
|
+
// what names the run in progress. A pointer naming a file that is not
|
|
780
|
+
// there answers nothing, and a pointer nobody deleted is not a run: the
|
|
781
|
+
// resolver's own staleness gate settles that from the trace's last line.
|
|
782
|
+
let activeFile = null;
|
|
783
|
+
try {
|
|
784
|
+
const cur = fs.readFileSync(path.join(logDir, ".current"), "utf8").trim();
|
|
785
|
+
if (cur) activeFile = cur;
|
|
786
|
+
} catch (_) {}
|
|
787
|
+
let phase = null;
|
|
459
788
|
try {
|
|
460
789
|
for (const f of fs.readdirSync(logDir)) {
|
|
461
790
|
if (!f.startsWith("run-") || !f.endsWith(".txt")) continue;
|
|
462
791
|
const full = path.join(logDir, f);
|
|
463
|
-
// Only traces touched since this session began. A trace from last
|
|
464
|
-
// week is not this session's spend.
|
|
465
792
|
let st;
|
|
466
793
|
try { st = fs.statSync(full); } catch (_) { continue; }
|
|
467
|
-
|
|
794
|
+
const active = f === activeFile;
|
|
795
|
+
// Only traces touched since this session began count towards the
|
|
796
|
+
// session's SPEND — a trace from last week is not this session's.
|
|
797
|
+
// The ACTIVE run is exempt from that filter: `status:` answers "what
|
|
798
|
+
// is ORC doing", which has nothing to do with who paid for it, and a
|
|
799
|
+
// run already going when this session opened would otherwise be
|
|
800
|
+
// invisible until it happened to write its next line. Its own
|
|
801
|
+
// staleness gate decides whether it is still running.
|
|
802
|
+
if (!active && st.mtimeMs < (led.started_at || 0)) continue;
|
|
468
803
|
const text = fs.readFileSync(full, "utf8");
|
|
469
804
|
// Count by the trace's OWN line timestamps, not the file's mtime. A
|
|
470
805
|
// run that was already going when this session started shares its
|
|
@@ -486,6 +821,12 @@ process.stdin.on("end", () => {
|
|
|
486
821
|
if (at >= sessionFloor) mine += 1;
|
|
487
822
|
}
|
|
488
823
|
spawns += mine;
|
|
824
|
+
// The phase comes off the text this loop already read — one more
|
|
825
|
+
// regex pass, no second file read.
|
|
826
|
+
if (active) {
|
|
827
|
+
const lm = /^run-([a-z0-9-]+?)-.+-\d{6}-\d{6}\.txt$/.exec(f);
|
|
828
|
+
if (lm) phase = resolvePhase(lm[1], text);
|
|
829
|
+
}
|
|
489
830
|
let openHere = 0;
|
|
490
831
|
try {
|
|
491
832
|
const pend = JSON.parse(fs.readFileSync(full + ".pending.json", "utf8"));
|
|
@@ -500,7 +841,10 @@ process.stdin.on("end", () => {
|
|
|
500
841
|
}
|
|
501
842
|
}
|
|
502
843
|
} catch (_) {}
|
|
503
|
-
led.dispatch = { spawns, running, lanes, scanned_at: now };
|
|
844
|
+
led.dispatch = { spawns, running, lanes, phase, scanned_at: now };
|
|
845
|
+
// MTok rides in the same throttled pass, and reads only the bytes the
|
|
846
|
+
// transcript has grown by since the last one.
|
|
847
|
+
led.tok = scanTokens(led, d.transcript_path || null);
|
|
504
848
|
}
|
|
505
849
|
|
|
506
850
|
led.updated_at = now;
|
|
@@ -509,19 +853,31 @@ process.stdin.on("end", () => {
|
|
|
509
853
|
fs.writeFileSync(sfile, JSON.stringify(led) + "\n");
|
|
510
854
|
} catch (_) {}
|
|
511
855
|
|
|
512
|
-
const dsp = led.dispatch || { spawns: 0, running: 0, lanes: [] };
|
|
856
|
+
const dsp = led.dispatch || { spawns: 0, running: 0, lanes: [], phase: null };
|
|
513
857
|
const parts = [];
|
|
858
|
+
// `status:` leads, because what ORC is doing right now is the one thing on
|
|
859
|
+
// this line that changes minute to minute. It is also the ONE segment
|
|
860
|
+
// allowed to vanish: a phase the disk cannot prove is hidden rather than
|
|
861
|
+
// guessed, and the glyph goes with it. It replaces v1.2.0's `lanes:` list —
|
|
862
|
+
// the running lane is its first word, and `orc stats` / `orc run list`
|
|
863
|
+
// still hold the whole session's history.
|
|
864
|
+
if (dsp.phase && dsp.phase.label)
|
|
865
|
+
parts.push(
|
|
866
|
+
motifFrame(dsp.phase.kind) + " status: " + dsp.phase.lane + " · " + dsp.phase.label
|
|
867
|
+
);
|
|
514
868
|
// `running` is never hidden, because an agent still in flight is the thing
|
|
515
869
|
// a user most needs to see (v1.2.0). Zero is simply not printed.
|
|
516
870
|
parts.push(
|
|
517
871
|
"agents " + dsp.spawns + (dsp.running ? " (" + dsp.running + " running)" : "")
|
|
518
872
|
);
|
|
519
873
|
parts.push("orc-extra: " + (extraOn ? "on" : "off"));
|
|
520
|
-
// An empty lane list means no ORC lane has dispatched yet this session —
|
|
521
|
-
// an ANSWER, not a gap, so it keeps its slot and says so.
|
|
522
|
-
parts.push("lanes: " + (dsp.lanes.length ? dsp.lanes.join(", ") : "none yet"));
|
|
523
874
|
if (led.started_at)
|
|
524
|
-
parts.push(Math.max(0, Math.round((now - led.started_at) / 60000)) + "m");
|
|
875
|
+
parts.push("Dur " + Math.max(0, Math.round((now - led.started_at) / 60000)) + "m");
|
|
876
|
+
// MTok keeps its slot in every state. An em dash says "not measured"; a `0`
|
|
877
|
+
// would say the session was free, and that is a different claim.
|
|
878
|
+
parts.push("MTok " + (fmtTokens(led.tok) || "—"));
|
|
879
|
+
const branch = gitBranch(projectDir);
|
|
880
|
+
if (branch) parts.push(branch);
|
|
525
881
|
line2 = " " + parts.join(" · ");
|
|
526
882
|
} catch (_) {
|
|
527
883
|
line2 = "";
|