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
1 change: 1 addition & 0 deletions docs/feature-map/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ and Forget. Legacy schedule rows state that the run uses Global Memory V1.
| Embeddings | Enable the vector store, pick its model, and view localized setup errors, warnings, and deferred repair status | Memory browser → vector card | `pages/overview/VectorMemoryCard.tsx`, `pages/overview/EmbeddingModelCard.tsx`, `pages/overview/embeddingStatusText.ts` (shared status-text helper; no new entry point) | `handlers/memory.py` | `GET /api/memory/embedding-status`, `POST /api/memory/enable-embeddings`, `POST /api/memory/embedding-model` |
| Memory graph | Entity/relation visualizer over the memory store | `/developer?tab=memory` | `pages/overview/MemoryGraphTab.tsx` | `handlers/memory.py` | `GET /api/memory/graph`, `GET /api/memory/observability` |
| Usage | Token and turn usage over time, including estimated Kiro CLI tokens for this month, last month, and each day | `/settings/overview?view=usage` | `pages/overview/UsageTab.tsx` | `handlers/usage.py`, `handlers/telemetry.py` | `GET /api/usage`, `GET /api/usage/kiro`, `GET /api/usage/turns` |
| Spend over time (credits) | Stacked area chart of the last 30 days' credit spend, layered by channel (where each session came from), agent, model, or the week a session was first seen in the window (the oldest layer, **Started by** a date, also holds sessions that began before it); cumulative by default or per day; hover or the arrow keys read out a day's total and each layer's value, the top layers are kept with the rest folded into **Other**, and a window the server had to shorten says how many of its oldest turns were left out and from which day every turn is counted; shown only when the default harness bills in credits (kiro-cli, KAS), with a notice in its place when that setting could not be read | `/settings/overview?view=usage` → **Spend over time (credits)** card, above Session activity (30 days) | `pages/overview/TokenStackedAreaChart.tsx` | `handlers/usage_series.py` | `GET /api/usage/series` |
| Kiro credit balance | The top-bar credit pill and its account modal. The gateway refreshes the reading on a timer: the free `GetUsageLimits` API first, then the `kiro-cli /usage` scrape (a local slash command over the same free call) whenever the API returns no plan, parked for 6 h after three consecutive scrape attempts that failed (timed out, errored) or produced no parseable plan. The modal's **Refresh** button runs that same refresh now — under the state's notice when there is no reading ("Could not read your balance." for a failed read, "No balance reading is available for this account yet." for `none` — a dash the pill shows only while the selected harness is kiro-cli (`agent.acp_backend` names the kiro id, read positively by `api/acpBackend.ts`); on any other harness that payload hides the pill, as it always did), compact beside the meter when there is one — and writes the result into the pill's query. Single-flight (409 while a refresh runs, shown as an `ErrorNotice` with the hand-off like every other refresh outcome, Refresh held until the modal's ~3 s re-read lands and the notice clears); one notice at a time (a failed refresh replaces the standing failure notice with the same one sentence, the stale line yields to the stale-refresh notice); Refresh is held while the parked notice says when to try again; a parked scrape is reported after the API attempt (`skipped: scrape_parked`): no new reading is fetched, and the cache then holds a same-identity prior reading dimmed `stale` or an unavailable marker. A refresh that comes back `stale` is reported as such — an `ErrorNotice` with the hand-off — rather than stamped as checked now. Every credential read — the API call and the `/usage` scrape alike — is bracketed by a whoami immediately before and one immediately after, and its reading is published only when the pair PROVES one account (`_whoami_agreement` = `same`: at least one matching identity field, none disagreeing); `different` and `unproven` publish nothing (two identity-less whoamis around the API fall through to the scrape; around the scrape they end the refresh with no reading), and every failure path keeps a prior reading only for the same account (`_same_identity`), so an account switch never shows the previous account's balance. Under the meter, one muted line projects whether the plan lasts to the reset (`api/creditForecast.ts`): the pace is the account's own used credits over the days the cycle has run (cycle start = one calendar month before `resets`), shaped by weekday from the Usage tab's Daily History credits (the shared `providerUsageQuery` over `GET /api/usage/kiro`, last 28 days, flat until 14 days of history); the line is omitted when no reset date is known, the plan is used up, bonus credits still hold a balance, less than a day of the cycle has run, or the report is still loading or refreshing, a failed read shows an `ErrorNotice` with the hand-off, and the run-out case carries the warn tone. No notification yet (#17112). No config key, no Settings control, no cost copy | Top bar → credit pill → **Kiro Account** (desktop); on the phone the pill is not shown and the same modal opens from the nav drawer → bottom group → **Kiro Account** row, or on the chat route from the chat drawer → nav rail → **Kiro Account** tile | `shell/topbar/kiroUsageReadout.tsx`, `App.tsx` (mounts the modal), `components/KiroAccountModal.tsx`, `api/kiroUsage.ts`, `api/creditForecast.ts` | `handlers/sessions.py`, `handlers/usage.py` | `GET /api/sessions/usage`, `POST /api/sessions/usage/refresh`, `GET /api/usage/kiro` |
| WakaTime coding activity and export | Coding stats for a named range plus project-grouped hours over a date range as a CSV or JSON download, for the productivity view and billable-hours invoicing | `/settings/overview?view=wakatime` | `pages/overview/WakaTimeTab.tsx` | `handlers/wakatime.py` | `GET /api/wakatime/stats`, `GET /api/wakatime/export` |
| Portability | Export and import the whole memory/config bundle, optionally with persistent chat history (add-only on import); both ends warn about crew agent templates the bundle does not carry. A memory-only export (`?components=memory`, never chats) asks first with a dialog listing what the file holds; a memory-only import (`?components=memory`, or any archive whose manifest declares memory only) merges just the memory and refuses Replace | `/settings/imports` | `pages/overview/PortabilityTab.tsx` | `handlers/portability.py` | `GET /api/portability/export`, `POST /api/portability/import`, `POST /api/portability/preview` |
Expand Down
68 changes: 68 additions & 0 deletions docs/system-specs/modules/learn-cron-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,74 @@ refresh can recover without clearing the report. A provider without usage
support shows neutral status text, not an error. No report is persisted to disk,
and the top-bar credit readout keeps its separate billing refresh policy.

