Skip to content

feat(native-chat): render Muse transcripts - #22998

Open
clichedmoog wants to merge 1 commit into
stablyai:mainfrom
clichedmoog:feat/native-chat-muse
Open

clichedmoog wants to merge 1 commit into
stablyai:mainfrom
clichedmoog:feat/native-chat-muse

Conversation

@clichedmoog

@clichedmoog clichedmoog commented Sep 26, 2026 •

Copy link
Copy Markdown

ELI5

Muse sessions now open in Orca's chat view (desktop toggle and mobile) instead of staying a raw terminal, like Claude, Codex, Grok and OMP already do.

What Changed

  • Added muse to NATIVE_CHAT_SUPPORTED_AGENT_LIST and the transcript-agent resolution. Muse joins grok/omp in requiring a locally readable transcript: its hook reports transcript_path: null, so under Model-A SSH there is no owning-host file to read.
  • New decodeMuseTranscriptLine over ~/.local/share/muse/sessions/YYYY/MM/DD/<uuid>/session.jsonl:
    • user_intent.accepted (refill blocks, model_messages fallback) -> user turn
    • assistant_message_committed -> assistant text
    • assistant_tool_calls_committed / tool_result_batch_committed -> tool turns (args JSON parsed, output kept raw)
    • user_input_prompt_requested / settled -> question card / user reply
    • cancelled terminal -> the shared interrupted row
    • Everything else (task lifecycle, reasoning, usage, reminders, retained_frame / retained_marker envelopes) skips, never throws.
  • Resolver: id-based search matching the parent-directory session id, reusing resolveMuseSessionsDir (XDG-aware). A subagent log nests one level deeper, so its parent dir is the child id — a parent id can never match a child log.
  • Wired through the full reader, the live tailer, and the journal legacy import. Reused museTimestampMs (now exported) for the microsecond-to-millisecond conversion.

Why

#22216 made Muse a first-class agent but left its conversation as a terminal: native chat renders each agent's own JSONL transcript and Muse had no decoder. This follows the same path #11523 (omp) established.

Alternatives considered:

  • Stateful multi-line assembly for richer turns — rejected; the decoder contract is stateless (at most one message per line), shared by every agent.
  • run :: started.prompt as a user-turn fallback — rejected; main logs always carry user_intent, and folding both double-renders (the AI Vault parser already dedupes this pair).

Linked Issue

Fixes #22990

Visual Proof

Desktop dev build on macOS (Settings > Experimental > Native Chat on), same Muse session:

Before (terminal) After (chat view)
Before: Muse session as raw terminal After: same session rendered as chat

Tables, code blocks, and file links render; the composer has no model/effort pill yet (follow-up, see below).

Note: the mobile toggle (Switch to chat view) cannot appear until the next companion release — the support list is compiled into the mobile binary, so the current TestFlight build still carries the pre-change list. No mobile code changes are needed beyond this PR: the next companion build picks up the shared gate automatically (covered by the mobile eligibility test).

