Skip to content

feat(session-summary): on-view session summaries for agent sessions - #51

Merged
nplez1 merged 3 commits into
nplez1/mainfrom
nplez1/Session-Summary
Oct 6, 2026
Merged

nplez1 merged 3 commits into
nplez1/mainfrom
nplez1/Session-Summary

Conversation

@nplez1

@nplez1 nplez1 commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​513 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​513
Prod 19 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1542 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1542

ELI5

When you come back to a long-running agent session after being away, you currently have to scroll through everything it did to understand where things stand. This PR adds a "Session summary" strip to the agent detail view in Activity: click it open and you get a short written brief — what the agent is trying to do, what it has finished, what it is doing right now, anything blocking it, and a timeline of the highlights — with a "Since you last looked" section at the top so you see what is new first.

What Changed

Before: Checking in on an agent meant scrolling its terminal output — slow, and only shows a narrow slice of what happened.

After: The Activity thread detail pane has a collapsed "Session summary" strip with a one-line hint (e.g. "12 events to catch up on"). Expanding it shows the summary: current activity, goal, plan, completed work, blockers, decisions, and a timestamped timeline whose entries point back to the transcript message they came from. Collapsing it remembers what you have seen, so the next visit leads with what is new.

Mechanism: The summary is a cached "materialized view" over the session's own transcript file (the JSONL the agent CLI writes), folded into a structured ledger by an LLM call. Key properties:

  • It only runs while you read it. Opening the strip starts the fold; closing it cancels in-flight work. Nothing runs for sessions nobody opens — no polling, no background LLM spend. Cheap non-LLM facts (state, timestamps, event counts) are tracked at all times so the hint line and staleness are free.
  • Long sessions stay cheap and resumable. The fold advances a cursor through the transcript in bounded chunks (extracted in parallel, merged deterministically). Closing mid-fold keeps completed chunks; reopening resumes where it stopped instead of starting over. Early history survives transcript trimming because it is folded early.
  • A failed fold never corrupts anything. Ledger state is atomic per fold; an unparseable model reply keeps the last good summary and simply retries next time.
  • The model is user-selectable through the existing Settings → Git → Source Control AI pickers (agent, model, thinking level, custom command) — the same configuration text generation already uses. No new settings surface.

Also included: the pane's UI follows the design system (tokens and existing primitives only), and the ledger cache lives in userData as a rebuildable cache.

Why

The problem is not "summarize the transcript" — it is "what changed since I last looked". That reframe drove the design: entries are append-only and counted, so a per-session last-seen cursor can lead with what is new.

Alternatives considered:

  • Full re-summarization on demand: simpler, but cost grows with session length and there is no delta view. The chunked cursor design costs one pass over only the unseen part.
  • A rolling "old summary + new delta" rewrite: bounded cost, but long sessions silently lose early detail. The ledger keeps facts updatable in place and an append-only timeline, so history is never rewritten.
  • Background/continuous summarization: freshest, but pays LLM cost for sessions nobody reads. Laziness costs only a short wait on open, mitigated by showing the deterministic skeleton immediately while the fold runs.
  • Having each agent summarize itself: unreliable across agent CLIs and costs the agent's own context. A separate observer reads the transcript and works for every agent.

Linked Issue

None — fork-local feature work (no external issue exists; happy to attach one if the maintainer wants tracking).

Visual Proof

Pending — a new strip and panel in the Activity detail pane; rendered before/after captures will be attached once the UI validation pass runs. Layout: one collapsed row below the session header (chevron + "Session summary" + hint), expanding to a scrollable brief with labeled sections and a timeline (kind badge, text, #message-index anchor).

Testing

  • Automated tests added/updated: 26 tests across src/main/session-summary/ covering ledger merge semantics (facts updated in place, timeline append-only), fold chunking/caching/atomic failure/cancellation, service lifecycle (open → fold → push, close cancels, reopen resumes from cursor, session-identity change resets the ledger, failed fold keeps last good ledger), and the settings→fold-params contract.

  • pnpm tc clean, pnpm test green for the new suites plus the touched register-core-handlers suite, pnpm run check:code-quality:changed gate green, pnpm format applied.

  • Platform: macOS. Manual rendered testing pending (noted above). No Windows-specific paths or processes are touched beyond the existing cross-platform spawn utilities (spawnSourceControlAgent, shared child-process).

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Authored with the pi coding agent (model: xiaomi/mimo-v2.6-pro) under human direction; design decisions reviewed and chosen by the human.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Remote SSH: transcripts are local-FS today; sessions whose transcript cannot resolve degrade to a "no readable transcript yet" panel — no error, no crash. The verdict vocabulary and execution-host boundary are untouched.
  • Status store: the summarizer is a subscriber/reader only; it adds no polling and no subprocess on the status path (per docs/reference/agent-status-store.md).
  • Performance: zero background LLM work; fold runs only on explicit panel open with bounded chunks and a per-fold cache. Transcript parsing is local and happens only on open.
  • Security/privacy: the ledger cache is derived from the session transcript and stored under userData with the same posture as existing scrollback/status caches; it is treated as rebuildable and never authoritative.
  • Mobile / cross-platform: untouched. Process spawning reuses the vetted spawnSourceControlAgent path (WSL/Windows-aware) and path.join for paths.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason — pending rendered pass (see Visual Proof)
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred) — typecheck/tests/changed-lines gate pass locally; full lint and build left to CI

