Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions admin/skills/operate-pi-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Observe (no approval needed):
daily buckets, per-flow and per-model rollups, subscription plan verdicts, provenance (`flow` filters to
one flow). Every dollar in the result is typed `{ usd, class, ... }` — quote the class with the number
(`metered` is measured, `estimated`/`seeded` are not), and never present an estimate as an exact spend.
The operator sees the same fold in the terminal via `/dispatch costs [7d|30d|mtd]`, and
`/dispatch costs whatif <provider/model> --flow <flow>` estimates what a flow would cost per run on
another model's rates.
The operator sees the same fold drawn as charts on the insights page (`/dispatch insights
[7d|30d|mtd]` writes and opens it), and `/dispatch insights whatif <provider/model> --flow <flow>`
estimates what a flow would cost per run on another model's rates.
- `dispatch_triggers` — the configured triggers with their array `index` (needed to edit/delete one).

Control (no approval needed — reversible and money-safe):
Expand Down
989 changes: 26 additions & 963 deletions admin/src/dashboard.ts

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions admin/src/graph-html.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ function normalizeModel(model) {
runs: intOr(n.runs, 0),
lastOutcome: typeof n.lastOutcome === "string" ? n.lastOutcome : null,
lastEndedAt: typeof n.lastEndedAt === "string" || Number.isFinite(n.lastEndedAt) ? n.lastEndedAt : null,
// Schedule facts (issue #181): with the terminal views gone this page is the last surface
// REQ-TOPOLOGY-GRAPH (h) has, so the facts the model always computed ride the tip here.
next: typeof n.next === "string" || Number.isFinite(n.next) ? n.next : null,
overdueMs: Number.isFinite(n.overdueMs) && n.overdueMs > 0 ? n.overdueMs : null,
isSub: n.isSub === true,
group: typeof n.group === "string" ? n.group : null,
// Prose-loop hints from the SKILL.md body; capped and clipped so a hostile skill cannot
Expand Down Expand Up @@ -738,6 +742,17 @@ function relTime(nowMs, v) {
// The hover tooltip content, prebuilt here so the page script never assembles markup: the client
// assigns these strings via textContent only, which is what makes "no innerHTML anywhere" testable
// as a plain substring ban.
/** A coarse duration for the schedule facts: minutes under an hour, hours under two days, else days. */
function relSpan(ms) {
if (!Number.isFinite(ms) || ms <= 0) return "<1m";
const mn = Math.floor(ms / 60000);
if (mn < 1) return "<1m";
if (mn < 60) return `${mn}m`;
const h = Math.floor(mn / 60);
if (h < 48) return `${h}h`;
return `${Math.floor(h / 24)}d`;
}

function buildTip(n, flags, groupLabel, nowMs) {
const lines = [];
if (n.kind === "trigger") {
Expand All @@ -762,6 +777,13 @@ function buildTip(n, flags, groupLabel, nowMs) {
} else {
lines.push("no runs in window");
}
// Overdue outranks a stale countdown; next counts against the injected instant, never a clock,
// so a stale page shows its stale countdown honestly (and byte-determinism holds).
if (n.overdueMs !== null) lines.push(`overdue ${relSpan(n.overdueMs)}`);
else {
const nextMs = typeof n.next === "number" ? n.next : typeof n.next === "string" ? Date.parse(n.next) : NaN;
if (Number.isFinite(nextMs) && Number.isFinite(nowMs) && nextMs > nowMs) lines.push(`next ${relSpan(nextMs - nowMs)}`);
}
}
if (n.aiTrigger) lines.push("chainable: ai-trigger allow");
if (n.kind === "skill" && n.loops.length > 0) {
Expand Down
222 changes: 67 additions & 155 deletions admin/src/index.ts

Large diffs are not rendered by default.

285 changes: 3 additions & 282 deletions admin/src/render.mjs

Large diffs are not rendered by default.

767 changes: 25 additions & 742 deletions admin/test/dashboard.test.mjs

Large diffs are not rendered by default.

159 changes: 0 additions & 159 deletions admin/test/graph-command.test.mjs

This file was deleted.

8 changes: 8 additions & 0 deletions admin/test/graph-html.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,14 @@ test("orphan dash, potential-vs-observed labels, the caps digits, and honesty co
dropped.meta.droppedObservedEdges = 4;
assert.ok(buildGraphHtml(dropped, { now: NOW }).includes("4 observed edges dropped"), "dropped-edge counter renders when set");

// Schedule facts in the tips (issue #181): with the terminal views gone this page is the last
// surface REQ-TOPOLOGY-GRAPH (h) has. The canned scheduler's next fire sits 191 days past NOW.
assert.ok(out.includes("next 191d"), "a cron tip counts down to the resident scheduler's next fire");
const overdue = buildGraphModel(CANNED());
const cron = overdue.nodes.find((n) => n.id === "trigger:0");
cron.overdueMs = 2 * 3600_000;
assert.ok(buildGraphHtml(overdue, { now: NOW }).includes("overdue 2h"), "overdue outranks the countdown");

// The two counters the text and TUI surfaces always stated and this page dropped (issue #175):
// three surfaces of one model must not disagree about what was refused or unreadable.
assert.ok(out.includes("1 chain requests refused (caps or gate)"), "the canned refusals reach the legend");
Expand Down
Loading
Loading