Skip to content

feat: add Gemini CLI + CodeBuddy sessions, polish Usage and Sessions UI#113

Merged
ymkiux merged 21 commits intomainfrom
feat/gemini-cli-sessions
Apr 29, 2026
Merged

feat: add Gemini CLI + CodeBuddy sessions, polish Usage and Sessions UI#113
ymkiux merged 21 commits intomainfrom
feat/gemini-cli-sessions

Conversation

@ymkiux
Copy link
Copy Markdown
Collaborator

@ymkiux ymkiux commented Apr 28, 2026

Summary

This PR expands the Sessions experience by adding support for Gemini CLI and CodeBuddy Code sessions, and includes documentation + Web UI polish.

Key Features

  • New session sources in Session Browser:
    • Gemini CLI
    • CodeBuddy Code
  • Resume command copy support:
    • Copy gemini -r <sessionId> / codebuddy -r <sessionId> resume commands
  • Usage page: add a “Current session” summary bar at the top:
    • API duration / Total duration / Tokens (aligned with the current usage range)

Web UI Improvements

  • Session list titles are forced to a single line (ellipsis truncation)
  • Tighten spacing in the Sessions filter toolbar
  • Keep Sessions header action buttons from wrapping/stacking (layout regression fixes)

Docs

  • README / README.zh: expand Session Management docs (sources, default paths/env overrides, export and resume guidance)

Tests

  • Updated and passing unit/e2e coverage (UI regressions, resume command, usage stats)

Summary by CodeRabbit

  • New Features

    • Full Gemini CLI & CodeBuddy session support (browse/query/view/plain/export/delete/restore), selectable UI sources, resume commands, installer targets, and a current-session usage strip.
  • Bug Fixes

    • Improved session parsing, accurate message counting, query scanning, path/trash normalization, and export/clip handling for both sources.
  • Tests

    • New E2E and unit tests covering Gemini/CodeBuddy flows and sessions header actions/layout.
  • Documentation

    • README, guides, and install docs updated to include Gemini CLI and CodeBuddy.
  • Style

    • Header actions layout and session title truncation CSS refinements.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

Adds first-class Gemini CLI and CodeBuddy Code session support across CLI discovery/caching, per-source path resolution, session-summary parsing/message extraction, export/trash flows, Web UI/source handling, tests, i18n, install targets, and documentation updates.

Changes

