| Age | Commit message (Collapse) | Author |
|
trimTranscript now drops oldest provisional chunks (the in-flight turn)
when committed chunks are exhausted. Previously it bailed with drop=0
when committed was empty, allowing unbounded provisional growth during
long generating turns (300+ chunks → browser crawls).
Root cause of the syncTail approach failing: the kernel emits
step-complete (line 360) BEFORE calling onStepComplete (line 542) —
chunks are persisted only after tool results come back, not when
step-complete fires. So syncTail on step-complete found nothing.
Reverted the applyHistory + syncTail-on-step-complete changes from
4e1d041. The new approach is simpler: trim provisional directly in
trimTranscript. Dropped chunks are lost temporarily (no Show Earlier)
but come back as committed when the turn seals and syncTail fetches
everything from the server.
686 tests green.
|
|
The backend now persists chunks at step boundaries during generation
(CR-6). The FE calls syncTail on each step-complete event to fetch
the newly committed chunks. applyHistory clears the provisional array
when new committed chunks arrive mid-generation (they're duplicates
of what was folded from live events). The accumulating chunk (current
in-progress step) is kept.
This means trimTranscript can now drop oldest committed chunks
uniformly during a long turn — no unbounded provisional growth. The
browser never holds more than chatLimit chunks, even mid-generation.
3 new tests: clears provisional on new committed during generation,
keeps provisional when no new chunks, keeps accumulating when clearing.
689 tests green.
|
|
boundaries
Consume the message-queue + steering handoff ([email protected], [email protected]).
Re-pinned file: deps + re-mirrored .dispatch/*.reference.md.
- fold steering AgentEvent into the transcript as a provisional user bubble
(after the tool-result it followed; no de-dup — the queue surface carried it)
- add rendererId: "message-queue" custom renderer (pure parser + MessageQueueList)
rendered as a compact panel above the Composer (hidden when queue is empty)
- add ChatStore.queueMessage / AppStore.queueMessage — sends chat.queue WS op
(trim/validate non-empty; auto-starts a turn if idle)
- Composer switches to chat.queue while generating (button → Queue, placeholder
→ Steer the conversation...)
- exhaustiveness guards updated for steering + chat.queue
- carry-to-new-turn needs no special handling (normal new turn)
664 tests green.
|
|
show-earlier backfill
Re-pinned [email protected]>0.10.0 + [email protected]>0.6.1 (reply
frontend-history-windowing-handoff.md); re-mirrored both .dispatch references.
- HistorySync port gains optional { limit?, beforeSeq? } (CR-5 params); the
app's createHistorySync appends them to GET /conversations/:id.
- COLD-cache fresh load now fetches ?sinceSeq=0&limit=<floor(0.75xL)> — a huge
conversation no longer ships whole to show 192 chunks. A warm-cache tail sync
stays unwindowed (windowing a tail that outgrew the limit would leave a
silent seq gap behind the cache).
- hasEarlier now derives from the [email protected] CONTRACT (1-based gap-free seqs):
loaded window starting above seq 1 => older history exists — covering both
locally-trimmed AND server-windowed transcripts (the watermark stays as the
merge floor only).
- showEarlier(): local cache first; when the cache doesn't reach far enough
back, backfills the missing older run via ?beforeSeq=<oldestKnown>&limit=
and persists it (next page-in is local). latestSeq windowed-read caveat is
satisfied structurally (tail cursor derives from the cache's max seq).
- live-probe: +6 CR-5 checks (seq origin, newest-k ascending, short-chat
exactness, beforeSeq paging, 400 validation x2). NOT yet run live — backend
was down at commit time; run pending.
- backend-handoff.md: CR-5 RESOLVED, pins/mirrors current. 602 tests green x2.
|
|
show-earlier page-in
Long transcripts no longer grow unbounded: past the chat limit (default 256
chunks, localStorage dispatch.chatLimit) the oldest ceil(limit/4) committed
chunks are unloaded in ONE bulk pass — never one-per-delta (old Dispatch's
scroll-jump-per-step bug) — and only while the reader is stuck to the bottom
(scrolled-up readers defer the trim; it catches up in whole quarters). A fresh
page load windows to the newest floor(0.75*limit). Unloading is purely local
(IndexedDB cache + server keep everything); a hiddenBeforeSeq watermark keeps
history merges from resurrecting unloaded chunks, and a 'Show earlier messages'
affordance pages a quarter back in from the cache with scroll-anchor
preservation. Thinking-collapse render keys stay stable across trims via a
hiddenThinkingCount ordinal base.
- core/chunks/trim.ts: pure policy (trim/window/restore/normalize) + tests
- chat store: chatLimit + canUnload deps, windowed load, showEarlier()
- composition root: dispatch.chatLimit localStorage knob + unload gate wired
to smart-scroll isAtBottom()
- backend CR-5 OPENED (not a blocker): ?limit=/?beforeSeq= on
GET /conversations/:id (courier backend-handoff-chat-limit.md)
- scripts/live-probe.ts: fix pre-existing stale TurnMetricsEntry reads
(m1.usage -> total.usage) that crashed the probe; 17/17 live checks pass
|
|
on stream
- subscribe every open conversation on load + WS reconnect (resync), unsubscribe on tab close
- derive a stream-based 'generating' state for watchers (Composer running indicator)
- fold the user-message turn event so watchers render the prompt mid-turn (de-dup vs sender's optimistic echo)
- re-pin [email protected] / [email protected]; re-mirror contracts; add user-message to the exhaustiveness guard
|
|
Backend context-size handoff: re-pin [email protected] / [email protected]
(+ re-mirror .dispatch reference snapshots). Thread the optional contextSize
through core/metrics (done fold + durable + selectCurrentContextSize: latest
turn's defined value, undefined=>unknown never 0, durable-wins-over-live).
Chat store exposes currentContextSize; ContextSizeBadge renders
"N tokens in context" / "context size unknown" above the composer.
GLOSSARY: add context size / context window. 533 tests green.
|
|
Consume [email protected] / [email protected] metrics: usage.stepId,
step-complete (ttft/decode/genTotal), done.durationMs/usage, and the
durable GET /conversations/:id/metrics endpoint.
- core/metrics: pure live-fold + durable-merge reducer; decode-rate TPS;
head-aligned, stable placement; progressive per-step rows (each shown as
its step ends) with the turn-total row gated on the done event.
- features/chat: store folds metric events + hydrates durable TurnMetrics;
ChatView renders inline step bubbles + a turn-total bubble.
- app: MetricsSync HTTP effect (tolerates 404) injected into chat stores.
- scripts/live-probe: drives the metrics path; live-verified 17/17 vs bin/up.
- docs: regenerate .dispatch wire/transport mirrors to 0.4.0; glossary terms
(turn/step metrics, TTFT, decode time, TPS, metrics bubble); trim handoff.
|
|
This reverts commit 48c6d85c3cc5a57a729f14068e2346b17ed62088.
|
|
Consume wire/transport-contract 0.3.0 (step-complete event + timing
fields on usage/tool-result/done). Pure core/telemetry module:
foldMetricEvent (reducer) + derived selectors (stepTps, turnTps, etc).
TelemetryState is pure data, no active-turn tracking — consumers pass
turnId to selectors. ChatStore wires foldMetricEvent into handleDelta
and exposes telemetry + currentTurnId. ChatView shows step-metrics
footer (time/TPS/tokens) on assistant text bubbles and durationMs badge
on tool cards. New TurnSummary component renders turn-level stats
(wall-clock, tokens, steps, TPS) in a DaisyUI stats block. Extended
live-probe to verify telemetry events against bin/up (pending backend
restart). 336 tests, typecheck 0, biome clean, build ok.
|
|
Bug 1 (sent message didn't appear until turn end): the transcript only folded
assistant AgentEvents, so the user's own message showed only after turn-sealed
resync. Add core/chunks appendUserMessage() (provisional user chunk, superseded
on history sync) and call it in chat send() — the message now renders instantly.
Bug 2 (tabs didn't persist on refresh): the app passed { storage: undefined }
to createLocalStore, which the adapter treats as a no-op store, so nothing was
saved. Default to globalThis.localStorage. Regression test exercises the
non-injected path.
Also updated app store tests for the echo (assistant-vs-user chunk filtering).
Verified: svelte-check 0/0, vitest 288 (stable x2), biome clean, build ok.
|
|
- features/tabs: pure tab-workspace reducer (create/select/close/setModel/
setTitle/deriveTitle, draft=null active) + injected-persistence runes store
- features/chat: mutable per-tab model (setModel) + delta routing guard
(ignore foreign conversationId) + ModelSelector.svelte + DaisyUI chat bubbles
/ composer (keeps streaming <details> keying fix)
- features/conversation-cache: surface delete(conversationId) on the wrapper
for tab-close local-forget
- adapters/local-storage: generic injected JSON localStore<T> (quota/corrupt-safe)
Verified: svelte-check 0/0, vitest 273, biome clean, build ok.
|
|
- adapters/idb: createIdbChunkStore implements the ConversationChunkStore port
over IndexedDB (compound [conversationId,seq] key, idempotent append, meta
store for lastAccess); 8 tests with fake-indexeddb
- features/chat: createChatStore (runes-thin over the core/chunks reducer, all
effects injected via ChatTransport/HistorySync/ConversationCache ports) +
ChatView/Composer svelte-thin UI; folds chat.delta, syncs on turn-sealed,
hydrates from cache then catches up; 25 tests
Verified green: svelte-check 0/0, vitest 202, biome clean, build ok.
|