nplez1 added 3 commits October 5, 2026 21:46
When checking back in on a long-running agent session, the user had no way to
catch up without scrolling the transcript. This adds a summary strip to the
Activity thread detail pane: expanding it folds the session's provider
transcript into a structured ledger (goal/plan/in-progress/done/blockers/
decisions) plus an append-only anchored timeline with a 'since you last
looked' section.

The summary is a materialized view built only while it is read: staleness
facts are tracked for free at all times, the fold runs on explicit open,
completed chunk extracts resume across close/reopen, and closing cancels
in-flight work. Fold chunks extract in parallel and merge deterministically;
a failed extract keeps the last good ledger.
The fold brain ran the default agent spec (Claude, default model) with no
settings influence. It now resolves agent/model/thinking-level/custom command
from the user's Source Control AI settings (Settings → Git) through the same
resolver text generation uses, so summaries run the model the user already
picked. The open request keeps an optional per-session override.
… kind-label exclusion

The summary panel's 15 `activity.sessionSummary.*` keys were never added to
en.json. That stayed hidden while the lint step failed first: with lint red, every
localization gate after it was skipped. Now that lint passes they run, and three
fail.

- Adds the keys with the panel's inline fallbacks (`pnpm run sync:localization-catalog`).
  Without them localization extraction, catalog verification and coverage all fail.
- Adds a reviewed exclusion for `error: 'Error'` in TIMELINE_KIND_LABELS. The key is a
  SessionSummaryTimelineKind discriminator, not a user-visible property, and the value
  is the inline default for `translate('activity.sessionSummary.kind.error', ...)`, so
  the audit's `object-property:error` heuristic is a false positive here.
@nplez1
nplez1 force-pushed the nplez1/Session-Summary branch from e6b5fab to d937fe5 Compare October 6, 2026 04:50
@nplez1
nplez1 merged this pull request into nplez1/main Oct 6, 2026
40 checks passed
nplez1 added a commit that referenced this pull request Oct 6, 2026
)

* feat(session-summary): on-view session summaries for agent sessions

When checking back in on a long-running agent session, the user had no way to
catch up without scrolling the transcript. This adds a summary strip to the
Activity thread detail pane: expanding it folds the session's provider
transcript into a structured ledger (goal/plan/in-progress/done/blockers/
decisions) plus an append-only anchored timeline with a 'since you last
looked' section.

The summary is a materialized view built only while it is read: staleness
facts are tracked for free at all times, the fold runs on explicit open,
completed chunk extracts resume across close/reopen, and closing cancels
in-flight work. Fold chunks extract in parallel and merge deterministically;
a failed extract keeps the last good ledger.

* feat(session-summary): use text-generation settings for the fold model

The fold brain ran the default agent spec (Claude, default model) with no
settings influence. It now resolves agent/model/thinking-level/custom command
from the user's Source Control AI settings (Settings → Git) through the same
resolver text generation uses, so summaries run the model the user already
picked. The open request keeps an optional per-session override.

* fix(session-summary): register the panel's string keys and review the kind-label exclusion

The summary panel's 15 `activity.sessionSummary.*` keys were never added to
en.json. That stayed hidden while the lint step failed first: with lint red, every
localization gate after it was skipped. Now that lint passes they run, and three
fail.

- Adds the keys with the panel's inline fallbacks (`pnpm run sync:localization-catalog`).
  Without them localization extraction, catalog verification and coverage all fail.
- Adds a reviewed exclusion for `error: 'Error'` in TIMELINE_KIND_LABELS. The key is a
  SessionSummaryTimelineKind discriminator, not a user-visible property, and the value
  is the inline default for `translate('activity.sessionSummary.kind.error', ...)`, so
  the audit's `object-property:error` heuristic is a false positive here.
