Generate session titles with the configured provider - #21
Open
artcashin wants to merge 70 commits into
Open
Conversation
Replaces the three-line summary with a richer report:
- Per-source file counts during the scan phase
- Live progress bar (TTY-only, 100ms throttle, sub-char eighth-block fill)
- Splits skipped into already_ingested / empty / duplicate_id so the
number is actionable instead of opaque
- Total messages ingested + elapsed time
Before:
Scanning 2 source(s)...
Found 4650 session file(s)
Ingested: 4313, Skipped: 337, Errors: 0
After:
Scanning 2 source(s)...
4,294 in /Users/me/.claude/projects
356 in /Users/me/.codex/sessions
Found 4,650 session file(s)
[████████████████████] 100% · 4,650/4,650
Ingested 4,313 session(s) (812,540 messages) in 47.2s
Skipped 337: 337 empty
Ingest Cursor session transcripts. Cursor records are {role, message}
with no `type` field, and content blocks share Claude Code's
{type:"text", text} shape, so the existing text extractors are reused.
Metadata absent from Cursor transcripts is derived after parsing:
- session id from the filename UUID
- start/end timestamps from file birthtime/mtime (Cursor stores none)
- project name from Cursor's encoded directory string (used verbatim)
`~/.cursor/projects` is not added to default sources; it stays opt-in
and is documented in the README along with the format's caveats.
Adds parser tests, an ingest regression test, fixtures, and docs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Manual groups over coding sessions: a Groups nav tab, one group per session, in-app create/rename/delete, membership in a separate table that survives re-ingest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 TDD tasks: schema, helper module, nav, views, routes, session control, verification. Corrects spec FK note (foreign_keys=ON means session_id must have no FK to survive re-ingest). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…listGroups ordering
One-way import of Claude Desktop groups (dframe-group-scopes) into the notebook: reader + reconcile (mirror by desktop_id, manual groups untouched), Ungrouped view, and a block-while-Desktop-open guard behind a single policy switch. Write-back deferred to Phase 2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 TDD tasks: desktop_id migration, classic-level reader (schema pinned against real store), reconcile/mirror, Ungrouped view, block-while-open guard, import route+button+CLI, verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approach A (re-parse source JSONL on demand). Default text-only; Show thinking/tools via query params re-parses source_path, warns if the file is unavailable. No ingest/schema change. Precedes the group work. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 TDD tasks: transcript parser, enriched session render + toggle controls + CSS, route params, verification. Re-parses source_path on demand; default view unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es, param-preservation test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Core match: collapsible tool calls (name + preview, result paired inside), thinking bubbles + token estimate, teal accent + rounded styling on the notebook's light palette. No markdown/diff (deferred), no new deps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3 TDD tasks: parser tool id/input enrichment, collapsible-tool + thinking-bubble render rework + CSS, verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t edge Final-review cleanups: remove redundant style="display:block" on the orphan tool_result div; update spec edge-case wording to match the implemented (extras-dropped) behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Umbrella doc: keep engineering-notebook backend, new React/Vite frontend matching claude-session-viewer's session display, plus Desktop groups. Recommends monorepo + a 5-phase roadmap (Phase 1 = session display parity). Evidence-based session-chain findings (resume = same session; subagents are the multi-file case). Each phase gets its own spec/plan. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
React/Vite app in web/, Hono JSON API (session list, structured transcript, subagent), polished session viewer (collapsible tools, thinking, subagent nesting, client-side hide/show toggles), Tailwind. Subagent->Task mapping via first-prompt match (viewer's strategy). Spikes deferred to plan: on-disk subagent layout, Vite<->Hono wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 tasks: Vite/Hono scaffold+wiring, structured transcript endpoint, subagent discovery via .meta.json + endpoints, session list, React API layer + list view, session viewer (collapsible tools/thinking/subagents + toggles), verification. Additive-only; legacy views/DB untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- web/ Vite React-TS + Tailwind SPA; App pings /api
- createApiRouter mounted at /api; GET /api/ping
- createApp({react}) serves web/dist SPA (assets + index.html fallback),
skipping legacy views; --react flag threaded through serve
- exclude web/ from backend tsconfig
Verified end-to-end: dev proxy + prod build; /api/ping, SPA, assets, and
client-route fallback all 200.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port claude-session-viewer's buildMessageTree + resolveActivePath: the transcript endpoint now reconstructs the uuid/parentUuid tree (bridging filtered meta/summary records) and returns the active branch instead of every flat record. Header shows the active-path length. Codex/uuid-less logs fall back to flat order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude Code sometimes emits a thinking block with only a signature (encrypted) and empty text. The viewer rendered these as blank boxes; we dropped them. Now the parser keeps empty thinking blocks and ThinkingBlock renders a "🔒 redacted thinking" marker, so it's visible that reasoning occurred even when the text is encrypted. Same change applied to the local claude-session-viewer's ThinkingBlock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the transcript in bounded batches (80) and grow as the reader scrolls (IntersectionObserver sentinel), so a 1000+ message session no longer freezes on first paint or when toggling thinking/tools. Small sessions render in one batch (jsdom tests unaffected). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift the toggle state into a TranscriptTogglesProvider (context) so the buttons live in the always-visible top bar, left of the search box, and stay usable no matter where you are in the session scroll. SessionView reads the toggles from context instead of owning them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- sessions.title / title_source columns - import captures each Claude Desktop session's title (titleSource desktop|user) from local_*.json (same files the group import reads) - generate a Desktop-style title via the Claude Agent SDK for Claude Code sessions without one; stored as title_source=generated - `engineering-notebook title [--generate --limit N]` CLI + POST /api/sessions/:id/title on-demand endpoint - API exposes titles (session list, group sessions, journal/project entry sessions); panel 2 and Groups list show the name instead of "Session N" Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…edits - Panel 3 h1 shows the stored session title (Desktop/generated), falling back to the first prompt. - summarize now generates a Desktop-style title for each summarized session that lacks one (applies Desktop titles first so it only LLM-generates where genuinely missing). - User-edited Desktop titles are sticky (never overwritten by auto or generated); auto/generated titles refresh on re-import. Expose title_source in the session API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A user record whose content is purely <local-command-stdout>…</…> is machine output, not a user turn — it was rendering as a 'You' bubble. Skip it in the tree build (bridging parentUuid through it), matching the same fix applied to the local claude-session-viewer's buildMessageTree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ingested subagent transcripts (<project>/<parentUUID>/subagents/agent-*.jsonl) now record parent_session_id — taken from the records' own sessionId without triggering the continuation prefix-skip (a separate continuationParentId drives that). A new relinkSubagents() fills the link (from the column or recovered from the path) and re-homes each subagent's project_id to its parent's project. It runs at the end of ingest and is order-independent + idempotent, so it also backfills. Adds idx_sessions_parent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/api/sessions/:id now returns the subagent backlink (parent_title, gated on is_subagent so continuations aren't mislabeled), subtask_title, spawn_tool_use_id, and the session's ingested subagents ordered chronologically. Journal, project, and group rows carry their nested subagents via a shared ingestedSubagents() helper (single batched query, filtered to ingested rows so there are no dead links). Ungrouped and the flat /api/sessions list exclude subagents. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Panel 2 nests each session's subagents (revealed once the session is selected), labelled by subtask title. Panel 3 gains: a compaction-proof SubagentIndex; a "Subagent of …" backlink (shown only for subagents) that navigates to the parent and scrolls/ring-highlights the exact spawn point, falling back to the index when the spawn was compacted off the transcript; inline spawn links matched by tool_use id (name-agnostic — Claude Code names the tool "Agent", not "Task"), visible even with tools hidden; and subtask titles as the panel header. The focus scroll fires once per target and the window grows-only, so reading past a spawn no longer snaps back. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The date was interpolated raw into the muted/error summary cards; wrap it in escapeHtml() like the surrounding fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ingestion was nested inside the remoteSources block, so a purely local setup never scanned/ingested on sync. Hoist the scanSources/ingestSessions call so it always runs, appending any remote cached paths when present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… toggle Compacted sessions keep their full pre-compaction messages in the same file, but active-path resolution prunes them (e.g. 1,036 shown of 3,122). Panel 3 now defaults to the uncompacted view: the transcript parser gains a `full` option (returns every message in file order, dropping the injected isCompactSummary blob) and a `compacted` flag; the endpoint honors ?full=1. A third top-bar toggle — "View compacted" / "View uncompacted" — switches modes, defaulting to uncompacted so pre-compaction detail (and its subagent spawn links) is shown. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… compacted Plumb the transcript's `compacted` flag to the toggle bar. The "View compacted" button is disabled (and greyed) unless the open session actually has a compacted view to switch to. Non-compacted sessions are forced back to the default uncompacted view so the disabled toggle never strands the reader in compacted mode; the toggle re-disables when leaving the session. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Light green (matching the other toggles) when the session has a compacted view; gray when it doesn't. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uped to remove)
Adds POST /api/sessions/:id/group ({ groupId } assigns, { groupId: null }
unassigns; 404s on missing session/group). In the Groups view, panel-2 session
rows are draggable and panel-1 group rows (and Ungrouped) are drop targets with a
hover highlight; dropping assigns/unassigns the conversation and refreshes the
counts and current list. Subagent rows stay non-draggable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`bun test` collected web/src/**/*.test.tsx and failed 18 of them with "document is not defined". Those are React component tests that need a DOM, which is configured for vitest in web/vite.config.ts — a file Bun's test runner does not read. Nothing was wrong with the tests themselves: they pass under their intended runner. The failure was invisible locally because the pre-commit hook runs the scoped `bun test ./src`, while both CI workflows run a bare `bun test`. main has no web tests, so this branch would have turned CI red on merge. bunfig.toml scopes Bun's runner to src/, leaving it responsible for the backend suite alone. That also excludes web/src/session/adapt.test.ts, which is DOM-free and did run under Bun, so CI now runs the web suite with vitest — covering all 20 web tests, which CI never executed at all. web/ is not a workspace of the root package, so its dependencies install separately. Verified: bare `bun test` 182 pass / 0 fail, `bun run test:web` 20 pass, typecheck clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 2, 2026
artcashin
marked this pull request as ready for review
August 2, 2026 20:03
OpenCode source adapter ----------------------- OpenCode keeps sessions in a single SQLite database rather than one file per session, so it cannot be scanned like the Claude Code and Codex sources. An opt-in adapter exports each session into a staging directory of JSONL files during `ingest`, which the normal scanner then picks up — leaving dedup, --force, grouping, summarization and the web UI untouched. Three things the implementation has to work around, each found empirically against OpenCode 1.18.0: - `opencode session list` only reports sessions belonging to the current working directory's project, so no single cwd can enumerate them all. Sessions are enumerated from OpenCode's database; transcripts still come from `opencode export`, which works from anywhere. That keeps the fragile part (transcript shape) on the supported CLI, with only a five-column query touching the schema. - `opencode export` yields empty stdout over a pipe but is complete when redirected to a file, and a single export can run to megabytes. Both call sites go through one runToFile helper. - is_subagent was derived purely from Claude Code's /subagents/ path convention, so flat-staged files always read as 0. Formats that state it outright now win; Claude Code keeps path detection, because its parentSessionId also covers continuations and would otherwise misflag them. Projects are keyed off each session's working directory, so OpenCode and Claude Code work in the same repo on the same day lands in one journal entry. Configurable summary provider ----------------------------- Summaries were hardcoded to Claude Haiku via the Agent SDK. A summary_provider config block now selects any OpenAI-compatible endpoint, including a local llama.cpp server. Absent config keeps the previous behaviour. The Claude auth preflight is skipped for non-Anthropic providers, which authenticate with their own key and would otherwise be blocked on every run. Entries record the model that actually produced them. API keys are referenced by environment variable name and never stored in config. Reasoning models spend completion tokens before emitting content, so an exhausted budget fails loudly rather than writing an empty summary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…inc#16, prime-radiant-inc#18) # Conflicts: # src/summarize.ts
Session titles were a second hardcoded call to Claude Haiku through the Agent SDK. With summaries routed through summary_provider, configuring a local model left titles silently still calling Anthropic — a provider setting that only half applies is worse than one that doesn't exist, because the surprise is invisible until a bill or an offline box makes it visible. generateTitle, titleSession and backfillTitles now take the provider and route through the shared complete() helper, so `summarize` and `backfill-titles` both honour it. Absent config still uses Claude Haiku, unchanged. This could not ship in the standalone OpenCode PR because src/titles.ts only exists on the React frontend branch, so it lands here on top of both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ime-radiant-inc#19 merge prime-radiant-inc#16 replaced ingest's single `skipped` counter with alreadyIngested / empty / duplicateId, deriving `skipped` from their sum, and dropped the no-start-time guard. prime-radiant-inc#19 keeps that guard and still increments `skipped`. Git merges the two cleanly: prime-radiant-inc#16's counters plus prime-radiant-inc#19's guard, referencing a variable that no longer exists. Tests pass; `tsc` does not: src/ingest.ts(108,9): error TS2304: Cannot find name 'skipped'. A session with no start time cannot be placed on a date, so it is as unusable as one with no messages and is counted as empty, which flows into the derived `skipped`. Keeping the guard rather than dropping it as prime-radiant-inc#16 did: without it a session with no timestamp is inserted with an empty started_at and misgroups. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
artcashin
force-pushed
the
feat/title-provider-wiring
branch
from
August 2, 2026 20:24
6044e18 to
b09ccb0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #20. Draft — blocked on #19 and #20.
Session titles were a second hardcoded call to Claude Haiku through the Agent SDK. With summaries routed through
summary_providerin #20, configuring a local model left titles silently still calling Anthropic. A provider setting that only half applies is worse than one that doesn't exist, because the surprise stays invisible until a bill or an offline box makes it visible.generateTitle,titleSessionandbackfillTitlesnow take the provider and route through the sharedcomplete()helper, so bothsummarizeandbackfill-titleshonour it. Absent config still uses Claude Haiku, unchanged.Why this is stacked rather than part of #20
src/titles.tsdoesn't exist onmain— it arrives with #19. So this wiring depends on both:src/titles.tsandsrc/desktop-groups.tssrc/llm.tsand thesummary_providerconfigThe branch is
feature/react-frontendmerged with #20's branch, plus one commit containing the wiring itself. Until both land, the diff againstmainshows all three; it collapses to the single wiring commit once they merge. Review that commit (6044e18) in isolation.The merge commit resolves one conflict in
summarize.ts, where #19 adds the titling block and #20 changes theupsertJournalEntrycall. Resolution is the union, with titles deliberately left calling Claude so the follow-up commit is exactly the wiring and nothing else.Testing
224 tests pass, typecheck clean. Written test-first:
src/titles.test.tsrunsgenerateTitleagainst a real local HTTP server and asserts the configured model is the one contacted, plus the answer-cleanup behaviour (quote stripping, first-non-empty-line).🤖 Generated with Claude Code