Follow-ups kept out of this PR per the single-topic rule: a live-terminal effort picker for Muse (midSession command driving /effort; the catalog choices already exist for workers). A model picker is not possible — Muse has no model-listing command (#22383).

Testing

  • I manually tested these changes locally (dev build, macOS: toggled a live Muse session between terminal and chat view)
  • Automated tests added/updated

Decoder (10 tests), resolver (2), shared agent-support, settings chips, and mobile eligibility cases. Also verified against 8,295 lines of real local Muse logs: 359 decoded (user 9 / assistant 178 / tool 171 / system 1), zero thrown, zero empty turns. pnpm tc, oxlint and oxfmt clean on all touched files; neighboring suites pass (transcript reader, tail reader, turn lifecycle, muse vault scanner, mobile eligibility). Tested on macOS only.

Full pnpm test locally: 89,211 passed; the only failures also fail on clean main without this change (missing Playwright browsers, macOS bash 3.2 set -u behavior in skill-recipe-shell, real-CLI handshake, pty timing) plus one self-pollution case (the suite's own tests/e2e/.cross-version-checkouts/ tripping the env-writer ratchet on reruns; passes once removed). No failure is caused by this change.

AI Disclosure

Written with AI assistance (Muse Code, Meta Muse Spark); verified with the repo's own test suites plus the real-log decode run above.

Review

AI self-review summary (per CONTRIBUTING):

  • Cross-platform: the decoder is pure string-to-object (no fs, shell, or path ops). Paths flow through the existing museSessionIdFromFilePath (basename/dirname) and resolveMuseSessionsDir (join/homedir/XDG_DATA_HOME), already used cross-platform by the AI Vault scanner and the usage provider. No new platform assumptions. Tested on macOS only; the date-sharded log layout is what Muse writes on all three platforms.
  • SSH/remote/local: Muse joins the local-transcript gate because its hook reports transcript_path: null (see the muse-events fixture), so under Model-A SSH the chat view refuses instead of reading a wrong disk — same as grok/omp. Local and runtime-owned SSH paths are unchanged.
  • Agent/integration compatibility: purely additive — one new union member plus one new branch per dispatch site; the satisfies never exhaustiveness gate still holds. Renderer and mobile need no code changes (generic predicates). No changes to hook, usage, or account systems.
  • Performance: decoder is O(line), stateless, no I/O; the resolver walk is extension- plus predicate-pruned over date-sharded dirs, same as its siblings. The tail path reuses the identical decoder (no extra parse pass beyond the existing design).
  • UI quality: no new components or strings (no i18n impact); rendering reuses the existing message/turn UI. Visual proof still pending (draft).
  • Security: reads only the local session logs the Vault scanner and usage provider already read; no credentials touched, no network, no new IPC surface. Tool args go through JSON.parse in try/catch (raw string fallback) — no eval.

Agent skill upstream boundary

  • Not applicable — no skill installer source, tests, fixtures, registry entries, path tables, comments, or documentation touched.

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
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (see Review above)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@clichedmoog
clichedmoog marked this pull request as ready for review September 26, 2026 03:52
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

The change adds Muse as a supported native chat transcript agent. It locates Muse session files by session ID, decodes supported transcript records into native chat messages, and routes Muse transcripts through the transcript readers and legacy journal import. Tests cover agent eligibility, file resolution, and transcript decoding.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to b57da

Muse messages stored in retained frames can be missing from native chat transcripts and journal imports. Handle those records before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to b57da

Locally stored conversations become available through more chat views. Existing location and readability controls limit access, but the link between a session and its owner has not been fully established.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The new content exposure is concentrated in locally readable Muse sessions and their desktop or mobile chat consumers; the examined remote-worker path does not extend ID-based lookup to remote filesystems.

Trust Boundaries and Controls

  • observed — Guest or WSL path failures do not fall back to host session-ID discovery. Muse ID lookup matches a file's immediate parent directory, which distinguishes a parent session from a nested child log; that predicate is not an account-owner check.

Resilience and Maintainability Implications

  • observed — The legacy import accumulates decoded messages and identities before journal replacement, and the existing epoch replacement uses transactional commit or rollback. Whether an adoption-time sequence check is atomic with a same-fence live write remains unproven; that synchronization path predates this decoder registration.

Hardening Proposals

  • proposed — If Muse sessions can span accounts within a selectable root, bind transcript selection to the originating account as well as the session ID before displaying or importing content. The available evidence does not establish that such cross-account selection is reachable.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning PR #22998 implements the requested Muse decoder, XDG-aware session resolver, native-chat reader, journal import, supported-agent registration, and local-transcript gating. The decoder skips lifecycle … Restrict Muse session resolution to the parent session file layout and do not resolve nested subagent logs. Update the resolver test so a child log remains unresolved.
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 13 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changed files support #22990. Agent-list updates, local-transcript eligibility, decoder tests, resolver tests, timestamp export, live transcript tailing, and legacy journal import are required int…
Title check ✅ Passed The title clearly and concisely describes the primary change: rendering Muse transcripts in native chat.
Description check ✅ Passed The description is complete and follows the repository template. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, AI disclosure, compatibility consideration…
Full details: Linked Issues check

Explanation

PR #22998 implements the requested Muse decoder, XDG-aware session resolver, native-chat reader, journal import, supported-agent registration, and local-transcript gating. The decoder skips lifecycle and unrelated records and covers user turns, assistant text, tool calls and results, question prompts and answers, and cancelled terminals. However, resolveMuseSessionFile recursively matches any .jsonl whose parent directory equals the requested ID. The added test explicitly resolves subagent/sess-muse-child/session.jsonl. This exposes Muse subagent logs through native chat, although #22990 specifies parent session files only and keeps subagent logs out of scope.

  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 9ce2b63a-0c73-4d0d-8503-2129297285f9

📥 Commits

Reviewing files that changed from the base of the PR and between d1eb64e and b57dad8.

📒 Files selected for processing (13)
  • mobile/src/session/mobile-native-chat-eligibility.test.ts
  • src/main/ai-vault/session-scanner-muse-parser.ts
  • src/main/native-chat/agent-session-journal/journal-legacy-import.ts
  • src/main/native-chat/session-file-resolver.test.ts
  • src/main/native-chat/session-file-resolver.ts
  • src/main/native-chat/transcript-line-decoders-muse.ts
  • src/main/native-chat/transcript-line-decoders.muse.test.ts
  • src/main/native-chat/transcript-line-decoders.ts
  • src/main/native-chat/transcript-reader.ts
  • src/main/native-chat/transcript-tail-reader.ts
  • src/renderer/src/components/settings/NativeChatSupportedAgents.test.tsx
  • src/shared/native-chat-agent-support.test.ts
  • src/shared/native-chat-agent-support.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread src/main/native-chat/transcript-line-decoders-muse.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Muse batches real conversation records inside retained_frame envelopes, and the new decoder discards those whole lines — assistant text and question cards will silently vanish from the chat view and live tail. Fix before merge; details inline.

Reviewed changes

  • Support gate — muse joins NativeChatTranscriptAgent, NATIVE_CHAT_SUPPORTED_AGENT_LIST, resolveNativeChatTranscriptAgent, and nativeChatRequiresLocalTranscript (native-chat-agent-support.ts), so Muse joins grok/omp behind the local-transcript gate.
  • Decoder — new stateless decodeMuseTranscriptLine (transcript-line-decoders-muse.ts) mapping user intent, assistant text, tool calls/results, question cards/answers, and a cancelled terminal into turns.
  • Resolver — id-based resolveMuseSessionFile matching the parent-directory session id through museSessionIdFromFilePath, plus a museSessionsDir override.
  • Wiring — threaded through the full reader, the live tailer, and the journal legacy import; museTimestampMs exported from the ai-vault parser and reused.
  • Tests — decoder, resolver, shared support, settings chip, and mobile eligibility cases updated/added.

ℹ️ User turns without a user_intent record are not decoded

The decoder reads only runtime.user_intent.accepted for user turns, but the ai-vault Muse parser keeps run :: started.prompt as an explicit fallback "for logs missing intent records" (session-scanner-muse-parser.ts:76-91, foldUserTurn's skipIfDuplicate). If those logs exist, the native chat view drops those user turns. The author rejected a naive add because a stateless decoder cannot dedupe the pair and would double-render intent-carrying turns — a real constraint, so this is a scope call worth confirming rather than a required fix.

Technical details
# `run.started.prompt` fallback

## Affected sites
- `src/main/native-chat/transcript-line-decoders-muse.ts:47-52` — only `runtime.user_intent.accepted` yields a user turn.
- `src/main/ai-vault/session-scanner-muse-parser.ts:186-189` — `foldSessionEvent` folds `started.prompt` as the fallback and relies on `foldUserTurn`'s dedupe (`:103-116`) to avoid double-counting.

## Open questions for the human
- Do real Muse logs ever carry a turn whose only user text is `run.started.prompt`? If not, the omission is harmless; if legacy logs do, the journal legacy import will render those sessions missing their prompt.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/native-chat/transcript-line-decoders-muse.ts

This branch has not been deployed

No deployments
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.

[Feature]: Render Muse transcripts in native chat

1 participant