nplez1 added a commit that referenced this pull request Oct 7, 2026
…and its own model setting (#56)

* feat(session-summary): show the session summary in a right-sidebar tab

The brief merged in #51 could never appear. It mounted only inside
ActivityThreadDetailPane, which only ActivityPrototypePage renders, and nothing
has called openActivityPage since the agent list became a sidebar tab (upstream
stablyai#18222). The panel was dead UI in every build.

Give it a live surface: a static right-sidebar `summary` tab, following the agent
session focused in the active workspace and nothing else.

- Strictly focus-driven (`getFocusedAgentPaneKeyForWorktree`). Another agent in
  the same workspace is not this pane's subject: showing the most recently active
  one reads as though it were the session the user is looking at. With nothing
  focused the pane asks the user to focus an agent session.
- Finished sessions already qualify, now pinned by a test: the fold resolves its
  transcript from a provider session, and main retains a `providerSessionOnly`
  remnant row for a dropped status row (`server-cleanup.ts`), so a hibernated pane
  the user focuses summarizes like a live one.
- The ledger view and header state are extracted from the activity strip
  (session-summary-ledger-view.tsx, session-summary-presentation.ts) so both hosts
  render one component instead of two copies.
- The tab is declared in the three allowlists a static tab needs: the shared
  union, the client-ui schema, and the main-process persistence normalizer.
  Dropping any one makes the tab vanish or reset to Explorer on restart, so the
  regression test moves onto the new value.

Wire-safe in both directions: an older host drops a value it cannot express
(tolerateUnknownValues) instead of failing the whole ui.set batch, and an older
client normalizes an unknown tab to Explorer.

Also adds docs/reference/session-summary.md, which two source files already
referenced but which did not exist.

* feat(session-summary): give the fold its own agent and model setting

A summary borrowed the commit-message Source Control AI recipe, so its cost could
not be changed without changing commit messages — and editing the commit prompt
looked like it should affect summaries when it never could. Give the fold its own
setting, same shape, separate storage:

- `sessionSummaryAi` (Settings → Agents → Session summaries): agent, model,
  per-model thinking effort, and the custom command for a custom agent. Resolved
  by `shared/session-summary-ai.ts`; the fold now reads it instead of operation
  'commitMessage'. Unset means the default agent and that agent's default model
  and effort, so nothing has to be configured for summaries to work.
- The selection rules (capability lookup, model/effort seeding on switch, patch
  building) move to `lib/ai-generation-settings.ts`, and the picker fields to
  `settings/ai-generation-fields.tsx`, so the AI-author card and this block share
  one implementation instead of two. The block is also registered in settings
  search, which is how the Agents pane is reached.
- Primitive typography is left to the primitives: the moved fields no longer pass
  `text-xs` to <Label>/<SelectTrigger> or `font-mono text-xs` to <Input> (the
  design-system gate forbids restyling them), and the labels sit above their
  controls instead of in a 92px column that only fitted the smaller type.

Tests: resolver units including "ignores the source-control AI choice entirely",
the fold-params contract, and the settings block's own behaviour.

Verified in the running app: the block shows Codex / GPT-5.4 / High from
`sessionSummaryAi` while `commitMessageAi` stays null, and the value persists in
profile state.

* fix(agent-spec): declare effort levels for Copilot's Claude models

Copilot's hosted Claude entries listed no thinking levels, so the model/effort
pickers hid the effort control for exactly the models users pick when they want
reasoning — while the CLI accepts `--effort` (an alias of `--reasoning-effort`)
for any of its models and validates the value list globally, not per model.

Declare the same low/medium/high/xhigh levels the GPT entries already use, with
the same 'low' default, so those entries expose effort in Settings and in the
per-action recipes.

* feat(session-summary): list a dynamic agent's models from its own CLI

The picker could not offer a model for a dynamic agent: Pi advertises a single
`Config default` stub, and its real list only exists in its CLI output. Selecting
Pi therefore gave no model choice and no effort control at all, even though the
fold passes `--model` and `--thinking` and `parsePiModels` reports 60+ models with
effort levels.

Probe once per agent and cache the answer:

- Resolve the selected agent through the spec, then replace its model list with
  whatever a probe reported for that agent (`discoveredModelsByAgent`, keyed by
  agent so another agent's probe can never be applied to this one). The existing
  panel only ever read discovered models in the resolver, never in the picker.
- Probe through `git:discoverCommitMessageModels` with no worktree or connection:
  host-native, which is where the fold spawns the CLI. Cache only a real probe —
  a `spec`-origin result is the built-in list and caching it would retire the
  probe for that agent permanently.
- Storage stays host-keyed (`discoveredModelsByAgentByHost.local`) while the pane
  resolves the local slot, because the fold reads the transcript locally too; a
  host-aware fold can then land without a migration.
- The pane says `Discovering models…` while the probe runs and falls back to the
  built-in list with an explanation when it fails, instead of silently showing a
  one-entry list.

Verified in the running app: selecting Pi exposes 63 probed models with effort
levels (Model: Deepseek Deepseek Flash, Thinking effort: Low) and caches them in
both slots.

* feat(ai-settings): keep the effort control visible for every model

The effort dropdown rendered only when the selected model declared thinking
levels, so it silently vanished for models that take none — Pi's four
`thinking: no` rows, Copilot's `auto` and `gpt-4.1` — which reads as a missing
feature rather than as a property of the model.

Render the row whenever a model is selected: enabled with that model's levels, or
disabled showing `—` with a one-line explanation. Levels stay model-scoped, which
is how `selectedThinkingByModel` is modelled.

Verified in the running app with Pi: `deepseek/deepseek-flash` shows an enabled
Low/…/Xhigh control, `omlx/Laguna-S-2.1-oQ4e` shows the disabled control and the
explanation, and the Agent and Model dropdowns stay independent of it.

* fix(session-summary): create the fold's scratch dir, and refuse an unusable agent

Two ways a fold could not do what the user asked:

- The scratch cwd (`<tmpdir>/orca-session-summary`) was never created, and the
  launcher passes it straight to spawn, so every fold failed with ENOENT on a
  machine that had never had that directory. The brain now creates it, because it
  is the component that requires it.
- A resolver that reported "nothing usable is configured" (Custom with an empty
  command, or every candidate agent disabled) was turned into the default agent.
  That runs a provider the user did not choose — possibly one they disabled — with
  their transcript. It now fails the fold with a message naming the setting.

* fix(session-summary): run the model and effort the pane displays

The pane could show one model while the fold ran another, and an effort the user
picked could be dropped:

- Effort was resolved through the spec/synthesis helper, which guesses thinking
  support from the model id (`withOpenAiThinking` matches gpt-5/codex). A probed
  Pi model therefore produced no thinking level, so picking High changed nothing.
  Resolution now prefers the probed model's own levels.
- A model a probe no longer lists was displayed as replaced while execution still
  passed the stale id; conversely, an unset choice displayed the probe's first
  model while execution passed `default`. The spec default now stays in the list,
  and a stored id is validated against the probe (or, for a static agent, the spec).

Both sides now read one rule, so what the dropdown says is what the CLI is given.

* fix(session-summary): follow the session, and stay honest about what it can do

- The pane keyed only on the pane key, so a new session started in the pane the
  user was already focused on kept the previous session's brief. The key now
  includes the provider session id, which makes the service reset the ledger.
- The tab was offered in every window, but a paired web client composes no
  `sessionSummary` bridge, so the pane would sit on its loading state forever.
  The activity bar now omits the tab when the bridge is missing, and the pane
  says so if it is somehow opened.
- A successful probe was cached for the life of the install with no way to list
  again, so models the CLI began reporting were unreachable. The pane offers
  Refresh models for a dynamic agent.

* fix(ci): prepare shared FPM tools before parallel Linux packaging
nplez1 pushed a commit that referenced this pull request Oct 19, 2026
…iew off the deprecated owner resolver (stablyai#30562)

* refactor(renderer): fold browser creation, file actions and doc-preview callers off the deprecated owner resolver

The creation-action policy and browser creation (#3, #48) fold together, so the provider the
policy offers is the one creation uses. Browser panes, page lifecycle and conversion, tab
duplication, the explorer tree pane, doc-preview actions and editor path moves (#21, #43, #45-#47,
#49, #50) read runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. File-explorer writes on a folder
(#51) take the owner from the rows alone, never from the focused server. Deprecated importers
16 -> 6.

* refactor(renderer): fold file preview off the deprecated owner resolver; Open in Orca Browser refuses a twin

A project two hosts publish reads as no server for the doc-preview plan, but the file:// path now
refuses it with its own message instead of opening this machine's copy (#7). Deprecated importers
6 -> 5.

* fix(renderer): a twin's file preview and open-externally refuse instead of opening this machine's copy

Codex P2 on the F7 folds: a folder twin read as no server, so the preview plan built a local
file:// tab and open-externally handed the path to this machine's OS. Both now check the owner
for ambiguity first. Also fix two test fixtures that broke the renderer typecheck.

* refactor(renderer): move the file:// open target out of file-preview.ts

file-preview.ts crossed its line limit after main grew it; the file:// target, its type and its two
refusal messages move to workspace-file-browser-open-target.ts, which they never depended on
file-preview for.

* test: mock the replacement resolver in the quick-action availability suite (Codex P2)
@nplez1
nplez1 deleted the nplez1/Session-Summary branch October 24, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant