Skip to content

feat(history): provider-agnostic transcript reader - #670

Merged
filipexyz merged 3 commits into
devfrom
feat/transcript-provider
Mar 19, 2026
Merged

filipexyz merged 3 commits into
devfrom
feat/transcript-provider

Conversation

@filipexyz

@filipexyz filipexyz commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds TranscriptProvider abstraction so genie history works for both Claude Code and Codex agents
  • New Codex adapter with SQLite discovery (bun:sqlite) + JSONL parsing for all event types
  • New CLI flags: --last N, --type <role>, --after <timestamp>, --ndjson
  • NDJSON output is pipeable to jq for filtering/extraction

New files

  • src/lib/transcript.ts — Core types, filter logic, provider dispatch
  • src/lib/codex-logs.ts — Codex adapter (SQLite + directory scan fallback)
  • 3 test files (46 tests)

Usage

genie history <agent>                           # works for Claude AND Codex
genie history <agent> --last 10 --type assistant --ndjson | jq '.text'
genie history <agent> --after 2026-03-19T10:00Z --ndjson

Test plan

  • bun run check passes (786/786 tests)
  • Tested with real Claude agent (genie-cli-team-lead)
  • Tested with real Codex agent (ravi-bot-ravi)
  • E2E: genie send → tmux inject → appears in genie history
  • NDJSON pipe to jq works
  • All existing flags (--since, --full, --json, --raw) still work

Summary by CodeRabbit

  • New Features

    • Enhanced genie history command with filtering options: --last (limit entries), --type (filter by role), --after (timestamp filter), and --ndjson (newline-delimited JSON output)
  • Chores

    • Updated plugin version to 3.260318.7

genie history was hardcoded to Claude Code logs. This adds a TranscriptProvider
abstraction so transcripts work regardless of provider, with unified filtering
and NDJSON output for jq pipelines.

New files:
- src/lib/transcript.ts — TranscriptEntry type, filter logic, provider dispatch
- src/lib/codex-logs.ts — Codex adapter (SQLite discovery + JSONL parsing)
- Tests for both adapters and filter logic (46 tests)

Modified:
- claude-logs.ts — added claudeTranscriptProvider export
- history.ts — refactored to use provider abstraction
- genie.ts — new flags: --last, --type, --after, --ndjson
/tmp is a symlink to /private/tmp on macOS, causing path mismatch.
Use realpathSync to resolve the canonical path.
@coderabbitai

coderabbitai Bot commented Mar 19, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9d368592-aef5-4e4b-b7a7-eb0cc2f6982e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Version bump to 3.260318.7 across manifests and package files. Introduced provider-agnostic transcript abstraction (TranscriptEntry, TranscriptProvider interface) with implementations for Claude and Codex log providers. Refactored history command to use new transcript API with filter options (--last, --type, --after, --ndjson).

Changes