Cohort / File(s) Summary
CLI core & session parsing
cli.js, cli/session-usage.js, lib/cli-sessions.js
Register gemini and codebuddy sources, add per-source tmp/project dir resolvers, wire parseGeminiSessionSummary/parseCodeBuddySessionSummary, and add CodeBuddy record→message extraction and message counting logic.
Session APIs: detail/plain/export/trash
cli.js (detail/export/trash flows, path resolution, trash normalization)
Support reading Gemini .json and CodeBuddy .jsonl sessions; parse Gemini header bytes and message arrays; parse CodeBuddy JSONL head/tail for usage and counts; strip leading system messages and apply message limits; normalize trash entries/extensions.
Web UI state, logic & templates
web-ui/app.js, web-ui/logic.sessions.mjs, web-ui/modules/.../session-browser.mjs, web-ui/modules/.../session-trash.mjs, web-ui/modules/app.methods.session-actions.mjs, web-ui/modules/app.methods.install.mjs, web-ui/partials/.../panel-sessions.html, web-ui/partials/.../layout-header.html
Treat gemini/codebuddy as first-class sources (state maps, normalization, query enablement, path loading, source-change handling, labels/dropdown options, resume-command generation) and add CLI install targets for both providers.
Tests & test-runner
tests/e2e/test-setup.js, tests/e2e/test-sessions.js, tests/unit/session-usage-backend.test.mjs, tests/unit/session-header-actions-layout.test.mjs, tests/unit/run.mjs, tests/unit/session-browser-timeline-regression.test.mjs
Provision Gemini session files for E2E and expose IDs to ctx; extend E2E assertions to Gemini; inject parser stubs in unit tests; add CSS layout contract test; add source-change/restore regression tests; include new test in runner.
Localization, styling & UI text
web-ui/modules/i18n.dict.mjs, web-ui/styles/controls-forms.css, web-ui/modules/app.computed.dashboard.mjs, web-ui/partials/.../panel-sessions.html, web-ui/styles/*.css
Add i18n labels for Gemini/CodeBuddy, add .sessions-header-actions CSS and related UI styling, update session truncation behavior and toolbar spacing, and surface current-session usage UI strings.
Docs & README
README.md, README.zh.md, site/guide/getting-started.md, site/index.md
Document Gemini CLI and CodeBuddy Code in session-management scope; expand codexmate export-session --source accepted values and add install command examples for new CLIs.
Misc wiring & small helpers
web-ui/modules/app.computed.session.mjs, web-ui/modules/app.methods.session-browser.mjs, tests/unit/...
Add usageCurrentSessionStats computed helper, session-path option maps and loading/persistence tweaks, validation/help strings, and minor label/behavior adjustments for new sources.

Sequence Diagram(s)

sequenceDiagram
  participant WebUI as Web UI
  participant API as Server/API
  participant FS as Filesystem
  participant Parser as SessionParser

  WebUI->>API: GET /session-detail?source=gemini&id=<id>
  API->>FS: resolve Gemini tmp dir & read `tmp/<project>/chats/<id>.json`
  FS-->>API: raw JSON (header bytes + messages)
  API->>Parser: parse header (sessionId, model, timestamps) and messages (strip leading system)
  Parser-->>API: normalized session {title, model, messages}
  API-->>WebUI: JSON response (sessionDetail with sourceLabel "Gemini CLI")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • awsl233777
  • SurviveM

Poem

🐰 I hop through tmp files, sniffing JSON dew,
Found Gemini chats and CodeBuddy too,
Parsers hum, messages fall in line,
Exports ready, labels set — all fine,
Hoppity hooray — sessions neatly new!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The PR title accurately summarizes the main feature additions: full session support for Gemini CLI and CodeBuddy, plus UI polish for Usage and Sessions panels.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gemini-cli-sessions

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cli.js (2)

3386-3400: ⚠️ Potential issue | 🟠 Major

Gemini inventory still collides with Codex cache/lookup helpers.

This path now passes source === "gemini", but the shared inventory helpers still collapse every non-claude source to codex. After a Codex browse, a Gemini browse can reuse the cached Codex inventory, and the sessionId -> filePath map is registered into the Codex store instead of the Gemini store. Please make the shared cache-key / lookup normalization source-aware before routing Gemini through this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 3386 - 3400, The cache/lookup is collapsing non-claude
sources to 'codex', causing Gemini to reuse Codex cache; fix by making the cache
key and cache access source-aware: update buildSessionInventoryCacheKey (or call
it with the normalizedSource) so the key includes normalizedSource, and ensure
getSessionInventoryCache and setSessionInventoryCache are invoked with/expect a
source (pass normalizedSource) rather than relying on helpers that collapse
non-claude to codex; specifically change listSessionInventoryBySource to use a
source-aware cache key and to pass normalizedSource into
getSessionInventoryCache/setSessionInventoryCache (or update those helper
implementations) so Gemini and Codex maintain separate caches and
sessionId->filePath maps.

5444-5494: ⚠️ Potential issue | 🔴 Critical

Gemini trash entries are still being serialized as Codex entries.

This function now accepts source === "gemini", but the shared trash helpers it calls still coerce every non-claude source to Codex semantics. A trashed Gemini session will therefore be indexed under source: "codex", pick up the wrong labels/provider defaults, and restore into the Codex sessions tree instead of .gemini/tmp/....

🧹 Nitpick comments (4)
web-ui/modules/app.methods.session-browser.mjs (1)

130-133: Source normalization is correct, but duplicated in three places.

Please centralize this mapping into one helper to avoid future source-drift bugs.

Refactor sketch
+        normalizeSessionPathSource(source) {
+            if (source === 'claude' || source === 'gemini' || source === 'all') return source;
+            return 'codex';
+        },

         syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
-            const targetSource = source === 'claude'
-                ? 'claude'
-                : (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
+            const targetSource = this.normalizeSessionPathSource(source);
             ...
         },

         refreshSessionPathOptions(source) {
-            const targetSource = source === 'claude'
-                ? 'claude'
-                : (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
+            const targetSource = this.normalizeSessionPathSource(source);
             ...
         },

         async loadSessionPathOptions(options = {}) {
-            const source = options.source === 'claude'
-                ? 'claude'
-                : (options.source === 'gemini' ? 'gemini' : (options.source === 'all' ? 'all' : 'codex'));
+            const source = this.normalizeSessionPathSource(options.source);
             ...
         },

Also applies to: 147-150, 171-174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.methods.session-browser.mjs` around lines 130 - 133,
Duplicate source-to-target mapping appears in multiple places; create a single
helper (e.g., normalizeSource or mapSourceToTarget) at module scope that
implements the logic (source === 'claude' ? 'claude' : source === 'gemini' ?
'gemini' : source === 'all' ? 'all' : 'codex') and replace the repeated inline
logic in the three locations that reference this mapping (the spots that read
this.sessionPathOptionsMap[targetSource] and the other two occurrences) to call
that helper instead; ensure the helper is imported/defined in the same module
scope and used consistently so all accesses use the same normalized targetSource
value.
tests/e2e/test-sessions.js (2)

6-6: Optional: add explicit path-list assertion for seeded Gemini path.

Since geminiSessionPath is already available from setup, asserting it appears in list-session-paths output would complete Gemini path-flow coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test-sessions.js` at line 6, Add an explicit assertion that the
seeded Gemini session path (geminiSessionPath) appears in the output of the
list-session-paths check: locate where the test already destructures
geminiSessionPath from ctx and where other session-path assertions run (the
list-session-paths response handling), then add an assertion that the response
includes geminiSessionPath (similar to the existing checks for sessionPath and
claudeSessionPath) so Gemini path flow is covered.

84-87: Strengthen Gemini export assertion to validate payload semantics.

Line [118]-Line [120] only checks existence. Consider also asserting expected text (e.g., hello from codexmate) and truncation flag behavior for tighter regression detection.

Also applies to: 99-101, 118-120

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test-sessions.js` around lines 84 - 87, Update the Gemini session
assertions to validate message semantics: in the test that calls
api('session-detail', { source: 'gemini', sessionId: geminiSessionId })
(variable sessionDetailGemini), keep the Array.isArray check but also assert
that at least one message's text includes "hello from codexmate" and that the
message object(s) has the expected truncation flag (e.g., isTruncated ===
true/false as the spec requires). Apply the same stronger checks to the other
two similar blocks referenced (the ones around the other sessionDetail
variables) so each asserts both the expected substring in message.text and the
correct truncation flag on the corresponding message object.
cli/session-usage.js (1)

85-87: Avoid exception-driven control flow for missing Gemini parser dependency.

At Line [85]-Line [87], an explicit function check is clearer than relying on a thrown TypeError and catch fallback.

Suggested refactor
-                summary = normalized.source === 'claude'
-                    ? parseClaudeSessionSummary(filePath, summaryOptions)
-                    : (normalized.source === 'gemini'
-                        ? parseGeminiSessionSummary(filePath, summaryOptions)
-                        : parseCodexSessionSummary(filePath, summaryOptions));
+                if (normalized.source === 'claude') {
+                    summary = parseClaudeSessionSummary(filePath, summaryOptions);
+                } else if (normalized.source === 'gemini') {
+                    summary = typeof parseGeminiSessionSummary === 'function'
+                        ? parseGeminiSessionSummary(filePath, summaryOptions)
+                        : null;
+                } else {
+                    summary = parseCodexSessionSummary(filePath, summaryOptions);
+                }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/session-usage.js` around lines 85 - 87, The current code relies on a
thrown TypeError to fall back from parseGeminiSessionSummary to
parseCodexSessionSummary; change this to an explicit check by testing the Gemini
parser's availability (e.g. typeof parseGeminiSessionSummary === 'function' or a
hasGeminiParser flag) before calling it, and if it's not available call
parseCodexSessionSummary with the same filePath and summaryOptions; update the
conditional expression around parseGeminiSessionSummary/parseCodexSessionSummary
to use that explicit check instead of relying on exception-driven control flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli.js`:
- Around line 4129-4130: The current assignment of cwd (const cwd = projectHash
? path.join(getGeminiTmpDir(), projectHash) : '') is synthesizing a temp-hash
path; change it to prefer the real workspace path provided in the session JSON
(use json.projectRoot or json.cwd) and only fall back to the tmp/hash when those
are missing. Update the code that computes cwd (the variable named cwd, and any
logic around projectHash/getGeminiTmpDir()) to use sessionJson.projectRoot ||
sessionJson.cwd || (projectHash ? path.join(getGeminiTmpDir(), projectHash) :
'') so Gemini summaries will use the real project path for filtering and
list-session-paths.
- Around line 4142-4149: The browse/list path currently returns summary objects
with messageCount: 0 and __messageCountExact: false, causing Gemini rows to
always show zero; update the listSessionBrowse() (the browse/list path that
builds these summary objects) to compute and assign the real message count
instead of the hardcoded zero—either call the existing message-count helper
(e.g., whatever function is used elsewhere to hydrate exact counts) or compute
counts inline and set messageCount and __messageCountExact = true for sessions
where counts are available; remove the hardcoded zero assignment in the summary
construction and ensure variable names messageCount and __messageCountExact are
populated from the real count computation.
- Around line 3018-3064: The current gemini branch reads the raw file bytes and
runs matchTokensInText against the serialized JSON, causing false positives;
instead parse the Gemini JSON and scan message-level text like the roleFilter
!== 'all' path: read/JSON.parse the file, iterate rawMessages (using
normalizeGeminiMessageRole, extractGeminiMessageText, extractMessageText), feed
each message to consumeSessionQueryMessage and stop on match, accumulate
snippets via truncateText when state.snippetLimit > 0, set
state.count/state.snippets accordingly, and finally return
buildSessionQueryScanResult; do not run matchTokensInText against the full
serialized JSON blob or limit to an arbitrary 512KB raw slice.

In `@web-ui/logic.sessions.mjs`:
- Line 32: The UI copy for the enabled-query i18n key is stale after adding
Gemini support; update the i18n string used when normalized queries include
'gemini' (the same logic branch that returns normalized === 'codex' ||
normalized === 'claude' || normalized === 'gemini' || normalized === 'all') to
mention Gemini (e.g., “supports Codex/Claude/Gemini” or equivalent), and ensure
any placeholder or tooltip that references enabled-query reflects the new
option.

In `@web-ui/partials/index/layout-header.html`:
- Line 372: The header currently maps sessionFilterSource to labels inline
(using t('sessions.source.all') but falling back to hardcoded names for others),
causing inconsistent labels vs the side rail; update the mapping logic that uses
sessionFilterSource so that all branches use i18n keys (e.g.,
t('sessions.source.claude'), t('sessions.source.gemini'),
t('sessions.source.codex')) instead of hardcoded strings like "Claude Code" or
"Gemini CLI" and ensure the same mapping is used in both places that reference
sessionFilterSource so Gemini is labeled consistently.

---

Outside diff comments:
In `@cli.js`:
- Around line 3386-3400: The cache/lookup is collapsing non-claude sources to
'codex', causing Gemini to reuse Codex cache; fix by making the cache key and
cache access source-aware: update buildSessionInventoryCacheKey (or call it with
the normalizedSource) so the key includes normalizedSource, and ensure
getSessionInventoryCache and setSessionInventoryCache are invoked with/expect a
source (pass normalizedSource) rather than relying on helpers that collapse
non-claude to codex; specifically change listSessionInventoryBySource to use a
source-aware cache key and to pass normalizedSource into
getSessionInventoryCache/setSessionInventoryCache (or update those helper
implementations) so Gemini and Codex maintain separate caches and
sessionId->filePath maps.

---

Nitpick comments:
In `@cli/session-usage.js`:
- Around line 85-87: The current code relies on a thrown TypeError to fall back
from parseGeminiSessionSummary to parseCodexSessionSummary; change this to an
explicit check by testing the Gemini parser's availability (e.g. typeof
parseGeminiSessionSummary === 'function' or a hasGeminiParser flag) before
calling it, and if it's not available call parseCodexSessionSummary with the
same filePath and summaryOptions; update the conditional expression around
parseGeminiSessionSummary/parseCodexSessionSummary to use that explicit check
instead of relying on exception-driven control flow.

In `@tests/e2e/test-sessions.js`:
- Line 6: Add an explicit assertion that the seeded Gemini session path
(geminiSessionPath) appears in the output of the list-session-paths check:
locate where the test already destructures geminiSessionPath from ctx and where
other session-path assertions run (the list-session-paths response handling),
then add an assertion that the response includes geminiSessionPath (similar to
the existing checks for sessionPath and claudeSessionPath) so Gemini path flow
is covered.
- Around line 84-87: Update the Gemini session assertions to validate message
semantics: in the test that calls api('session-detail', { source: 'gemini',
sessionId: geminiSessionId }) (variable sessionDetailGemini), keep the
Array.isArray check but also assert that at least one message's text includes
"hello from codexmate" and that the message object(s) has the expected
truncation flag (e.g., isTruncated === true/false as the spec requires). Apply
the same stronger checks to the other two similar blocks referenced (the ones
around the other sessionDetail variables) so each asserts both the expected
substring in message.text and the correct truncation flag on the corresponding
message object.

In `@web-ui/modules/app.methods.session-browser.mjs`:
- Around line 130-133: Duplicate source-to-target mapping appears in multiple
places; create a single helper (e.g., normalizeSource or mapSourceToTarget) at
module scope that implements the logic (source === 'claude' ? 'claude' : source
=== 'gemini' ? 'gemini' : source === 'all' ? 'all' : 'codex') and replace the
repeated inline logic in the three locations that reference this mapping (the
spots that read this.sessionPathOptionsMap[targetSource] and the other two
occurrences) to call that helper instead; ensure the helper is imported/defined
in the same module scope and used consistently so all accesses use the same
normalized targetSource value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43901e00-13de-4c77-b270-8da6c71d82aa

📥 Commits

Reviewing files that changed from the base of the PR and between bdeb0dd and 3fca3d7.

📒 Files selected for processing (12)
  • cli.js
  • cli/session-usage.js
  • tests/e2e/test-sessions.js
  • tests/e2e/test-setup.js
  • tests/unit/session-usage-backend.test.mjs
  • web-ui/app.js
  • web-ui/logic.sessions.mjs
  • web-ui/modules/app.methods.session-browser.mjs
  • web-ui/modules/app.methods.session-trash.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-sessions.html
📜 Review details
🔇 Additional comments (10)
tests/unit/session-usage-backend.test.mjs (1)

59-61: Good defensive test guard for unintended Gemini parsing.

This makes unexpected Gemini summary parsing fail fast in unit tests that should stay on lightweight paths.

web-ui/partials/index/panel-sessions.html (1)

42-42: Gemini source selector is wired correctly.

The new option is correctly bound to sessionFilterSource and uses i18n.

web-ui/modules/i18n.dict.mjs (1)

536-536: i18n additions are complete and consistent.

sessions.source.gemini is added in both locales with matching key structure.

Also applies to: 1554-1554

web-ui/app.js (1)

181-182: State initialization for Gemini is complete.

All three session-path state maps include gemini, so per-source loading/caching stays consistent.

Also applies to: 187-188, 193-194

web-ui/logic.sessions.mjs (1)

39-39: Source normalization update looks correct.

Including gemini in normalizeSessionSource aligns UI normalization with backend-supported source values.

web-ui/modules/app.methods.session-trash.mjs (1)

13-15: Gemini trash metadata mapping is correctly integrated.

source and fallback sourceLabel now handle Gemini consistently with existing Codex/Claude behavior.

Also applies to: 21-21

tests/e2e/test-setup.js (2)

208-223: Gemini fixture setup is clean and deterministic.

This test data shape is consistent with the Gemini session readers and gives stable E2E behavior.


235-236: Context propagation for Gemini session metadata looks good.

Passing both geminiSessionId and geminiSessionPath into ctx is the right wiring for downstream E2E steps.

cli/session-usage.js (1)

15-17: Gemini source normalization is correctly handled.

Keeping gemini as a first-class source here is the right behavior for list-sessions-usage.

tests/e2e/test-sessions.js (1)

27-31: Gemini session listing coverage is solid.

These assertions correctly validate both source: 'gemini' and source: 'all' behaviors.

Also applies to: 37-37

Comment thread cli.js
Comment thread cli.js
Comment on lines +4129 to +4130
const cwd = projectHash ? path.join(getGeminiTmpDir(), projectHash) : '';

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.

⚠️ Potential issue | 🟠 Major

Use the real project path in Gemini summaries.

cwd is being synthesized as ~/.gemini/tmp/<project_hash>, but the detail/plain/export readers already look for json.projectRoot / json.cwd. That means Gemini sessions won't participate correctly in path filtering or list-session-paths, because the browser indexes the tmp hash path instead of the workspace path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4129 - 4130, The current assignment of cwd (const cwd =
projectHash ? path.join(getGeminiTmpDir(), projectHash) : '') is synthesizing a
temp-hash path; change it to prefer the real workspace path provided in the
session JSON (use json.projectRoot or json.cwd) and only fall back to the
tmp/hash when those are missing. Update the code that computes cwd (the variable
named cwd, and any logic around projectHash/getGeminiTmpDir()) to use
sessionJson.projectRoot || sessionJson.cwd || (projectHash ?
path.join(getGeminiTmpDir(), projectHash) : '') so Gemini summaries will use the
real project path for filtering and list-session-paths.

Comment thread cli.js
Comment thread web-ui/logic.sessions.mjs Outdated
export function isSessionQueryEnabled(source) {
const normalized = normalizeSessionSource(source, '');
return normalized === 'codex' || normalized === 'claude' || normalized === 'all';
return normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'all';
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.

⚠️ Potential issue | 🟡 Minor

Query capability changed, but UI copy now appears stale.

With Line 32 enabling Gemini queries, the current placeholder text (“supports Codex/Claude”) becomes inaccurate for Gemini source selection. Please update the enabled-query i18n copy accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/logic.sessions.mjs` at line 32, The UI copy for the enabled-query i18n
key is stale after adding Gemini support; update the i18n string used when
normalized queries include 'gemini' (the same logic branch that returns
normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' ||
normalized === 'all') to mention Gemini (e.g., “supports Codex/Claude/Gemini” or
equivalent), and ensure any placeholder or tooltip that references enabled-query
reflects the new option.

Comment thread web-ui/partials/index/layout-header.html Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/styles/controls-forms.css`:
- Around line 92-121: The current CSS forces .selector-actions and
.sessions-header-actions to never wrap, causing localized button labels to
overflow on narrow screens; add a responsive breakpoint (e.g., max-width
small/compact viewport) that overrides these rules for .selector-actions and
.sessions-header-actions to allow wrapping/reflow: switch .selector-actions to a
wrapping layout (e.g., change grid-auto-flow/auto-columns or use inline-flex
with flex-wrap: wrap), remove or override white-space: nowrap for both
containers, and ensure .btn-tool and .btn-tool-compact keep sensible flex: 0 0
auto but can wrap or break lines; also reduce gaps at that breakpoint to
preserve compact spacing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 291efa14-cbe5-4845-b36f-56a3ac431843

📥 Commits

Reviewing files that changed from the base of the PR and between 3fca3d7 and 4004b99.

📒 Files selected for processing (7)
  • tests/unit/session-browser-timeline-regression.test.mjs
  • web-ui/modules/app.computed.dashboard.mjs
  • web-ui/modules/app.methods.session-browser.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-sessions.html
  • web-ui/styles/controls-forms.css
✅ Files skipped from review due to trivial changes (1)
  • web-ui/modules/i18n.dict.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • web-ui/partials/index/panel-sessions.html
  • web-ui/modules/app.methods.session-browser.mjs
📜 Review details
🔇 Additional comments (9)
web-ui/modules/app.computed.dashboard.mjs (1)

77-81: LGTM — Gemini is handled correctly here.

The new gemini branch matches the supported source normalization and uses the existing localized label key, so this stays aligned with the rest of the dashboard.

web-ui/partials/index/layout-header.html (2)

217-218: LGTM — this label mapping now covers Gemini.

The sidebar source label now matches the supported sessionFilterSource values and keeps the UI consistent with the new Gemini session source.


372-373: LGTM — the status strip now matches the sidebar.

This mirrors the updated source-label mapping, so Gemini is shown consistently in both header locations.

tests/unit/session-browser-timeline-regression.test.mjs (4)

8-10: Good addition: the helper import keeps the new tests isolated.

No concerns here.


36-55: Solid regression coverage for session-source changes.

This test directly exercises the event.target.value path and verifies the single reload behavior.


57-96: Good coverage for the URL-backed restore path.

The test matches the intended s_source behavior and confirms the reload happens once.


98-140: Good coverage for the cached-source restore path.

This validates the localStorage fallback and the expected single reload.

web-ui/styles/controls-forms.css (2)

17-23: Looks good.

Keeping the header on one line while adding a controlled gap is the right direction for this compact toolbar.


79-89: Good truncation behavior here.

min-width: 0 plus ellipsis/nowrap is the correct flex-item pattern to keep long titles from pushing the actions off-screen.

Comment on lines 92 to 121
.selector-actions {
display: flex;
gap: var(--spacing-xs);
display: inline-grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
column-gap: var(--spacing-xs);
align-items: center;
justify-content: end;
white-space: nowrap;
flex: 0 0 auto;
}

.selector-actions > .btn-tool,
.selector-actions > .btn-tool-compact {
flex: 0 0 auto;
}

.sessions-header-actions {
display: inline-flex !important;
flex-direction: row !important;
flex-wrap: nowrap !important;
align-items: center;
gap: var(--spacing-xs);
white-space: nowrap;
}

.sessions-header-actions > .btn-tool,
.sessions-header-actions > .btn-tool-compact {
white-space: nowrap;
flex: 0 0 auto;
}
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.

⚠️ Potential issue | 🟡 Minor

Add a responsive fallback for narrow widths.

Forcing both the header and action group to never wrap can make localized button labels overflow horizontally on smaller screens. Please keep a breakpoint that lets the actions reflow, or this layout may regress on compact viewports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/styles/controls-forms.css` around lines 92 - 121, The current CSS
forces .selector-actions and .sessions-header-actions to never wrap, causing
localized button labels to overflow on narrow screens; add a responsive
breakpoint (e.g., max-width small/compact viewport) that overrides these rules
for .selector-actions and .sessions-header-actions to allow wrapping/reflow:
switch .selector-actions to a wrapping layout (e.g., change
grid-auto-flow/auto-columns or use inline-flex with flex-wrap: wrap), remove or
override white-space: nowrap for both containers, and ensure .btn-tool and
.btn-tool-compact keep sensible flex: 0 0 auto but can wrap or break lines; also
reduce gaps at that breakpoint to preserve compact spacing.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
README.md (2)

32-60: ⚠️ Potential issue | 🟡 Minor

Mirror the Gemini update in the duplicated session docs.

These bullets are fine, but the later repeated Sessions Mode / export-session sections in this README still say Codex + Claude only. The file now reads as if Gemini is supported in one place and not in another.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 32 - 60, Update the duplicated session documentation
to mention Gemini wherever Codex and Claude are listed: find the "Sessions Mode"
and "export-session" sections and add Gemini to the tool lists and examples so
they match the earlier "Session Management" bullets (e.g., change phrases like
"Codex + Claude only" to "Codex + Claude + Gemini" and update any example
commands or descriptions that enumerate supported providers to include Gemini).
Ensure the wording, command examples, and any usage/analytics descriptions
mirror the earlier section that already includes Gemini.

166-181: ⚠️ Potential issue | 🟡 Minor

Update the install heading to include Gemini.

The block now includes a Gemini CLI install command, but the heading still says "Claude Code CLI" only. That makes the section title narrower than its contents.

Proposed fix
-### Install Codex CLI / Claude Code CLI (optional)
+### Install Codex CLI / Claude Code / Gemini CLI (optional)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 166 - 181, Update the section heading "Install Codex
CLI / Claude Code CLI (optional)" to reflect all included CLIs (Codex, Claude
Code, Gemini, etc.) so the title matches the commands; locate the heading string
"Install Codex CLI / Claude Code CLI (optional)" and change it to something
inclusive (e.g., "Install Codex, Claude Code, Gemini CLI (optional)"), ensuring
it corresponds to the listed command `npm install -g `@google/gemini-cli`` in the
block.
README.zh.md (1)

32-61: ⚠️ Potential issue | 🟡 Minor

同步更新后面的重复会话文档。

这里已经写入 Gemini 了,但后面重复的 会话模式 / export-session 段落仍然只写 Codex + Claude。文档会出现前后不一致,容易误导读者。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.zh.md` around lines 32 - 61, Documentation inconsistency: the earlier
"会话管理" section includes Gemini but the later duplicated "会话模式" and
"export-session" sections only mention Codex + Claude; update those repeated
sections ("会话模式" and "export-session") to include Gemini (and any
Gemini-specific notes or CLI flags) so the document is consistent with "会话管理"
and other references to Gemini in README.zh.md.
cli.js (4)

6202-6298: ⚠️ Potential issue | 🟠 Major

codexmate export-session still can't reach this Gemini path.

exportSessionData() now handles source === "gemini", but parseExportSessionArgs() and printExportSessionUsage() in the same file still only accept/display codex|claude. The CLI rejects the new source before this code runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 6202 - 6298, The CLI never reaches exportSessionData's
"gemini" branch because parseExportSessionArgs and printExportSessionUsage still
only accept/display "codex|claude"; update both to include "gemini". In
parseExportSessionArgs (the arg parsing/validation function) add "gemini" as an
allowed source value and ensure any switch/conditional that maps params.source
to internal source strings handles "gemini" the same way as "codex"/"claude". In
printExportSessionUsage (the help/usage printer) add "gemini" to the list of
supported sources shown to users so the CLI will accept and advertise the new
option. Ensure the same canonical label ("gemini") is used so
exportSessionData’s source === 'gemini' branch is reachable.

9181-9196: ⚠️ Potential issue | 🟠 Major

Don't advertise Gemini usage rows until the usage UI accepts them.

This action now accepts source=gemini, but web-ui/logic.sessions.mjs:250-270 still hard-filters usage groups to codex/claude, and web-ui/logic.sessions.mjs:286-295 only totals those two sources. That makes the API surface ahead of the consumer and contradicts the PR note that usage stays Codex/Claude-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 9181 - 9196, The CLI case 'list-sessions-usage'
currently accepts "gemini" as a valid source; change the validation logic to
only allow "codex", "claude", or "all" (remove "gemini" from the allowed set and
error text) so the API doesn't advertise Gemini before the UI supports it;
update the error message in the result to reflect the allowed values and leave
the call to listSessionUsage({ ...usageParams, source: source || 'all' })
unchanged so behavior stays the same for codex/claude/all.

3393-3406: ⚠️ Potential issue | 🔴 Critical

Gemini inventory cache currently collides with Codex.

listSessionInventoryBySource('gemini', ...) now calls into the shared inventory cache, but buildSessionInventoryCacheKey() still folds every non-claude source into codex. In the common source=all flow, the Codex branch populates that key first, so the Gemini branch reads Codex sessions back from cache and Gemini sessions disappear entirely. registerSessionFileLookupEntries() has the same normalization bug, so the dedicated Gemini lookup map never gets populated either.

🔧 Suggested fix
 function buildSessionInventoryCacheKey(source, limit, options = {}) {
-    const normalizedSource = source === 'claude' ? 'claude' : 'codex';
+    const normalizedSource = source === 'claude' || source === 'gemini' ? source : 'codex';
     const normalizedLimit = Number.isFinite(Number(limit))
         ? Math.max(1, Math.floor(Number(limit)))
         : 1;
@@
 function registerSessionFileLookupEntries(source, sessions = []) {
-    const normalizedSource = source === 'claude' ? 'claude' : 'codex';
+    const normalizedSource = source === 'claude' || source === 'gemini' ? source : 'codex';
     const store = g_sessionFileLookupCache[normalizedSource];
     if (!(store instanceof Map) || !Array.isArray(sessions)) {
         return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 3393 - 3406, The cache key normalization is collapsing
every non-`claude` source into `codex`, causing Gemini to read Codex entries;
update the logic so the cache and lookup maps use the actual normalized source
(preserving 'gemini' and 'codex') instead of always folding to 'codex' — adjust
the normalization and callers of buildSessionInventoryCacheKey,
getSessionInventoryCache, setSessionInventoryCache, and
registerSessionFileLookupEntries to pass/use the true normalizedSource (e.g.,
keep normalizedSource = source === 'claude' || source === 'gemini' ? source :
'codex' and ensure buildSessionInventoryCacheKey accepts/uses that value rather
than re-folding), and confirm listGeminiSessions populates the Gemini-specific
lookup map via registerSessionFileLookupEntries.

5451-5500: ⚠️ Potential issue | 🔴 Critical

Gemini trash entries are still persisted as Codex.

trashSessionData() now allows source === "gemini", but buildSessionTrashEntry() still collapses any non-claude source to codex. That mislabels the trash entry and makes restore fail because resolveSessionRestoreTarget() validates the original Gemini path against the Codex root.

🔧 Suggested fix
 function buildSessionTrashEntry(summary, options = {}) {
-    const source = options.source === 'claude' ? 'claude' : 'codex';
+    const source = options.source === 'claude'
+        ? 'claude'
+        : (options.source === 'gemini' ? 'gemini' : 'codex');
@@
-    const sourceLabel = source === 'claude' ? 'Claude Code' : 'Codex';
+    const sourceLabel = source === 'claude'
+        ? 'Claude Code'
+        : (source === 'gemini' ? 'Gemini CLI' : 'Codex');
@@
 function resolveSessionRestoreTarget(entry) {
@@
-    const root = normalized.source === 'claude' ? getClaudeProjectsDir() : getCodexSessionsDir();
+    const root = normalized.source === 'claude'
+        ? getClaudeProjectsDir()
+        : (normalized.source === 'gemini' ? getGeminiTmpDir() : getCodexSessionsDir());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 5451 - 5500, The trash entry is being mislabeled because
buildSessionTrashEntry collapses any non-claude source to "codex", so when
trashing a Gemini session the entry.source becomes "codex" and restore
validation fails; change buildSessionTrashEntry (and any helper it uses) to
accept and preserve the original source string (e.g., "gemini") instead of
coercing to "codex" so callers like trashSessionData (where
buildSessionTrashEntry is called) store entry.source = source; after that,
ensure resolveSessionRestoreTarget's validation logic recognizes "gemini" roots
(or maps the preserved "gemini" entry back to the correct Gemini root) so
restore checks succeed.
♻️ Duplicate comments (4)
web-ui/styles/controls-forms.css (1)

17-23: ⚠️ Potential issue | 🟡 Minor

Add a compact-width escape hatch for these nowrap rules.

These overrides keep both the selector header and session actions permanently on one line, so localized labels can still overflow on narrow viewports. Please keep a small-screen breakpoint that lets the actions wrap/reflow instead of locking nowrap everywhere.

Suggested responsive override
+@media (max-width: 640px) {
+    .selector-header,
+    .sessions-header-actions {
+        flex-wrap: wrap !important;
+        white-space: normal;
+    }
+
+    .selector-actions {
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: flex-end;
+    }
+}

Also applies to: 92-115

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/styles/controls-forms.css` around lines 17 - 23, The .selector-header
rule currently forces flex-wrap: nowrap which prevents localized labels and
.session-actions from wrapping on narrow viewports; add a compact-width escape
hatch via a small-screen media query (e.g., max-width ~480px) that overrides
.selector-header (and the related .session-actions styles around lines 92-115)
to set flex-wrap: wrap and relax alignment/gap so actions can reflow (optionally
set align-items and gap to more vertical-friendly values) on small screens.
cli.js (3)

4135-4145: ⚠️ Potential issue | 🟠 Major

Prefer the workspace path in Gemini summaries.

cwd is still synthesized as ~/.gemini/tmp/<project_hash>, but the detail/plain/export readers already prefer json.projectRoot / json.cwd. That means path filtering and list-session-paths will index the temp bucket instead of the real project path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4135 - 4145, The returned summary is setting cwd to the
temporary path (cwd variable via getGeminiTmpDir() + projectHash) which causes
downstream readers to index the temp bucket instead of the real project path;
change the summary to prefer the real workspace path when available (use
json.projectRoot or json.cwd if present) before falling back to the synthesized
tmp path. Locate the object returned in the function that builds the Gemini
summary (the cwd variable and the return block with source:'gemini') and make
cwd resolve to json.projectRoot || json.cwd || (projectHash ?
path.join(getGeminiTmpDir(), projectHash) : '') so the real project root is used
when present.

4148-4155: ⚠️ Potential issue | 🟠 Major

Browse rows will still show Gemini sessions as messageCount: 0.

listSessionBrowse() returns these summary objects directly and never hydrates exact counts afterward, so hardcoding zero here leaves every Gemini row wrong.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4148 - 4155, The code is overwriting real session
summary counts with zeros (messageCount, totalTokens, inputTokens, etc.) causing
Gemini rows from listSessionBrowse() to display messageCount: 0; instead, stop
hardcoding zeros and copy the corresponding values from the incoming summary
object returned by listSessionBrowse() (e.g., use sessionSummary.messageCount,
sessionSummary.totalTokens, sessionSummary.inputTokens,
sessionSummary.outputTokens, sessionSummary.reasoningOutputTokens) and set
__messageCountExact to the correct boolean from the summary (e.g.,
sessionSummary.__messageCountExact) so Gemini sessions show their actual counts.

3024-3070: ⚠️ Potential issue | 🟠 Major

Keep Gemini content search message-based for roleFilter="all" too.

This branch still token-matches against the serialized JSON blob / first 512 KB instead of parsed messages, so metadata can satisfy a content query and later chat content can be missed. queryScope=content/all should stay message-based here as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 3024 - 3070, The Gemini branch currently skips
message-level scanning when state.roleFilter === 'all' and instead token-matches
the raw JSON blob; fix it by reusing the message-parsing loop used in the
state.roleFilter !== 'all' branch: parse the file into json/messages, iterate
rawMessages, skip invalid entries, derive role via normalizeGeminiMessageRole
and text via extractMessageText(extractGeminiMessageText(...)), then call
consumeSessionQueryMessage(state, { role, text }) for each message (and break
when it returns true); preserve snippetLimit behavior (push truncateText(text)
when appropriate) and only fall back to raw matchTokensInText if message parsing
fails or json.messages is not an array; return
buildSessionQueryScanResult(state) as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@cli.js`:
- Around line 6202-6298: The CLI never reaches exportSessionData's "gemini"
branch because parseExportSessionArgs and printExportSessionUsage still only
accept/display "codex|claude"; update both to include "gemini". In
parseExportSessionArgs (the arg parsing/validation function) add "gemini" as an
allowed source value and ensure any switch/conditional that maps params.source
to internal source strings handles "gemini" the same way as "codex"/"claude". In
printExportSessionUsage (the help/usage printer) add "gemini" to the list of
supported sources shown to users so the CLI will accept and advertise the new
option. Ensure the same canonical label ("gemini") is used so
exportSessionData’s source === 'gemini' branch is reachable.
- Around line 9181-9196: The CLI case 'list-sessions-usage' currently accepts
"gemini" as a valid source; change the validation logic to only allow "codex",
"claude", or "all" (remove "gemini" from the allowed set and error text) so the
API doesn't advertise Gemini before the UI supports it; update the error message
in the result to reflect the allowed values and leave the call to
listSessionUsage({ ...usageParams, source: source || 'all' }) unchanged so
behavior stays the same for codex/claude/all.
- Around line 3393-3406: The cache key normalization is collapsing every
non-`claude` source into `codex`, causing Gemini to read Codex entries; update
the logic so the cache and lookup maps use the actual normalized source
(preserving 'gemini' and 'codex') instead of always folding to 'codex' — adjust
the normalization and callers of buildSessionInventoryCacheKey,
getSessionInventoryCache, setSessionInventoryCache, and
registerSessionFileLookupEntries to pass/use the true normalizedSource (e.g.,
keep normalizedSource = source === 'claude' || source === 'gemini' ? source :
'codex' and ensure buildSessionInventoryCacheKey accepts/uses that value rather
than re-folding), and confirm listGeminiSessions populates the Gemini-specific
lookup map via registerSessionFileLookupEntries.
- Around line 5451-5500: The trash entry is being mislabeled because
buildSessionTrashEntry collapses any non-claude source to "codex", so when
trashing a Gemini session the entry.source becomes "codex" and restore
validation fails; change buildSessionTrashEntry (and any helper it uses) to
accept and preserve the original source string (e.g., "gemini") instead of
coercing to "codex" so callers like trashSessionData (where
buildSessionTrashEntry is called) store entry.source = source; after that,
ensure resolveSessionRestoreTarget's validation logic recognizes "gemini" roots
(or maps the preserved "gemini" entry back to the correct Gemini root) so
restore checks succeed.

In `@README.md`:
- Around line 32-60: Update the duplicated session documentation to mention
Gemini wherever Codex and Claude are listed: find the "Sessions Mode" and
"export-session" sections and add Gemini to the tool lists and examples so they
match the earlier "Session Management" bullets (e.g., change phrases like "Codex
+ Claude only" to "Codex + Claude + Gemini" and update any example commands or
descriptions that enumerate supported providers to include Gemini). Ensure the
wording, command examples, and any usage/analytics descriptions mirror the
earlier section that already includes Gemini.
- Around line 166-181: Update the section heading "Install Codex CLI / Claude
Code CLI (optional)" to reflect all included CLIs (Codex, Claude Code, Gemini,
etc.) so the title matches the commands; locate the heading string "Install
Codex CLI / Claude Code CLI (optional)" and change it to something inclusive
(e.g., "Install Codex, Claude Code, Gemini CLI (optional)"), ensuring it
corresponds to the listed command `npm install -g `@google/gemini-cli`` in the
block.

In `@README.zh.md`:
- Around line 32-61: Documentation inconsistency: the earlier "会话管理" section
includes Gemini but the later duplicated "会话模式" and "export-session" sections
only mention Codex + Claude; update those repeated sections ("会话模式" and
"export-session") to include Gemini (and any Gemini-specific notes or CLI flags)
so the document is consistent with "会话管理" and other references to Gemini in
README.zh.md.

---

Duplicate comments:
In `@cli.js`:
- Around line 4135-4145: The returned summary is setting cwd to the temporary
path (cwd variable via getGeminiTmpDir() + projectHash) which causes downstream
readers to index the temp bucket instead of the real project path; change the
summary to prefer the real workspace path when available (use json.projectRoot
or json.cwd if present) before falling back to the synthesized tmp path. Locate
the object returned in the function that builds the Gemini summary (the cwd
variable and the return block with source:'gemini') and make cwd resolve to
json.projectRoot || json.cwd || (projectHash ? path.join(getGeminiTmpDir(),
projectHash) : '') so the real project root is used when present.
- Around line 4148-4155: The code is overwriting real session summary counts
with zeros (messageCount, totalTokens, inputTokens, etc.) causing Gemini rows
from listSessionBrowse() to display messageCount: 0; instead, stop hardcoding
zeros and copy the corresponding values from the incoming summary object
returned by listSessionBrowse() (e.g., use sessionSummary.messageCount,
sessionSummary.totalTokens, sessionSummary.inputTokens,
sessionSummary.outputTokens, sessionSummary.reasoningOutputTokens) and set
__messageCountExact to the correct boolean from the summary (e.g.,
sessionSummary.__messageCountExact) so Gemini sessions show their actual counts.
- Around line 3024-3070: The Gemini branch currently skips message-level
scanning when state.roleFilter === 'all' and instead token-matches the raw JSON
blob; fix it by reusing the message-parsing loop used in the state.roleFilter
!== 'all' branch: parse the file into json/messages, iterate rawMessages, skip
invalid entries, derive role via normalizeGeminiMessageRole and text via
extractMessageText(extractGeminiMessageText(...)), then call
consumeSessionQueryMessage(state, { role, text }) for each message (and break
when it returns true); preserve snippetLimit behavior (push truncateText(text)
when appropriate) and only fall back to raw matchTokensInText if message parsing
fails or json.messages is not an array; return
buildSessionQueryScanResult(state) as before.

In `@web-ui/styles/controls-forms.css`:
- Around line 17-23: The .selector-header rule currently forces flex-wrap:
nowrap which prevents localized labels and .session-actions from wrapping on
narrow viewports; add a compact-width escape hatch via a small-screen media
query (e.g., max-width ~480px) that overrides .selector-header (and the related
.session-actions styles around lines 92-115) to set flex-wrap: wrap and relax
alignment/gap so actions can reflow (optionally set align-items and gap to more
vertical-friendly values) on small screens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6b593e0f-f72c-485c-b837-8b3ce7b6ce32

📥 Commits

Reviewing files that changed from the base of the PR and between 4004b99 and 15169c2.

📒 Files selected for processing (9)
  • README.md
  • README.zh.md
  • cli.js
  • site/guide/getting-started.md
  • site/index.md
  • tests/unit/run.mjs
  • tests/unit/session-header-actions-layout.test.mjs
  • web-ui/modules/app.methods.install.mjs
  • web-ui/styles/controls-forms.css
✅ Files skipped from review due to trivial changes (3)
  • tests/unit/run.mjs
  • site/index.md
  • site/guide/getting-started.md
📜 Review details
🧰 Additional context used
🪛 LanguageTool
README.md

[grammar] ~32-~32: Ensure spelling is correct
Context: ...ter/export/delete) with Usage analytics overview - Plugins (Prompt templates): reusable te...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~60-~60: Ensure spelling is correct
Context: ...ied Codex + Claude + Gemini CLI session list - Local session pinning with persistent p...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (3)
tests/unit/session-header-actions-layout.test.mjs (1)

1-75: Looks good.

This is a focused contract test for the new sessions header layout rules, and the isolation check is a useful safeguard.

README.zh.md (1)

169-184: LGTM.

安装说明和 Gemini CLI 的新增命令是对齐的,这一段文档更新是完整的。

web-ui/modules/app.methods.install.mjs (1)

101-153: Gemini install commands are wired through consistently.

The new gemini target follows the existing package-manager matrix and registry handling without changing the public API.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cli.js (2)

5781-5796: ⚠️ Potential issue | 🔴 Critical

Trash entries still downcast new sources to Codex.

trashSessionData() now passes gemini/codebuddy here, but buildSessionTrashEntry() still normalizes every non-claude source to codex. Those trash entries will be listed under the wrong source, and restore will resolve against the Codex root instead of the original Gemini/CodeBuddy location.

Suggested fix
 function buildSessionTrashEntry(summary, options = {}) {
-    const source = options.source === 'claude' ? 'claude' : 'codex';
+    const source = options.source === 'claude'
+        ? 'claude'
+        : (options.source === 'gemini'
+            ? 'gemini'
+            : (options.source === 'codebuddy' ? 'codebuddy' : 'codex'));
     const sessionId = options.sessionId || summary.sessionId || path.basename(options.originalFilePath || summary.filePath || '', '.jsonl');
@@
-    const sourceLabel = source === 'claude' ? 'Claude Code' : 'Codex';
+    const sourceLabel = source === 'claude'
+        ? 'Claude Code'
+        : (source === 'gemini' ? 'Gemini CLI' : (source === 'codebuddy' ? 'CodeBuddy Code' : 'Codex'));
@@
-        provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
+        provider: (claudeIndexEntry && typeof claudeIndexEntry.provider === 'string' && claudeIndexEntry.provider.trim())
             ? claudeIndexEntry.provider.trim()
-            : (summary.provider || (source === 'claude' ? 'claude' : 'codex')),
+            : (summary.provider || (source === 'claude'
+                ? 'claude'
+                : (source === 'gemini' ? 'gemini' : (source === 'codebuddy' ? 'codebuddy' : 'codex')))),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 5781 - 5796, The trash entries are being normalized to
'codex' incorrectly; update the normalization so that buildSessionTrashEntry
preserves the original source values (e.g., 'gemini' or 'codebuddy') instead of
downcasting every non-'claude' to 'codex'. Modify buildSessionTrashEntry (and
any helper normalizer used by it) to accept and store the source verbatim from
the caller (trashSessionData passes source) and ensure restore logic uses that
stored source when resolving the root location; remove or change any conditional
that maps non-'claude' to 'codex' so the original source field is retained in
the trash entry.

3441-3459: ⚠️ Potential issue | 🔴 Critical

Fix inventory cache key collisions for Gemini/CodeBuddy.

listSessionInventoryBySource() now sends gemini and codebuddy through the shared inventory cache, but buildSessionInventoryCacheKey() still normalizes every non-claude source to codex. After one source warms the cache, later lookups can return the wrong session set until invalidation.

Suggested fix
-function buildSessionInventoryCacheKey(source, limit, options = {}) {
-    const normalizedSource = source === 'claude' ? 'claude' : 'codex';
+function buildSessionInventoryCacheKey(source, limit, options = {}) {
+    const normalizedSource =
+        source === 'claude' || source === 'gemini' || source === 'codebuddy'
+            ? source
+            : 'codex';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 3441 - 3459, listSessionInventoryBySource normalizes
non-claude sources to 'codex' but then calls buildSessionInventoryCacheKey which
still treats non-claude as 'codex', causing gemini/codebuddy cache collisions;
fix by creating the cache key from the actual normalizedSource (preserve the
real source string like 'gemini' or 'codebuddy') or pass the original source
into buildSessionInventoryCacheKey so getSessionInventoryCache and
setSessionInventoryCache use a key that includes the true source; update calls
in listSessionInventoryBySource to use the corrected key generation and ensure
getSessionInventoryCache/setSessionInventoryCache are invoked with that same
key.
♻️ Duplicate comments (4)
web-ui/modules/i18n.dict.mjs (1)

1559-1565: ⚠️ Potential issue | 🟡 Minor

Update English query placeholder to include Gemini/CodeBuddy.

Line 1564 still says Codex/Claude even though the enabled sources now include Gemini and CodeBuddy, so EN guidance is outdated.

🌐 Suggested i18n fix
-        'sessions.query.placeholder.enabled': 'Search keywords (Codex/Claude, e.g. claude code)',
+        'sessions.query.placeholder.enabled': 'Search keywords (Codex/Claude/Gemini/CodeBuddy, e.g. claude code)',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/i18n.dict.mjs` around lines 1559 - 1565, Update the English
i18n entry 'sessions.query.placeholder.enabled' to replace the outdated
"Codex/Claude" example with the current sources (e.g., "Gemini/CodeBuddy") so
the placeholder reflects available sources; locate the key
'sessions.query.placeholder.enabled' in web-ui/modules/i18n.dict.mjs and change
its string to include "Gemini/CodeBuddy" (also consider updating
'sessions.query.placeholder.disabled' if its guidance needs to mention the new
sources).
cli.js (3)

3071-3117: ⚠️ Potential issue | 🟠 Major

Keep Gemini content scans message-based.

When roleFilter === 'all', this branch matches against raw serialized JSON and only the first 512 KB. Queries can hit metadata like projectRoot/model and miss later messages, so queryScope=content/all becomes inaccurate for Gemini.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 3071 - 3117, The Gemini branch currently falls back to
scanning raw file text (first ~512KB) when state.roleFilter === 'all', which can
match metadata instead of actual messages; update the logic so Gemini sessions
are always scanned message-by-message: parse the JSON file (as done in the
roleFilter !== 'all' branch), iterate rawMessages and for each entry use
normalizeGeminiMessageRole and extractGeminiMessageText and call
consumeSessionQueryMessage to drive state (breaking early on match), then return
buildSessionQueryScanResult(state); only if JSON parsing fails should you fall
back to the byte-read/raw-text token check using matchTokensInText. Ensure you
reuse the same symbols (session.source, state.roleFilter,
normalizeGeminiMessageRole, extractGeminiMessageText,
consumeSessionQueryMessage, matchTokensInText, buildSessionQueryScanResult) so
behavior is consistent.

4346-4356: ⚠️ Potential issue | 🟠 Major

Use the real workspace path in Gemini summaries.

cwd is still synthesized as ~/.gemini/tmp/<project_hash>. The detail/plain/export readers already prefer json.projectRoot / json.cwd, so path filtering and list-session-paths will index the temp hash path instead of the actual project path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4346 - 4356, The returned object currently sets cwd to
the synthesized temp path (using projectHash and getGeminiTmpDir()), which
causes readers to index the temp hash path; change the logic so the returned cwd
uses the real workspace path (e.g., projectRoot or json.cwd if present) and only
fall back to the temp path when the real project root is unavailable. Update the
code that defines cwd (and the returned object property cwd) to prefer
projectRoot/json.cwd first, then projectHash-based getGeminiTmpDir() as a
fallback so list-session-paths and detail/plain/export readers see the real
project path.

4359-4366: ⚠️ Potential issue | 🟠 Major

Gemini browse rows still report messageCount: 0.

This summary still hardcodes messageCount to zero with __messageCountExact: false. listSessionBrowse() returns these summaries directly, so Gemini sessions stay at zero in the browser list even though exact counting exists elsewhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 4359 - 4366, The summary object currently hardcodes
messageCount: 0 and __messageCountExact: false; update the code that constructs
the browse summary (the object returned by listSessionBrowse / the summary
creation block) to use the real message count and mark it exact — e.g., set
messageCount to the actual count (from the session summary payload or
messages.length) and set __messageCountExact: true (and remove any unconditional
zeroing of totalTokens/inputTokens if real values are available). Locate the
summary construction near listSessionBrowse and replace the hardcoded
zeros/flags with the corresponding fields from the session summary response.
🧹 Nitpick comments (2)
web-ui/modules/app.methods.install.mjs (1)

101-167: Consider generating CLI command rows from a package map to reduce drift risk.

The new targets work, but the matrix now has high repetition across package managers. A small data-driven builder will reduce copy/paste errors as more CLIs are added.

♻️ Suggested refactor
+            const targetPackages = {
+                claude: '@anthropic-ai/claude-code',
+                codebuddy: '@tencent-ai/codebuddy-code',
+                gemini: '@google/gemini-cli',
+                codex: codexPackage
+            };
             const matrix = {
                 claude: {
                     install: '',
                     update: '',
                     uninstall: ''
                 },
                 codebuddy: {
                     install: '',
                     update: '',
                     uninstall: ''
                 },
                 gemini: {
                     install: '',
                     update: '',
                     uninstall: ''
                 },
                 codex: {
                     install: '',
                     update: '',
                     uninstall: ''
                 }
             };
+            const apply = (pm, installVerb, updateVerb, removeVerb) => {
+                Object.entries(targetPackages).forEach(([target, pkg]) => {
+                    const installPkg = target === 'codex' ? codexInstallPackage : pkg;
+                    matrix[target].install = `${pm} ${installVerb} -g ${installPkg}`;
+                    matrix[target].update = `${pm} ${updateVerb} -g ${pkg}`;
+                    matrix[target].uninstall = `${pm} ${removeVerb} -g ${pkg}`;
+                });
+            };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.methods.install.mjs` around lines 101 - 167, The matrix
has repetitive hard-coded command strings; replace it with a data-driven
builder: define a package map (e.g., packages = { claude:
'@anthropic-ai/claude-code', codebuddy: '@tencent-ai/codebuddy-code', gemini:
'@google/gemini-cli', codex: codexPackage }) and a manager-templates map (e.g.,
templates for 'pnpm','bun','npm' with install/update/uninstall patterns using
placeholders), then iterate over Object.keys(packages) to populate
matrix[target].install/update/uninstall by plugging packages[target] (or
codexInstallPackage for codex.install) into the selected manager templates based
on the manager variable; preserve the special case for platform === 'termux'
when computing npm update for codex. Use the existing symbols matrix, manager,
platform, codexPackage, and codexInstallPackage to locate and replace the
current hard-coded blocks.
lib/cli-sessions.js (1)

170-171: Use nullish fallback when selecting CodeBuddy content.

If record.message exists but record.message.content is empty, current logic skips a valid record.content fallback.

🛠️ Suggested adjustment
-            const content = record.message ? record.message.content : record.content;
+            const content = record?.message?.content ?? record.content;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/cli-sessions.js` around lines 170 - 171, The current selection uses
record.message ? record.message.content : record.content which treats an
empty/undefined message.content incorrectly; update the logic where content is
derived (the lines that set content and call extractMessageText) to prefer
record.message.content when it is present and non-null/undefined and otherwise
fall back to record.content (i.e., use a nullish fallback for
record.message.content), then pass that resulting content into
extractMessageText to compute text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli.js`:
- Around line 8409-8414: The CLI help lists "gemini" and "codebuddy" as valid
--source values but parseExportSessionArgs() still only validates/handles
"codex" and "claude", causing a validation error; update
parseExportSessionArgs() so its allowedSources/validation logic includes
"gemini" and "codebuddy" and extend any downstream switch/handler that branches
on the source (the same place that treats "codex"/"claude") to handle "gemini"
and "codebuddy" or, alternatively, change the usage text to only list the
sources currently supported—ensure the change is applied around the
parseExportSessionArgs function and any source-specific export handling
functions referenced there.

In `@README.md`:
- Line 60: README has inconsistent "session-source" scope after adding Gemini
and CodeBuddy; update all relevant sections (the top-line list, "Sessions Mode",
and export/source examples) to reflect unified support for Codex, Claude,
Gemini, and CodeBuddy. Search for section headings "Sessions Mode" and any
examples showing "export source" or provider-specific examples and replace
provider-specific language or filters with neutral or inclusive wording (e.g.,
"supported providers: Codex, Claude, Gemini, CodeBuddy" or remove hard-coded
provider filters), and ensure examples/CLI flags match the new unified
session-source semantics throughout the README so all sections are consistent.

---

Outside diff comments:
In `@cli.js`:
- Around line 5781-5796: The trash entries are being normalized to 'codex'
incorrectly; update the normalization so that buildSessionTrashEntry preserves
the original source values (e.g., 'gemini' or 'codebuddy') instead of
downcasting every non-'claude' to 'codex'. Modify buildSessionTrashEntry (and
any helper normalizer used by it) to accept and store the source verbatim from
the caller (trashSessionData passes source) and ensure restore logic uses that
stored source when resolving the root location; remove or change any conditional
that maps non-'claude' to 'codex' so the original source field is retained in
the trash entry.
- Around line 3441-3459: listSessionInventoryBySource normalizes non-claude
sources to 'codex' but then calls buildSessionInventoryCacheKey which still
treats non-claude as 'codex', causing gemini/codebuddy cache collisions; fix by
creating the cache key from the actual normalizedSource (preserve the real
source string like 'gemini' or 'codebuddy') or pass the original source into
buildSessionInventoryCacheKey so getSessionInventoryCache and
setSessionInventoryCache use a key that includes the true source; update calls
in listSessionInventoryBySource to use the corrected key generation and ensure
getSessionInventoryCache/setSessionInventoryCache are invoked with that same
key.

---

Duplicate comments:
In `@cli.js`:
- Around line 3071-3117: The Gemini branch currently falls back to scanning raw
file text (first ~512KB) when state.roleFilter === 'all', which can match
metadata instead of actual messages; update the logic so Gemini sessions are
always scanned message-by-message: parse the JSON file (as done in the
roleFilter !== 'all' branch), iterate rawMessages and for each entry use
normalizeGeminiMessageRole and extractGeminiMessageText and call
consumeSessionQueryMessage to drive state (breaking early on match), then return
buildSessionQueryScanResult(state); only if JSON parsing fails should you fall
back to the byte-read/raw-text token check using matchTokensInText. Ensure you
reuse the same symbols (session.source, state.roleFilter,
normalizeGeminiMessageRole, extractGeminiMessageText,
consumeSessionQueryMessage, matchTokensInText, buildSessionQueryScanResult) so
behavior is consistent.
- Around line 4346-4356: The returned object currently sets cwd to the
synthesized temp path (using projectHash and getGeminiTmpDir()), which causes
readers to index the temp hash path; change the logic so the returned cwd uses
the real workspace path (e.g., projectRoot or json.cwd if present) and only fall
back to the temp path when the real project root is unavailable. Update the code
that defines cwd (and the returned object property cwd) to prefer
projectRoot/json.cwd first, then projectHash-based getGeminiTmpDir() as a
fallback so list-session-paths and detail/plain/export readers see the real
project path.
- Around line 4359-4366: The summary object currently hardcodes messageCount: 0
and __messageCountExact: false; update the code that constructs the browse
summary (the object returned by listSessionBrowse / the summary creation block)
to use the real message count and mark it exact — e.g., set messageCount to the
actual count (from the session summary payload or messages.length) and set
__messageCountExact: true (and remove any unconditional zeroing of
totalTokens/inputTokens if real values are available). Locate the summary
construction near listSessionBrowse and replace the hardcoded zeros/flags with
the corresponding fields from the session summary response.

In `@web-ui/modules/i18n.dict.mjs`:
- Around line 1559-1565: Update the English i18n entry
'sessions.query.placeholder.enabled' to replace the outdated "Codex/Claude"
example with the current sources (e.g., "Gemini/CodeBuddy") so the placeholder
reflects available sources; locate the key 'sessions.query.placeholder.enabled'
in web-ui/modules/i18n.dict.mjs and change its string to include
"Gemini/CodeBuddy" (also consider updating 'sessions.query.placeholder.disabled'
if its guidance needs to mention the new sources).

---

Nitpick comments:
In `@lib/cli-sessions.js`:
- Around line 170-171: The current selection uses record.message ?
record.message.content : record.content which treats an empty/undefined
message.content incorrectly; update the logic where content is derived (the
lines that set content and call extractMessageText) to prefer
record.message.content when it is present and non-null/undefined and otherwise
fall back to record.content (i.e., use a nullish fallback for
record.message.content), then pass that resulting content into
extractMessageText to compute text.

In `@web-ui/modules/app.methods.install.mjs`:
- Around line 101-167: The matrix has repetitive hard-coded command strings;
replace it with a data-driven builder: define a package map (e.g., packages = {
claude: '@anthropic-ai/claude-code', codebuddy: '@tencent-ai/codebuddy-code',
gemini: '@google/gemini-cli', codex: codexPackage }) and a manager-templates map
(e.g., templates for 'pnpm','bun','npm' with install/update/uninstall patterns
using placeholders), then iterate over Object.keys(packages) to populate
matrix[target].install/update/uninstall by plugging packages[target] (or
codexInstallPackage for codex.install) into the selected manager templates based
on the manager variable; preserve the special case for platform === 'termux'
when computing npm update for codex. Use the existing symbols matrix, manager,
platform, codexPackage, and codexInstallPackage to locate and replace the
current hard-coded blocks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6532094b-f886-40a5-9732-7e1330eb7e59

📥 Commits

Reviewing files that changed from the base of the PR and between 15169c2 and a4c681c.

📒 Files selected for processing (14)
  • README.md
  • README.zh.md
  • cli.js
  • cli/session-usage.js
  • lib/cli-sessions.js
  • site/guide/getting-started.md
  • site/index.md
  • tests/unit/session-usage-backend.test.mjs
  • web-ui/logic.sessions.mjs
  • web-ui/modules/app.computed.dashboard.mjs
  • web-ui/modules/app.methods.install.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/layout-header.html
  • web-ui/partials/index/panel-sessions.html
✅ Files skipped from review due to trivial changes (2)
  • site/guide/getting-started.md
  • README.zh.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • web-ui/modules/app.computed.dashboard.mjs
  • site/index.md
  • web-ui/partials/index/layout-header.html
  • cli/session-usage.js
  • web-ui/partials/index/panel-sessions.html
📜 Review details
🧰 Additional context used
🪛 LanguageTool
README.md

[grammar] ~32-~32: Ensure spelling is correct
Context: ...ter/export/delete) with Usage analytics overview - Plugins (Prompt templates): reusable te...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~60-~60: Ensure spelling is correct
Context: ...e + Gemini CLI + CodeBuddy Code session list - Local session pinning with persisten...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (2)
web-ui/logic.sessions.mjs (1)

30-40: Good source normalization/query-gating update for new providers.

Line 32 and Line 39 correctly keep source normalization and query eligibility in sync for gemini and codebuddy.

tests/unit/session-usage-backend.test.mjs (1)

59-64: Good guardrails for newly wired source parsers.

These default throwing stubs are a solid safety net for this test harness and make unexpected gemini/codebuddy fallback parsing fail fast while still allowing explicit overrides via bindings.

Comment thread cli.js
Comment on lines +8409 to 8414
console.log('\n用法: codexmate export-session --source <codex|claude|gemini|codebuddy> (--session-id <ID>|--file <PATH>) [--output <PATH>] [--max-messages <N|all|Infinity>]');
console.log('\n示例:');
console.log(' codexmate export-session --source codex --session-id 123456');
console.log(' codexmate export-session --source claude --file "~/.claude/projects/demo/session.jsonl"');
console.log(' codexmate export-session --source codebuddy --file "~/.codebuddy/projects/demo/session.jsonl"');
console.log(' codexmate export-session --source codex --session-id 123456 --max-messages=all');
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.

⚠️ Potential issue | 🟠 Major

CLI help advertises sources the parser still rejects.

The updated usage text says export-session --source gemini|codebuddy, but parseExportSessionArgs() still only accepts codex or claude. From the CLI, the new sources exit with a validation error instead of exporting.

Suggested fix
-    if (normalizedSource && normalizedSource !== 'codex' && normalizedSource !== 'claude') {
-        errors.push('参数 --source 仅支持 codex 或 claude');
+    if (
+        normalizedSource
+        && normalizedSource !== 'codex'
+        && normalizedSource !== 'claude'
+        && normalizedSource !== 'gemini'
+        && normalizedSource !== 'codebuddy'
+    ) {
+        errors.push('参数 --source 仅支持 codex、claude、gemini 或 codebuddy');
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli.js` around lines 8409 - 8414, The CLI help lists "gemini" and "codebuddy"
as valid --source values but parseExportSessionArgs() still only
validates/handles "codex" and "claude", causing a validation error; update
parseExportSessionArgs() so its allowedSources/validation logic includes
"gemini" and "codebuddy" and extend any downstream switch/handler that branches
on the source (the same place that treats "codex"/"claude") to handle "gemini"
and "codebuddy" or, alternatively, change the usage text to only list the
sources currently supported—ensure the change is applied around the
parseExportSessionArgs function and any source-specific export handling
functions referenced there.

Comment thread README.md
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
web-ui/partials/index/layout-header.html (1)

217-217: ⚠️ Potential issue | 🟡 Minor

Use the same i18n mapping here too.

This branch still hardcodes English labels, while the main status strip already uses t('sessions.source.*'). That leaves the sidebar out of sync for localized builds.

Suggested fix
-                            <span>{{ t('sessions.sourceLabel', { value: (sessionFilterSource === 'all' ? t('sessions.source.all') : (sessionFilterSource === 'claude' ? 'Claude Code' : (sessionFilterSource === 'gemini' ? 'Gemini CLI' : (sessionFilterSource === 'codebuddy' ? 'CodeBuddy Code' : 'Codex')))) }) }}</span>
+                            <span>{{ t('sessions.sourceLabel', { value: (sessionFilterSource === 'all' ? t('sessions.source.all') : (sessionFilterSource === 'claude' ? t('sessions.source.claudeCode') : (sessionFilterSource === 'gemini' ? t('sessions.source.gemini') : (sessionFilterSource === 'codebuddy' ? t('sessions.source.codebuddy') : t('sessions.source.codex'))))) }) }}</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/partials/index/layout-header.html` at line 217, The session source
label is hardcoded to English; update the template expression that builds the
span text to use the same i18n keys as the status strip (use
t('sessions.source.claude'), t('sessions.source.gemini'),
t('sessions.source.codebuddy'), t('sessions.source.codex')) instead of literal
strings, keeping the outer call t('sessions.sourceLabel', { value: ... }) and
the sessionFilterSource variable used to select the correct key so localized
builds show the same translations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web-ui/partials/index/layout-header.html`:
- Line 217: The session source label is hardcoded to English; update the
template expression that builds the span text to use the same i18n keys as the
status strip (use t('sessions.source.claude'), t('sessions.source.gemini'),
t('sessions.source.codebuddy'), t('sessions.source.codex')) instead of literal
strings, keeping the outer call t('sessions.sourceLabel', { value: ... }) and
the sessionFilterSource variable used to select the correct key so localized
builds show the same translations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d928817-36c1-406b-865c-d385b3ae9a6a

📥 Commits

Reviewing files that changed from the base of the PR and between a4c681c and bfd07ab.

📒 Files selected for processing (2)
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/layout-header.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-ui/modules/i18n.dict.mjs
📜 Review details
🔇 Additional comments (1)
web-ui/partials/index/layout-header.html (1)

372-382: LGTM — the current source chip is now localized.

The status-strip branch uses the existing source translation keys for all known values and keeps a sensible fallback to Codex.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/unit/session-resume-command.test.mjs (1)

21-47: Add one edge-case assertion for shell-quoting safety.

Current cases only use simple IDs. Adding a sessionId with spaces/quotes would lock in the quoting contract for new providers too.

Example assertion to add
+    assert.strictEqual(
+        methods.buildResumeCommand.call({ ...contextBase, sessionResumeWithYolo: true }, { source: 'gemini', sessionId: "gm ' 1" }),
+        "gemini -r 'gm '\\'' 1'"
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/session-resume-command.test.mjs` around lines 21 - 47, Add an
edge-case assertion to tests for buildResumeCommand to ensure shell-quoting
safety: call methods.buildResumeCommand (from createSessionActionMethods) with a
context (e.g., contextBase or modified) and a sessionId that contains spaces
and/or quotes (e.g., 'id with space' or 'id"with\'quotes') and assert the
returned command correctly quotes or escapes the sessionId for the given
source(s) (codex, codebuddy, gemini) so quoting behavior is explicit and
protected for new providers; update the existing test block around
methods.buildResumeCommand usage to include this extra assertion.
web-ui/modules/app.methods.session-actions.mjs (1)

141-149: Consider reusing shared source normalization to avoid drift.

This method normalizes source inline; using the shared normalizer from web-ui/logic.sessions.mjs:35-43 would keep source handling consistent as providers evolve.

Suggested refactor
- const source = session && session.source ? String(session.source).trim().toLowerCase() : '';
+ const source = normalizeSessionSource(session && session.source ? session.source : '', '');

  const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
  const arg = this.quoteResumeArg(sessionId);
  if (source === 'codebuddy') {
      return `codebuddy -r ${arg}`;
  }
  if (source === 'gemini') {
      return `gemini -r ${arg}`;
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.methods.session-actions.mjs` around lines 141 - 149, The
inline normalization of source should be replaced with the shared normalizer
exported by logic.sessions.mjs (use the module's exported function for source
normalization) instead of repeating String(session.source).trim().toLowerCase();
import that normalizer into app.methods.session-actions.mjs, call it to compute
source, then keep the existing sessionId conversion and the call to
this.quoteResumeArg(sessionId) and the conditional branches that return
`codebuddy -r ${arg}` / `gemini -r ${arg}` unchanged; this ensures consistent
provider handling as providers evolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/session-resume-command.test.mjs`:
- Around line 21-47: Add an edge-case assertion to tests for buildResumeCommand
to ensure shell-quoting safety: call methods.buildResumeCommand (from
createSessionActionMethods) with a context (e.g., contextBase or modified) and a
sessionId that contains spaces and/or quotes (e.g., 'id with space' or
'id"with\'quotes') and assert the returned command correctly quotes or escapes
the sessionId for the given source(s) (codex, codebuddy, gemini) so quoting
behavior is explicit and protected for new providers; update the existing test
block around methods.buildResumeCommand usage to include this extra assertion.

In `@web-ui/modules/app.methods.session-actions.mjs`:
- Around line 141-149: The inline normalization of source should be replaced
with the shared normalizer exported by logic.sessions.mjs (use the module's
exported function for source normalization) instead of repeating
String(session.source).trim().toLowerCase(); import that normalizer into
app.methods.session-actions.mjs, call it to compute source, then keep the
existing sessionId conversion and the call to this.quoteResumeArg(sessionId) and
the conditional branches that return `codebuddy -r ${arg}` / `gemini -r ${arg}`
unchanged; this ensures consistent provider handling as providers evolve.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 485d300c-6474-4395-9c42-526eac276804

📥 Commits

Reviewing files that changed from the base of the PR and between bfd07ab and 6d4de6d.

📒 Files selected for processing (2)
  • tests/unit/session-resume-command.test.mjs
  • web-ui/modules/app.methods.session-actions.mjs
📜 Review details
🔇 Additional comments (2)
web-ui/modules/app.methods.session-actions.mjs (1)

120-120: Good guard for resume command availability.

The source allowlist + non-empty sessionId check is correct and prevents invalid resume actions in the UI flow.

tests/unit/session-resume-command.test.mjs (1)

12-19: Nice coverage for multi-source resume availability.

This validates the key gating logic for codex / codebuddy / gemini and empty-session rejection.

@ymkiux ymkiux changed the title feat: support Gemini CLI sessions in session browser feat: add CodeBuddy sessions to the session browser + copy resume command for Gemini/CodeBuddy Apr 29, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

276-282: ⚠️ Potential issue | 🟡 Minor

Update “Sessions Mode” wording to include Gemini CLI + CodeBuddy Code.

Right now the “Session Management” section describes a unified Codex/Claude/Gemini CLI/CodeBuddy Code session experience, but “Web UI → Sessions Mode” still says only “Unified Codex + Claude sessions”, which is likely to mislead users. Consider updating that block to match the unified provider list.

🛠️ Proposed fix
### Sessions Mode
-- Unified Codex + Claude sessions
+- Unified Codex + Claude + Gemini CLI + CodeBuddy Code sessions
  - Browser / Usage subview switching
  - Local pin/unpin with persistent storage and pinned-first ordering
  - Search, filter, export, delete, batch cleanup
  - Usage view includes 7d / 30d session trends, message trends, source share, and top paths
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 276 - 282, Update the "### Sessions Mode" block so
its first bullet reflects the full unified provider list instead of only
"Unified Codex + Claude sessions"; replace that bullet with a phrase that
explicitly includes Gemini CLI and CodeBuddy Code (e.g., "Unified Codex, Claude,
Gemini CLI, and CodeBuddy Code sessions") and ensure any related bullets or
wording in the same "Sessions Mode" section remain consistent with the Session
Management description.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 59-73: Update the "Copy resume command" README entry to reflect
that Codex supports an optional --yolo flag: change the Codex example `codex
resume <sessionId>` to include the optional flag (e.g., `codex resume
<sessionId> [--yolo]` or `codex resume <sessionId> (optionally --yolo)`), while
leaving the Gemini and CodeBuddy examples (`gemini -r <sessionId>`, `codebuddy
-r <sessionId>`) unchanged so it’s clear only `codex resume` accepts `--yolo`.
- Around line 32-33: The README has a malformed concatenated bullet where the
sessions and Plugins lines run together; edit the two list items so they are
separate Markdown bullets: ensure the line "- Local Codex/Claude/Gemini
CLI/CodeBuddy Code sessions (list/filter/export/delete) with Usage analytics
overview" is on its own line, followed by a newline and the separate line "-
Plugins (Prompt templates): reusable templates with variables and one-click
copy", removing any duplicated fragment or trailing text that merged the two
entries.

---

Outside diff comments:
In `@README.md`:
- Around line 276-282: Update the "### Sessions Mode" block so its first bullet
reflects the full unified provider list instead of only "Unified Codex + Claude
sessions"; replace that bullet with a phrase that explicitly includes Gemini CLI
and CodeBuddy Code (e.g., "Unified Codex, Claude, Gemini CLI, and CodeBuddy Code
sessions") and ensure any related bullets or wording in the same "Sessions Mode"
section remain consistent with the Session Management description.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6fcf13c-b385-4869-993b-9e7792418dfa

📥 Commits

Reviewing files that changed from the base of the PR and between 4526446 and 336432a.

📒 Files selected for processing (2)
  • README.md
  • README.zh.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.zh.md
📜 Review details
🧰 Additional context used
🪛 LanguageTool
README.md

[grammar] ~32-~32: Ensure spelling is correct
Context: ...ter/export/delete) with Usage analytics overview - Plugins (Prompt templates): reusable te...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (1)
README.md (1)

71-71: No issues found. The README correctly documents the export-session command flags. Verification confirms --session-id and --file match the CLI implementation in cli.js (lines 8409–8414, 8440–8444), where both flags are explicitly parsed and used in examples.

Comment thread README.md
Comment on lines +32 to 33
- Local Codex/Claude/Gemini CLI/CodeBuddy Code sessions (list/filter/export/delete) with Usage analytics overview
- Plugins (Prompt templates): reusable templates with variables and one-click copy
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.

⚠️ Potential issue | 🟡 Minor

Fix malformed bullet concatenation around the session/Plugins bullets.

At around line ~32, the sessions bullet appears to include trailing text like with Usage analytics overview - Plugins ... in the same line, which suggests a missing newline or a duplicated fragment. This is consistent with the static-analysis orthography/format hint and can easily render incorrectly in Markdown.

🛠️ Proposed fix
- - Local Codex/Claude/Gemini CLI/CodeBuddy Code sessions (list/filter/export/delete) with Usage analytics overview - Plugins (Prompt templates): reusable templates with variables and one-click copy
+ - Local Codex/Claude/Gemini CLI/CodeBuddy Code sessions (list/filter/export/delete) with Usage analytics overview
🧰 Tools
🪛 LanguageTool

[grammar] ~32-~32: Ensure spelling is correct
Context: ...ter/export/delete) with Usage analytics overview - Plugins (Prompt templates): reusable te...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~33-~33: Ensure spelling is correct
Context: ... templates with variables and one-click copy - Task orchestration: plan/queue/run/revi...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 32 - 33, The README has a malformed concatenated
bullet where the sessions and Plugins lines run together; edit the two list
items so they are separate Markdown bullets: ensure the line "- Local
Codex/Claude/Gemini CLI/CodeBuddy Code sessions (list/filter/export/delete) with
Usage analytics overview" is on its own line, followed by a newline and the
separate line "- Plugins (Prompt templates): reusable templates with variables
and one-click copy", removing any duplicated fragment or trailing text that
merged the two entries.

Comment thread README.md
Comment on lines 59 to 73
**Session Management**
- Unified Codex + Claude session list
- Unified Codex + Claude + Gemini CLI + CodeBuddy Code session list
- Session locations (local-first, configurable):
- Codex: `~/.codex/sessions/*.jsonl` (or `$CODEX_HOME/sessions`, `$XDG_CONFIG_HOME/codex/sessions`)
- Claude: `~/.claude/projects/**/**/*.jsonl` (or `$CLAUDE_HOME/projects`, `$XDG_CONFIG_HOME/claude/projects`)
- Gemini: `~/.gemini/tmp/*/chats/*.json` (or `$GEMINI_HOME/tmp`, `$XDG_CONFIG_HOME/gemini/tmp`)
- CodeBuddy: `~/.codebuddy/projects/**/**/*.jsonl` (or `$CODEBUDDY_CODE_HOME_DIR/projects`)
- Local session pinning with persistent pinned state and pinned-first ordering
- Keyword/source/cwd filters
- Keyword/source/cwd/role/time filters, plus shareable filter links
- Copy resume command (Codex/Gemini/CodeBuddy): `codex resume <sessionId>` / `gemini -r <sessionId>` / `codebuddy -r <sessionId>`
- Fast search UX: short-lived query result caching to avoid rescanning on each keystroke
- Usage subview with 7d / 30d session trends, message trends, source share, and top paths
- Markdown export
- Markdown export (Web UI + `codexmate export-session`, supports `--session-id` or `--file`)
- Session-level and message-level delete (supports batch), with a local recycle bin for restore/purge
- Large-session preview optimization (fast tail preview path)
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.

⚠️ Potential issue | 🟡 Minor

Fix “Copy resume command” entry to include Codex’s optional --yolo.

The “Copy resume command” bullet currently shows:

  • codex resume <sessionId>
  • gemini -r <sessionId>
  • codebuddy -r <sessionId>

…but your PR objective states Codex supports (optionally --yolo) while Gemini/CodeBuddy do not. Updating the README avoids confusion.

🛠️ Proposed fix
- - Copy resume command (Codex/Gemini/CodeBuddy): `codex resume <sessionId>` / `gemini -r <sessionId>` / `codebuddy -r <sessionId>`
+ - Copy resume command (Codex/Gemini/CodeBuddy): `codex resume <sessionId> [--yolo]` / `gemini -r <sessionId>` / `codebuddy -r <sessionId>`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 59 - 73, Update the "Copy resume command" README
entry to reflect that Codex supports an optional --yolo flag: change the Codex
example `codex resume <sessionId>` to include the optional flag (e.g., `codex
resume <sessionId> [--yolo]` or `codex resume <sessionId> (optionally --yolo)`),
while leaving the Gemini and CodeBuddy examples (`gemini -r <sessionId>`,
`codebuddy -r <sessionId>`) unchanged so it’s clear only `codex resume` accepts
`--yolo`.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-ui/modules/app.computed.session.mjs`:
- Around line 553-561: The computed currently pulls aggregate values from
this.sessionUsageCharts.summary (totalTokens, activeDurationMs, totalDurationMs)
which shows totals across sessions; change it to derive metrics for the current
session using this.activeSession (or the per-session entry in
this.sessionUsageCharts.sessions keyed by this.activeSession.id) so the labels
under usageCurrentSessionStats reflect the active session only; update
references to use activeSession usage fields (or rename the computed if you
intend to keep aggregate data) and ensure formatUsageDuration and
formatCompactUsageSummaryNumber call sites use the per-session values instead of
summary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 57031b4a-37d8-43b2-a62a-4a61d4be0d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 336432a and 75756c0.

📒 Files selected for processing (9)
  • tests/unit/compact-layout-ui.test.mjs
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/modules/app.computed.session.mjs
  • web-ui/modules/i18n.dict.mjs
  • web-ui/partials/index/panel-usage.html
  • web-ui/styles/responsive.css
  • web-ui/styles/sessions-list.css
  • web-ui/styles/sessions-toolbar-trash.css
  • web-ui/styles/sessions-usage.css
💤 Files with no reviewable changes (1)
  • web-ui/styles/responsive.css
✅ Files skipped from review due to trivial changes (5)
  • web-ui/partials/index/panel-usage.html
  • web-ui/styles/sessions-usage.css
  • tests/unit/web-ui-behavior-parity.test.mjs
  • web-ui/styles/sessions-list.css
  • web-ui/styles/sessions-toolbar-trash.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-ui/modules/i18n.dict.mjs
📜 Review details
🔇 Additional comments (1)
tests/unit/compact-layout-ui.test.mjs (1)

72-74: Assertions now match the single-line truncation contract.

Nice adjustment—these checks align with the current .session-item-title CSS behavior (nowrap + ellipsis + hidden) and keep the test intent clear.

Comment on lines +553 to +561
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
? this.sessionUsageCharts.summary
: null;
if (!summary) return null;
const t = typeof this.t === 'function' ? this.t : null;
return {
apiDurationLabel: formatUsageDuration(summary.activeDurationMs || 0, { compact: true, lang: this.lang }),
totalDurationLabel: formatUsageDuration(summary.totalDurationMs || 0, { compact: true, lang: this.lang }),
tokenLabel: formatCompactUsageSummaryNumber(summary.totalTokens || 0),
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.

⚠️ Potential issue | 🟠 Major

usageCurrentSessionStats is using aggregate usage, not the current session.

Line 553 and Line 561 read from this.sessionUsageCharts.summary (totalTokens, durations), which is aggregated across sessionsUsageList. With the current label (当前会话 / usage.currentSession.*), this will display incorrect values when more than one session is included.

Suggested fix (derive from this.activeSession or rename the strip)
         usageCurrentSessionStats() {
-            const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
-                ? this.sessionUsageCharts.summary
-                : null;
-            if (!summary) return null;
+            const session = this.activeSession && typeof this.activeSession === 'object'
+                ? this.activeSession
+                : null;
+            if (!session) return null;
             const t = typeof this.t === 'function' ? this.t : null;
+            const createdAtMs = Date.parse(session.createdAt || '');
+            const updatedAtMs = Date.parse(session.updatedAt || '');
+            const totalDurationMs = Number.isFinite(createdAtMs) && Number.isFinite(updatedAtMs)
+                ? Math.max(0, updatedAtMs - createdAtMs)
+                : 0;
+            const activeDurationMs = Number.isFinite(Number(session.activeDurationMs))
+                ? Math.max(0, Math.floor(Number(session.activeDurationMs)))
+                : totalDurationMs;
+            const totalTokens = Number.isFinite(Number(session.totalTokens))
+                ? Math.max(0, Math.floor(Number(session.totalTokens)))
+                : 0;
             return {
-                apiDurationLabel: formatUsageDuration(summary.activeDurationMs || 0, { compact: true, lang: this.lang }),
-                totalDurationLabel: formatUsageDuration(summary.totalDurationMs || 0, { compact: true, lang: this.lang }),
-                tokenLabel: formatCompactUsageSummaryNumber(summary.totalTokens || 0),
+                apiDurationLabel: formatUsageDuration(activeDurationMs, { compact: true, lang: this.lang }),
+                totalDurationLabel: formatUsageDuration(totalDurationMs, { compact: true, lang: this.lang }),
+                tokenLabel: formatCompactUsageSummaryNumber(totalTokens),
                 label: t ? t('usage.currentSession.title') : '当前会话',
                 apiDurationText: t ? t('usage.currentSession.apiDuration') : 'API时长',
                 totalDurationText: t ? t('usage.currentSession.totalDuration') : '总时长',
                 tokenText: t ? t('usage.currentSession.tokens') : 'Token'
             };
         },
📝 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.

Suggested change
const summary = this.sessionUsageCharts && this.sessionUsageCharts.summary
? this.sessionUsageCharts.summary
: null;
if (!summary) return null;
const t = typeof this.t === 'function' ? this.t : null;
return {
apiDurationLabel: formatUsageDuration(summary.activeDurationMs || 0, { compact: true, lang: this.lang }),
totalDurationLabel: formatUsageDuration(summary.totalDurationMs || 0, { compact: true, lang: this.lang }),
tokenLabel: formatCompactUsageSummaryNumber(summary.totalTokens || 0),
const session = this.activeSession && typeof this.activeSession === 'object'
? this.activeSession
: null;
if (!session) return null;
const t = typeof this.t === 'function' ? this.t : null;
const createdAtMs = Date.parse(session.createdAt || '');
const updatedAtMs = Date.parse(session.updatedAt || '');
const totalDurationMs = Number.isFinite(createdAtMs) && Number.isFinite(updatedAtMs)
? Math.max(0, updatedAtMs - createdAtMs)
: 0;
const activeDurationMs = Number.isFinite(Number(session.activeDurationMs))
? Math.max(0, Math.floor(Number(session.activeDurationMs)))
: totalDurationMs;
const totalTokens = Number.isFinite(Number(session.totalTokens))
? Math.max(0, Math.floor(Number(session.totalTokens)))
: 0;
return {
apiDurationLabel: formatUsageDuration(activeDurationMs, { compact: true, lang: this.lang }),
totalDurationLabel: formatUsageDuration(totalDurationMs, { compact: true, lang: this.lang }),
tokenLabel: formatCompactUsageSummaryNumber(totalTokens),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/modules/app.computed.session.mjs` around lines 553 - 561, The computed
currently pulls aggregate values from this.sessionUsageCharts.summary
(totalTokens, activeDurationMs, totalDurationMs) which shows totals across
sessions; change it to derive metrics for the current session using
this.activeSession (or the per-session entry in this.sessionUsageCharts.sessions
keyed by this.activeSession.id) so the labels under usageCurrentSessionStats
reflect the active session only; update references to use activeSession usage
fields (or rename the computed if you intend to keep aggregate data) and ensure
formatUsageDuration and formatCompactUsageSummaryNumber call sites use the
per-session values instead of summary.

@ymkiux ymkiux changed the title feat: add CodeBuddy sessions to the session browser + copy resume command for Gemini/CodeBuddy feat: add Gemini CLI + CodeBuddy sessions, polish Usage and Sessions UI Apr 29, 2026
@ymkiux ymkiux merged commit 0a748f9 into main Apr 29, 2026
9 checks passed
@ymkiux ymkiux deleted the feat/gemini-cli-sessions branch April 29, 2026 07:53
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