@khalilgharbaoui/opencode-claude-code-plugin 0.57.2 → 0.59.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.d.ts +22 -4
- package/dist/index.js +1043 -279
- package/dist/index.js.map +1 -1
- package/dist/tui.js +586 -85
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +23 -12
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,25 +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 MIN_EFFORT_RATIO_SAMPLES = 2;
|
|
501
|
-
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}$/;
|
|
502
577
|
var pathOverride2 = null;
|
|
503
|
-
function
|
|
578
|
+
function turnProgressStorePath() {
|
|
504
579
|
if (pathOverride2) return pathOverride2;
|
|
505
580
|
const stateRoot = process.env.XDG_STATE_HOME || join2(homedir2(), ".local", "state");
|
|
506
|
-
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");
|
|
507
633
|
}
|
|
508
634
|
function turnHistoryKey(agent, model) {
|
|
509
635
|
return `${agent || "main"}/${model}`;
|
|
510
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
|
+
}
|
|
511
646
|
var INJECTED_BLOCKS = [
|
|
512
647
|
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
513
648
|
/<dcp-system-reminder>[\s\S]*?<\/dcp-system-reminder>/g,
|
|
@@ -535,6 +670,13 @@ function isSample(value) {
|
|
|
535
670
|
if (sample.calls !== void 0 && (typeof sample.calls !== "number" || !Number.isFinite(sample.calls) || sample.calls < 0)) {
|
|
536
671
|
return false;
|
|
537
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
|
+
}
|
|
538
680
|
return true;
|
|
539
681
|
}
|
|
540
682
|
function cleanSample(sample) {
|
|
@@ -543,13 +685,16 @@ function cleanSample(sample) {
|
|
|
543
685
|
...sample.chars !== void 0 ? { chars: Math.round(sample.chars) } : {},
|
|
544
686
|
...sample.effort ? { effort: sample.effort } : {},
|
|
545
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 } : {},
|
|
546
691
|
at: sample.at
|
|
547
692
|
};
|
|
548
693
|
}
|
|
549
|
-
function
|
|
694
|
+
function readEntries3(file, now) {
|
|
550
695
|
const out = /* @__PURE__ */ new Map();
|
|
551
696
|
try {
|
|
552
|
-
const parsed = JSON.parse(
|
|
697
|
+
const parsed = JSON.parse(readFileSync3(file, "utf8"));
|
|
553
698
|
const entries = parsed && typeof parsed === "object" ? parsed.turns : null;
|
|
554
699
|
if (!entries || typeof entries !== "object") return out;
|
|
555
700
|
const cutoff = now - TURN_SAMPLE_MAX_AGE_MS;
|
|
@@ -570,28 +715,62 @@ function createTurnHistoryReader(file = turnHistoryStorePath) {
|
|
|
570
715
|
const path = file();
|
|
571
716
|
let next = "";
|
|
572
717
|
try {
|
|
573
|
-
const stats =
|
|
718
|
+
const stats = statSync3(path);
|
|
574
719
|
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
575
720
|
} catch {
|
|
576
721
|
next = "missing";
|
|
577
722
|
}
|
|
578
723
|
if (next !== identity) {
|
|
579
724
|
identity = next;
|
|
580
|
-
cached = next === "missing" ? /* @__PURE__ */ new Map() :
|
|
725
|
+
cached = next === "missing" ? /* @__PURE__ */ new Map() : readEntries3(path, now);
|
|
581
726
|
}
|
|
582
727
|
return cached;
|
|
583
728
|
}
|
|
584
729
|
};
|
|
585
730
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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;
|
|
593
770
|
}
|
|
594
|
-
|
|
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);
|
|
595
774
|
}
|
|
596
775
|
function charsWeight(a, b) {
|
|
597
776
|
if (a === void 0 || b === void 0) return 1;
|
|
@@ -601,7 +780,21 @@ function charsWeight(a, b) {
|
|
|
601
780
|
function effortWeight(a, b) {
|
|
602
781
|
return a === void 0 || b === void 0 || a === b ? 1 : OTHER_EFFORT_WEIGHT;
|
|
603
782
|
}
|
|
604
|
-
function
|
|
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
|
+
}
|
|
787
|
+
function weightedMedian(values, weights) {
|
|
788
|
+
const order = values.map((_, index) => index).sort((a, b) => values[a] - values[b]);
|
|
789
|
+
const total = order.reduce((sum, index) => sum + weights[index], 0);
|
|
790
|
+
let cumulative = 0;
|
|
791
|
+
for (const index of order) {
|
|
792
|
+
cumulative += weights[index];
|
|
793
|
+
if (cumulative >= total / 2) return values[index];
|
|
794
|
+
}
|
|
795
|
+
return values[order[order.length - 1]];
|
|
796
|
+
}
|
|
797
|
+
function effortScales(samples, weights, effort) {
|
|
605
798
|
const scales = /* @__PURE__ */ new Map();
|
|
606
799
|
if (effort === void 0) return scales;
|
|
607
800
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -615,7 +808,7 @@ function effortScales(samples, charsWeights, effort) {
|
|
|
615
808
|
if (!own || own.length < MIN_EFFORT_RATIO_SAMPLES) return scales;
|
|
616
809
|
const medianOf = (indices) => weightedMedian(
|
|
617
810
|
indices.map((index) => samples[index].ms),
|
|
618
|
-
indices.map((index) =>
|
|
811
|
+
indices.map((index) => weights[index])
|
|
619
812
|
);
|
|
620
813
|
const ownMedian = medianOf(own);
|
|
621
814
|
for (const [other, indices] of groups) {
|
|
@@ -625,39 +818,260 @@ function effortScales(samples, charsWeights, effort) {
|
|
|
625
818
|
}
|
|
626
819
|
return scales;
|
|
627
820
|
}
|
|
628
|
-
function
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
821
|
+
function usableSamples(samples) {
|
|
822
|
+
return samples.filter((sample) => Number.isFinite(sample.ms) && sample.ms >= 0 && !sample.failed);
|
|
823
|
+
}
|
|
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;
|
|
849
|
+
}
|
|
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)
|
|
646
891
|
);
|
|
647
|
-
|
|
892
|
+
if (kept.length < own.length && kept.length >= MIN_CONDITIONED_SAMPLES) {
|
|
893
|
+
fit = shrink(moments(kept), usual, CONDITIONED_KAPPA) ?? usual;
|
|
894
|
+
}
|
|
648
895
|
}
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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 };
|
|
655
1069
|
}
|
|
656
1070
|
|
|
657
1071
|
// src/account-usage.ts
|
|
658
|
-
import { mkdirSync as
|
|
659
|
-
import { homedir as
|
|
660
|
-
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";
|
|
661
1075
|
var UNTIMED_READING_TTL_MS = 5 * 60 * 60 * 1e3;
|
|
662
1076
|
var USAGE_READING_MAX_AGE_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
663
1077
|
var MAX_USAGE_ACCOUNTS = 32;
|
|
@@ -665,11 +1079,11 @@ var NAME = /^[a-z0-9_-]{1,64}$/;
|
|
|
665
1079
|
var STATUS = /^[a-z_]{1,32}$/;
|
|
666
1080
|
var TIER = /^[A-Za-z0-9][A-Za-z0-9 ]{0,23}$/;
|
|
667
1081
|
var MAX_RESETS_TEXT = 80;
|
|
668
|
-
var
|
|
1082
|
+
var pathOverride4 = null;
|
|
669
1083
|
function accountUsageStorePath() {
|
|
670
|
-
if (
|
|
671
|
-
const stateRoot = process.env.XDG_STATE_HOME ||
|
|
672
|
-
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");
|
|
673
1087
|
}
|
|
674
1088
|
function finite(value) {
|
|
675
1089
|
return typeof value === "number" && Number.isFinite(value);
|
|
@@ -714,7 +1128,7 @@ function readKnown(parsed) {
|
|
|
714
1128
|
function parseStore(file, now) {
|
|
715
1129
|
const out = emptyStore();
|
|
716
1130
|
try {
|
|
717
|
-
const parsed = JSON.parse(
|
|
1131
|
+
const parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
718
1132
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return out;
|
|
719
1133
|
out.known = readKnown(parsed);
|
|
720
1134
|
const accounts = parsed.accounts;
|
|
@@ -748,7 +1162,7 @@ function createAccountUsageReader(file = accountUsageStorePath) {
|
|
|
748
1162
|
const path = file();
|
|
749
1163
|
let next = "";
|
|
750
1164
|
try {
|
|
751
|
-
const stats =
|
|
1165
|
+
const stats = statSync4(path);
|
|
752
1166
|
next = `${stats.mtimeMs}:${stats.size}:${stats.ino}`;
|
|
753
1167
|
} catch {
|
|
754
1168
|
next = "missing";
|
|
@@ -911,6 +1325,65 @@ function accountUsageLines(usage, now, width = SIDEBAR_ROW_WIDTH, known = /* @__
|
|
|
911
1325
|
});
|
|
912
1326
|
}
|
|
913
1327
|
|
|
1328
|
+
// src/plugin-notice.ts
|
|
1329
|
+
var NOTICE_RPC_ID = "opencode-claude-code-plugin";
|
|
1330
|
+
var NOTICE_EVENT = "notice";
|
|
1331
|
+
var NOTICE_EVENT_TYPE = `rpc.${NOTICE_RPC_ID}.${NOTICE_EVENT}`;
|
|
1332
|
+
var NOTICE_RPC_DEFINITION = {
|
|
1333
|
+
id: NOTICE_RPC_ID,
|
|
1334
|
+
methods: {},
|
|
1335
|
+
events: {
|
|
1336
|
+
[NOTICE_EVENT]: {
|
|
1337
|
+
schema: {
|
|
1338
|
+
type: "object",
|
|
1339
|
+
properties: {
|
|
1340
|
+
id: { type: "string" },
|
|
1341
|
+
level: { type: "string", enum: ["warn", "error"] },
|
|
1342
|
+
message: { type: "string" }
|
|
1343
|
+
},
|
|
1344
|
+
required: ["id", "level", "message"],
|
|
1345
|
+
additionalProperties: false
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
var TOAST_TITLE = "claude-code";
|
|
1351
|
+
var TOAST_DURATION_MS = 1e4;
|
|
1352
|
+
var MAX_TOAST_CHARS = 240;
|
|
1353
|
+
function toastMessage(message) {
|
|
1354
|
+
const flat = message.replace(/\s+/g, " ").trim();
|
|
1355
|
+
if (flat.length <= MAX_TOAST_CHARS) return flat;
|
|
1356
|
+
return `${flat.slice(0, MAX_TOAST_CHARS - 1)}\u2026`;
|
|
1357
|
+
}
|
|
1358
|
+
function readPluginNotice(type, data) {
|
|
1359
|
+
if (type !== NOTICE_EVENT_TYPE) return void 0;
|
|
1360
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1361
|
+
const { id, level, message } = data;
|
|
1362
|
+
if (typeof id !== "string" || id.length === 0) return void 0;
|
|
1363
|
+
if (level !== "warn" && level !== "error") return void 0;
|
|
1364
|
+
if (typeof message !== "string" || message.trim().length === 0) return void 0;
|
|
1365
|
+
return { id, level, message: toastMessage(message) };
|
|
1366
|
+
}
|
|
1367
|
+
var MAX_SEEN_NOTICES = 256;
|
|
1368
|
+
function createNoticeToaster(show) {
|
|
1369
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1370
|
+
return (type, data) => {
|
|
1371
|
+
const notice = readPluginNotice(type, data);
|
|
1372
|
+
if (!notice || seen.has(notice.id)) return false;
|
|
1373
|
+
seen.add(notice.id);
|
|
1374
|
+
if (seen.size > MAX_SEEN_NOTICES) {
|
|
1375
|
+
const oldest = seen.values().next().value;
|
|
1376
|
+
if (oldest !== void 0) seen.delete(oldest);
|
|
1377
|
+
}
|
|
1378
|
+
try {
|
|
1379
|
+
show(notice);
|
|
1380
|
+
return true;
|
|
1381
|
+
} catch {
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
914
1387
|
// src/tui.ts
|
|
915
1388
|
var SUBAGENTS_TUI_PLUGIN_ID = "opencode-claude-code-plugin.subagents";
|
|
916
1389
|
var SECTION_ORDER = 900;
|
|
@@ -930,6 +1403,8 @@ var ROLE_COLOR = {
|
|
|
930
1403
|
effort: "effort",
|
|
931
1404
|
"effort-faded": "effortFaded",
|
|
932
1405
|
"bar-done": "barDone",
|
|
1406
|
+
"bar-likely": "barLikely",
|
|
1407
|
+
"bar-band": "barBand",
|
|
933
1408
|
// Past every recorded turn: the warning colour, which the effort already takes.
|
|
934
1409
|
"bar-over": "effort",
|
|
935
1410
|
"bar-track": "barTrack",
|
|
@@ -971,6 +1446,9 @@ function createSidebar(host) {
|
|
|
971
1446
|
let recordMap = attempt(() => records.read(), /* @__PURE__ */ new Map());
|
|
972
1447
|
const history = createTurnHistoryReader();
|
|
973
1448
|
let historyMap = attempt(() => history.read(), /* @__PURE__ */ new Map());
|
|
1449
|
+
let turnModel = attempt(() => buildTurnModel(historyMap), void 0);
|
|
1450
|
+
const liveProgress = createTurnProgressReader();
|
|
1451
|
+
let progressMap = attempt(() => liveProgress.read(), /* @__PURE__ */ new Map());
|
|
974
1452
|
const [recordVersion, setRecordVersion] = createSignal(0);
|
|
975
1453
|
const watch = createStatusWatch();
|
|
976
1454
|
const fills = createFillMemory();
|
|
@@ -989,10 +1467,12 @@ function createSidebar(host) {
|
|
|
989
1467
|
const next = attempt(() => records.read(), recordMap);
|
|
990
1468
|
const nextHistory = attempt(() => history.read(), historyMap);
|
|
991
1469
|
if (next !== recordMap || nextHistory !== historyMap) {
|
|
1470
|
+
if (nextHistory !== historyMap) turnModel = attempt(() => buildTurnModel(nextHistory), turnModel);
|
|
992
1471
|
recordMap = next;
|
|
993
1472
|
historyMap = nextHistory;
|
|
994
1473
|
setRecordVersion((value) => value + 1);
|
|
995
1474
|
}
|
|
1475
|
+
progressMap = attempt(() => liveProgress.read(), progressMap);
|
|
996
1476
|
setNow(Date.now());
|
|
997
1477
|
}
|
|
998
1478
|
setUsage(attempt(() => usageReader.read(), usage()));
|
|
@@ -1020,21 +1500,23 @@ function createSidebar(host) {
|
|
|
1020
1500
|
}
|
|
1021
1501
|
};
|
|
1022
1502
|
const progressOf = (sessionID) => attempt(() => host.progress(sessionID), EMPTY_SOURCE);
|
|
1023
|
-
const estimateFor = (sessionID, measure, view, agentFallback) => {
|
|
1024
|
-
if (view.state !== "running") return view;
|
|
1503
|
+
const estimateFor = (sessionID, measure, view, agentFallback, children = []) => {
|
|
1504
|
+
if (view.state !== "running" || !turnModel) return view;
|
|
1025
1505
|
const record = recordMap.get(sessionID);
|
|
1026
1506
|
if (!record?.model) return view;
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
const estimate = estimateTurn(samples, {
|
|
1507
|
+
const tag = sessionTag(sessionID);
|
|
1508
|
+
const estimate = turnModel.estimate(turnHistoryKey(record.agent ?? agentFallback, record.model), {
|
|
1030
1509
|
elapsedMs: view.elapsedMs,
|
|
1031
1510
|
chars: measure.chars,
|
|
1032
1511
|
effort: record.effort,
|
|
1033
|
-
calls: measure.calls
|
|
1512
|
+
calls: measure.calls,
|
|
1513
|
+
out: liveOutput(tag ? progressMap.get(tag) : void 0, measure.startedAt),
|
|
1514
|
+
session: tag,
|
|
1515
|
+
now: Date.now()
|
|
1034
1516
|
});
|
|
1035
1517
|
if (!estimate) return view;
|
|
1036
1518
|
const turn = measure.startedAt ?? 0;
|
|
1037
|
-
return { ...view, estimate:
|
|
1519
|
+
return { ...view, estimate: fills.holdEstimate(sessionID, turn, floorByChildren(estimate, view.elapsedMs, children)) };
|
|
1038
1520
|
};
|
|
1039
1521
|
const deriveView = (sessionID) => {
|
|
1040
1522
|
recordVersion();
|
|
@@ -1047,11 +1529,13 @@ function createSidebar(host) {
|
|
|
1047
1529
|
now: clock
|
|
1048
1530
|
});
|
|
1049
1531
|
const lines = /* @__PURE__ */ new Map();
|
|
1532
|
+
const waitedOn = [];
|
|
1050
1533
|
for (const row of rows) {
|
|
1051
1534
|
if (row.state !== "running" || !row.sessionID) continue;
|
|
1052
1535
|
const measure2 = measureProgress(progressOf(row.sessionID));
|
|
1053
|
-
const line = rowProgress(measure2, { started: row.started, now: clock });
|
|
1054
|
-
lines.set(row.sessionID,
|
|
1536
|
+
const line = estimateFor(row.sessionID, measure2, rowProgress(measure2, { started: row.started, now: clock }), row.agent);
|
|
1537
|
+
lines.set(row.sessionID, line);
|
|
1538
|
+
if (!row.background) waitedOn.push(line);
|
|
1055
1539
|
}
|
|
1056
1540
|
const status = gathered.status(sessionID);
|
|
1057
1541
|
const memory = watch.observe(sessionID, status, clock);
|
|
@@ -1062,7 +1546,7 @@ function createSidebar(host) {
|
|
|
1062
1546
|
stoppedAt: gathered.stoppedAt(sessionID) ?? memory.stoppedAt,
|
|
1063
1547
|
now: clock
|
|
1064
1548
|
});
|
|
1065
|
-
const progress = session ? estimateFor(sessionID, measure, session) : void 0;
|
|
1549
|
+
const progress = session ? estimateFor(sessionID, measure, session, void 0, waitedOn) : void 0;
|
|
1066
1550
|
return { rows, lines, progress };
|
|
1067
1551
|
};
|
|
1068
1552
|
const styled = (segment) => {
|
|
@@ -1319,6 +1803,8 @@ function registerV1(api) {
|
|
|
1319
1803
|
effortFaded: mix(theme.warning ?? muted, muted, 0.45),
|
|
1320
1804
|
hover: theme.backgroundElement,
|
|
1321
1805
|
barDone: theme.success ?? theme.accent,
|
|
1806
|
+
barLikely: mix(theme.success ?? theme.accent, muted, 0.45),
|
|
1807
|
+
barBand: mix(muted, theme.success ?? theme.accent, 0.3),
|
|
1322
1808
|
barTrack: mix(muted, theme.backgroundPanel ?? theme.backgroundElement, 0.5),
|
|
1323
1809
|
fraction: theme.text,
|
|
1324
1810
|
tool: theme.info ?? theme.accent,
|
|
@@ -1438,6 +1924,8 @@ function setupV2(context) {
|
|
|
1438
1924
|
effortFaded: mix(theme.text.feedback.warning?.base ?? muted, muted, 0.45),
|
|
1439
1925
|
hover: theme.background.raised.base,
|
|
1440
1926
|
barDone: theme.text.feedback.success.base ?? accent,
|
|
1927
|
+
barLikely: mix(theme.text.feedback.success.base ?? accent, muted, 0.45),
|
|
1928
|
+
barBand: mix(muted, theme.text.feedback.success.base ?? accent, 0.3),
|
|
1441
1929
|
barTrack: mix(muted, theme.background.base ?? theme.background.raised.base, 0.5),
|
|
1442
1930
|
fraction: theme.text.base,
|
|
1443
1931
|
tool: theme.text.feedback.info?.base ?? accent,
|
|
@@ -1525,8 +2013,21 @@ function setupV2(context) {
|
|
|
1525
2013
|
}),
|
|
1526
2014
|
void 0
|
|
1527
2015
|
);
|
|
2016
|
+
const toastNotice = createNoticeToaster(
|
|
2017
|
+
(notice) => context.ui.toast?.show({
|
|
2018
|
+
title: TOAST_TITLE,
|
|
2019
|
+
message: notice.message,
|
|
2020
|
+
variant: notice.level === "error" ? "error" : "warning",
|
|
2021
|
+
duration: TOAST_DURATION_MS
|
|
2022
|
+
})
|
|
2023
|
+
);
|
|
2024
|
+
const stopNotices = attempt(
|
|
2025
|
+
() => context.data.listen?.((event) => void toastNotice(event?.details?.type, event?.details?.data)),
|
|
2026
|
+
void 0
|
|
2027
|
+
);
|
|
1528
2028
|
return () => {
|
|
1529
2029
|
if (release) attempt(release, void 0);
|
|
2030
|
+
if (stopNotices) attempt(stopNotices, void 0);
|
|
1530
2031
|
sidebar.dispose();
|
|
1531
2032
|
};
|
|
1532
2033
|
}
|