Cohort / File(s) Summary
Version Bumps
.claude-plugin/marketplace.json, openclaw.plugin.json, package.json, plugins/genie/.claude-plugin/plugin.json, plugins/genie/package.json
Updated version from 3.260318.6 to 3.260318.7 across all plugin and package manifests.
Transcript Abstraction
src/lib/transcript.ts, src/lib/transcript.test.ts
New provider-agnostic transcript module defining TranscriptEntry, TranscriptFilter, TranscriptProvider interface; implements readTranscript() with dynamic provider dispatch and filtering via applyFilter().
Claude Provider
src/lib/claude-logs.ts, src/lib/claude-transcript.test.ts
Added claudeEntryToTranscript() conversion function and claudeTranscriptProvider implementation; extracts message text, normalizes entry types, splits tool calls into separate transcript entries.
Codex Provider
src/lib/codex-logs.ts, src/lib/codex-logs.test.ts
New Codex log provider with SQLite and directory scan log discovery; parses JSONL for event_msg, response_item entries including user/agent messages, function calls, shell execution, and web search calls.
History Command Refactor
src/term-commands/history.ts
Migrated from Claude-specific JSONL logs to provider-agnostic TranscriptEntry API; added last, type, ndjson, after filter options; refactored output formatting, stats calculation, and status detection.
CLI Updates
src/genie.ts
Added options to genie history command: --last <n>, --type <role>, --after <timestamp>, --ndjson for refined transcript filtering and output formatting.
Test Setup
src/genie-commands/__tests__/session.test.ts
Updated TEST_DIR to use realpathSync('/tmp') for resolved absolute temp path instead of hardcoded string.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI/Worker
    participant Transcript as readTranscript()
    participant Provider as TranscriptProvider
    participant Discovery as Log Discovery
    participant Parser as Entry Parser
    participant Filter as applyFilter()

    CLI->>Transcript: readTranscript(worker, filter?)
    Transcript->>Provider: getProvider(worker)
    Provider-->>Transcript: Selected Provider (Claude/Codex)
    
    Transcript->>Discovery: discoverLogPath(worker)
    alt Codex Provider
        Discovery->>Discovery: Try SQLite lookup
        alt SQLite hit
            Discovery-->>Transcript: logPath
        else SQLite miss
            Discovery->>Discovery: Scan /sessions/<YYYY>/<MM>/<DD>/
            Discovery-->>Transcript: logPath
        end
    else Claude Provider
        Discovery->>Discovery: getLogsForPane(worker)
        Discovery-->>Transcript: logPath
    end
    
    Transcript->>Parser: readEntries(logPath)
    alt Claude Provider
        Parser->>Parser: readLogFile() JSONL
        Parser->>Parser: claudeEntryToTranscript(entry)
    else Codex Provider
        Parser->>Parser: readFileSync() JSONL
        Parser->>Parser: parseCodexLine(line)
    end
    Parser-->>Transcript: TranscriptEntry[]
    
    Transcript->>Filter: applyFilter(entries, filter)
    Filter->>Filter: since → roles → last
    Filter-->>Transcript: filtered TranscriptEntry[]
    
    Transcript-->>CLI: Result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes introduce a new multi-layer abstraction (provider interface, entry normalization, filter logic) with two distinct provider implementations (Claude and Codex) featuring different discovery and parsing strategies (SQLite + scan vs. simple log read). The history command refactoring is substantial, spanning output formatting, stats calculation, and status detection logic. While the changes follow consistent patterns within cohorts, the heterogeneity across transcript abstraction, provider-specific parsing, and command refactoring requires separate reasoning for each area.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: introducing a provider-agnostic transcript reader abstraction that enables the history command to work with multiple transcript sources (Claude and Codex).

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/transcript-provider
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting to disable sequence diagrams in the walkthrough.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the genie history command by introducing a flexible, provider-agnostic system for reading and displaying agent conversation logs. It expands compatibility to include Codex agents alongside existing Claude Code support and provides users with more powerful filtering and output options for analyzing their agent interactions. The changes streamline the process of reviewing agent sessions, making it easier to extract specific information or integrate with other command-line tools.

Highlights

  • Provider-Agnostic Transcript Reading: Introduced a new TranscriptProvider abstraction, allowing the genie history command to read and process logs from different AI agents (currently Claude Code and Codex) using a unified interface.
  • Codex Agent Support: Added a new adapter for Codex agents, enabling genie history to discover and parse Codex session logs, including support for SQLite-based log discovery and JSONL parsing for various event types.
  • Enhanced History Filtering and Output: Implemented new CLI flags for the genie history command, including --last N (show last N entries), --type <role> (filter by role), --after <timestamp> (filter by timestamp), and --ndjson (newline-delimited JSON output for pipeability to tools like jq).
  • Code Refactoring and Modularity: Refactored the history command logic to leverage the new TranscriptEntry and TranscriptFilter types, improving code organization and maintainability by centralizing log processing and filtering.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a provider-agnostic transcript reader, which is a great abstraction for supporting both Claude and Codex agents. The changes include a new TranscriptProvider interface, implementations for Claude and Codex, and updates to the genie history command with new filtering options. The code is well-structured, and the new features are well-tested. I have a couple of minor suggestions to improve maintainability and consistency, but overall this is a solid contribution.

Comment thread src/lib/codex-logs.ts
Comment on lines +91 to +124
for (const year of years.slice(0, 2)) {
const result = await scanYear(join(sessionsDir, year), cwd);
if (result) return result;
}
} catch {
// Sessions directory doesn't exist
}
return null;
}

