great-cto 3.27.0 → 3.27.2
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "great-cto",
|
|
3
3
|
"description": "You already have the agent. This is everything around it. great_cto runs Claude Code as a pipeline of 70 specialist agents \u2014 an independent model checks each stage before the next builds on it, spending caps refuse rather than warn, and three decisions stay yours: what gets built, how, and whether it ships.",
|
|
4
|
-
"version": "3.27.
|
|
4
|
+
"version": "3.27.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Alexander Velikiy",
|
|
7
7
|
"url": "https://hashnode.com/@Greatcto"
|
|
@@ -3035,6 +3035,44 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3035
3035
|
@media (prefers-reduced-motion: reduce) {
|
|
3036
3036
|
*, *::before, *::after { transition-duration: 1ms !important; animation-duration: 1ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
|
|
3037
3037
|
}
|
|
3038
|
+
/* ── Fidelity pass (v3.27.1): the density the design canvas draws. These come
|
|
3039
|
+
after the rules they override — same specificity, later wins. ── */
|
|
3040
|
+
.metric-card { padding: 14px 16px; }
|
|
3041
|
+
.metric-card::before { display: none; }
|
|
3042
|
+
.metric-num { font-size: var(--fs-num-m); }
|
|
3043
|
+
.metric-label { margin-top: 6px; }
|
|
3044
|
+
.inbox-greet { font-family: var(--sans); font-size: var(--fs-small); color: var(--text2); line-height: 1.5; margin: 0 0 12px; font-weight: 400; letter-spacing: 0; }
|
|
3045
|
+
.inbox-row { padding: 8px 12px; gap: 12px; }
|
|
3046
|
+
.inbox-row .ttl { font-size: var(--fs-small); }
|
|
3047
|
+
/* A stalled task is one line: id · reason · title · agent, and the re-run button
|
|
3048
|
+
sits at the row's right instead of on a second grid row under it. */
|
|
3049
|
+
.inbox-row { grid-template-columns: 110px 1fr auto auto auto; align-items: center; }
|
|
3050
|
+
.inbox-row .actions:empty { display: none; }
|
|
3051
|
+
.ledger-title-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
3052
|
+
.side-task-actions .gate-btn { flex: 0 0 auto; min-width: 96px; }
|
|
3053
|
+
.inbox-row .ttl { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
|
|
3054
|
+
.inbox-row .ttl > :first-child { display: inline; }
|
|
3055
|
+
.inbox-row .ttl .why { display: inline-block; font-size: var(--fs-eyebrow); padding: 0 6px; line-height: 18px; white-space: nowrap; }
|
|
3056
|
+
.inbox-row .ttl .meta { margin-top: 0; }
|
|
3057
|
+
.inbox-row .ttl .ttl-desc { flex-basis: 100%; -webkit-line-clamp: 1; margin-top: 0; }
|
|
3058
|
+
.inbox-row .id { padding-top: 0; }
|
|
3059
|
+
.agent-row .budget-chip { display: block; font-size: var(--fs-eyebrow); line-height: 14px; padding: 0 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 96px; margin-left: auto; }
|
|
3060
|
+
.inbox-section-head h3 { font-size: var(--fs-small); }
|
|
3061
|
+
.fleet-title-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
3062
|
+
.fleet-controls-row { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; }
|
|
3063
|
+
.fleet-controls-row input[type="search"] { width: 260px; max-width: 100%; background: var(--bg-muted); border: 1px solid var(--border); color: var(--text); font-family: var(--mono); font-size: var(--fs-small); padding: 6px 10px; }
|
|
3064
|
+
.fleet-controls-row select { background: var(--bg-muted); border: 1px solid var(--border); color: var(--text); font-family: var(--mono); font-size: var(--fs-small); padding: 5px 8px; }
|
|
3065
|
+
.fleet-list { padding: 0; margin: 0; list-style: none; }
|
|
3066
|
+
.fleet-list > li { margin: 0; }
|
|
3067
|
+
.fleet-head, .agent-row { display: grid; grid-template-columns: 10px minmax(200px, 1fr) minmax(220px, 1.3fr) 130px 48px 56px 100px 16px; gap: 12px; align-items: center; }
|
|
3068
|
+
.fleet-head { padding: 6px 12px; font-size: var(--fs-eyebrow); letter-spacing: .08em; text-transform: uppercase; color: var(--text2); border-bottom: 1px solid var(--border); }
|
|
3069
|
+
.agent-row { min-height: 36px; padding: 4px 12px; border-radius: 0; border: none; border-bottom: 1px solid var(--border); width: 100%; }
|
|
3070
|
+
.agent-row .ar-name { font-size: var(--fs-small); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3071
|
+
.agent-row .ar-posture { display: flex; gap: 6px; flex-wrap: nowrap; overflow: hidden; }
|
|
3072
|
+
.agent-row .ar-status { width: 6px; height: 6px; border-radius: 50%; }
|
|
3073
|
+
.agent-row .ar-chev { color: var(--text3); }
|
|
3074
|
+
.budgets-table .bt-desc { display: none; }
|
|
3075
|
+
.mp-card { padding: 14px 16px; }
|
|
3038
3076
|
/* ── 768–1199: the sidebar becomes a 56px icon rail (DESIGN §9). Labels leave
|
|
3039
3077
|
the screen but not the element — every nav item carries its name as
|
|
3040
3078
|
aria-label and title. The Views block and the switcher's text go with the
|
|
@@ -3056,8 +3094,15 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3056
3094
|
|
|
3057
3095
|
.harness-hosts, .so-options { grid-template-columns: 1fr; }
|
|
3058
3096
|
|
|
3059
|
-
.
|
|
3060
|
-
.
|
|
3097
|
+
.fleet-head { display: none; }
|
|
3098
|
+
.agent-row { grid-template-columns: 10px 1fr auto auto auto 16px; grid-template-areas: "dot name name name name chev" "dot posture posture posture posture chev" "dot last runs pass spend chev"; row-gap: 4px; padding: 8px 12px; }
|
|
3099
|
+
.agent-row .ar-status { grid-area: dot; }
|
|
3100
|
+
.agent-row .ar-name { grid-area: name; white-space: normal; }
|
|
3101
|
+
.agent-row .ar-posture { grid-area: posture; flex-wrap: wrap; }
|
|
3102
|
+
.agent-row .ar-last { grid-area: last; }
|
|
3103
|
+
.agent-row .ar-chev { grid-area: chev; }
|
|
3104
|
+
.fleet-controls-row { flex-wrap: wrap; }
|
|
3105
|
+
.fleet-controls-row input[type="search"] { width: 100%; }
|
|
3061
3106
|
|
|
3062
3107
|
/* The one asymmetry DESIGN §3.2 draws: an expensive or unclassified gate's
|
|
3063
3108
|
Reject/Approve stack full-width at phone width; a routine gate's may sit
|
|
@@ -3319,6 +3364,10 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3319
3364
|
<!-- Inbox -->
|
|
3320
3365
|
<div class="panel active" id="panel-inbox">
|
|
3321
3366
|
<div class="inbox-page">
|
|
3367
|
+
<!-- Fidelity pass (v3.27.1): the design's header is the screen's name and
|
|
3368
|
+
its count; the sentence the board used to shout in 52px display
|
|
3369
|
+
type is still said, one line, muted, because it carries the stall. -->
|
|
3370
|
+
<div class="panel-head" style="margin-bottom:6px"><h1 class="page-title" style="margin:0">Decisions <span id="decisions-count" class="muted" style="font-weight:400;font-size:var(--fs-title-s)"></span></h1></div>
|
|
3322
3371
|
<div class="inbox-greet" id="inbox-greet">Good morning. Here's what needs your decision.</div>
|
|
3323
3372
|
<!-- Top rung of the evidence ladder: are the bytes that were reviewed
|
|
3324
3373
|
still the bytes in the tree? Hidden while they match — and now only
|
|
@@ -3507,34 +3556,22 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3507
3556
|
the redesign: a count of installed agents is not a decision (brief,
|
|
3508
3557
|
"What must NOT be on the board"). The view title carries the one
|
|
3509
3558
|
number that is — how many need attention, of how many. -->
|
|
3510
|
-
<section aria-label="Fleet summary">
|
|
3511
|
-
<h1 class="page-title" id="fleet-view-title">Fleet</h1>
|
|
3559
|
+
<section aria-label="Fleet summary" class="fleet-title-row">
|
|
3560
|
+
<h1 class="page-title" id="fleet-view-title" style="margin:0">Fleet</h1>
|
|
3512
3561
|
</section>
|
|
3513
3562
|
|
|
3514
3563
|
<!-- §3.1 Fleet controls -->
|
|
3515
|
-
<section aria-label="Fleet controls"
|
|
3516
|
-
<
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
</
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
<label for="fleet-sort-by">Sort by</label>
|
|
3527
|
-
<select id="fleet-sort-by">
|
|
3528
|
-
<option value="runs_30d">runs (30d)</option>
|
|
3529
|
-
<option value="last_run">last run</option>
|
|
3530
|
-
<option value="success_rate">success rate</option>
|
|
3531
|
-
<option value="savings_x">savings ×</option>
|
|
3532
|
-
<option value="llm_usd_30d_est">LLM spend (30d)</option>
|
|
3533
|
-
<option value="slug">slug A→Z</option>
|
|
3534
|
-
</select>
|
|
3535
|
-
<button type="button" id="fleet-reset-filters">Reset filters</button>
|
|
3536
|
-
</div>
|
|
3537
|
-
</div>
|
|
3564
|
+
<section aria-label="Fleet controls" class="fleet-controls-row">
|
|
3565
|
+
<input type="search" id="fleet-search" placeholder="Filter agents ( / )" aria-label="Filter agents by slug" />
|
|
3566
|
+
<label for="fleet-sort-by" class="muted cap">Sort</label>
|
|
3567
|
+
<select id="fleet-sort-by">
|
|
3568
|
+
<option value="runs_30d">runs (30d)</option>
|
|
3569
|
+
<option value="last_run">last run</option>
|
|
3570
|
+
<option value="success_rate">success rate</option>
|
|
3571
|
+
<option value="llm_usd_30d_est">LLM spend (30d)</option>
|
|
3572
|
+
<option value="slug">slug A→Z</option>
|
|
3573
|
+
</select>
|
|
3574
|
+
<button type="button" id="fleet-reset-filters" class="btn-ghost-sm">Reset</button>
|
|
3538
3575
|
</section>
|
|
3539
3576
|
|
|
3540
3577
|
<!-- §3.1 Fleet list -->
|
|
@@ -3595,7 +3632,21 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3595
3632
|
|
|
3596
3633
|
<div class="panel" id="panel-budgets" role="tabpanel">
|
|
3597
3634
|
<div class="metrics-page">
|
|
3598
|
-
<
|
|
3635
|
+
<div class="ledger-title-row">
|
|
3636
|
+
<h1 class="page-title" style="margin:0">Ledger</h1>
|
|
3637
|
+
<!-- The period governs every figure below it, so it sits where the
|
|
3638
|
+
figures start — beside the name, not under the stuck list. -->
|
|
3639
|
+
<div class="period-toolbar" style="margin:0">
|
|
3640
|
+
<span class="period-label">PERIOD</span>
|
|
3641
|
+
<div class="period-chips" id="period-chips">
|
|
3642
|
+
<button class="period-chip" data-period="1">1D</button>
|
|
3643
|
+
<button class="period-chip" data-period="7">7D</button>
|
|
3644
|
+
<button class="period-chip active" data-period="30">30D</button>
|
|
3645
|
+
<button class="period-chip" data-period="90">90D</button>
|
|
3646
|
+
<button class="period-chip" data-period="365">1Y</button>
|
|
3647
|
+
</div>
|
|
3648
|
+
</div>
|
|
3649
|
+
</div>
|
|
3599
3650
|
<!-- Ledger (great_cto-ki1x.7): the one decision this screen supports is
|
|
3600
3651
|
"is something running that I should stop". Two figures, the stage
|
|
3601
3652
|
strip and the stuck list answer it; the caps table below is where
|
|
@@ -3604,16 +3655,6 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3604
3655
|
<!-- From the old Metrics tab (great_cto-ki1x.16): the period chips, the caps
|
|
3605
3656
|
card and the cost panel keep their ids — the code that fills them did
|
|
3606
3657
|
not move, only the screen that shows them. -->
|
|
3607
|
-
<div class="period-toolbar">
|
|
3608
|
-
<span class="period-label">PERIOD</span>
|
|
3609
|
-
<div class="period-chips" id="period-chips">
|
|
3610
|
-
<button class="period-chip" data-period="1">1D</button>
|
|
3611
|
-
<button class="period-chip" data-period="7">7D</button>
|
|
3612
|
-
<button class="period-chip active" data-period="30">30D</button>
|
|
3613
|
-
<button class="period-chip" data-period="90">90D</button>
|
|
3614
|
-
<button class="period-chip" data-period="365">1Y</button>
|
|
3615
|
-
</div>
|
|
3616
|
-
</div>
|
|
3617
3658
|
<!-- Budgets live HERE, on Metrics, not two clicks deep in the Agents
|
|
3618
3659
|
drill-in. A control the operator could not find is a control they do
|
|
3619
3660
|
not have — and it was invisible on top of that, hidden whenever a
|
|
@@ -3621,16 +3662,11 @@ h1, h2, h3, h4 { font-weight: 600; font-family: var(--sans); }
|
|
|
3621
3662
|
needs the button to declare one. -->
|
|
3622
3663
|
<div class="mp-section-label" style="margin-top:24px">Agent budgets</div>
|
|
3623
3664
|
<div id="agent-budgets" class="mp-card" style="padding:12px 16px;font-size:var(--fs-small);margin-bottom:4px;"></div>
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
A cap on what one agent may spend. When measured spend passes it, the pipeline
|
|
3630
|
-
refuses to dispatch that agent and says so — it does not fail quietly.
|
|
3631
|
-
Spend is measured from verdicts that carry a cost; while none does, the cap
|
|
3632
|
-
cannot fire and says that too. An estimate never refuses.
|
|
3633
|
-
</p>
|
|
3665
|
+
<!-- The old cost panel (four tiles: 'vs human team', a projected month, a
|
|
3666
|
+
daily burn, an empty chart) left in the fidelity pass — a forecast and a
|
|
3667
|
+
human-team comparison are two of the brief's refusals, and the
|
|
3668
|
+
Ledger head already carries spend with its provenance. -->
|
|
3669
|
+
|
|
3634
3670
|
<!-- Whether the judge is connected at all. Without a key nothing breaks
|
|
3635
3671
|
— every stage is scored `unverifiable`, honestly — but "not
|
|
3636
3672
|
connected" and "nothing to check" read the same from here, and the
|
|
@@ -3957,6 +3993,7 @@ function gateChip(t) {
|
|
|
3957
3993
|
}
|
|
3958
3994
|
function renderInbox(d) {
|
|
3959
3995
|
inboxSecondOpinion = d?.second_opinion || null;
|
|
3996
|
+
{ const c = document.getElementById('decisions-count'); const n = (d?.pending_gates || []).length; if (c) c.textContent = n ? `${n} waiting` : 'nothing waiting'; }
|
|
3960
3997
|
for (const g of (d?.pending_gates || [])) if (g?.id && g.reversibility) GATE_REVERSIBILITY[g.id] = g.reversibility;
|
|
3961
3998
|
if (d && Array.isArray(d.pending_gates)) d = { ...d, pending_gates: sortGatesByCostOfUndo(d.pending_gates) };
|
|
3962
3999
|
// Kept so renderResume can ask what this section is already showing, rather
|
|
@@ -4523,6 +4560,11 @@ function renderInboxList(rootId, items, countId, opts = {}) {
|
|
|
4523
4560
|
+ `<button type="button" class="gate-btn" title="Copy a git diff limited to the reviewed files"`
|
|
4524
4561
|
+ ` onclick="event.stopPropagation(); copyDiff(${esc(JSON.stringify(JSON.stringify(t.__files)))})">Copy diff</button></div>`
|
|
4525
4562
|
: '';
|
|
4563
|
+
const unblock = (!canDecide && !t.__synthetic && t.id && (t.raw_status || t.status) === 'blocked')
|
|
4564
|
+
? `<div class="actions" onclick="event.stopPropagation()">`
|
|
4565
|
+
+ `<button type="button" class="gate-btn gate-approve" title="Set the task back to open"`
|
|
4566
|
+
+ ` onclick="event.stopPropagation(); taskDecision('${esc(t.id)}', 'open')">Unblock</button></div>`
|
|
4567
|
+
: '';
|
|
4526
4568
|
const rerun = (!canDecide && !t.__synthetic && t.agent && t.id)
|
|
4527
4569
|
? `<div class="actions" onclick="event.stopPropagation()">`
|
|
4528
4570
|
+ `<button type="button" class="gate-btn" title="Copy the line that re-runs this agent"`
|
|
@@ -4577,7 +4619,7 @@ function renderInboxList(rootId, items, countId, opts = {}) {
|
|
|
4577
4619
|
${canDecide ? gateChip(t) : `<span class="id">${esc(t.id || '')}</span>`}
|
|
4578
4620
|
<span class="ttl">${canDecide ? '' : reasonChip}${esc(canDecide ? String(t.title || '').replace(/^gate:[a-z-]+\s*[—–-]?\s*/, '') : t.title)}${desc}<span class="meta">${meta}</span>${tierNote}</span>
|
|
4579
4621
|
${canDecide ? reviewerCells(t) : ''}
|
|
4580
|
-
${actions}${rerun}${diffBtn}
|
|
4622
|
+
${actions}${unblock}${rerun}${diffBtn}
|
|
4581
4623
|
</div>`;
|
|
4582
4624
|
}).join('');
|
|
4583
4625
|
}
|
|
@@ -7847,11 +7889,22 @@ function openSide(task) {
|
|
|
7847
7889
|
Reject
|
|
7848
7890
|
</button>
|
|
7849
7891
|
</div>` : '';
|
|
7892
|
+
// A task that is not a gate still needs a decision the operator can take
|
|
7893
|
+
// here: a blocked one is unblocked, an open one is started, either is closed.
|
|
7894
|
+
// Before this the drawer showed the status and told the operator to go run
|
|
7895
|
+
// `bd update` — a board that could read the state but not change it.
|
|
7896
|
+
const taskActions = (!task.is_gate && rawStatus !== 'closed') ? `
|
|
7897
|
+
<div class="side-gate-actions side-task-actions">
|
|
7898
|
+
${rawStatus === 'blocked' ? `<button class="gate-btn gate-approve" onclick="taskDecision('${esc(task.id)}', 'open')">Unblock</button>` : ''}
|
|
7899
|
+
${rawStatus === 'open' ? `<button class="gate-btn gate-approve" onclick="taskDecision('${esc(task.id)}', 'in_progress')">Start</button>` : ''}
|
|
7900
|
+
${rawStatus === 'in_progress' || rawStatus === 'open' ? `<button class="gate-btn" onclick="taskDecision('${esc(task.id)}', 'blocked')">Block</button>` : ''}
|
|
7901
|
+
<button class="gate-btn gate-reject" onclick="taskDecision('${esc(task.id)}', 'closed')">Close</button>
|
|
7902
|
+
</div>` : '';
|
|
7850
7903
|
_sideTask = task; // so the doc viewer's "← back" can restore this task
|
|
7851
7904
|
document.getElementById('side-body').innerHTML = `
|
|
7852
7905
|
<div class="side-title">${esc(task.title)}</div>
|
|
7853
7906
|
${desc}
|
|
7854
|
-
${gateActions}
|
|
7907
|
+
${gateActions}${taskActions}
|
|
7855
7908
|
<div class="props">
|
|
7856
7909
|
${rows.map(([k, v]) => `<div class="prop-key">${k}</div><div class="prop-val">${v}</div>`).join('')}
|
|
7857
7910
|
</div>
|
|
@@ -7884,6 +7937,23 @@ function openSide(task) {
|
|
|
7884
7937
|
</div>`).join('')}`;
|
|
7885
7938
|
});
|
|
7886
7939
|
}
|
|
7940
|
+
// One status change, through the endpoint the board has had since the kanban:
|
|
7941
|
+
// POST /api/tasks/:id/status. Closing asks once — it is the only transition the
|
|
7942
|
+
// list stops showing; the rest are visible and reversible from the same drawer.
|
|
7943
|
+
async function taskDecision(id, status) {
|
|
7944
|
+
if (status === 'closed' && !confirm(`Close ${id}? It leaves every list on this board.`)) return;
|
|
7945
|
+
const r = await api(`/api/tasks/${encodeURIComponent(id)}/status${pqs()}`, {
|
|
7946
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }),
|
|
7947
|
+
}).catch(() => null);
|
|
7948
|
+
if (!r || r.error) { showToast('✗ ' + esc(r?.error || 'status not changed'), 'error', 4000); return; }
|
|
7949
|
+
const said = { open: 'unblocked', in_progress: 'started', blocked: 'blocked', closed: 'closed' }[status] || status;
|
|
7950
|
+
showToast(`<strong>${esc(id)}</strong> ${said}`, status === 'closed' ? 'error' : 'success', 2500);
|
|
7951
|
+
if (_sideTask && _sideTask.id === id) {
|
|
7952
|
+
if (status === 'closed') closeSide();
|
|
7953
|
+
else openSide({ ..._sideTask, status, raw_status: status });
|
|
7954
|
+
}
|
|
7955
|
+
refreshInbox();
|
|
7956
|
+
}
|
|
7887
7957
|
// ── Documents referenced by a task: extraction + the inline side-panel viewer ──
|
|
7888
7958
|
let _sideTask = null;
|
|
7889
7959
|
function extractDocRefs(task) {
|
|
@@ -8201,7 +8271,7 @@ function switchTab(id, el) {
|
|
|
8201
8271
|
// promised 50 unread and the screen showed settings. Declared and not
|
|
8202
8272
|
// consumed, in the UI this time.
|
|
8203
8273
|
if (lid === 'notifications') loadNotifHistory();
|
|
8204
|
-
if (lid === 'budgets')
|
|
8274
|
+
if (lid === 'budgets') loadBudgetsPage();
|
|
8205
8275
|
if (lid === 'agents') {
|
|
8206
8276
|
// pqsd(), not pqs(): this call omitted `days` entirely, so the server applied
|
|
8207
8277
|
// its 30-day default and overwrote whatever window the period chips had
|
|
@@ -8530,7 +8600,8 @@ function renderFleetList() {
|
|
|
8530
8600
|
const order = [...DOMAINS, ...Object.keys(byDomain).filter((d) => !DOMAINS.includes(d))];
|
|
8531
8601
|
const expanded = order.filter((d) => (byDomain[d] || []).length);
|
|
8532
8602
|
const collapsed = order.filter((d) => !(byDomain[d] || []).length && totals[d]);
|
|
8533
|
-
const
|
|
8603
|
+
const head = `<li class="fleet-head" aria-hidden="true"><span></span><span>Agent</span><span>Posture</span><span>Last verdict</span><span class="tab">Runs</span><span class="tab">Pass</span><span class="tab">30d $</span><span></span></li>`;
|
|
8604
|
+
const rows = head + expanded.map((d) => `
|
|
8534
8605
|
<li class="fleet-group-head"><span>${esc(d)} · ${byDomain[d].length} of ${totals[d] || 0}</span></li>
|
|
8535
8606
|
${byDomain[d].map(agentRowHtml).join('')}`).join('');
|
|
8536
8607
|
const tail = collapsed.length
|
|
@@ -8586,18 +8657,12 @@ function agentRowHtml(a) {
|
|
|
8586
8657
|
onclick="openAgentDrawer('${esc(a.slug)}')"
|
|
8587
8658
|
onkeydown="if(event.key==='Enter'){event.preventDefault();openAgentDrawer('${esc(a.slug)}')}">
|
|
8588
8659
|
<span class="ar-status" aria-hidden="true"></span>
|
|
8589
|
-
<span class="ar-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
<span class="mono cap tab" title="pass rate over those runs">${pass}</span>
|
|
8596
|
-
<span class="mono cap tab" title="LLM spend, last 30 days">${spend}</span>
|
|
8597
|
-
${budgetChip(a.budget)}
|
|
8598
|
-
${a.retired ? '<span style="color:var(--agent-retired)">retired</span>' : ''}
|
|
8599
|
-
</span>
|
|
8600
|
-
</span>
|
|
8660
|
+
<span class="ar-name">${esc(a.slug)} <span class="mono cap muted" style="font-weight:400">${model}</span>${a.retired ? ' <span class="cap" style="color:var(--agent-retired)">retired</span>' : ''}</span>
|
|
8661
|
+
<span class="ar-posture">${postureChips(a)}</span>
|
|
8662
|
+
<span class="mono cap ar-last">${last}</span>
|
|
8663
|
+
<span class="mono cap tab" title="runs in the last 30 days">${runs}</span>
|
|
8664
|
+
<span class="mono cap tab" title="pass rate over those runs">${pass}</span>
|
|
8665
|
+
<span class="mono cap tab" title="LLM spend, last 30 days">${spend}${budgetChip(a.budget)}</span>
|
|
8601
8666
|
<span class="ar-chev" aria-hidden="true">›</span>
|
|
8602
8667
|
</button>
|
|
8603
8668
|
</li>`;
|
package/package.json
CHANGED