The Usage tab's "Spend over time (credits)" card is a stacked area chart with one layer
per bucket of the chosen dimension, backed by `GET /api/usage/series`
(`dashboard/handlers/usage_series.py`). The route reads the same per-turn
shards as the Daily History credits, admits rows by the same guard (a `tokens`
row with a parseable local day), and answers a dense local-day axis -- every
day of the shard retention window, zero-filled -- plus one series per bucket
of the requested dimension: `by=channel` (the default), `agent`, `model`, or
`cohort`, the ISO week in which the row's session was first seen inside the
window, keyed by its Monday and assigned in a second pass once every session is
known. The channel is derived from the row's session key with
`telemetry_channel_of`, never read from the row's own `surface` field, for the
reason `cost_breakdown` documents: historical rows can carry a wrong but
non-empty surface and the row schema has no writer marker, so the key is the
authority and this chart buckets spend by the same closed channel vocabulary as
the Spend panel. The model is canonicalised across provider id migrations like
the daily chart does. The value stacked is the row's `credits`, the unit the
kiro-cli backend and its KAS relay bill in; a harness that bills in tokens or
dollars writes rows with no credits, so the Usage tab mounts the card only once
a loaded config names a credits-billing default harness (`agent.acp_backend`
via `reportsCredits` in `api/acpBackend.ts`, stated positively like
`isKiroBackend`), never on a guess and never as "not claude"; a config read
that failed with nothing cached (`failedWithNoData`) is said in an
`ErrorNotice` where the card would stand, carrying the failed request's report
for the agent hand-off and a Try again button that re-reads, since on a
kiro-cli install a silent absence would read as "this harness has no credits"; turns a crew
member ran on another harness (`agent.member_acp_backend`) land in the same
store and count whatever credits they recorded. The top seven buckets by
window total (the session palette's hue count) are kept and the rest fold into
one `other` series carrying its member count; a row whose dimension value is
empty or absent lands in an explicit `unattributed` series rather than being
guessed at or dropped, so the stack's top edge always equals the day's total
spend. Series arrive in stack order, bottom first: value dimensions
largest-first, cohorts oldest-first, then `other`, then `unattributed`. An
unknown `by` is a 400 (`invalid_dimension`) rather than a silent substitution,
and an app token is answered 404 with a
SEL `app_isolation` audit record -- the payload is the whole install's spend
with no slot filter, unlike the row-isolated `/api/usage/turns`. The parsed rows
are cached on the shard fingerprint the other readers use, so switching
dimension re-aggregates in memory; the cache is bounded on both axes
(`MAX_ROWS` rows, `MAX_FIELD_CHARS` per retained string), newest shards are read
first so a window past the cap keeps its newest rows, and the payload discloses
the shortening (`truncated`, `dropped_rows`, and `complete_from`, the oldest
day every row of which was kept, never reaching past an unreadable shard --
`null` when the cap tripped inside the newest shard) instead of serving a
partial window as the whole one. The chart stacks
and, in its default cumulative view,
prefix-sums the series client-side (`pages/overview/TokenStackedAreaChart.tsx`,
`d3`'s `stack`/`area`, axis top rounded to a nice number); its view choices
persist in browser storage; the top-N buckets take the session palette's hues
and `other` / `unattributed` the muted tokens; channel ids are shown capitalised
with the unrecognised-key channel labelled apart from the `other` fold; the plot
is a keyboard-operable slider over the day axis whose value text reads the
focused day out; an `unattributed` layer earns a one-line caption under the
controls and a truncated window a warning-toned one that leads with the
consequence (the totals undercount) and, when the server could name it, from
which day every turn is counted. Cohort layers are labelled "Week of" their
Monday except the oldest one drawn, labelled "Started by" its Sunday (or the
axis's last day while that week is still running): a cohort is the week a
session was first seen inside the window, so the oldest layer also holds
sessions that began before the window, and the cohort caption says so. A failed first load shows a titled `ErrorNotice`, the
server's text on its own line under the title, with a Try again button that
refetches; a failed refresh over a drawn chart shows the same shape above the
stack it keeps showing. Changing the dimension
keeps the previous stack on screen, dimmed and `aria-busy` with a "Loading…"
cue beside the select, until the new answer arrives (`keepPreviousData`); each
answer carries the dimension it was split by, so the stand-in keeps its own
labels and caption rather than the new selection's.

## Gateway restart

The dashboard restart endpoint and successful update applies share
Expand Down
1 change: 1 addition & 0 deletions src/kiro_crew/dashboard/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ async def api_tasks_summary(request):
api_kiro_usage,
api_usage,
)
from kiro_crew.dashboard.handlers.usage_series import api_usage_series # noqa: E402, F401
from kiro_crew.dashboard.handlers.wakatime import ( # noqa: E402, F401
api_wakatime_export,
api_wakatime_stats,
Expand Down
Loading
Loading