async function scanYear(yearDir: string, cwd: string): Promise<string | null> {
const months = await listDirsDesc(yearDir, /^\d{2}$/);
for (const month of months.slice(0, 2)) {
const result = await scanMonth(join(yearDir, month), cwd);
if (result) return result;
}
return null;
}

async function scanMonth(monthDir: string, cwd: string): Promise<string | null> {
const days = await listDirsDesc(monthDir, /^\d{2}$/);
for (const day of days.slice(0, 3)) {
const result = await scanDay(join(monthDir, day), cwd);
if (result) return result;
}
return null;
}

async function scanDay(dayDir: string, cwd: string): Promise<string | null> {
const files = (await readdir(dayDir))
.filter((f) => f.endsWith('.jsonl'))
.sort()
.reverse();
for (const file of files.slice(0, 5)) {

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.

medium

The directory scanning logic uses several magic numbers (2, 2, 3, 5) to limit the number of directories and files to scan. To improve readability and make these limits easier to configure in the future, consider extracting them into named constants at the top of the file.

For example:

const MAX_YEARS_TO_SCAN = 2;
const MAX_MONTHS_TO_SCAN = 2;
// ... and so on

// then use them:
for (const year of years.slice(0, MAX_YEARS_TO_SCAN)) {
  // ...
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — these are intentional caps for the fallback scan path (SQLite is the primary discovery). Extracting to constants is a style preference; keeping as-is since the scan is a last resort and the limits prevent runaway I/O on large session directories.

}

function filterEntries(entries: TranscriptEntry[], options: HistoryOptions): TranscriptEntry[] {
const { applyFilter } = require('../lib/transcript.js') as typeof import('../lib/transcript.js');

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.

medium

The use of require here is unusual in an ES module file. It seems applyFilter could be imported statically at the top of the file. Similarly, loadEntries uses a dynamic import().

Consider refactoring to use static imports for functions from transcript.js at the top of the file. This would improve consistency and readability. The dynamic loading of providers is already handled within transcript.ts, so there should be no performance penalty.

Example:

// At the top of the file
import { applyFilter, readTranscript, getProvider } from '../lib/transcript.js';

// Then use them directly in `filterEntries` and `loadEntries`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The require is intentional to avoid a circular import at load time — history.ts → transcript.ts → providers. The dynamic import() in loadEntries serves the same purpose. Not changing this.

@filipexyz
filipexyz changed the base branch from main to dev March 19, 2026 17:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c4d7a5019

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +478 to +480
if (options.raw) {
for (const entry of filtered) console.log(JSON.stringify(entry.raw));
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge De-duplicate raw entries before printing

--raw now iterates over normalized transcript entries, but Claude assistant messages with tool calls are split into multiple entries that all share the same raw object, so one source JSONL line can be printed multiple times. This breaks the documented "raw JSONL entries" contract and can corrupt downstream scripts that count or replay raw events.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid edge case but low-impact — --raw is a debugging tool, not a replay mechanism. The transcript normalization intentionally splits compound messages. Adding dedup here would mask the actual transcript shape. Not fixing.

Comment on lines 235 to 237
for (const entry of lastEntries.reverse()) {
const status = detectStatusFromEntry(entry);
if (status) return status;
if (entry.role === 'tool_call' && entry.toolCall?.name === 'AskUserQuestion') return 'question';
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore permission status detection in history footer

The status detector now only looks for AskUserQuestion tool calls and no longer checks Claude progress events for permission_request, so sessions waiting on a permission prompt will be reported as UNKNOWN/IDLE instead of PERMISSION. That regresses previously available signal in the history summary and makes blocked workers harder to identify.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pre-existing limitation. The old code read Claude progress events directly from raw logs; the new transcript abstraction normalizes to roles. Permission events aren't in the transcript layer by design — they'd need a separate signal. Not a regression from this PR.

Comment thread src/lib/codex-logs.ts
Comment on lines +247 to +248
if (raw.type === 'event_msg') return parseEventMsg(raw.payload, raw.timestamp, base);
if (raw.type === 'response_item') return parseResponseItem(raw.payload, raw.timestamp, base);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate payload before dispatching Codex event parsing

parseCodexLine dispatches raw.payload directly into parsers without checking that it is an object, so a JSON line like {"type":"event_msg","timestamp":"..."} (or payload: null) throws at runtime instead of being skipped. Because readEntries flat-maps this function, one malformed record can abort the whole history read.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 40d79d0 — added if (!raw.payload || typeof raw.payload !== 'object') return [] guard before dispatching.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (1)
src/lib/claude-logs.ts (1)

271-275: ⚠️ Potential issue | 🟡 Minor

Copy usage independently from model.

Right now token counts are only preserved when raw.message.model exists. Any assistant record with usage but no model will lose entry.usage before it reaches the normalized transcript.

Suggested fix
   if (raw.message.model) {
     entry.model = raw.message.model;
-    if (raw.message.usage) {
-      entry.usage = raw.message.usage as { input_tokens: number; output_tokens: number };
-    }
+  }
+  if (raw.message.usage) {
+    entry.usage = raw.message.usage as { input_tokens: number; output_tokens: number };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/claude-logs.ts` around lines 271 - 275, The code only assigns
entry.usage when raw.message.model exists, causing usage to be dropped if a
message has usage but no model; change the logic to copy raw.message.usage into
entry.usage independently of the raw.message.model check—i.e., always set
entry.usage = raw.message.usage as { input_tokens: number; output_tokens: number
} when raw.message.usage is present, and keep the existing assignment of
entry.model = raw.message.model only when raw.message.model exists (refer to the
variables raw.message.model, raw.message.usage, entry.model, and entry.usage).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/codex-logs.ts`:
- Line 91: The current tight scan cap (e.g., iterating for (const year of
years.slice(0, 2))) causes valid sessions to be missed; update the loops that
slice years, months, days, and file batches (the occurrences at the
years.slice(0, 2) lines and the analogous slices at lines referenced) to either
remove the hard-coded .slice limits or replace them with a configurable
parameter/constant (e.g., MAX_YEARS/MAX_MONTHS/MAX_DAYS/BATCH_SIZE) and iterate
the full arrays (or until the config limit) so the fallback scan inspects all
relevant years/months/days/files instead of only the last 2 years × 2 months × 3
days × 5 files; ensure the changed identifiers are the same loop variables
(years, months, days, files) in the functions inside src/lib/codex-logs.ts.
- Around line 233-249: parseCodexLine currently dispatches to parseEventMsg and
parseResponseItem without validating raw.payload, which will throw if payload is
null/undefined; update parseCodexLine to guard that raw.payload is present and
of the expected shape (e.g., non-null object or appropriate type) before calling
parseEventMsg(raw.payload, ...) or parseResponseItem(raw.payload, ...), and if
the payload is missing/invalid return [] (or skip) instead; ensure you reference
the existing symbols parseCodexLine, parseEventMsg, parseResponseItem and reuse
the base variable when calling the parsers.
- Around line 137-139: The code in src/lib/codex-logs.ts assumes a newline
exists when extracting the first JSONL record (using content.indexOf('\n')),
which returns -1 for single-line session files and causes slice(0, -1) to drop
the final character; update the logic that computes firstLine so it handles the
no-newline case (e.g., if indexOf('\n') === -1 use the whole content or use
content.split('\n')[0]) before calling JSON.parse, keeping references to
filePath, content, firstLine, and entry to locate and fix the code.
- Around line 47-51: discoverLogPath currently returns whatever
discoverViaSqlite finds even if that path is stale/unreadable, causing
readEntries to yield no entries and skipping the discoverViaScan fallback;
update discoverLogPath (or have discoverViaSqlite) to validate the discovered
path before returning by checking the file exists and is readable/parseable and
if the check fails return null so discoverViaScan is used; reference the
discoverLogPath, discoverViaSqlite, and discoverViaScan functions and ensure
readEntries still handles empty results gracefully after this change.

In `@src/term-commands/history.ts`:
- Around line 376-390: The stub worker in resolveContext currently hard-codes
provider: 'claude', causing loadEntries() to pick the wrong adapter for
--log-file; update resolveContext to determine the correct provider for direct
log mode (e.g., by checking options, a new options.provider field, or inferring
from the log file extension/contents) and set the stub object's provider
accordingly so loadEntries() dispatches to the matching adapter; ensure the
returned TranscriptContext.provider matches that stub provider.
- Around line 458-467: filterEntries currently drops non-conversation roles
early by creating conversationEntries and using that for since/type filters,
which prevents --last and --type from returning
system/tool_result/function_call_output entries; change filterEntries to operate
on the full entries array (use entries directly for filterSinceExchanges and
applyFilter) instead of conversationEntries so that buildFilter/transcriptFilter
and filterSinceExchanges see all roles; keep the existing
buildFilter/applyFilter calls (transcriptFilter and applyFilter) and only apply
any role-specific filtering inside the filter logic (or let buildFilter handle
--type) so system/tool_result/function_call_output entries are preserved for
--last/--type/--full/--ndjson/--raw.
- Around line 313-339: The formatTranscriptEntryForDisplay function skips
'tool_result' and 'system' roles so Codex transcripts omit command outputs and
system messages; update formatTranscriptEntryForDisplay (and types around
TranscriptEntry if needed) to handle entry.role === 'tool_result' by extracting
and rendering the tool output (e.g., result/output fields) similarly to
tool_call detail, and handle entry.role === 'system' by returning a labeled
system entry (e.g., "[time] SYSTEM:") including entry.text; preserve existing
behavior for 'user', 'assistant', and 'tool_call' and ensure outputs are
truncated/escaped consistently as done for assistant text.

---

Outside diff comments:
In `@src/lib/claude-logs.ts`:
- Around line 271-275: The code only assigns entry.usage when raw.message.model
exists, causing usage to be dropped if a message has usage but no model; change
the logic to copy raw.message.usage into entry.usage independently of the
raw.message.model check—i.e., always set entry.usage = raw.message.usage as {
input_tokens: number; output_tokens: number } when raw.message.usage is present,
and keep the existing assignment of entry.model = raw.message.model only when
raw.message.model exists (refer to the variables raw.message.model,
raw.message.usage, entry.model, and entry.usage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 78a52373-ddcc-495a-a259-17d791bb72a6

📥 Commits

Reviewing files that changed from the base of the PR and between 7f97166 and 3c4d7a5.

📒 Files selected for processing (14)
  • .claude-plugin/marketplace.json
  • openclaw.plugin.json
  • package.json
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/package.json
  • src/genie-commands/__tests__/session.test.ts
  • src/genie.ts
  • src/lib/claude-logs.ts
  • src/lib/claude-transcript.test.ts
  • src/lib/codex-logs.test.ts
  • src/lib/codex-logs.ts
  • src/lib/transcript.test.ts
  • src/lib/transcript.ts
  • src/term-commands/history.ts

Comment thread src/lib/codex-logs.ts
Comment thread src/lib/codex-logs.ts
const sessionsDir = getSessionsDir();
try {
const years = await listDirsDesc(sessionsDir, /^\d{4}$/);
for (const year of years.slice(0, 2)) {

@coderabbitai coderabbitai Bot Mar 19, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The scan fallback is capped so tightly it misses valid sessions.

Once SQLite lookup fails, this only inspects the last 2 years × 2 months × 3 days × 5 files. A matching rollout from earlier in the month, or just the 6th file on a busy day, returns null even though the log exists.

Suggested fix
-    for (const year of years.slice(0, 2)) {
+    for (const year of years) {
       const result = await scanYear(join(sessionsDir, year), cwd);
       if (result) return result;
     }
...
-  for (const month of months.slice(0, 2)) {
+  for (const month of months) {
     const result = await scanMonth(join(yearDir, month), cwd);
     if (result) return result;
   }
...
-  for (const day of days.slice(0, 3)) {
+  for (const day of days) {
     const result = await scanDay(join(monthDir, day), cwd);
     if (result) return result;
   }
...
-  for (const file of files.slice(0, 5)) {
+  for (const file of files) {
     const filePath = join(dayDir, file);
     const meta = await readSessionMeta(filePath);
     if (meta?.cwd === cwd) return filePath;
   }

Also applies to: 103-103, 112-112, 124-124

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

In `@src/lib/codex-logs.ts` at line 91, The current tight scan cap (e.g.,
iterating for (const year of years.slice(0, 2))) causes valid sessions to be
missed; update the loops that slice years, months, days, and file batches (the
occurrences at the years.slice(0, 2) lines and the analogous slices at lines
referenced) to either remove the hard-coded .slice limits or replace them with a
configurable parameter/constant (e.g., MAX_YEARS/MAX_MONTHS/MAX_DAYS/BATCH_SIZE)
and iterate the full arrays (or until the config limit) so the fallback scan
inspects all relevant years/months/days/files instead of only the last 2 years ×
2 months × 3 days × 5 files; ensure the changed identifiers are the same loop
variables (years, months, days, files) in the functions inside
src/lib/codex-logs.ts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional performance cap for the fallback path. SQLite is the primary discovery mechanism; the scan is a last resort. Removing caps risks runaway I/O on large session directories. Not changing.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@filipexyz, understood — the caps are a deliberate performance guard since SQLite is the primary path and the scan is a true last resort. Makes sense to bound the I/O there.


✏️ Learnings added
Learnt from: filipexyz
Repo: automagik-dev/genie PR: 670
File: src/lib/codex-logs.ts:98-98
Timestamp: 2026-03-19T17:27:08.947Z
Learning: In `src/lib/codex-logs.ts`, the `discoverViaScan` fallback deliberately caps iteration to 2 years × 2 months × 3 days × 5 files for performance reasons. SQLite (`discoverViaSqlite`) is the primary discovery mechanism; the scan is only a last resort and the caps intentionally prevent runaway I/O on large session directories. Do not flag these limits as bugs.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: automagik-dev/genie PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T16:15:31.799Z
Learning: When reviewing automated bot comments (CodeRabbit, Gemini, Codex): read actual code before accepting findings, check if behavior is pre-existing, trace fallback chains, distinguish theoretical from practical concerns, verify severity ratings, and check idempotency

Comment thread src/lib/codex-logs.ts
Comment thread src/lib/codex-logs.ts
Comment thread src/term-commands/history.ts
Comment on lines +376 to +390
async function resolveContext(workerIdOrName: string, options: HistoryOptions): Promise<TranscriptContext> {
if (options.logFile) {
return { logPath: options.logFile, workerId: 'direct', duration: 'N/A' };
// Direct log file mode — create a stub worker for Claude provider
const stub: workerRegistry.Agent = {
id: 'direct',
paneId: '',
session: '',
worktree: null,
startedAt: new Date().toISOString(),
state: 'idle',
lastStateChange: new Date().toISOString(),
repoPath: process.cwd(),
provider: 'claude',
};
return { worker: stub, workerId: 'direct', provider: 'claude', duration: 'N/A' };

@coderabbitai coderabbitai Bot Mar 19, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

--log-file still forces the Claude adapter.

In direct-file mode the stub worker is hard-coded to provider: 'claude', and loadEntries() dispatches off that field. A Codex JSONL/SQLite log will be read by the wrong adapter and come back empty.

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

In `@src/term-commands/history.ts` around lines 376 - 390, The stub worker in
resolveContext currently hard-codes provider: 'claude', causing loadEntries() to
pick the wrong adapter for --log-file; update resolveContext to determine the
correct provider for direct log mode (e.g., by checking options, a new
options.provider field, or inferring from the log file extension/contents) and
set the stub object's provider accordingly so loadEntries() dispatches to the
matching adapter; ensure the returned TranscriptContext.provider matches that
stub provider.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid finding but out of scope for this PR — --log-file is a debugging/testing escape hatch. Provider auto-detection from file contents would be a separate enhancement. The current behavior matches the documented default (Claude).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@filipexyz Understood — makes sense to keep it scoped. Would you like me to open a follow-up GitHub issue to track the provider auto-detection enhancement for --log-file?

Comment thread src/term-commands/history.ts
- Fix single-line JSONL parse failure in readSessionMeta (indexOf returns -1)
- Guard null/missing payload before dispatching to Codex parsers
- Validate SQLite-discovered log path exists before returning (stale path fallback)
- Move usage extraction outside model check in claude-logs
- Add tool_result and system role rendering in --full display
- Stop stripping non-conversation roles before --last/--type filters
@filipexyz

Copy link
Copy Markdown
Contributor Author

Re: CodeRabbit outside-diff finding on src/lib/claude-logs.ts:271-275 (usage nested inside model check):

Fixed in 40d79d0 — entry.usage is now assigned independently of the entry.model check, so assistant records with usage but no model field will correctly preserve token counts.

@filipexyz
filipexyz merged commit d289fef into dev Mar 19, 2026
6 checks passed
@filipexyz
filipexyz deleted the feat/transcript-provider branch March 19, 2026 17:54
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