clementine-agent 1.18.87 → 1.18.89
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/cli/cron.js +3 -0
- package/dist/cli/dashboard.js +143 -5
- package/dist/gateway/cron-scheduler.js +6 -0
- package/dist/gateway/router.d.ts +2 -0
- package/dist/gateway/router.js +1 -0
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
package/dist/cli/cron.js
CHANGED
|
@@ -149,6 +149,7 @@ export async function cmdCronRun(jobName) {
|
|
|
149
149
|
// to the Event store.
|
|
150
150
|
const sideChannel = gateway.consumeLastCronRunMetadata?.();
|
|
151
151
|
const runIdFromGateway = sideChannel?.runId;
|
|
152
|
+
const totalCostFromGateway = sideChannel?.totalCostUsd;
|
|
152
153
|
const entry = {
|
|
153
154
|
jobName: job.name,
|
|
154
155
|
startedAt: startedAt.toISOString(),
|
|
@@ -159,6 +160,8 @@ export async function cmdCronRun(jobName) {
|
|
|
159
160
|
outputPreview: response ? response.slice(0, 200) : undefined,
|
|
160
161
|
trigger,
|
|
161
162
|
...(runIdFromGateway ? { id: runIdFromGateway } : {}),
|
|
163
|
+
// 1.18.89: per-run cost for the dashboard Cost column / Health Strip tile.
|
|
164
|
+
...(totalCostFromGateway != null ? { totalCostUsd: totalCostFromGateway } : {}),
|
|
162
165
|
};
|
|
163
166
|
// PRD Phase 1.1: goal-orientation evaluator (mirrors the daemon path).
|
|
164
167
|
if (job.successSchema || (job.successCriteriaText && job.successCriteriaText.trim())) {
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -15207,6 +15207,40 @@ if('serviceWorker' in navigator){navigator.serviceWorker.getRegistrations().then
|
|
|
15207
15207
|
/* ── Recent history row hover (Tasks page bottom zone) ── */
|
|
15208
15208
|
.history-row { transition: background 0.12s ease; }
|
|
15209
15209
|
.history-row:hover { background: var(--bg-hover); }
|
|
15210
|
+
/* PRD §12 / 1.18.88: Health Strip — six glanceable tiles above the
|
|
15211
|
+
Tasks pane. Always visible, refreshes on SSE + 30s poll. */
|
|
15212
|
+
.health-strip {
|
|
15213
|
+
display: grid;
|
|
15214
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
15215
|
+
gap: 10px;
|
|
15216
|
+
margin-bottom: 18px;
|
|
15217
|
+
}
|
|
15218
|
+
.health-tile {
|
|
15219
|
+
background: var(--bg-secondary);
|
|
15220
|
+
border: 1px solid var(--border);
|
|
15221
|
+
border-radius: var(--radius);
|
|
15222
|
+
padding: 12px 14px;
|
|
15223
|
+
display: flex;
|
|
15224
|
+
flex-direction: column;
|
|
15225
|
+
gap: 4px;
|
|
15226
|
+
}
|
|
15227
|
+
.health-tile-label {
|
|
15228
|
+
font-size: 10px;
|
|
15229
|
+
color: var(--text-muted);
|
|
15230
|
+
text-transform: uppercase;
|
|
15231
|
+
letter-spacing: 0.05em;
|
|
15232
|
+
font-weight: 500;
|
|
15233
|
+
}
|
|
15234
|
+
.health-tile-value {
|
|
15235
|
+
font-size: 20px;
|
|
15236
|
+
font-weight: 600;
|
|
15237
|
+
color: var(--text-primary);
|
|
15238
|
+
line-height: 1.2;
|
|
15239
|
+
}
|
|
15240
|
+
.health-tile-sub {
|
|
15241
|
+
font-size: 11px;
|
|
15242
|
+
color: var(--text-muted);
|
|
15243
|
+
}
|
|
15210
15244
|
/* PRD Phase 1.2: "Run task once" running-state pulse on the Last run tab. */
|
|
15211
15245
|
@keyframes pulse {
|
|
15212
15246
|
0%, 100% { opacity: 0.4; transform: scale(0.85); }
|
|
@@ -23739,6 +23773,93 @@ function renderRunningCard(item) {
|
|
|
23739
23773
|
+ '</div></div>';
|
|
23740
23774
|
}
|
|
23741
23775
|
|
|
23776
|
+
// ── PRD §12 / 1.18.88: Health Strip ───────────────────────────────────
|
|
23777
|
+
// Six glanceable tiles above the Tasks pane: 24h runs / success rate /
|
|
23778
|
+
// P50 latency / P95 latency / active runs / top failure category.
|
|
23779
|
+
// Computed client-side from /api/cron/runs (already fetched by refreshCron).
|
|
23780
|
+
async function refreshHealthStrip() {
|
|
23781
|
+
var strip = document.getElementById('health-strip');
|
|
23782
|
+
if (!strip) return;
|
|
23783
|
+
var runs = [];
|
|
23784
|
+
try {
|
|
23785
|
+
var r = await apiFetch('/api/cron/runs?limit=200');
|
|
23786
|
+
var d = await r.json();
|
|
23787
|
+
runs = (d && d.runs) || [];
|
|
23788
|
+
} catch (e) { /* leave the strip empty if fetch fails */ }
|
|
23789
|
+
// Filter to last 24h.
|
|
23790
|
+
var cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
23791
|
+
var last24 = runs.filter(function(rn) { return rn.startedAt && new Date(rn.startedAt).getTime() >= cutoff; });
|
|
23792
|
+
// Success / failure split.
|
|
23793
|
+
var ok = last24.filter(function(rn) { return rn.status === 'ok'; }).length;
|
|
23794
|
+
var failed = last24.filter(function(rn) { return rn.status === 'error' || rn.status === 'timeout' || rn.status === 'lost'; }).length;
|
|
23795
|
+
var successRate = last24.length > 0 ? Math.round((ok / last24.length) * 100) : null;
|
|
23796
|
+
// Latency distribution (terminal runs only — exclude in-progress).
|
|
23797
|
+
var durations = last24
|
|
23798
|
+
.filter(function(rn) { return rn.durationMs != null && rn.status !== 'running' && rn.status !== 'lost'; })
|
|
23799
|
+
.map(function(rn) { return rn.durationMs; })
|
|
23800
|
+
.sort(function(a, b) { return a - b; });
|
|
23801
|
+
function pct(arr, p) {
|
|
23802
|
+
if (arr.length === 0) return null;
|
|
23803
|
+
var idx = Math.min(arr.length - 1, Math.floor(arr.length * p / 100));
|
|
23804
|
+
return arr[idx];
|
|
23805
|
+
}
|
|
23806
|
+
var p50 = pct(durations, 50);
|
|
23807
|
+
var p95 = pct(durations, 95);
|
|
23808
|
+
// Active runs come from /api/build/operations runningNow which refreshCron
|
|
23809
|
+
// has already loaded into the cronJobsData side-channel; tap operations
|
|
23810
|
+
// summary if it's hanging around in the DOM, otherwise fetch it ourselves.
|
|
23811
|
+
var activeRuns = 0;
|
|
23812
|
+
try {
|
|
23813
|
+
var ops = await apiFetch('/api/build/operations?hours=1&limit=10').then(function(rr) { return rr.json(); });
|
|
23814
|
+
activeRuns = ((ops && ops.runningNow) || []).length;
|
|
23815
|
+
} catch (e) { /* fall back to 0 */ }
|
|
23816
|
+
// Top failure category for the day.
|
|
23817
|
+
var catCounts = {};
|
|
23818
|
+
for (var i = 0; i < last24.length; i++) {
|
|
23819
|
+
var c = last24[i].failureCategory;
|
|
23820
|
+
if (c) catCounts[c] = (catCounts[c] || 0) + 1;
|
|
23821
|
+
}
|
|
23822
|
+
var topCat = null, topCount = 0;
|
|
23823
|
+
Object.keys(catCounts).forEach(function(k) {
|
|
23824
|
+
if (catCounts[k] > topCount) { topCat = k; topCount = catCounts[k]; }
|
|
23825
|
+
});
|
|
23826
|
+
// Render six tiles.
|
|
23827
|
+
function tile(label, value, sub, color) {
|
|
23828
|
+
return '<div class="health-tile">'
|
|
23829
|
+
+ '<div class="health-tile-label">' + esc(label) + '</div>'
|
|
23830
|
+
+ '<div class="health-tile-value"' + (color ? ' style="color:' + color + '"' : '') + '>' + (value === null || value === undefined ? '—' : esc(String(value))) + '</div>'
|
|
23831
|
+
+ (sub ? '<div class="health-tile-sub">' + esc(sub) + '</div>' : '')
|
|
23832
|
+
+ '</div>';
|
|
23833
|
+
}
|
|
23834
|
+
var srColor = successRate === null ? null
|
|
23835
|
+
: successRate >= 95 ? 'var(--green)'
|
|
23836
|
+
: successRate >= 80 ? 'var(--yellow)'
|
|
23837
|
+
: 'var(--red)';
|
|
23838
|
+
// 1.18.89: cost tile. Sums totalCostUsd across the 24h window. Runs that
|
|
23839
|
+
// pre-date 1.18.89 don't have the field — they contribute 0 (the user
|
|
23840
|
+
// sees a partial number that grows as new runs land).
|
|
23841
|
+
var totalCost = 0;
|
|
23842
|
+
var runsWithCost = 0;
|
|
23843
|
+
for (var ci = 0; ci < last24.length; ci++) {
|
|
23844
|
+
if (typeof last24[ci].totalCostUsd === 'number') {
|
|
23845
|
+
totalCost += last24[ci].totalCostUsd;
|
|
23846
|
+
runsWithCost++;
|
|
23847
|
+
}
|
|
23848
|
+
}
|
|
23849
|
+
var costLabel = runsWithCost === 0 ? '—' : (totalCost < 0.01 ? '$' + totalCost.toFixed(4) : '$' + totalCost.toFixed(2));
|
|
23850
|
+
var costSub = runsWithCost === 0 ? 'no priced runs yet' : runsWithCost + ' of ' + last24.length + ' runs priced';
|
|
23851
|
+
|
|
23852
|
+
var html = '';
|
|
23853
|
+
html += tile('Runs · 24h', last24.length, ok + ' ok · ' + failed + ' failed');
|
|
23854
|
+
html += tile('Success rate', successRate === null ? '—' : (successRate + '%'), null, srColor);
|
|
23855
|
+
html += tile('Cost · 24h', costLabel, costSub);
|
|
23856
|
+
html += tile('P50 latency', p50 === null ? '—' : formatDurationMs(p50), 'median run time');
|
|
23857
|
+
html += tile('P95 latency', p95 === null ? '—' : formatDurationMs(p95), '95th percentile');
|
|
23858
|
+
html += tile('Running now', activeRuns, activeRuns === 0 ? 'idle' : 'live');
|
|
23859
|
+
html += tile('Top failure', topCat ? _runListCategoryLabel(topCat) : '—', topCat ? topCount + ' run' + (topCount === 1 ? '' : 's') : 'no failures', topCat ? _runListCategoryColor(topCat) : null);
|
|
23860
|
+
strip.innerHTML = html;
|
|
23861
|
+
}
|
|
23862
|
+
|
|
23742
23863
|
// ── PRD Phase 3: Run list ──────────────────────────────────────────────
|
|
23743
23864
|
// Single sortable/filterable table of every CronRunEntry across all tasks.
|
|
23744
23865
|
// Filters: status, task name, time window. Browser-local saved views.
|
|
@@ -23875,10 +23996,10 @@ function renderRunListBody(allRuns) {
|
|
|
23875
23996
|
return html;
|
|
23876
23997
|
}
|
|
23877
23998
|
// Table — same shape as the Recent History list on the Tasks page,
|
|
23878
|
-
// but sortable and with a Trigger column.
|
|
23999
|
+
// but sortable and with a Trigger + Cost column.
|
|
23879
24000
|
html += '<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius)">';
|
|
23880
|
-
html += '<div style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr)
|
|
23881
|
-
+ '<div></div><div title="Goal verdict">Goal</div><div>Task</div><div>Trigger</div><div>Started</div><div>Duration</div><div></div>'
|
|
24001
|
+
html += '<div style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 80px minmax(160px,1fr) 70px 70px auto;gap:10px;padding:8px 14px;border-bottom:1px solid var(--border);font-size:11px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.04em;font-weight:500">'
|
|
24002
|
+
+ '<div></div><div title="Goal verdict">Goal</div><div>Task</div><div>Trigger</div><div>Started</div><div>Duration</div><div title="Total cost in USD">Cost</div><div></div>'
|
|
23882
24003
|
+ '</div>';
|
|
23883
24004
|
for (var i = 0; i < filtered.length; i++) {
|
|
23884
24005
|
var entry = filtered[i] || {};
|
|
@@ -23926,7 +24047,14 @@ function renderRunListBody(allRuns) {
|
|
|
23926
24047
|
} else if (entry.outputPreview) {
|
|
23927
24048
|
preview = '<div style="font-size:11px;color:var(--text-muted);margin-top:2px;word-break:break-word">' + esc(String(entry.outputPreview).slice(0, 120)) + '</div>';
|
|
23928
24049
|
}
|
|
23929
|
-
|
|
24050
|
+
// 1.18.89: cost label. Showing 4 decimals for sub-penny costs (Haiku
|
|
24051
|
+
// runs land in fractions of a cent), 2 decimals when ≥ $0.01.
|
|
24052
|
+
var costLabel = '—';
|
|
24053
|
+
if (entry.totalCostUsd != null) {
|
|
24054
|
+
var c = entry.totalCostUsd;
|
|
24055
|
+
costLabel = c < 0.01 ? '$' + c.toFixed(4) : '$' + c.toFixed(2);
|
|
24056
|
+
}
|
|
24057
|
+
html += '<div class="history-row" data-trace-job="' + esc(jobName) + '" style="display:grid;grid-template-columns:24px 24px minmax(180px,1.2fr) 80px minmax(160px,1fr) 70px 70px auto;gap:10px;align-items:start;padding:8px 14px;border-bottom:1px solid var(--border);cursor:pointer">'
|
|
23930
24058
|
+ '<div style="color:' + statusColor + ';font-size:14px;line-height:18px;text-align:center" title="' + esc(status) + '">' + statusIcon + '</div>'
|
|
23931
24059
|
+ goalCell
|
|
23932
24060
|
+ '<div style="min-width:0">'
|
|
@@ -23937,6 +24065,7 @@ function renderRunListBody(allRuns) {
|
|
|
23937
24065
|
+ '<div style="font-size:11px;color:' + triggerColor + ';line-height:18px">' + esc(triggerLabel) + '</div>'
|
|
23938
24066
|
+ '<div style="font-size:12px;color:var(--text-secondary);line-height:18px">' + esc(startedLabel) + '</div>'
|
|
23939
24067
|
+ '<div style="font-size:12px;color:var(--text-muted);line-height:18px">' + esc(durationLabel) + '</div>'
|
|
24068
|
+
+ '<div style="font-size:12px;color:var(--text-muted);line-height:18px;font-family:\\x27JetBrains Mono\\x27,monospace">' + esc(costLabel) + '</div>'
|
|
23940
24069
|
+ '<div style="display:flex;gap:6px;align-items:center"><button class="btn-sm" onclick="event.stopPropagation();openRunOrTrace(\\x27' + safeName + '\\x27,' + (entry.id ? '\\x27' + jsStr(entry.id) + '\\x27' : 'null') + ')" style="font-size:11px;padding:3px 8px">' + (entry.id ? 'Open run' : 'Trace') + '</button></div>'
|
|
23941
24070
|
+ '</div>';
|
|
23942
24071
|
}
|
|
@@ -24375,7 +24504,11 @@ async function refreshCron() {
|
|
|
24375
24504
|
var visibleRunning = ownerScoped ? (ops.runningNow || []).filter(function(i) { return buildOpsOwnerMatches(i.owner || ''); }) : (ops.runningNow || []);
|
|
24376
24505
|
var ownerFilter = getBuildOwnerFilter();
|
|
24377
24506
|
|
|
24378
|
-
|
|
24507
|
+
// PRD §12 / 1.18.88: Health Strip placeholder. The runs payload from
|
|
24508
|
+
// /api/cron/runs (already fetched alongside ops) feeds the metrics.
|
|
24509
|
+
// Render an empty shell first; refreshHealthStrip fills it in.
|
|
24510
|
+
var html = '<div id="health-strip" class="health-strip"></div>';
|
|
24511
|
+
html += renderOperationsSummary(ops);
|
|
24379
24512
|
|
|
24380
24513
|
// ── Zone 1 — Running now (promoted to top, primary "what's live" view) ──
|
|
24381
24514
|
if (visibleRunning.length > 0) {
|
|
@@ -24424,6 +24557,11 @@ async function refreshCron() {
|
|
|
24424
24557
|
html += renderRecentHistoryList(historyData);
|
|
24425
24558
|
|
|
24426
24559
|
panel.innerHTML = html;
|
|
24560
|
+
// PRD §12 / 1.18.88: populate the Health Strip after the panel renders.
|
|
24561
|
+
// Fire-and-forget — the strip lives at the top, fills in async.
|
|
24562
|
+
if (typeof refreshHealthStrip === 'function') {
|
|
24563
|
+
refreshHealthStrip().catch(function() { /* non-fatal */ });
|
|
24564
|
+
}
|
|
24427
24565
|
panel.onclick = function(ev) {
|
|
24428
24566
|
var target = ev.target;
|
|
24429
24567
|
while (target && target.id !== 'panel-cron') {
|
|
@@ -1210,6 +1210,9 @@ export class CronScheduler {
|
|
|
1210
1210
|
trigger,
|
|
1211
1211
|
// 1.18.85: stable UUID linking this run to its Event store entries.
|
|
1212
1212
|
...(cronMetadata?.runId ? { id: cronMetadata.runId } : {}),
|
|
1213
|
+
// 1.18.89: per-run cost for the Run list + Health Strip. Comes from
|
|
1214
|
+
// SDK's ResultMessage.total_cost_usd via runAgent's side-channel.
|
|
1215
|
+
...(cronMetadata?.totalCostUsd != null ? { totalCostUsd: cronMetadata.totalCostUsd } : {}),
|
|
1213
1216
|
// Trick capability metadata — surfaced by the dashboard's
|
|
1214
1217
|
// "ran with: …" line. Omit empty arrays to keep the JSONL light.
|
|
1215
1218
|
...(cronMetadata?.skillsApplied?.length ? { skillsApplied: cronMetadata.skillsApplied } : {}),
|
|
@@ -1306,6 +1309,9 @@ export class CronScheduler {
|
|
|
1306
1309
|
// can show trigger + open the partial Event log if any.
|
|
1307
1310
|
trigger,
|
|
1308
1311
|
...(errCronMetadata?.runId ? { id: errCronMetadata.runId } : {}),
|
|
1312
|
+
// 1.18.89: cost on the error path too — partial-completion runs may
|
|
1313
|
+
// still have spent budget before the throw.
|
|
1314
|
+
...(errCronMetadata?.totalCostUsd != null ? { totalCostUsd: errCronMetadata.totalCostUsd } : {}),
|
|
1309
1315
|
...(errCronMetadata?.skillsApplied?.length ? { skillsApplied: errCronMetadata.skillsApplied } : {}),
|
|
1310
1316
|
...(errCronMetadata?.skillsMissing?.length ? { skillsMissing: errCronMetadata.skillsMissing } : {}),
|
|
1311
1317
|
...(errCronMetadata?.allowedToolsApplied?.length ? { allowedToolsApplied: errCronMetadata.allowedToolsApplied } : {}),
|
package/dist/gateway/router.d.ts
CHANGED
|
@@ -239,6 +239,8 @@ export declare class Gateway {
|
|
|
239
239
|
mcpServersApplied: string[];
|
|
240
240
|
/** PRD §6 / 1.18.85: run UUID from runAgent. */
|
|
241
241
|
runId?: string;
|
|
242
|
+
/** PRD §12 / 1.18.89: total cost in USD from runAgent's SDK result. */
|
|
243
|
+
totalCostUsd?: number;
|
|
242
244
|
} | undefined;
|
|
243
245
|
requestApproval(descriptionOrId: string, explicitId?: string): Promise<boolean | string>;
|
|
244
246
|
resolveApproval(requestId: string, result: boolean | string): void;
|
package/dist/gateway/router.js
CHANGED
|
@@ -2024,6 +2024,7 @@ export class Gateway {
|
|
|
2024
2024
|
allowedToolsApplied: cronResult.allowedToolsApplied,
|
|
2025
2025
|
mcpServersApplied: cronResult.mcpServersApplied,
|
|
2026
2026
|
runId: cronResult.runId,
|
|
2027
|
+
totalCostUsd: cronResult.totalCostUsd,
|
|
2027
2028
|
};
|
|
2028
2029
|
logger.info({
|
|
2029
2030
|
jobName,
|
package/dist/types.d.ts
CHANGED
|
@@ -514,6 +514,11 @@ export interface CronRunEntry {
|
|
|
514
514
|
* whose status indicates a failure (error/timeout/lost/cancelled). The
|
|
515
515
|
* Run list filter chip and Run detail header read from this field. */
|
|
516
516
|
failureCategory?: RunFailureCategory;
|
|
517
|
+
/** PRD §12 / 1.18.89: total cost in USD as reported by the SDK's
|
|
518
|
+
* ResultMessage.total_cost_usd. Stamped on success/error entries by
|
|
519
|
+
* the scheduler from the runAgent result side-channel. Powers the
|
|
520
|
+
* Run list Cost column and the Health Strip's 24h cost tile. */
|
|
521
|
+
totalCostUsd?: number;
|
|
517
522
|
/** PRD Phase 1: did the run accomplish what it was supposed to?
|
|
518
523
|
* Computed at run-end when the Task has successSchema or successCriteriaText.
|
|
519
524
|
* - status='pass' both configured checks passed (or the only one configured did)
|