Repository navigation
feat(native-chat): answer /context in chat view for OpenClaude and OMP - #22294
Conversation
Over a terminal-backed chat, Claude's /context paints a grid only the hidden terminal sees and records nothing in the transcript, so the chat showed nothing. The chat host now answers it itself: the transcript decoder keeps each assistant response's API usage and model, and the composer replies with the same estimate the CLI statusline shows (input + cache tokens against the model's window) instead of typing the command into the PTY. The command is also listed in the curated Claude catalog so the slash menu offers it.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (10)
Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review. 📝 WalkthroughWalkthroughNative chat now records OMP assistant model, provider, token usage, and compaction metadata. Context usage uses model-specific context-window token counts. Supported local commands can return formatted answers without PTY dispatch. OpenClaude command output is surfaced and rendered in chat. Composer interruption logic now uses a dedicated hook. Priority: ➖ Normal Merge Risk: 🟡 Moderate · up to Some locally answered commands can unintentionally carry an earlier image into the next message, while OMP /context can omit the model window and percentage. Both issues should be fixed before merge. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: e3dfc9a3-ed50-4d85-b4d9-f90b654ca319
📒 Files selected for processing (21)
src/main/native-chat/transcript-line-decoders-claude-usage.test.tssrc/main/native-chat/transcript-line-decoders-claude.tssrc/renderer/src/components/native-chat/NativeChatComposer.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.tsxsrc/renderer/src/components/native-chat/native-chat-command-marker.tssrc/renderer/src/components/native-chat/native-chat-composer-types.tssrc/renderer/src/components/native-chat/native-chat-context-usage-answer.tssrc/renderer/src/components/native-chat/native-chat-pending.test.tssrc/renderer/src/components/native-chat/use-native-chat-composer-interrupt.tssrc/renderer/src/components/native-chat/use-native-chat-local-command-answer.tssrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.tssrc/renderer/src/components/native-chat/use-native-chat-pty-composer-send.tssrc/renderer/src/i18n/locales/en.jsonsrc/shared/claude-context-window.tssrc/shared/native-chat-context-usage.test.tssrc/shared/native-chat-context-usage.tssrc/shared/native-chat-local-commands.test.tssrc/shared/native-chat-local-commands.tssrc/shared/native-chat-slash-commands.test.tssrc/shared/native-chat-slash-commands.tssrc/shared/native-chat-types.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.
| const localAnswer = answerCommandLocally?.(text) ?? null | ||
| if (localAnswer !== null) { | ||
| // Why: the host answers in place of the PTY, so the composer resets as | ||
| // after a send but nothing is written to the terminal. | ||
| emitNativeChatPickerItemAccepted({ agent, itemKind: 'command' }) | ||
| emitNativeChatSendClassified({ agent, outcome: 'command' }) | ||
| onSlashCommand?.(text, localAnswer) | ||
| setHistory((previous) => pushHistory(previous, text)) | ||
| setDraft('') | ||
| setCaret(0) | ||
| setActiveSuggestion(0) | ||
| clearSkillOrigin() | ||
| setNotice(null) | ||
| return |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '1,140p' src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.ts
sed -n '40,100p' src/renderer/src/components/native-chat/use-native-chat-pty-composer-send.ts
rg -n 'clearImageAttachments|imageAttachments|useNativeChatPickerCommandDispatch' src/renderer/src/components/native-chatRepository: stablyai/orca
Length of output: 17356
🏁 Script executed:
sed -n '145,340p' src/renderer/src/components/native-chat/NativeChatComposer.tsx
sed -n '1,220p' src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsx
rg -n -C 5 'answerCommandLocally|/context|context' src/renderer/src/components/native-chat src/renderer/src | head -160Repository: stablyai/orca
Length of output: 26981
🏁 Script executed:
printf '%s\n' '--- NativeChatComposer ---'
sed -n '145,340p' src/renderer/src/components/native-chat/NativeChatComposer.tsx
printf '%s\n' '--- picker dispatch tests ---'
sed -n '1,220p' src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsx
printf '%s\n' '--- local answer references ---'
rg -n -C 5 'answerCommandLocally|/context|context' src/renderer/src/components/native-chat src/renderer/src | head -160Repository: stablyai/orca
Length of output: 27586
🏁 Script executed:
printf '%s\n' '--- answerCommandLocally bindings ---'
rg -n -C 4 'answerCommandLocally' src/renderer/src --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- context answer bindings ---'
rg -n -C 5 'formatNativeChatContextUsageAnswer|contextUsage|context usage|/context' src/renderer/src/components/native-chat src/renderer/src --glob '*.ts' --glob '*.tsx' | head -240Repository: stablyai/orca
Length of output: 37331
Clear image attachments after a locally answered picker command.
In a PTY session, selecting /context with an image attached enters the local-answer branch. That branch resets the composer and returns without clearing the attachment. The next message can include that stale image.
Suggested fix
setCaret(0)
setActiveSuggestion(0)
clearSkillOrigin()
+ clearImageAttachments()
setNotice(null)
return📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const localAnswer = answerCommandLocally?.(text) ?? null | |
| if (localAnswer !== null) { | |
| // Why: the host answers in place of the PTY, so the composer resets as | |
| // after a send but nothing is written to the terminal. | |
| emitNativeChatPickerItemAccepted({ agent, itemKind: 'command' }) | |
| emitNativeChatSendClassified({ agent, outcome: 'command' }) | |
| onSlashCommand?.(text, localAnswer) | |
| setHistory((previous) => pushHistory(previous, text)) | |
| setDraft('') | |
| setCaret(0) | |
| setActiveSuggestion(0) | |
| clearSkillOrigin() | |
| setNotice(null) | |
| return | |
| const localAnswer = answerCommandLocally?.(text) ?? null | |
| if (localAnswer !== null) { | |
| // Why: the host answers in place of the PTY, so the composer resets as | |
| // after a send but nothing is written to the terminal. | |
| emitNativeChatPickerItemAccepted({ agent, itemKind: 'command' }) | |
| emitNativeChatSendClassified({ agent, outcome: 'command' }) | |
| onSlashCommand?.(text, localAnswer) | |
| setHistory((previous) => pushHistory(previous, text)) | |
| setDraft('') | |
| setCaret(0) | |
| setActiveSuggestion(0) | |
| clearSkillOrigin() | |
| clearImageAttachments() | |
| setNotice(null) | |
| return |
There was a problem hiding this comment.
Important
claudeContextWindowTokens keys the 1M window off a [1m] suffix that Claude Code does not write into the transcript's message.model, so every 1M-context session reports a percentage roughly 5× too high.
Reviewed changes
- Decoder usage capture — Claude assistant rows now carry
modeland the API token usage; all-zero rows the CLI synthesizes are dropped. - Context derivation — newest usage-bearing response (
input + cache read + cache write) against the model window, with compact token formatting. - Host-answered
/context— the typed and picker paths record the answer on the command marker instead of writing to the PTY; the marker renders the answer in place ofRan /x. - Catalog + i18n —
contextadded to the Claude slash catalog; two new English strings. - Composer interrupt refactor — ESC/stop logic extracted to
use-native-chat-composer-interrupt.tswith no behavior change.
The structured lane really is untouched: dispatchPickerCommand and send both route to sendStructured when structuredTransport is set, so answerCommandLocally never intercepts it. The text.trim() in the PTY send hook is also safe because classifyNativeChatSend gates on the untrimmed draft.
ℹ️ Mobile now offers /context but cannot answer it
getAgentSlashCommands is shared with the mobile app, so adding context to CLAUDE_COMMANDS surfaces it in the mobile picker too. Mobile forwards it to the terminal as before, where Claude paints a grid the chat never sees — so on mobile the new menu row looks inert. Worth a deliberate decision: keep the row desktop-only, or wire the same host answer into the mobile composer.
Technical details
# Mobile picker offers a command only the desktop host answers
## Affected sites
- `src/shared/native-chat-slash-commands.ts` — `context` added to `CLAUDE_COMMANDS`, which is the
shared catalog (`getAgentSlashCommands`) consumed by mobile.
- Mobile composer (`mobile/src/session/…`) does not pass any `answerCommandLocally` equivalent.
## Open questions for the human
- Is a discoverable-but-inert `/context` on mobile acceptable for this PR, or should the catalog
entry be scoped to the desktop host until mobile answers it?DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
The /context answer sized the window from the transcript's model id, which never carries the [1m] suffix, so a 1M session read as 200k (e.g. 450k / 200k, 225%). The window now comes from the session's tracked model resolved through the host CLI's own model listing (the resolvedModel field the list_models probe already returned), and only a [1m] id that matches the model the transcript last answered with is sized; anything else reports the used figure alone. Also: - Offer /context only in the desktop terminal-backed composer; the shared catalog is mobile's too, and mobile cannot answer it. - Answer a typed /context even with images attached, as the picker does, and keep the attachments armed instead of sending them to the terminal. - Report nothing until a response lands after a /compact or /clear sent from the chat, instead of the pre-compaction size.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (52a3ed1), two commits landed and both prior threads were addressed:
- Reworked the 1M window source —
claudeContextWindowTokensnow takes the host's resolved session model (resolvedSessionModel(), the tracked catalog entry'sresolvedModel) and only states 1M when its base matches the transcript's last model, instead of keying off a[1m]suffix the transcript never carries. - Degraded the answer honestly —
windowTokens/percentageare now nullable; when no window can be established the host prints the used figure alone rather than guessing 200k. - Added a post-reset guard —
/contextanswers "unavailable" when the newest usage-bearing response predates a/compactor/clearthis pane sent, backed by the generalizedlatestCommandSentAtmarker helper. - Kept attachments on a host-answered command — the typed and picker local-answer paths now both leave image attachments armed (a real send still clears them), with tests pinning each path.
- Scoped
/contextto the terminal lane — removed from the shared per-agent catalog so mobile no longer offers an inert row;ptyLaneCommandsmerges the local commands only for the desktop PTY menu. - Carried
resolvedModelend to end — Claude'slist_modelsprobe now parses and spreads it throughCommitMessageModel/CatalogModelinto the discovery cache, covered by tests at each hop; hosts that predate the field degrade to an unknown window.
I traced the discovery pipeline (probe → discoverNativeChatCatalogModels → enrichment cache → replaceModels → resolvedSessionModel()) and confirmed /context reaches the typed path as command via the catalog-backed classifyNativeChatSend. The structured lane remains untouched.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…orts usage A host that predates usage decoding, or a scraped view, decodes Claude's replies without a model or usage, so the chat promised an answer after the next response that would never come. Once the agent has answered and no answer names its model, say usage is not available for this session.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 4d602afb-7791-4a07-a3db-99986a77f06d
📒 Files selected for processing (4)
src/renderer/src/components/native-chat/native-chat-context-command.test.tsxsrc/renderer/src/components/native-chat/native-chat-context-usage-answer.tssrc/renderer/src/components/native-chat/use-native-chat-local-command-answer.tssrc/renderer/src/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
- src/renderer/src/i18n/locales/en.json
Included review availability: Your plan provides up to 10 included reviews per hour; 2 remain after this review.
| if (!sessionReportsUsage(args.messages)) { | ||
| return formatNativeChatContextUsageUnreported() |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 'model|usage|NativeChatMessage' \
src/main/native-chat/transcript-line-decoders-claude.ts \
src/shared/native-chat-context-usage.ts \
src/shared/native-chat-types.tsRepository: stablyai/orca
Length of output: 19870
🏁 Script executed:
#!/bin/bash
# Read the reviewed file at the critical lines
sed -n '1,50p' src/renderer/src/components/native-chat/use-native-chat-local-command-answer.ts
# Search for sessionReportsUsage definition and related functions
rg -n 'sessionReportsUsage|formatNativeChatContextUsageUnreported|formatNativeChatContextUsageAnswer' src/ -A 5 -B 1Repository: stablyai/orca
Length of output: 7720
🏁 Script executed:
sed -n '49,70p' src/renderer/src/components/native-chat/use-native-chat-local-command-answer.tsRepository: stablyai/orca
Length of output: 838
Verify that assistant messages carry model whenever they carry usage.
sessionReportsUsage() returns false when an assistant (non-hook) message lacks a model field, triggering an early return that reports the session as permanently unavailable. However, deriveNativeChatContextUsage() can derive used tokens from an assistant message with usage but no model, by setting model to null and omitting the window size.
The Claude decoder emits model and usage as independent optional properties (lines 133–134 of transcript-line-decoders-claude.ts). If the decoder can produce a message with usage but no model, the early return at lines 30–31 would suppress derivable usage.
Confirm that the decoder guarantees model is present whenever usage is emitted for an assistant message. If this guarantee does not hold, either:
- Remove the early return and let
deriveNativeChatContextUsage()attempt derivation (it handles nullmodelcorrectly), or - Check for
usagepresence insessionReportsUsage()in addition to or instead ofmodelpresence.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (06dc157), one commit landed:
- Added an "unreported" answer —
answerNativeChatLocalCommandnow calls a newsessionReportsUsageguard before deriving: when the agent has answered but no decoded response names a model (a host that predates usage decoding, or a scraped view),/contextsays "Context usage is not available for this session." instead of wrongly promising a later answer. - Kept the pending answer for live previews — hook-sourced rows are ignored by that guard, so a preview with no transcript reply still reads "not known yet".
- Added the English string and a test —
components.native-chat.context.unreported, with the test pinning the unreported, empty-session, and hook-preview branches.
I traced the new guard against the decoder (model/usage are attached together, synthetic rows carry <synthetic> with no usage, scrape rows carry neither) and the routing: it sits before the compact/clear staleness check, so a post-reset /context still reports "not known yet" rather than "unavailable". The full PR diff and both prior threads (already resolved) were re-read.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Claude and Codex answer /context themselves in the structured chat lane, so the terminal-backed Claude answer, its [1m] window rule, and the resolved-model plumbing through the model listing go away. OMP's /context only paints a panel inside its TUI. The OMP decoder now keeps the provider, model and usage of each reply (none for aborted or errored replies, which OMP's own gauge skips) and turns compaction rows into the existing compaction boundary. The OMP model listing keeps each model's context window, and the chat answers /context from the last reply's prompt size against the window of the provider/model that served it.
OpenClaude writes its /context grid to the transcript as a <local-command-stdout> row, which the chat hid as harness noise. The live message preparation now turns the stdout row that answers an opted-in command (/context for OpenClaude) into command output with the ANSI colors stripped, and the notice row renders command output in monospace so the grid keeps its columns. Replies to other local commands stay hidden, and Claude is not opted in. OpenClaude's slash menu now lists /context; Claude's does not.
…st replaced The previous commit rewrote NativeChatNoticeRow.test.tsx wholesale, deleting main's compaction, tone, plan, provider-notice and old-reader coverage. Restore it and pin monospace command output through the message row instead.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟠 Major · Propagate contextWindowTokens into the capability payload. · commit-message-agent-spec.ts:203-210
src/shared/commit-message-agent-spec.ts:203-210
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPropagate
contextWindowTokensinto the capability payload.
toCommitMessageAgentCapability()drops the new field. A remote capability response therefore loses the discovered window before the renderer buildsCatalogModel.contextWindowTokens.Copy the field when it is defined.
Proposed fix
models: spec.models.map((model) => ({ id: model.id, label: model.label, ...(model.description ? { description: model.description } : {}), ...(model.thinkingLevels ? { thinkingLevels: [...model.thinkingLevels] } : {}), ...(model.defaultThinkingLevel ? { defaultThinkingLevel: model.defaultThinkingLevel } : {}), - ...(model.supportsFastMode ? { supportsFastMode: true } : {}) + ...(model.supportsFastMode ? { supportsFastMode: true } : {}), + ...(model.contextWindowTokens !== undefined + ? { contextWindowTokens: model.contextWindowTokens } + : {}) }))
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Advanced
Run ID: 55d97f90-cd38-4299-bc0a-e26209c34e58
📒 Files selected for processing (28)
src/main/native-chat/transcript-line-decoders-omp.tssrc/main/native-chat/transcript-line-decoders.omp.test.tssrc/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsxsrc/renderer/src/components/native-chat/NativeChatNoticeRow.tsxsrc/renderer/src/components/native-chat/NativeChatResolvedView.tsxsrc/renderer/src/components/native-chat/native-chat-command-marker.tssrc/renderer/src/components/native-chat/native-chat-context-command.test.tsxsrc/renderer/src/components/native-chat/native-chat-live-message-preparation.test.tssrc/renderer/src/components/native-chat/native-chat-live-message-preparation.tssrc/renderer/src/components/native-chat/native-chat-pty-session-options.tssrc/renderer/src/components/native-chat/native-chat-session-option-discovery.tssrc/renderer/src/components/native-chat/native-chat-session-option-enrichment.test.tssrc/renderer/src/components/native-chat/use-native-chat-local-command-answer.tssrc/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.tssrc/renderer/src/components/native-chat/use-native-chat-pty-composer-send.tssrc/shared/agent-session-option-catalog-omp.test.tssrc/shared/agent-session-option-catalog-types.tssrc/shared/commit-message-agent-spec.tssrc/shared/native-chat-command-output.test.tssrc/shared/native-chat-command-output.tssrc/shared/native-chat-context-usage.test.tssrc/shared/native-chat-context-usage.tssrc/shared/native-chat-local-commands.test.tssrc/shared/native-chat-local-commands.tssrc/shared/native-chat-slash-commands.test.tssrc/shared/native-chat-slash-commands.tssrc/shared/native-chat-types.tssrc/shared/omp-model-list-probe.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/shared/native-chat-types.ts
- src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
Included review availability: Your plan provides up to 10 included reviews per hour; 3 remain after this review.
There was a problem hiding this comment.
Important
The OMP and OpenClaude /context paths are sound and covered by targeted tests, but this revision drops the pre-existing NativeChatNoticeRow test coverage.
Reviewed changes
Since the prior pullfrog review (20f03c6), this PR pivoted from a Claude host answer to OMP + OpenClaude:
- Answered
/contextfor OMP from the transcript. The decoder now keeps each reply'sprovider/model/usage and turnscompactionrows into the compaction divider; the answer sumsinput + cacheRead + cacheWriteand resolves the window fromomp models --jsonfor the model that actually served the reply. - Surfaced OpenClaude's own
/contextreport. A<local-command-stdout>row is paired with the newest command envelope written at or before it and rendered as plain command output, gated to OpenClaude. - Removed the Claude path (
claude-context-window, the resolved-model threading) and scoped the host answer to OMP. - Added a
command-outputpresentation rendered in monospace viaNativeChatNoticeRow. - Rewrote
NativeChatNoticeRow.test.tsx, dropping its prior coverage (inline).
I verified the new tests pass, ran the affected integration suites (native-chat-context-command, live-message-preparation, pending, session-option-enrichment, composer-catalog, agent-profiles), and confirmed the changed-code quality gate is clean. The OpenClaude surfacing correctly depends on context being in getVerifiedNativeChatCommands('openclaude') so surfaceSkillInvocationUserTurns leaves the envelope for surfaceNativeChatCommandOutputs to pair.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…ts report The shared slash catalog now lists /context for OpenClaude because the desktop chat surfaces its transcript reply. Mobile's transcript fold does not, so the command did nothing there. Mobile now offers the catalog minus commands answered only by a surfaced reply, which returns its OpenClaude menu and send classification to main's behavior.
Left from the earlier design; the answer text states the estimate itself.
…text Skill surfacing ran first and rewrote non-catalog envelopes such as /model into plain user text, so command-output pairing no longer saw them. A later /model reply then paired with the earlier /context envelope and appeared in the chat. Pair outputs against the raw envelopes before skill surfacing.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (0c36584), four commits landed, all addressing prior feedback:
- Kept OpenClaude
/contextoff mobile —getMobileNativeChatCommandsfilters out commands answered only by a surfaced reply, so mobile's menu and send classification no longer offer an inert row. - Fixed reply pairing —
prepareNativeChatLiveMessagesnow surfaces command outputs before skill-envelope rewriting, so/model's envelope is still visible to the pairing pass and its reply no longer mispairs with an earlier/context. - Dropped the dead
estimatedflag —NativeChatContextUsage.estimatedwas alwaystrueand never read;contextTokensFromUsageis now module-private. - Restored the notice-row suite — the 13 pre-existing
NativeChatNoticeRowtests return alongside the new monospace command-output test.
I re-read the full PR diff, re-ran the affected suites (native-chat-command-output, native-chat-context-usage, native-chat-live-message-preparation, NativeChatNoticeRow, mobile mobile-native-chat-send-classification) and pnpm tc:web; all pass. The prior open thread on the dropped test coverage is resolved.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…n and show it as an aside After a compaction the agent has already answered, so the unknown-usage line now promises the next response instead. The host answer is one sentence, so it draws as the muted line it replaces rather than a monospace grid. The provider/model selector now comes from one helper shared by the OMP listing parser and the answer, and new tests drive real OpenClaude and OMP transcript lines through the decoders. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (640b55d), one commit landed:
- Reworded the OMP pending answer — "available once the agent has answered in this session" becomes "available after the agent next responds", which also reads correctly for the post-compaction gap where a response predates the boundary.
- Drew the OMP answer as the aside it replaces —
commandMarkersAsMessagesnow rendersmarker.outputas plainsystemtext instead ofpresentation: 'command-output'; the monospace grid presentation stays reserved for OpenClaude's transcript-sourced report, which is the only column-laid-out output. - Shared the OMP model selector —
ompModelSelectoris extracted and reused by bothparseOmpModelListand the renderer's window lookup, removing the duplicatedprovider/modelrule. - Added decoder-level coverage — a new Claude-decoder test drives the OpenClaude
/contextrows end to end (pairing, noise filtering, colors stripped), and the OMP decoder test pins prompt-to-window derivation plus the compaction boundary.
I re-ran the affected suites (transcript-line-decoders-claude-command-output, transcript-line-decoders.omp, native-chat-pending, native-chat-context-command, agent-session-option-catalog-omp) and pnpm tc:web / pnpm tc:node; all pass. The prior open thread is resolved.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…context replies on the catalog A `<local-command-stdout>` row now answers the command row its `parentUuid` names, carried on decoded messages as the optional `parentId`, instead of the newest command row at or before its timestamp. A reply with no loaded linked row (older host, command outside the tail window) stays hidden. How each agent's `/context` is answered is declared once, as `reply` on its catalog row (`composer` for OMP, `transcript` for OpenClaude). The slash menu, the composer's answer, the transcript surfacing and the mobile catalog all derive from it, and both send paths share one intercept. Mobile no longer lists OMP `/context`, which did nothing there.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (9679aef), one refactor commit landed:
- Paired command replies by transcript row link — the Claude decoder now carries
parentUuidas an optionalparentId, andsurfaceNativeChatCommandOutputssurfaces a<local-command-stdout>row only when the row it links to parses as a command envelope declaring atranscriptreply. This replaces the timestamp/order heuristic, so a reply stamped before its command still pairs, and a reply with no loaded linked row (older host, or a command envelope outside the loaded tail window) stays hidden — the documented degradation. - Moved the reply declaration onto the catalog row —
SlashCommandSuggestion.replyis now the single place/contextis declared (composerfor OMP,transcriptfor OpenClaude); the slash menu,getNativeChatCommandReply,getAgentAnsweredNativeChatCommands, the composer answer, and transcript surfacing all read it. - Routed both send paths through one intercept —
answerNativeChatCommandInComposernow handles the typed and picker paths (trim, history, draft/caret, skill origin, notice), with attachments staying armed, removing the duplicated local-answer blocks. - Kept
/contextoff mobile for both agents — mobile's menu and send classification now derive fromgetAgentAnsweredNativeChatCommands, so OMP's inert/contextno longer appears. - Bounded
parentIdon the worker wire —boundWorkerTranscriptMessagesmapsparentIdthrough the sameboundIdentifierdigest as the parent'sid, keeping the link joinable. - Removed
native-chat-local-commands.tsand its obsolete test, superseded by the catalog declaration; no remaining importers.
I traced the link end to end: the decoder sets id from record.uuid and parentId from record.parentUuid (confirmed against the new real-session fixture), the assembler's mergeOne keeps transcript objects verbatim (same-source never replaces; cross-source replacement inserts the transcript copy as-is), and RPC sanitizeMessage spreads the message, so parentId reaches the renderer intact. The prepareNativeChatLiveMessages order swap is neutral for the tested cases because surfaceSkillInvocationUserTurns leaves catalog envelopes untouched. Ran the seven affected suites (66 tests), pnpm typecheck:node, and pnpm typecheck:web — all green.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Review status: ready to mergeScope.
How it works
Review. Five review rounds plus a separate architecture review. Fixed along the way:
The architecture review led to matching replies by row link and to the single declaration above. The final round found nothing to change. Validation
Known gaps
|
…are no transcript reply Every Claude-format row now carries its parent link, so once a session holds any local-command reply (/model, /compact) the surfacing pass built a map of every message on each live update and changed nothing for agents like Claude. Agents whose catalog declares no transcript reply now return before the scan.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior pullfrog review (ef691da), one commit landed:
- Skipped command-output surfacing when no reply is declared —
surfaceNativeChatCommandOutputsnow builds the set of catalog command names whose row declaresreply: 'transcript'once per call and returns the message list unchanged when that set is empty, so agents like Claude, Codex, and OMP skip the per-update map build entirely. The per-rowgetNativeChatCommandReplylookup is replaced by aSet.has, which resolves against the samegetVerifiedNativeChatCommands(agent)catalog and is equivalent for the unique command names it holds.
I confirmed the guard preserves behavior: the catalog is static per agent with no duplicate names, so the set membership matches the old lookup exactly, and the existing "leaves agents that declare no transcript reply untouched" case already pins the early return. native-chat-command-output.test.ts passes (5 tests).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
Review status: ready to merge (head
|
Resolve conflicts with main and adopt the context usage helpers that landed there: the terminal-lane usage type is now AgentSessionTokenUsage, the token sum is contextTokensFromUsage, and the answer uses the shared formatContextTokenCount. Add the /context answer strings to the non-English catalogs.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This run re-reviewed the only commit since the prior pullfrog review (10996cd): c1b1c9b, a merge of origin/main (98171a89) into the branch. git show c1b1c9b --cc names 31 conflict-resolved files; I read all 22 combined-hunk files and confirmed neither side's work was dropped.
- Adopted main's shared context-usage helpers — the branch's
NativeChatTokenUsage, privatecontextTokensFromUsage, and localformatContextTokenCountare replaced by main'sAgentSessionTokenUsage/contextTokensFromUsage(agent-session-context-usage.ts) and the renderer'sformatContextTokenCount(native-chat-context-usage-summary.ts). Expectations updated for main's capital1M. - Combined the Claude decoder changes — main's
unwrapClaudePastedContentBlockon user blocks sits alongside the branch's...parent(parentId), which the OpenClaude reply pairing reads. - Combined the composer optimistic-send refactor with the local-answer intercept — main's
optimisticSendOutcome/record/deliveryand the branch'sanswerCommandLocallyandonSlashCommand(command, output?)both survive;answerNativeChatCommandInComposerstill early-returns the typed and picked paths. - Merged the
NativeChatMessageshape — main'sAgentJournalProducerLinkagefields (journalPosition,sentAs,queued, …) plus the branch'smodel/provider/usage/parentId; the worker payload stripsjournalPositionwhile still boundingparentId. - Kept the
command-outputpresentation ahead of main's host-status rows — the OMP aside and OpenClaude monospace grid branches are unchanged. - Added the
/contextanswer strings to the non-English catalogs and reconciled main's new native-chat keys inenand translations.
Verified: 11 affected suites (111 tests) green, pnpm tc:node, pnpm tc:web, pnpm run verify:localization-catalogs, and pnpm run verify:rpc-params-catalog all pass. All prior pullfrog threads are resolved.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

ELI5
In the terminal-backed Chat view,
/contextnow shows an answer for OpenClaude and OMP sessions. Before, it showed nothing for either. Claude, Codex and Grok are not changed by this PR.What Changed
OpenClaude.
/contextwas not in the slash menu. Typed by hand, it ran in the hidden terminal. OpenClaude writes its report to the transcript, but the chat treated that row as internal noise and hid it, so nothing appeared./contextis in OpenClaude's slash menu. The chat shows OpenClaude's own report, the same grid its terminal draws (model, used/window tokens and percentage, usage by category), in monospace with the terminal colors removed.OMP.
/contextwas in the menu, but it only opens a panel inside OMP's terminal and writes nothing to the session file. The chat showed onlyRan /context.Ranline, drawn the same muted way, for exampleContext: 21.7k / 272k tokens (8%), estimated from the last response.If OMP's model list does not give that model's window, the answer isContext: 21.7k tokens used, estimated from the last response.Before the first reply, or after a compaction until the next reply, it says usage is not known yet and becomes available after the agent next responds. When the remote host is older than this change, it says usage is not available for this session. Compactions now also show as a "Context compacted" divider in the OMP chat.Claude, Codex, Grok. In the terminal-backed Chat view, Claude
/contextbehaves as on main: it goes to the terminal and nothing appears in chat. Claude and Codex answer/contextthemselves in the structured chat session, which is where they are moving. Grok is a follow-up.Mechanism.
<local-command-stdout>row. Claude-format transcripts link every row to the row before it (parentUuid), and OpenClaude writes the reply row linked to its<command-name>/context</command-name>row. The transcript decoder now keeps that link on each message as an optionalparentId(the id of the row this one follows). A reply row is shown only when the row it links to is a command whose declared reply istranscript(only/contextfor OpenClaude). The matched row becomes command output (plain text, ANSI removed). Replies to other local commands (/model,/compact, and so on) and Claude's rows stay hidden as before. A reply with no link (an older host) or whose command row is outside the loaded part of the transcript also stays hidden, as on main. The notice row now renders command output in monospace so the grid keeps its columns.compactionrow becomes the existing compaction divider.omp models --jsonalready reports each model'scontextWindow, and the model list parser now keeps it as an optionalcontextWindowTokenson the model listing the chat already holds. The answer takes the newest reply with usage, sums input plus both cache counts, and looks up the window for that reply'sprovider/model. A compaction after that reply means nothing is known until the next one./contextis answered is written once, asreplyon the agent's slash-command catalog row (composerfor OMP: the chat answers it and never sends it;transcriptfor OpenClaude: the chat shows the row the agent writes). The slash menu, the composer's answer, the transcript surfacing and the mobile menu all read that field, and the typed and picked send paths share one intercept. No other command declares a reply.[1m]window rule, and the resolved-model field threaded through Claude's model listing, the commit-message model types and the terminal session options, plus the Claude decoder's usage capture.Compatibility. The new message fields (
provider,usage) and the new listing field (contextWindowTokens) are optional. An older remote host sends neither, and the chat then says usage is not available or leaves out the window. An older client ignores them. The newparentIdmessage field is optional too: an older host does not send it, and the chat then keeps OpenClaude's reply hidden, as on main; an older client ignores it. One change in what the host sends: OMP compaction rows now arrive as a system message. Older desktop clients already render that as the compaction divider, and mobile shows it as a line of text.Why
contextSnapshot.promptTokensit stores on replies as input + cache read + cache write, and in a real session they match (680 + 20,992 = 21,672). So no separate prompt-size field is stored. The window comes from OMP's own model list, looked up for the model that actually answered, not the one selected in the picker.[1m]models. Grok is deferred to keep this PR focused./contextappear in the menu, so the row and its reply cannot drift apart, and OMP has no profile (adding one would also change its skill-prefix handling)./contextto the terminal and pointing the user there (no answer in the chat), or running a second headless CLI per question (an extra process, and it would not see the live session's state).Linked Issue
Related to #22292. That issue is about Claude. This PR no longer changes Claude's terminal-backed chat, because Claude's
/contextis answered in the structured chat session instead.Visual Proof
Captured on macOS in an isolated dev instance of this branch (head
10996cd347), driven over the Chrome DevTools Protocol, with the terminal-backed Chat view for every agent.OpenClaude:
/contextis listed in the slash menu.OpenClaude: typed
/contextshows OpenClaude's own report in monospace, laid out exactly as OpenClaude writes it, with no escape codes.OpenClaude: a second
/context, picked from the menu. Each report stays with its own command.OpenClaude:
/modelafterwards. Its reply row stays hidden, as on main.OMP: the chat answers
/contextitself, and nothing is typed into OMP's terminal (next image). This machine has no working OMP provider login, so OMP never replied and the answer is the "not known yet" case. The case with a reply is covered by a unit test that runs a real OMP reply and a realomp models --jsonrow: 21.7k / 272k (8%).Claude (unchanged): the menu does not list
/context, which matches main, and a normal prompt still gets its reply.Codex (unchanged): the slash menu opens and a normal prompt gets its reply.
Testing
OpenClaude 0.31.0 was run in an isolated home directory with a provider URL that goes nowhere, running
/context,/cost,/context. Each/contextwrote a caveat row, a<command-name>/context</command-name>row and a<local-command-stdout>row with the colored grid, and each reply row'sparentUuidis its command row'suuid. (/costreplies in asystemrow the decoder does not read.) Timestamps are not reliably in write order: one command row was stamped 1 ms before the row it follows, which is why replies are matched by row link. The whole session file, with the session id and folder scrubbed, is the test fixture.OMP 17.0.5: checked in the shipped CLI how
/contextis computed (last non-aborted, non-errored reply;promptTokens= input + cache read + cache write; compaction rows end the search). Also checked thecompactionrow fields and thecontextWindowfield inomp models --json. Checked against a real local session reply (identifiers removed).Unit tests cover: the OMP decoder (provider, model and usage kept; aborted and errored replies skipped; compaction rows), the listing window through parsing and discovery, the terminal session options lookup, the answer text (window, no window, compaction, older host), real OpenClaude and OMP transcript lines driven through the transcript decoders into the reply surfacing and the usage answer (including the OMP reply's
provider/modelmatching its listing row), the typed and picked composer paths, reply matching for OpenClaude by row link (a/contextand a/modelbatch interleaved at one timestamp, a reply stamped before its command, no link, a command row outside the loaded window), the declared replies and the per-surface menus, the noise filter, monospace rendering, and mobile leaving OpenClaude and OMP/contextout of its menu. The mobile test was not run through the mobile setup (it needs Expo, which is not installed here); it was run against a copy of the module with the shared code linked in. Each regression test was checked by removing the fix and confirming it fails.Electron (macOS, isolated dev instance, CDP): OpenClaude typed and picked
/contextrender its own report; Claude's menu omits/context; OMP answers in the chat without writing to its terminal. The OMP case with a reply was not captured live because this machine has no working OMP provider login.I manually tested these changes locally
Automated tests added/updated, or explained why not below
Mobile: mobile does not run the desktop message preparation and its markdown rows cannot keep the report's columns, so mobile leaves OpenClaude's
/contextout of its menu and send classification, exactly as on main. Mobile change: mobile also no longer lists OMP's/context. On main it was listed but did nothing there (OMP draws the answer inside its terminal and writes nothing the chat reads), and mobile cannot compute the answer the desktop composer gives. Typed by hand it still goes to the terminal. Known limitation: showing these answers on mobile would need the desktop's transcript handling plus a mobile layout that keeps the report's columns, left for a follow-up. OMP compaction rows show on mobile as a plain "Context compacted" line, the same way mobile already shows other agents' compaction rows. After OMP/new(also/clear), OMP starts a new session file. The answer resets only if the chat follows that switch, or for/clearsent from the chat. Linux and Windows were not exercised, and nothing here is platform-specific.Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)