@khalilgharbaoui/opencode-claude-code-plugin 0.57.1 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +740 -183
- package/dist/index.js.map +1 -1
- package/dist/tui.js +543 -71
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +16 -8
package/dist/tui.js
CHANGED
|
@@ -347,19 +347,39 @@ function createStatusWatch() {
|
|
|
347
347
|
}
|
|
348
348
|
function createFillMemory() {
|
|
349
349
|
const held = /* @__PURE__ */ new Map();
|
|
350
|
+
const hold = (key, turn, fill) => {
|
|
351
|
+
const entry = held.get(key);
|
|
352
|
+
if (entry && entry.turn === turn) {
|
|
353
|
+
entry.fill = Math.max(entry.fill, fill);
|
|
354
|
+
return entry.fill;
|
|
355
|
+
}
|
|
356
|
+
if (!entry && held.size >= STATUS_WATCH_CAP) {
|
|
357
|
+
const oldest = held.keys().next().value;
|
|
358
|
+
if (oldest !== void 0) held.delete(oldest);
|
|
359
|
+
}
|
|
360
|
+
held.set(key, { turn, fill });
|
|
361
|
+
return fill;
|
|
362
|
+
};
|
|
350
363
|
return {
|
|
351
|
-
hold
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return fill;
|
|
364
|
+
hold,
|
|
365
|
+
/**
|
|
366
|
+
* The same hold for a whole estimate: its fill and both edges of its band,
|
|
367
|
+
* each at its highest this turn, so neither the lit cells nor the band's
|
|
368
|
+
* reach ever step back while the total rises. The total is not held: it
|
|
369
|
+
* is the text, and the text says what the model says now. Each edge at
|
|
370
|
+
* its highest stays on its side of the fill, because it is on that side
|
|
371
|
+
* at every moment (elapsed over a longer total, and over a shorter one).
|
|
372
|
+
*/
|
|
373
|
+
holdEstimate(sessionID, turn, estimate) {
|
|
374
|
+
const fill = hold(sessionID, turn, estimate.fill);
|
|
375
|
+
if (!estimate.band) return { ...estimate, fill };
|
|
376
|
+
const sure = hold(`${sessionID}\0sure`, turn, estimate.band.sure);
|
|
377
|
+
const maybe = hold(`${sessionID}\0maybe`, turn, estimate.band.maybe);
|
|
378
|
+
return {
|
|
379
|
+
...estimate,
|
|
380
|
+
fill,
|
|
381
|
+
band: { ...estimate.band, sure: Math.min(sure, fill), maybe: Math.max(maybe, fill) }
|
|
382
|
+
};
|
|
363
383
|
}
|
|
364
384
|
};
|
|
365
385
|
}
|
|
@@ -372,6 +392,53 @@ function estimateCells(estimate, width = BAR_WIDTH) {
|
|
|
372
392
|
if (estimate.over) return width;
|
|
373
393
|
return Math.min(width - 1, Math.max(0, Math.round(estimate.fill * width)));
|
|
374
394
|
}
|
|
395
|
+
function estimateBandCells(estimate, width = BAR_WIDTH) {
|
|
396
|
+
const likely = estimateCells(estimate, width);
|
|
397
|
+
if (estimate.over || !estimate.band) return { sure: likely, likely, band: likely };
|
|
398
|
+
const sure = Math.min(likely, Math.max(0, Math.round(estimate.band.sure * width)));
|
|
399
|
+
const band = Math.max(likely, Math.min(width, Math.round(estimate.band.maybe * width)));
|
|
400
|
+
return { sure, likely, band };
|
|
401
|
+
}
|
|
402
|
+
var TURN_PROGRESS_SLACK_MS = 1e3;
|
|
403
|
+
function liveOutput(record, startedAt) {
|
|
404
|
+
if (!record || startedAt === void 0) return void 0;
|
|
405
|
+
return record.turn >= startedAt - TURN_PROGRESS_SLACK_MS ? record.out : void 0;
|
|
406
|
+
}
|
|
407
|
+
function floorByChildren(estimate, elapsedMs, children) {
|
|
408
|
+
if (estimate.over) return estimate;
|
|
409
|
+
let median = 0;
|
|
410
|
+
let low = 0;
|
|
411
|
+
let high = 0;
|
|
412
|
+
for (const child of children) {
|
|
413
|
+
const own = child.estimate;
|
|
414
|
+
if (child.state !== "running" || !own || own.over) continue;
|
|
415
|
+
const left = (ms) => Math.max(0, ms - child.elapsedMs);
|
|
416
|
+
median = Math.max(median, left(own.totalMs));
|
|
417
|
+
low = Math.max(low, left(own.band?.lowMs ?? own.totalMs));
|
|
418
|
+
high = Math.max(high, left(own.band?.highMs ?? own.totalMs));
|
|
419
|
+
}
|
|
420
|
+
const elapsed = Math.max(0, elapsedMs);
|
|
421
|
+
const totalMs = Math.max(estimate.totalMs, elapsed + median);
|
|
422
|
+
const band = estimate.band;
|
|
423
|
+
if (totalMs === estimate.totalMs && (!band || band.lowMs >= elapsed + low && band.highMs >= elapsed + high)) {
|
|
424
|
+
return estimate;
|
|
425
|
+
}
|
|
426
|
+
const fill = totalMs > 0 ? elapsed / totalMs : 0;
|
|
427
|
+
if (!band) return { ...estimate, totalMs, fill };
|
|
428
|
+
const lowMs = Math.min(totalMs, Math.max(band.lowMs, elapsed + low));
|
|
429
|
+
const highMs = Math.max(totalMs, band.highMs, elapsed + high);
|
|
430
|
+
return {
|
|
431
|
+
...estimate,
|
|
432
|
+
totalMs,
|
|
433
|
+
fill,
|
|
434
|
+
band: {
|
|
435
|
+
lowMs,
|
|
436
|
+
highMs,
|
|
437
|
+
sure: highMs > 0 ? elapsed / highMs : 0,
|
|
438
|
+
maybe: lowMs > 0 ? Math.min(1, elapsed / lowMs) : 0
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
375
442
|
function formatElapsed(ms) {
|
|
376
443
|
const seconds = Math.max(0, Math.floor(ms / 1e3));
|
|
377
444
|
if (seconds < 60) return `${seconds}s`;
|
|
@@ -388,13 +455,20 @@ function layoutProgressLine(view, lead, width = SIDEBAR_ROW_WIDTH, frame = 0) {
|
|
|
388
455
|
{ text: " ", role: "muted" }
|
|
389
456
|
];
|
|
390
457
|
const estimate = !ended && !view.plan ? view.estimate : void 0;
|
|
391
|
-
const filled = ended ? BAR_WIDTH : view.plan ? filledCells(view.plan) : estimate ? estimateCells(estimate) : void 0;
|
|
392
|
-
const litRole = ended ? endedRole : estimate?.over ? "bar-over" : "bar-done";
|
|
393
458
|
const bar = [];
|
|
394
|
-
|
|
395
|
-
for (let cell = 0; cell < BAR_WIDTH; cell += 1)
|
|
396
|
-
|
|
397
|
-
|
|
459
|
+
const cellsOf = (segment) => {
|
|
460
|
+
for (let cell = 0; cell < BAR_WIDTH; cell += 1) bar.push(segment(cell));
|
|
461
|
+
};
|
|
462
|
+
if (ended) {
|
|
463
|
+
cellsOf(() => ({ text: BAR_DONE, role: endedRole }));
|
|
464
|
+
} else if (view.plan) {
|
|
465
|
+
const filled = filledCells(view.plan);
|
|
466
|
+
cellsOf((cell) => cell < filled ? { text: BAR_DONE, role: "bar-done" } : { text: BAR_TODO, role: "bar-track" });
|
|
467
|
+
} else if (estimate) {
|
|
468
|
+
const cells3 = estimateBandCells(estimate);
|
|
469
|
+
cellsOf(
|
|
470
|
+
(cell) => estimate.over ? { text: BAR_DONE, role: "bar-over" } : cell < cells3.sure ? { text: BAR_DONE, role: "bar-done" } : cell < cells3.likely ? { text: BAR_DONE, role: "bar-likely" } : cell < cells3.band ? { text: BAR_TODO, role: "bar-band" } : { text: BAR_TODO, role: "bar-track" }
|
|
471
|
+
);
|
|
398
472
|
}
|
|
399
473
|
const required = [];
|
|
400
474
|
if (view.plan) {
|
|
@@ -489,24 +563,86 @@ function createSpawnRecordReader(file = spawnRecordStorePath) {
|
|
|
489
563
|
}
|
|
490
564
|
|
|
491
565
|
// src/turn-history.ts
|
|
566
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
567
|
+
import { homedir as homedir3 } from "os";
|
|
568
|
+
import { join as join3 } from "path";
|
|
569
|
+
|
|
570
|
+
// src/turn-progress-store.ts
|
|
492
571
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
493
572
|
import { homedir as homedir2 } from "os";
|
|
494
573
|
import { join as join2 } from "path";
|
|
495
|
-
var
|
|
496
|
-
var
|
|
497
|
-
var
|
|
498
|
-
var CHARS_BANDWIDTH = Math.log(4);
|
|
499
|
-
var OTHER_EFFORT_WEIGHT = 0.5;
|
|
500
|
-
var CHARS_FLOOR = 50;
|
|
574
|
+
var MAX_TURN_PROGRESS = 64;
|
|
575
|
+
var TURN_PROGRESS_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
576
|
+
var TAG = /^[0-9a-f]{8}$/;
|
|
501
577
|
var pathOverride2 = null;
|
|
502
|
-
function
|
|
578
|
+
function turnProgressStorePath() {
|
|
503
579
|
if (pathOverride2) return pathOverride2;
|
|
504
580
|
const stateRoot = process.env.XDG_STATE_HOME || join2(homedir2(), ".local", "state");
|
|
505
|
-
return join2(stateRoot, "opencode-claude-code-plugin", "turn-
|
|
581
|
+
return join2(stateRoot, "opencode-claude-code-plugin", "turn-progress.json");
|
|
582
|
+
}
|
|
583
|
+
function isProgress(value) {
|
|
584
|
+
const record = value;
|
|
585
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) return false;
|
|
586
|
+
const count = (field) => typeof field === "number" && Number.isFinite(field) && field >= 0;
|
|
587
|
+
return count(record.turn) && count(record.out) && count(record.at);
|
|
588
|
+
}
|
|
589
|
+
function readEntries2(file, now) {
|
|
590
|
+
const out = /* @__PURE__ */ new Map();
|
|
591
|
+
try {
|
|
592
|
+
const parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
593
|
+
const entries = parsed && typeof parsed === "object" ? parsed.turns : null;
|
|
594
|
+
if (!entries || typeof entries !== "object") return out;
|
|
595
|
+
const cutoff = now - TURN_PROGRESS_MAX_AGE_MS;
|
|
596
|
+
const valid = Object.entries(entries).filter((entry) => TAG.test(entry[0]) && isProgress(entry[1])).filter(([, record]) => record.at >= cutoff).sort((a, b) => a[1].at - b[1].at).slice(-MAX_TURN_PROGRESS);
|
|
597
|
+
for (const [tag, record] of valid) out.set(tag, { turn: record.turn, out: Math.round(record.out), at: record.at });
|
|
598
|
+
} catch {
|
|
599
|
+
}
|
|
600
|
+
return out;
|
|
601
|
+
}
|
|
602
|
+
function createTurnProgressReader(file = turnProgressStorePath) {
|
|
603
|
+
let identity = "";
|
|
604
|
+
let cached = /* @__PURE__ */ new Map();
|
|
605
|
+
return {
|
|
606
|
+
read(now = Date.now()) {
|
|
607
|
+
const path = file();
|
|
608
|
+
let next = "";
|
|
609
|
+
try {
|
|
610
|
+
const stats = statSync2(path);
|
|
611
|
+
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
612
|
+
} catch {
|
|
613
|
+
next = "missing";
|
|
614
|
+
}
|
|
615
|
+
if (next !== identity) {
|
|
616
|
+
identity = next;
|
|
617
|
+
cached = next === "missing" ? /* @__PURE__ */ new Map() : readEntries2(path, now);
|
|
618
|
+
}
|
|
619
|
+
return cached;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/turn-history.ts
|
|
625
|
+
var MAX_TURN_SAMPLES = 48;
|
|
626
|
+
var TURN_SAMPLE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
627
|
+
var SESSION_TAG = /^[0-9a-f]{8}$/;
|
|
628
|
+
var pathOverride3 = null;
|
|
629
|
+
function turnHistoryStorePath() {
|
|
630
|
+
if (pathOverride3) return pathOverride3;
|
|
631
|
+
const stateRoot = process.env.XDG_STATE_HOME || join3(homedir3(), ".local", "state");
|
|
632
|
+
return join3(stateRoot, "opencode-claude-code-plugin", "turn-history.json");
|
|
506
633
|
}
|
|
507
634
|
function turnHistoryKey(agent, model) {
|
|
508
635
|
return `${agent || "main"}/${model}`;
|
|
509
636
|
}
|
|
637
|
+
function sessionTag(sessionID) {
|
|
638
|
+
if (!sessionID || sessionID === "default") return void 0;
|
|
639
|
+
let hash = 2166136261;
|
|
640
|
+
for (let index = 0; index < sessionID.length; index += 1) {
|
|
641
|
+
hash ^= sessionID.charCodeAt(index);
|
|
642
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
643
|
+
}
|
|
644
|
+
return hash.toString(16).padStart(8, "0");
|
|
645
|
+
}
|
|
510
646
|
var INJECTED_BLOCKS = [
|
|
511
647
|
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
512
648
|
/<dcp-system-reminder>[\s\S]*?<\/dcp-system-reminder>/g,
|
|
@@ -531,6 +667,16 @@ function isSample(value) {
|
|
|
531
667
|
return false;
|
|
532
668
|
}
|
|
533
669
|
if (sample.effort !== void 0 && typeof sample.effort !== "string") return false;
|
|
670
|
+
if (sample.calls !== void 0 && (typeof sample.calls !== "number" || !Number.isFinite(sample.calls) || sample.calls < 0)) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
if (sample.out !== void 0 && (typeof sample.out !== "number" || !Number.isFinite(sample.out) || sample.out < 0)) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
if (sample.failed !== void 0 && typeof sample.failed !== "boolean") return false;
|
|
677
|
+
if (sample.session !== void 0 && (typeof sample.session !== "string" || !SESSION_TAG.test(sample.session))) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
534
680
|
return true;
|
|
535
681
|
}
|
|
536
682
|
function cleanSample(sample) {
|
|
@@ -538,13 +684,17 @@ function cleanSample(sample) {
|
|
|
538
684
|
ms: Math.round(sample.ms),
|
|
539
685
|
...sample.chars !== void 0 ? { chars: Math.round(sample.chars) } : {},
|
|
540
686
|
...sample.effort ? { effort: sample.effort } : {},
|
|
687
|
+
...sample.calls !== void 0 ? { calls: Math.round(sample.calls) } : {},
|
|
688
|
+
...sample.out !== void 0 ? { out: Math.round(sample.out) } : {},
|
|
689
|
+
...sample.failed ? { failed: true } : {},
|
|
690
|
+
...sample.session ? { session: sample.session } : {},
|
|
541
691
|
at: sample.at
|
|
542
692
|
};
|
|
543
693
|
}
|
|
544
|
-
function
|
|
694
|
+
function readEntries3(file, now) {
|
|
545
695
|
const out = /* @__PURE__ */ new Map();
|
|
546
696
|
try {
|
|
547
|
-
const parsed = JSON.parse(
|
|
697
|
+
const parsed = JSON.parse(readFileSync3(file, "utf8"));
|
|
548
698
|
const entries = parsed && typeof parsed === "object" ? parsed.turns : null;
|
|
549
699
|
if (!entries || typeof entries !== "object") return out;
|
|
550
700
|
const cutoff = now - TURN_SAMPLE_MAX_AGE_MS;
|
|
@@ -565,19 +715,75 @@ function createTurnHistoryReader(file = turnHistoryStorePath) {
|
|
|
565
715
|
const path = file();
|
|
566
716
|
let next = "";
|
|
567
717
|
try {
|
|
568
|
-
const stats =
|
|
718
|
+
const stats = statSync3(path);
|
|
569
719
|
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
570
720
|
} catch {
|
|
571
721
|
next = "missing";
|
|
572
722
|
}
|
|
573
723
|
if (next !== identity) {
|
|
574
724
|
identity = next;
|
|
575
|
-
cached = next === "missing" ? /* @__PURE__ */ new Map() :
|
|
725
|
+
cached = next === "missing" ? /* @__PURE__ */ new Map() : readEntries3(path, now);
|
|
576
726
|
}
|
|
577
727
|
return cached;
|
|
578
728
|
}
|
|
579
729
|
};
|
|
580
730
|
}
|
|
731
|
+
|
|
732
|
+
// src/turn-model.ts
|
|
733
|
+
var MIN_TURN_SAMPLES = 3;
|
|
734
|
+
var CHARS_BANDWIDTH = Math.log(4);
|
|
735
|
+
var OTHER_EFFORT_WEIGHT = 0.5;
|
|
736
|
+
var MIN_EFFORT_RATIO_SAMPLES = 2;
|
|
737
|
+
var RECENCY_HALF_LIFE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
738
|
+
var MIN_CONDITIONED_SAMPLES = 1;
|
|
739
|
+
var SHRINKAGE_KAPPA = 2;
|
|
740
|
+
var CONDITIONED_KAPPA = 1;
|
|
741
|
+
var DEFAULT_LOG_SIGMA = 0.8;
|
|
742
|
+
var MIN_LOG_SIGMA = 0.2;
|
|
743
|
+
var MAX_LOG_SIGMA = 1.6;
|
|
744
|
+
var CALIBRATION_PRIOR_RESIDUALS = 12;
|
|
745
|
+
var MAX_ASSESSED_PER_KEY = 16;
|
|
746
|
+
var MAX_CALIBRATION_RESIDUALS = 512;
|
|
747
|
+
var GRID_LOW = -8;
|
|
748
|
+
var GRID_HIGH = 8;
|
|
749
|
+
var GRID_STEP = 0.04;
|
|
750
|
+
var CHARS_FLOOR = 50;
|
|
751
|
+
function normalCdf(z) {
|
|
752
|
+
if (!Number.isFinite(z)) return z > 0 ? 1 : 0;
|
|
753
|
+
const x = Math.abs(z);
|
|
754
|
+
const t = 1 / (1 + 0.2316419 * x);
|
|
755
|
+
const poly = t * (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
|
|
756
|
+
const tail = Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI) * poly;
|
|
757
|
+
return z >= 0 ? 1 - tail : tail;
|
|
758
|
+
}
|
|
759
|
+
function normalQuantile(p) {
|
|
760
|
+
const q = Math.min(1 - 1e-12, Math.max(1e-12, p));
|
|
761
|
+
const a = [-39.69683028665376, 220.9460984245205, -275.9285104469687, 138.357751867269, -30.66479806614716, 2.506628277459239];
|
|
762
|
+
const b = [-54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572];
|
|
763
|
+
const c = [-0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783];
|
|
764
|
+
const d = [0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416];
|
|
765
|
+
const low = 0.02425;
|
|
766
|
+
if (q < low || q > 1 - low) {
|
|
767
|
+
const r2 = Math.sqrt(-2 * Math.log(q < low ? q : 1 - q));
|
|
768
|
+
const x = (((((c[0] * r2 + c[1]) * r2 + c[2]) * r2 + c[3]) * r2 + c[4]) * r2 + c[5]) / ((((d[0] * r2 + d[1]) * r2 + d[2]) * r2 + d[3]) * r2 + 1);
|
|
769
|
+
return q < low ? x : -x;
|
|
770
|
+
}
|
|
771
|
+
const r = q - 0.5;
|
|
772
|
+
const s = r * r;
|
|
773
|
+
return (((((a[0] * s + a[1]) * s + a[2]) * s + a[3]) * s + a[4]) * s + a[5]) * r / (((((b[0] * s + b[1]) * s + b[2]) * s + b[3]) * s + b[4]) * s + 1);
|
|
774
|
+
}
|
|
775
|
+
function charsWeight(a, b) {
|
|
776
|
+
if (a === void 0 || b === void 0) return 1;
|
|
777
|
+
const distance = Math.log((a + CHARS_FLOOR) / (b + CHARS_FLOOR)) / CHARS_BANDWIDTH;
|
|
778
|
+
return Math.exp(-0.5 * distance * distance);
|
|
779
|
+
}
|
|
780
|
+
function effortWeight(a, b) {
|
|
781
|
+
return a === void 0 || b === void 0 || a === b ? 1 : OTHER_EFFORT_WEIGHT;
|
|
782
|
+
}
|
|
783
|
+
function recencyWeight(now, at) {
|
|
784
|
+
const age = Math.max(0, now - at);
|
|
785
|
+
return Math.pow(0.5, age / RECENCY_HALF_LIFE_MS);
|
|
786
|
+
}
|
|
581
787
|
function weightedMedian(values, weights) {
|
|
582
788
|
const order = values.map((_, index) => index).sort((a, b) => values[a] - values[b]);
|
|
583
789
|
const total = order.reduce((sum, index) => sum + weights[index], 0);
|
|
@@ -588,38 +794,284 @@ function weightedMedian(values, weights) {
|
|
|
588
794
|
}
|
|
589
795
|
return values[order[order.length - 1]];
|
|
590
796
|
}
|
|
591
|
-
function
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
797
|
+
function effortScales(samples, weights, effort) {
|
|
798
|
+
const scales = /* @__PURE__ */ new Map();
|
|
799
|
+
if (effort === void 0) return scales;
|
|
800
|
+
const groups = /* @__PURE__ */ new Map();
|
|
801
|
+
samples.forEach((sample, index) => {
|
|
802
|
+
if (sample.effort === void 0) return;
|
|
803
|
+
const group = groups.get(sample.effort) ?? [];
|
|
804
|
+
group.push(index);
|
|
805
|
+
groups.set(sample.effort, group);
|
|
806
|
+
});
|
|
807
|
+
const own = groups.get(effort);
|
|
808
|
+
if (!own || own.length < MIN_EFFORT_RATIO_SAMPLES) return scales;
|
|
809
|
+
const medianOf = (indices) => weightedMedian(
|
|
810
|
+
indices.map((index) => samples[index].ms),
|
|
811
|
+
indices.map((index) => weights[index])
|
|
812
|
+
);
|
|
813
|
+
const ownMedian = medianOf(own);
|
|
814
|
+
for (const [other, indices] of groups) {
|
|
815
|
+
if (other === effort || indices.length < MIN_EFFORT_RATIO_SAMPLES) continue;
|
|
816
|
+
const otherMedian = medianOf(indices);
|
|
817
|
+
if (ownMedian > 0 && otherMedian > 0) scales.set(other, ownMedian / otherMedian);
|
|
818
|
+
}
|
|
819
|
+
return scales;
|
|
595
820
|
}
|
|
596
|
-
function
|
|
597
|
-
return
|
|
821
|
+
function usableSamples(samples) {
|
|
822
|
+
return samples.filter((sample) => Number.isFinite(sample.ms) && sample.ms >= 0 && !sample.failed);
|
|
598
823
|
}
|
|
599
|
-
function
|
|
600
|
-
const usable = samples
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
824
|
+
function prepare(samples, input, now) {
|
|
825
|
+
const usable = usableSamples(samples);
|
|
826
|
+
const base = usable.map((sample) => charsWeight(input.chars, sample.chars) * recencyWeight(now, sample.at));
|
|
827
|
+
const scales = effortScales(usable, base, input.effort);
|
|
828
|
+
return usable.map((sample, index) => {
|
|
829
|
+
const ms = sample.ms * (sample.effort !== void 0 ? scales.get(sample.effort) ?? 1 : 1);
|
|
830
|
+
return {
|
|
831
|
+
ms,
|
|
832
|
+
y: Math.log(Math.max(1, ms)),
|
|
833
|
+
weight: base[index] * effortWeight(input.effort, sample.effort),
|
|
834
|
+
calls: sample.calls,
|
|
835
|
+
out: sample.out,
|
|
836
|
+
sameEffort: input.effort === void 0 || sample.effort === void 0 || input.effort === sample.effort,
|
|
837
|
+
session: sample.session
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
function moments(set) {
|
|
842
|
+
let sumW = 0;
|
|
843
|
+
let sumW2 = 0;
|
|
844
|
+
let sumWY = 0;
|
|
845
|
+
for (const sample of set) {
|
|
846
|
+
sumW += sample.weight;
|
|
847
|
+
sumW2 += sample.weight * sample.weight;
|
|
848
|
+
sumWY += sample.weight * sample.y;
|
|
610
849
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
);
|
|
615
|
-
const
|
|
616
|
-
|
|
850
|
+
if (!(sumW > 0)) return void 0;
|
|
851
|
+
const mean = sumWY / sumW;
|
|
852
|
+
let sumWD = 0;
|
|
853
|
+
for (const sample of set) sumWD += sample.weight * (sample.y - mean) * (sample.y - mean);
|
|
854
|
+
const n = sumW * sumW / sumW2;
|
|
855
|
+
const variance = n > 1 ? sumWD / sumW * (n / (n - 1)) : 0;
|
|
856
|
+
return { mean, variance, n };
|
|
857
|
+
}
|
|
858
|
+
function shrink(own, prior, kappa = SHRINKAGE_KAPPA, meanKappa = kappa) {
|
|
859
|
+
if (!own) return prior;
|
|
860
|
+
const priorMu = prior?.mu ?? own.mean;
|
|
861
|
+
const priorVariance = prior ? prior.sigma * prior.sigma : DEFAULT_LOG_SIGMA * DEFAULT_LOG_SIGMA;
|
|
862
|
+
const mu = (own.n * own.mean + meanKappa * priorMu) / (own.n + meanKappa);
|
|
863
|
+
const variance = (own.n * own.variance + kappa * priorVariance) / (own.n + kappa);
|
|
864
|
+
return { mu, sigma: Math.min(MAX_LOG_SIGMA, Math.max(MIN_LOG_SIGMA, Math.sqrt(Math.max(0, variance)))) };
|
|
865
|
+
}
|
|
866
|
+
function agentPool(history, key, effort, now) {
|
|
867
|
+
const slash = key.indexOf("/");
|
|
868
|
+
if (slash < 0) return { count: 0, n: 0 };
|
|
869
|
+
const prefix = key.slice(0, slash + 1);
|
|
870
|
+
const pool = [];
|
|
871
|
+
for (const [other, samples] of history) {
|
|
872
|
+
if (other === key || !other.startsWith(prefix)) continue;
|
|
873
|
+
pool.push(...prepare(samples, { effort }, now));
|
|
874
|
+
}
|
|
875
|
+
const measured = moments(pool);
|
|
876
|
+
return { fit: shrink(measured, void 0), count: pool.length, n: measured?.n ?? 0 };
|
|
877
|
+
}
|
|
878
|
+
function fitKey(own, pool, input) {
|
|
879
|
+
if (own.length + pool.count < MIN_TURN_SAMPLES) return void 0;
|
|
880
|
+
const poolPull = pool.fit ? SHRINKAGE_KAPPA * pool.n / (pool.n + SHRINKAGE_KAPPA) : SHRINKAGE_KAPPA;
|
|
881
|
+
const key = shrink(moments(own), pool.fit, SHRINKAGE_KAPPA, poolPull);
|
|
882
|
+
if (!key) return void 0;
|
|
883
|
+
const mine = input.session === void 0 ? [] : own.filter((sample) => sample.session === input.session);
|
|
884
|
+
const usual = mine.length > 0 ? shrink(moments(mine), key) ?? key : key;
|
|
885
|
+
const calls = input.calls !== void 0 && input.calls > 0 ? input.calls : void 0;
|
|
886
|
+
const out = input.out !== void 0 && input.out > 0 ? input.out : void 0;
|
|
887
|
+
let fit = usual;
|
|
888
|
+
if (calls !== void 0 || out !== void 0) {
|
|
889
|
+
const kept = own.filter(
|
|
890
|
+
(sample) => (calls === void 0 || sample.calls === void 0 || sample.calls >= calls) && (out === void 0 || sample.out === void 0 || !sample.sameEffort || sample.out >= out)
|
|
891
|
+
);
|
|
892
|
+
if (kept.length < own.length && kept.length >= MIN_CONDITIONED_SAMPLES) {
|
|
893
|
+
fit = shrink(moments(kept), usual, CONDITIONED_KAPPA) ?? usual;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const maxMs = own.length > 0 ? own.reduce((longest, sample) => Math.max(longest, sample.ms), 0) : Number.POSITIVE_INFINITY;
|
|
897
|
+
return { fit, usual, maxMs };
|
|
898
|
+
}
|
|
899
|
+
var NORMAL_CALIBRATION = { count: 0, weight: 0, cdf: normalCdf, quantile: normalQuantile };
|
|
900
|
+
function weightedQuantile(sorted, total, q) {
|
|
901
|
+
let cumulative = 0;
|
|
902
|
+
let previous;
|
|
903
|
+
for (const entry of sorted) {
|
|
904
|
+
const at = (cumulative + entry.weight / 2) / total;
|
|
905
|
+
cumulative += entry.weight;
|
|
906
|
+
if (at >= q) {
|
|
907
|
+
if (!previous) return entry.value;
|
|
908
|
+
return previous.value + (q - previous.at) / (at - previous.at) * (entry.value - previous.value);
|
|
909
|
+
}
|
|
910
|
+
previous = { value: entry.value, at };
|
|
911
|
+
}
|
|
912
|
+
return sorted[sorted.length - 1].value;
|
|
913
|
+
}
|
|
914
|
+
function calibrationFrom(residuals, weights) {
|
|
915
|
+
const entries = residuals.map((value, index) => ({ value, weight: weights?.[index] ?? 1 })).filter((entry) => Number.isFinite(entry.value) && Number.isFinite(entry.weight) && entry.weight > 0).slice(-MAX_CALIBRATION_RESIDUALS);
|
|
916
|
+
if (entries.length === 0) return NORMAL_CALIBRATION;
|
|
917
|
+
let total = 0;
|
|
918
|
+
let squares = 0;
|
|
919
|
+
let sum = 0;
|
|
920
|
+
for (const entry of entries) {
|
|
921
|
+
total += entry.weight;
|
|
922
|
+
squares += entry.weight * entry.weight;
|
|
923
|
+
sum += entry.weight * entry.value;
|
|
924
|
+
}
|
|
925
|
+
const n = total * total / squares;
|
|
926
|
+
const mean = sum / total;
|
|
927
|
+
let deviation = 0;
|
|
928
|
+
for (const entry of entries) deviation += entry.weight * (entry.value - mean) * (entry.value - mean);
|
|
929
|
+
const sd = n > 1 ? Math.sqrt(deviation / total * (n / (n - 1))) : 0;
|
|
930
|
+
const sorted = [...entries].sort((a, b) => a.value - b.value);
|
|
931
|
+
const iqr = weightedQuantile(sorted, total, 0.75) - weightedQuantile(sorted, total, 0.25);
|
|
932
|
+
const spreads = [sd, iqr / 1.349].filter((spread2) => spread2 > 0);
|
|
933
|
+
const spread = spreads.length > 0 ? Math.min(...spreads) : 1;
|
|
934
|
+
const width = Math.max(0.1, 0.9 * spread * Math.pow(n, -0.2));
|
|
935
|
+
const weight = n / (n + CALIBRATION_PRIOR_RESIDUALS);
|
|
936
|
+
const size = Math.round((GRID_HIGH - GRID_LOW) / GRID_STEP) + 1;
|
|
937
|
+
const table = new Float64Array(size);
|
|
938
|
+
for (let index = 0; index < size; index += 1) {
|
|
939
|
+
const z = GRID_LOW + index * GRID_STEP;
|
|
940
|
+
let kernel = 0;
|
|
941
|
+
for (const entry of entries) kernel += entry.weight * normalCdf((z - entry.value) / width);
|
|
942
|
+
table[index] = weight * (kernel / total) + (1 - weight) * normalCdf(z);
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
count: entries.length,
|
|
946
|
+
weight,
|
|
947
|
+
cdf(z) {
|
|
948
|
+
if (Number.isNaN(z)) return 0.5;
|
|
949
|
+
const position = (Math.min(GRID_HIGH, Math.max(GRID_LOW, z)) - GRID_LOW) / GRID_STEP;
|
|
950
|
+
const index = Math.min(size - 2, Math.floor(position));
|
|
951
|
+
return table[index] + (position - index) * (table[index + 1] - table[index]);
|
|
952
|
+
},
|
|
953
|
+
quantile(p) {
|
|
954
|
+
if (!(p > table[0])) return GRID_LOW;
|
|
955
|
+
if (!(p < table[size - 1])) return GRID_HIGH;
|
|
956
|
+
let low = 0;
|
|
957
|
+
let high = size - 1;
|
|
958
|
+
while (high - low > 1) {
|
|
959
|
+
const mid = low + high >> 1;
|
|
960
|
+
if (table[mid] < p) low = mid;
|
|
961
|
+
else high = mid;
|
|
962
|
+
}
|
|
963
|
+
const span = table[high] - table[low];
|
|
964
|
+
return GRID_LOW + (low + (span > 0 ? (p - table[low]) / span : 1)) * GRID_STEP;
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
function quantileMs(fit, calibration, truncatedAtMs, q) {
|
|
969
|
+
const passed = truncatedAtMs > 0 ? calibration.cdf((Math.log(truncatedAtMs) - fit.mu) / fit.sigma) : 0;
|
|
970
|
+
return Math.exp(fit.mu + fit.sigma * calibration.quantile(passed + q * (1 - passed)));
|
|
971
|
+
}
|
|
972
|
+
var EMPTY_HISTORY = /* @__PURE__ */ new Map();
|
|
973
|
+
function buildTurnModel(history = EMPTY_HISTORY, now = Date.now()) {
|
|
974
|
+
const pools = /* @__PURE__ */ new Map();
|
|
975
|
+
const poolFor = (key, effort) => {
|
|
976
|
+
const id = `${key}\0${effort ?? ""}`;
|
|
977
|
+
let pool = pools.get(id);
|
|
978
|
+
if (!pool) {
|
|
979
|
+
pool = agentPool(history, key, effort, now);
|
|
980
|
+
pools.set(id, pool);
|
|
981
|
+
}
|
|
982
|
+
return pool;
|
|
983
|
+
};
|
|
984
|
+
const assessed = /* @__PURE__ */ new Map();
|
|
985
|
+
const residuals = [];
|
|
986
|
+
const residualWeights = [];
|
|
987
|
+
for (const [key, samples] of history) {
|
|
988
|
+
const usable = usableSamples(samples);
|
|
989
|
+
if (usable.length < 2) continue;
|
|
990
|
+
const rows = [];
|
|
991
|
+
for (const held of usable.slice(-MAX_ASSESSED_PER_KEY)) {
|
|
992
|
+
const input = { chars: held.chars, effort: held.effort, session: held.session };
|
|
993
|
+
const own = prepare(
|
|
994
|
+
usable.filter((sample) => sample !== held),
|
|
995
|
+
input,
|
|
996
|
+
now
|
|
997
|
+
);
|
|
998
|
+
const fitted = fitKey(own, poolFor(key, held.effort), input);
|
|
999
|
+
if (!fitted) continue;
|
|
1000
|
+
rows.push({ ms: held.ms, fit: fitted.fit });
|
|
1001
|
+
residuals.push((Math.log(Math.max(1, held.ms)) - fitted.fit.mu) / fitted.fit.sigma);
|
|
1002
|
+
residualWeights.push(recencyWeight(now, held.at));
|
|
1003
|
+
}
|
|
1004
|
+
if (rows.length > 0) assessed.set(key, rows);
|
|
1005
|
+
}
|
|
1006
|
+
const calibration = calibrationFrom(residuals, residualWeights);
|
|
1007
|
+
const estimate = (key, input) => {
|
|
1008
|
+
const at = input.now ?? now;
|
|
1009
|
+
const own = prepare(history.get(key) ?? [], input, at);
|
|
1010
|
+
const fitted = fitKey(own, poolFor(key, input.effort), input);
|
|
1011
|
+
if (!fitted) return void 0;
|
|
1012
|
+
const elapsed = Math.max(0, Number.isFinite(input.elapsedMs) ? input.elapsedMs : 0);
|
|
1013
|
+
if (elapsed > fitted.maxMs) {
|
|
1014
|
+
return { totalMs: quantileMs(fitted.usual, calibration, 0, 0.5), fill: 1, over: true };
|
|
1015
|
+
}
|
|
1016
|
+
const totalMs = Math.max(elapsed, quantileMs(fitted.fit, calibration, elapsed, 0.5));
|
|
1017
|
+
const lowMs = Math.min(totalMs, Math.max(elapsed, quantileMs(fitted.fit, calibration, elapsed, 0.25)));
|
|
1018
|
+
const highMs = Math.max(totalMs, quantileMs(fitted.fit, calibration, elapsed, 0.75));
|
|
1019
|
+
return {
|
|
1020
|
+
totalMs,
|
|
1021
|
+
fill: totalMs > 0 ? elapsed / totalMs : 0,
|
|
1022
|
+
over: false,
|
|
1023
|
+
band: {
|
|
1024
|
+
lowMs,
|
|
1025
|
+
highMs,
|
|
1026
|
+
sure: highMs > 0 ? elapsed / highMs : 0,
|
|
1027
|
+
maybe: lowMs > 0 ? Math.min(1, elapsed / lowMs) : 0
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
};
|
|
1031
|
+
const assess = () => {
|
|
1032
|
+
const keys = [];
|
|
1033
|
+
for (const [key, samples] of history) {
|
|
1034
|
+
const usable = usableSamples(samples);
|
|
1035
|
+
const failed = samples.filter((sample) => sample.failed).length;
|
|
1036
|
+
if (usable.length === 0 && failed === 0) continue;
|
|
1037
|
+
const durations = usable.map((sample) => sample.ms).sort((a, b) => a - b);
|
|
1038
|
+
const typicalMs = durations.length > 0 ? durations[Math.floor((durations.length - 1) / 2)] : 0;
|
|
1039
|
+
const rows = assessed.get(key) ?? [];
|
|
1040
|
+
const misses = [];
|
|
1041
|
+
let over = 0;
|
|
1042
|
+
let inBand = 0;
|
|
1043
|
+
for (const row of rows) {
|
|
1044
|
+
const expected = Math.exp(row.fit.mu + row.fit.sigma * calibration.quantile(0.5));
|
|
1045
|
+
const low = Math.exp(row.fit.mu + row.fit.sigma * calibration.quantile(0.25));
|
|
1046
|
+
const high = Math.exp(row.fit.mu + row.fit.sigma * calibration.quantile(0.75));
|
|
1047
|
+
misses.push(Math.abs(Math.log(Math.max(1, row.ms) / expected)));
|
|
1048
|
+
if (row.ms > expected) over += 1;
|
|
1049
|
+
if (row.ms >= low && row.ms <= high) inBand += 1;
|
|
1050
|
+
}
|
|
1051
|
+
misses.sort((a, b) => a - b);
|
|
1052
|
+
keys.push({
|
|
1053
|
+
key,
|
|
1054
|
+
turns: usable.length,
|
|
1055
|
+
failed,
|
|
1056
|
+
typicalMs,
|
|
1057
|
+
assessed: rows.length,
|
|
1058
|
+
...rows.length > 0 ? {
|
|
1059
|
+
missFactor: Math.exp(misses[Math.floor((misses.length - 1) / 2)]),
|
|
1060
|
+
overShare: over / rows.length,
|
|
1061
|
+
inBandShare: inBand / rows.length
|
|
1062
|
+
} : {}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
keys.sort((a, b) => b.turns - a.turns || a.key.localeCompare(b.key));
|
|
1066
|
+
return { keys, residuals: residuals.length, calibrationWeight: calibration.weight };
|
|
1067
|
+
};
|
|
1068
|
+
return { estimate, calibration, assess };
|
|
617
1069
|
}
|
|
618
1070
|
|
|
619
1071
|
// src/account-usage.ts
|
|
620
|
-
import { mkdirSync as
|
|
621
|
-
import { homedir as
|
|
622
|
-
import { join as
|
|
1072
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync4, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1073
|
+
import { homedir as homedir4 } from "os";
|
|
1074
|
+
import { join as join4 } from "path";
|
|
623
1075
|
var UNTIMED_READING_TTL_MS = 5 * 60 * 60 * 1e3;
|
|
624
1076
|
var USAGE_READING_MAX_AGE_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
625
1077
|
var MAX_USAGE_ACCOUNTS = 32;
|
|
@@ -627,11 +1079,11 @@ var NAME = /^[a-z0-9_-]{1,64}$/;
|
|
|
627
1079
|
var STATUS = /^[a-z_]{1,32}$/;
|
|
628
1080
|
var TIER = /^[A-Za-z0-9][A-Za-z0-9 ]{0,23}$/;
|
|
629
1081
|
var MAX_RESETS_TEXT = 80;
|
|
630
|
-
var
|
|
1082
|
+
var pathOverride4 = null;
|
|
631
1083
|
function accountUsageStorePath() {
|
|
632
|
-
if (
|
|
633
|
-
const stateRoot = process.env.XDG_STATE_HOME ||
|
|
634
|
-
return
|
|
1084
|
+
if (pathOverride4) return pathOverride4;
|
|
1085
|
+
const stateRoot = process.env.XDG_STATE_HOME || join4(homedir4(), ".local", "state");
|
|
1086
|
+
return join4(stateRoot, "opencode-claude-code-plugin", "account-usage.json");
|
|
635
1087
|
}
|
|
636
1088
|
function finite(value) {
|
|
637
1089
|
return typeof value === "number" && Number.isFinite(value);
|
|
@@ -676,7 +1128,7 @@ function readKnown(parsed) {
|
|
|
676
1128
|
function parseStore(file, now) {
|
|
677
1129
|
const out = emptyStore();
|
|
678
1130
|
try {
|
|
679
|
-
const parsed = JSON.parse(
|
|
1131
|
+
const parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
680
1132
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return out;
|
|
681
1133
|
out.known = readKnown(parsed);
|
|
682
1134
|
const accounts = parsed.accounts;
|
|
@@ -710,7 +1162,7 @@ function createAccountUsageReader(file = accountUsageStorePath) {
|
|
|
710
1162
|
const path = file();
|
|
711
1163
|
let next = "";
|
|
712
1164
|
try {
|
|
713
|
-
const stats =
|
|
1165
|
+
const stats = statSync4(path);
|
|
714
1166
|
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
715
1167
|
} catch {
|
|
716
1168
|
next = "missing";
|
|
@@ -892,6 +1344,8 @@ var ROLE_COLOR = {
|
|
|
892
1344
|
effort: "effort",
|
|
893
1345
|
"effort-faded": "effortFaded",
|
|
894
1346
|
"bar-done": "barDone",
|
|
1347
|
+
"bar-likely": "barLikely",
|
|
1348
|
+
"bar-band": "barBand",
|
|
895
1349
|
// Past every recorded turn: the warning colour, which the effort already takes.
|
|
896
1350
|
"bar-over": "effort",
|
|
897
1351
|
"bar-track": "barTrack",
|
|
@@ -933,6 +1387,9 @@ function createSidebar(host) {
|
|
|
933
1387
|
let recordMap = attempt(() => records.read(), /* @__PURE__ */ new Map());
|
|
934
1388
|
const history = createTurnHistoryReader();
|
|
935
1389
|
let historyMap = attempt(() => history.read(), /* @__PURE__ */ new Map());
|
|
1390
|
+
let turnModel = attempt(() => buildTurnModel(historyMap), void 0);
|
|
1391
|
+
const liveProgress = createTurnProgressReader();
|
|
1392
|
+
let progressMap = attempt(() => liveProgress.read(), /* @__PURE__ */ new Map());
|
|
936
1393
|
const [recordVersion, setRecordVersion] = createSignal(0);
|
|
937
1394
|
const watch = createStatusWatch();
|
|
938
1395
|
const fills = createFillMemory();
|
|
@@ -951,10 +1408,12 @@ function createSidebar(host) {
|
|
|
951
1408
|
const next = attempt(() => records.read(), recordMap);
|
|
952
1409
|
const nextHistory = attempt(() => history.read(), historyMap);
|
|
953
1410
|
if (next !== recordMap || nextHistory !== historyMap) {
|
|
1411
|
+
if (nextHistory !== historyMap) turnModel = attempt(() => buildTurnModel(nextHistory), turnModel);
|
|
954
1412
|
recordMap = next;
|
|
955
1413
|
historyMap = nextHistory;
|
|
956
1414
|
setRecordVersion((value) => value + 1);
|
|
957
1415
|
}
|
|
1416
|
+
progressMap = attempt(() => liveProgress.read(), progressMap);
|
|
958
1417
|
setNow(Date.now());
|
|
959
1418
|
}
|
|
960
1419
|
setUsage(attempt(() => usageReader.read(), usage()));
|
|
@@ -982,16 +1441,23 @@ function createSidebar(host) {
|
|
|
982
1441
|
}
|
|
983
1442
|
};
|
|
984
1443
|
const progressOf = (sessionID) => attempt(() => host.progress(sessionID), EMPTY_SOURCE);
|
|
985
|
-
const estimateFor = (sessionID, measure, view, agentFallback) => {
|
|
986
|
-
if (view.state !== "running") return view;
|
|
1444
|
+
const estimateFor = (sessionID, measure, view, agentFallback, children = []) => {
|
|
1445
|
+
if (view.state !== "running" || !turnModel) return view;
|
|
987
1446
|
const record = recordMap.get(sessionID);
|
|
988
1447
|
if (!record?.model) return view;
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
1448
|
+
const tag = sessionTag(sessionID);
|
|
1449
|
+
const estimate = turnModel.estimate(turnHistoryKey(record.agent ?? agentFallback, record.model), {
|
|
1450
|
+
elapsedMs: view.elapsedMs,
|
|
1451
|
+
chars: measure.chars,
|
|
1452
|
+
effort: record.effort,
|
|
1453
|
+
calls: measure.calls,
|
|
1454
|
+
out: liveOutput(tag ? progressMap.get(tag) : void 0, measure.startedAt),
|
|
1455
|
+
session: tag,
|
|
1456
|
+
now: Date.now()
|
|
1457
|
+
});
|
|
992
1458
|
if (!estimate) return view;
|
|
993
1459
|
const turn = measure.startedAt ?? 0;
|
|
994
|
-
return { ...view, estimate:
|
|
1460
|
+
return { ...view, estimate: fills.holdEstimate(sessionID, turn, floorByChildren(estimate, view.elapsedMs, children)) };
|
|
995
1461
|
};
|
|
996
1462
|
const deriveView = (sessionID) => {
|
|
997
1463
|
recordVersion();
|
|
@@ -1004,11 +1470,13 @@ function createSidebar(host) {
|
|
|
1004
1470
|
now: clock
|
|
1005
1471
|
});
|
|
1006
1472
|
const lines = /* @__PURE__ */ new Map();
|
|
1473
|
+
const waitedOn = [];
|
|
1007
1474
|
for (const row of rows) {
|
|
1008
1475
|
if (row.state !== "running" || !row.sessionID) continue;
|
|
1009
1476
|
const measure2 = measureProgress(progressOf(row.sessionID));
|
|
1010
|
-
const line = rowProgress(measure2, { started: row.started, now: clock });
|
|
1011
|
-
lines.set(row.sessionID,
|
|
1477
|
+
const line = estimateFor(row.sessionID, measure2, rowProgress(measure2, { started: row.started, now: clock }), row.agent);
|
|
1478
|
+
lines.set(row.sessionID, line);
|
|
1479
|
+
if (!row.background) waitedOn.push(line);
|
|
1012
1480
|
}
|
|
1013
1481
|
const status = gathered.status(sessionID);
|
|
1014
1482
|
const memory = watch.observe(sessionID, status, clock);
|
|
@@ -1019,7 +1487,7 @@ function createSidebar(host) {
|
|
|
1019
1487
|
stoppedAt: gathered.stoppedAt(sessionID) ?? memory.stoppedAt,
|
|
1020
1488
|
now: clock
|
|
1021
1489
|
});
|
|
1022
|
-
const progress = session ? estimateFor(sessionID, measure, session) : void 0;
|
|
1490
|
+
const progress = session ? estimateFor(sessionID, measure, session, void 0, waitedOn) : void 0;
|
|
1023
1491
|
return { rows, lines, progress };
|
|
1024
1492
|
};
|
|
1025
1493
|
const styled = (segment) => {
|
|
@@ -1276,6 +1744,8 @@ function registerV1(api) {
|
|
|
1276
1744
|
effortFaded: mix(theme.warning ?? muted, muted, 0.45),
|
|
1277
1745
|
hover: theme.backgroundElement,
|
|
1278
1746
|
barDone: theme.success ?? theme.accent,
|
|
1747
|
+
barLikely: mix(theme.success ?? theme.accent, muted, 0.45),
|
|
1748
|
+
barBand: mix(muted, theme.success ?? theme.accent, 0.3),
|
|
1279
1749
|
barTrack: mix(muted, theme.backgroundPanel ?? theme.backgroundElement, 0.5),
|
|
1280
1750
|
fraction: theme.text,
|
|
1281
1751
|
tool: theme.info ?? theme.accent,
|
|
@@ -1395,6 +1865,8 @@ function setupV2(context) {
|
|
|
1395
1865
|
effortFaded: mix(theme.text.feedback.warning?.base ?? muted, muted, 0.45),
|
|
1396
1866
|
hover: theme.background.raised.base,
|
|
1397
1867
|
barDone: theme.text.feedback.success.base ?? accent,
|
|
1868
|
+
barLikely: mix(theme.text.feedback.success.base ?? accent, muted, 0.45),
|
|
1869
|
+
barBand: mix(muted, theme.text.feedback.success.base ?? accent, 0.3),
|
|
1398
1870
|
barTrack: mix(muted, theme.background.base ?? theme.background.raised.base, 0.5),
|
|
1399
1871
|
fraction: theme.text.base,
|
|
1400
1872
|
tool: theme.text.feedback.info?.base ?? accent,
|