Commit Graph
20 Commits
Author SHA1 Message Date
bowenliang123 2835230485 feat(client): tighten the network graph cards and drop the corner jump
- Cards keep a 178px minimum width (slot pitch floor 150 -> 190) with tighter
  padding (9x10 -> 7x9) and a 100px height, so dense families read wider and
  calmer
- The corner arrow button is gone: in the Fleet seat a card click selects and
  the jump stays with the boards' own open buttons, while the sidebar seat
  keeps opening on click; the select-mode subtitle no longer names an arrow
2026-10-12 00:38:17 +08:00
bowenliang123 928abdac97 feat(client): rebuild the Fleet tab around one stable three-shape layout
- Overview strip: family stats (agents/running/done/tokens/est. cost) with an
  adaptive progress bar — shared tasks when a team serves any, else subagent
  completion, and no bar for a solo session
- Agent network: taller link channel (level pitch 144→168), node cap 25→40,
  and a card click now selects (ring + the task board's owner filter) instead
  of jumping sessions; the jump moves to a corner button on each card
- The graph's cursor-following inspector leaves the Fleet tab (the boards
  carry that detail); the sidebar mount keeps it
- Members board absorbs the roster: team cards section plus a compact
  other-agents section, one search/status/sort toolbar steering both; the
  standalone FleetList card is gone
- Task board: completion bar, status/owner grouping switch, owner pinning and
  hover lineage from task rows, transitive prerequisite-chain highlighting,
  and a guidance empty state without a team
- Both boards stay mounted with guidance notes in team-less and solo
  sessions, so the page never reshapes between session kinds
- Hoist displayCurrencyOf into cost.ts (was duplicated per mount)
2026-10-11 23:35:29 +08:00
bowenliang123 f0ee3195ac feat: rebuild the Fleet tab as the agent fleet cockpit
Split the Agent network card into graph + cursor-following inspector
(delegation prompt, latest reply, descriptor, owned tasks, comms), add
the members board and the status-grouped task board off the agentTeam
projection with inline dependency chains, a filterable/sortable roster,
and an on-demand comms timeline over relay/mailbox/settled records.

Data rides a new /api/dsh-context/fleet route (one observeSession read
per session through the shared cold-read gate); hover, pin, owner
filter, and task focus are lifted to the view so every panel steers the
same inspector.
2026-10-11 07:52:58 +08:00
bowenliang123 ace15cac11 feat(client): move the agent network onto its own Fleet tab
The Agent network card leaves the Context tab's foot for a page of its own —
the Fleet / 编队 tab, right of Context (order 30). The card is unchanged,
but both mounts now read one shared `useContextSession` data path and fold
the current agent's figures with one `agentSelfOf`, so the panel and the tab
cannot disagree; the Fleet page keeps the same cold-start retry note and the
same baseline gate. The tab label key splits into `tab.context` / `tab.fleet`.

The right Sidebar's Context panel keeps the card inline — its column has no
room for a tab of its own — so the center Context tab renders it only for the
sidebar host, and nothing else leaves that tab.

The stats board's Agent Network cell used to scroll to the card further down
the same page; it now activates the Fleet tab (activateContextTab is
generalized to activateViewTab). Its miss arm is unreachable for our own
tabs: the board and the Fleet tab ride the same placement gate.

The bundle smoke's fake registry now records `register()` calls instead of an
inject callback's return value, matching the real registry — one inject may
declare several registrations, which the second tab exposed.
2026-10-10 20:10:55 +08:00
bowenliang123 887615497d feat(client): show the agent cards' work state in the harness's own glyphs
The header popup marks each subagent with a rolling spinner while it
works and a green dot once it is done; the cards had no marker at all,
because their only candidate signal never existed. SessionSummary has
no completion field, so the row read was dead and always false — the
popup takes its answer from subagentTiming.lastTurnCompleted, and so
does a card now. A session that is not a subagent never carries that
projection, so its own folded steps against a stopped session stand in.

The glyph is the harness's own StateDot primitive at one fixed size, so
a card flipping between states never shifts its title.
2026-10-10 12:33:05 +08:00
bowenliang123 cd26173459 style(client): widen the agent card grid
The natural cell pitch was 208px, which left the two-line titles on a
subagent's card hugging the edge. Every card takes the same, larger
pitch — 276px — so the graph stays one grid and no card claims room
its neighbour does not get. A stage narrower than the pitch still
compresses it, so the family never overflows horizontally.
2026-10-10 12:00:41 +08:00
bowenliang123 38fe279213 feat(client): give every agent card a duration and a step count
The card footer carried the run's duration alone, so two things were
missing: a node that never produced timing rendered a footer with no
figure on the left, and the current agent's card showed nothing at all —
the harness only ever writes a subagentTiming projection onto subagent
sessions, so a parent session has no duration to read from the list.

The step count joins the duration, and the current agent's own
whole-step time reaches its card from the tab's timing totals. The
inspector reads the same figure under the same name, and the README
follows.
2026-10-10 11:59:54 +08:00
bowenliang123 32cb738e88 feat(client): rework the agent network as live cards
Each node becomes a card: title over a fixed two-line zone (every card's
rows align), the agent's consumed tokens with an estimated price when the
models.dev book has the rate, a composition bar replacing the donut (free
window is the track), and a footer with the run's duration at the left and
the bar's occupancy figure at the right.

Links are S-curves fanned out of the parent card's foot in the family hue
with joint plugs at both ends; hovering or focusing a card lights its whole
lineage (ancestors + subtree) while the rest of the graph dims. Sibling
order follows the parent's subagentCatalog projection (the harness header
popup's own spawn order) when cached, with the activity heuristic as the
degrade. Narrow panes fold the family into single-column bands instead of
crushing cards, and the stage measures its content box, so the phantom
horizontal scrollbar at exact-fit widths is gone.

The inspector gains the hovered node's composition readout; keyboard focus
drives the same inspector/lineage affordance as hover. Card surfaces step
one layer up with the l2 border (the inspector's own nesting recipe) so the
cards read clearly in both themes.
2026-10-10 11:24:47 +08:00
bowenliang123 87a2893190 refactor(comments): clean up the test-suite comments to the guideline
Apply the AGENTS.md comments guideline to every .ts under tests/: remove
what the code already expresses, keep only what it cannot.

Removed: spec-header coverage inventories the describe/test tree already
names; comments restating the next line's assertion or its message; helper
JSDoc whose body already says it; within-file duplicates; five stale
claims (trendChart's "six aggregated turns" against agg.length === 8,
agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest
call LAST" against a loop that feeds it first, fold-surface's unreachable
plain-JSON note, contextView's "two turns" for a one-turn fixture).

Kept and condensed: the projection-freeze class, strict wire/state-schema
hazards, retention and pricing windows, pairing rules, hostile-input
rationale, fixture and expectation-array ground truth, jsdom mechanics,
and issue-numbered regressions.

Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage
inventories dropped; every surviving comment line is within the 140-column
limit, with 36 wrapped lines joined onto fuller ones.

No logic, string, identifier, import or directive changed: a comment-strip
gate proves each file's comment-free code is byte-identical to HEAD, and a
line-level proof pairs all 845 changed lines with an unchanged code part.
pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100%
coverage) && pnpm run build all pass.
2026-10-10 00:50:55 +08:00
bowenliang123 7c87ce2b76 refactor(comments): rewrap test comments onto fuller lines
Merge consecutive short comment lines in tests/** up to the 140-col limit
without touching their text: comment-stripped content, comment text (whitespace
normalised) and line-comment runs are all proven identical per file. This is the
line-count half of the cleanup only; the content pass over tests/** is still
outstanding.
2026-10-10 00:26:42 +08:00
bowenliang123 c4ad2e8a9c feat(context): rebuild the Context Stats card as a session flow diagram
The six-cell grid becomes a left-to-right flow: two source cards (Input /
Output — human inputs plus the whole session's file reads/writes, with
searches and image reads when they happen; Context Events broken down by
kind) feed the highlighted Current Session node (turns / steps / subagents
— the subtree's session count, folded from the session list alongside the
cost usage), which drains into the two effect cards (Tool Calls with the
three most-called tools and an overflow pill; Cost with the family figure
and, when a subagent subtree reports, the this-agent/subagents split).
Measured bezier ribbons — a translucent band plus a traveling dash per
element, not a conservative Sankey — connect the nodes and flip to a
top-to-bottom stack when the card narrows past the container-query fold.
2026-10-07 04:23:36 +08:00
bowenliang123 fbb00303d9 feat(context): read the occupancy percent at one decimal, off the kernel's own value
The card's reading was the ring's integer (`Math.round`), so a 101.6k /
1.0M session printed 10% while the kernel's own occupancy read 10.2%.

The percent now comes from one shared `occupancyPercent` helper that
mirrors dsh's `contextOccupancy` — the composer ring's derivation
(`min(100, round(used / window * 100))`, `used` = `contextPressure`'s
`projectedTokens ?? pressureTokens`) — on a 0.1 grid, and keeps the
kernel's numerator precedence ahead of the plugin's derived anchor. The
kernel symbol is unreachable from a client plugin (no package export, a
self-registering browser bundle), so the formula is mirrored by
necessity, as docs/compatibility.md now records.

The Agent Network rings read the same helper (whole tenths stay compact
there), and the number row wraps so the wider reading folds to its own
line instead of spilling on the narrowest panes.
2026-10-07 01:00:02 +08:00
bowenliang123 646344162a fix(client): jump to sessions through the view owner's navigation verb (issue #90)
dsh's 0.1.6 selection refactor moved session selection to the view owner:
uiWorkspace.openSession is the verb the built-in sidebar's row click uses,
and the sessions service no longer carries open(). The plugin's jump (the
Context Dashboard's session cards and the Agent network card's nodes) still
called the retired sessions.open spelling, so on 0.1.7-rc.2 the panel closed
but no session opened — silently, by the verb's fail-soft design.

One shared openSessionVia (services.ts) now serves both call sites: it
re-proves both faces per call, prefers the view owner's verb (invoked bound
to the service instance), and keeps the retired sessions.open as the
degradation path. The per-baseline sessionNav seam pins both spellings
presence-as-declared in the real-code matrix, and the always-on client-face
probe drives the jump through each generation's exact composition (the V4
baseline serves no sessions-side selection at all — the exact shape issue
#90 died on).
2026-09-25 21:37:32 +08:00
Bowen Liang 29a8514c7e feat(client): price the stats board's cost cells across the whole agent family
The Context card's cost cell now prices the current agent TOGETHER with
every subagent session's cumulative billed totals, and a new subagent-cost
cell prices the subagents' share alone; both hover bubbles explain their
scope, the merged one listing the per-model rates of every billed side.
The subagents' usage folds out of the session-list snapshot's warm
contextTimeline rows (subagentCostFoldOf, whole descendant subtree,
cycles cut), with cold relatives' slim heads standing in. Those head
fetches leave the Agent network card's factory closure for a shared
page-scope cache (agentHeads.ts) the stats board's seat reads too, so a
relative is fetched once no matter how many cards consume it; the card's
list-snapshot subscription moves there as useSessionsSnapshot.
2026-09-16 00:12:31 +08:00
Bowen Liang dae94c225a fix(client): backfill cold relatives' composition in the Agent network
The session-list block serves projection values only from the host's
projection cache, so a relative that never attached since the timeline unit
last changed lists pressure-only (occupancy without composition) and its
ring degraded to the single fallback arc — every subagent ring looked green
until its session was opened. The card now fetches those nodes' slim heads
from the /api detail route (one read per session per page, failures sticky)
and re-folds the forest with the row's missing contextTimeline injected; a
row's own value always wins, and any fetch failure degrades back to the
pressure-only ring.
2026-09-15 08:33:23 +08:00
Bowen Liang afd9af63f0 feat: surface skill occupancy through a dedicated skill-injection category
The fold buckets all three skill-content shapes the harness injects — the
<available_skills> catalog digest, a user-explicit /name invocation's
instructions message, and the content a skill-tool load returns — into a
new 'skill' composition category instead of burying them in inject/tool
(closes #66). Skill-tool loads keep their tool identity stamped, so the
call tallies, hit counts, and failure marking are unchanged.

stateVersion 18 → 19: the per-category sums change shape, so cached rows
refold from the durable log on upgrade (docs/compatibility.md).

The trend chart, overview, Token donut, stats, and the Context browser all
gain the seventh bucket (orange-500); the browser's skill section pins its
rows like injects, so long sessions keep listing what the sums count.
2026-09-14 01:11:37 +08:00
Bowen Liang 62ae0f9c69 perf: split the context timeline into a slim wire head plus an on-demand detail channel
The contextTimeline projection value rode every harness delivery channel
WHOLE: each session.list row, every control baseline, every follow
snapshot, and a full-value broadcast on every state change (with a
full-value schema parse per push). The heavy collections (request records,
context events, the served surface window, the removed-node archive) are
only ever rendered by an OPEN Context tab/modal, so they moved off the
wire value.

Wire head (buildTimelineHead, ~1KB): the composition breakdown, the stats
board's precomputed counters (turns/steps/injects/compactions/prunes), the
newest request's billing summary (the headline's derived anchor), the
timing/cost totals, and `detailRev` — a revision marker that bumps exactly
on detail-mutating folds, the open tab's refetch signal.

Detail channel (host/detail.ts): the pre-v0.9 generic Connection RPC shape
returns as one narrow endpoint, `/dsh-context` `detail`, serving the
collections off the registry's live fold state (`stateOf` — no second
fold, no private copy). A nested `ctx.inject(['connection', 'sessions'])`
arms it load-order-free; a deployment whose faces never compose keeps the
inline wire value unchanged. The baseline-gate fallback path is untouched.

Client (client/timelineSource.ts): one per-session detail store shared by
the tab and the /context modal — fetch on open, then debounced
trailing-edge refetches whenever the pushed detailRev outruns the served
detail (single-flight, latest-wins by revision, refold reset, failure
backoff, absence stops trailing). Detail-driven cards show a pending/retry
note instead of a misleading empty state; the counters and headline render
off the head immediately. Both wire generations are reconciled per value
(new client + old host renders the inline value untouched; old client + new
host degrades to empty charts in the stale-watch window, never a white
screen). The chat-jump relay holds its one-shot pin until the detail lands.

Fold state keeps the full shape: no stateVersion bump, no cached-row
invalidation (the slim view is recomputed per read for cold checkpoints).
Compat matrix gains the detail-channel seam probes (HostConnectionRpc,
the browser caller, the registry stateOf), verified against the
0.1.2-rc.1 tag.

Measured over 263 real session logs from a live deployment, plus live RPC:
- per-session wire value: 84–150KB (top sessions) -> 0.6–0.9KB (~100-165x)
- all sessions aggregate (session.list rows / control baselines):
  4.4MB -> 0.2MB (~22x)
- per-client push traffic over one big session's lifetime:
  157MB -> 1.8MB (~87x), and the per-push full-value zod parse shrinks
  by the same factor
- detail payloads now flow only for the session being viewed, only while
  its tab/modal is open (120KB measured live for the biggest session);
  background sessions cost zero detail traffic

Verified visually against the running harness (big/small/cold sessions,
a live turn refreshing in place, past-step reconstruction, header epoch
and tool schema reads, the /context modal, the Agent network card) with
byte-identical card output. 1110 tests, per-file 100% coverage.
2026-09-06 04:08:20 +08:00
Bowen Liang cb93e4c74c refactor: share the settings vocabulary and dedupe the client fetch/overlay patterns
- The settings preference vocabulary (DefaultGranularity/DefaultTrendMode/
  DefaultFileSort/PluginSettings/SettingsField) was declared in both
  host/settings.ts and client/settings.ts — declared once in
  shared/types.ts now, re-exported at both ends.
- The Context browser's two fetch-on-miss state machines (row content,
  header epoch) were line-identical and had already drifted (the row
  read's idle note said 'no content') — one useFetchOnMiss hook plus one
  fetchMissNote helper (components/fetchOnMiss.tsx).
- The /context modal and the upgrade gate duplicated the capture-phase
  Escape handling; useEscapeClose (components/escapeClose.ts) is the one
  overlay contract, and the gate gains the modal's focus restore.
- The five/four identical useProjection reads in contextView/contextModal
  collapse into services.ts's projectionOf; agentTree drops its private
  asRecord copy for the exported one.
- agentTree's compact duration formatter is renamed fmtDurationCompact —
  same name as format.ts's locale-aware fmtDuration with different
  behavior was an import trap.
- Dead surface removed: Snapshot.occupancy (unfolded since 0.11 and
  undeliverable under the strict wire schema), CAT_COLORS,
  HistoryEventLike, and three exports nothing consumed (EVENT_ICONS,
  ContextViewProps, the FileSort alias).
- Comment drift fixed: the conversation join has a single useChat seat
  since the matrix narrowed to 0.1.2-rc.1+ (three sites), and
  services.ts's header no longer claims the module is type-only.
2026-09-05 22:19:01 +08:00
Bowen Liang 8205075877 fix(client): re-prove the meter projections at the boundary and catch settings write rejections
- tokenUsageOf/contextPressureOf were blind casts: any record passed.
  tokenUsage now requires the strict four-bucket wire shape (a partial
  value would silently undercount the billed total); contextPressure
  re-proves each of its three independent last-wins fields and no longer
  lets state-internal fields (surfaceTokens) ride along.
- headersOf now validates every tool entry (name string, finite tokens,
  plugin string when present) — a junk row threw on the browser's blind
  tool.name/tokens reads, blanking the card instead of degrading.
- timelineOf's fast path required Array.isArray but not record entries:
  a null/primitive element passed through and threw downstream
  (req.seq on null). Junk entries now take the sanitizing slow path.
- The settings scope write promise rejects on transport failure (dsh
  keeps only its queue tail fulfilled) — catch it and roll the
  optimistic echo back to the scope's truth instead of floating an
  unhandled rejection.
2026-09-05 22:17:54 +08:00
Bowen Liang 1a31b33191 feat: Agent Network card — live multi-agent context map in the Context tab (#21)
A new Agent Network card at the foot of the Context tab: the whole agent family (current agent, ancestors, subagents at any depth) as a live, fully responsive node map — fused context rings per session, family-colored lineage, halo status language, hover inspector, and click-to-jump into any agent's session. 693 tests, 100% coverage.
2026-08-26 23:09:19 +08:00