Skip to content

Conversation

@sogoiii
Copy link
Contributor

@sogoiii sogoiii commented Jan 4, 2026

Summary

Add --continue (-C) flag that resumes the most recent conversation session for the current project.

Features

  • Session persistence: Automatically saves conversation state after each turn
  • Full context restoration: Restores both API conversation history and UI display state
  • Project isolation: Sessions are scoped per-project using SHA-256 hash validation
  • Prompt compatibility: Can combine --continue with --prompt to resume and immediately send a new message

Usage

# Resume last session
llxprt --continue

# Resume and send a prompt
llxprt --continue --prompt "continue working on the auth feature"

Technical Details

New files:

  • SessionPersistenceService.ts - Core persistence logic with atomic writes
  • SessionPersistenceService.test.ts - 30 unit tests with comprehensive coverage

Session storage location:

~/.llxprt/tmp/{project-hash}/chats/persisted-session-{timestamp}.json

Exported types:

  • PersistedSession - Full session schema
  • PersistedUIHistoryItem - UI history item interface
  • PersistedToolCall - Tool call display information

Validation & error handling:

  • Runtime type guards for persisted UI history
  • Graceful fallback with user warnings on invalid data
  • Clear messaging when no previous session exists

Changed Files

 packages/cli/src/config/config.ts                             |  10 +
 packages/cli/src/gemini.tsx                                   |  47 ++
 packages/cli/src/ui/App.tsx                                   |   3 +-
 packages/cli/src/ui/AppContainer.tsx                          | 409 +++++++++++
 packages/core/src/config/config.ts                            |   7 +
 packages/core/src/index.ts                                    |   7 +
 packages/core/src/storage/SessionPersistenceService.ts        | 286 ++++++++
 packages/core/src/storage/SessionPersistenceService.test.ts   | 538 ++++++++++++
 9 files changed, 1307 insertions(+), 1 deletion(-)

Testing

  • 30 unit tests covering all persistence scenarios
  • Edge cases: corrupted files, missing directories, large histories, special characters

Closes #147

Summary by CodeRabbit

  • New Features

    • Added --continue (-C) CLI flag and UI support to resume previous sessions seamlessly
  • Bug Fixes / Reliability

    • Improved session persistence and restoration for conversation history, metadata, and timestamps
  • Tests

    • Added comprehensive tests covering session save/load, corruption handling, and time/hash behaviors
  • Documentation

    • Added a planning doc outlining improvements and validation for session-resume behavior

✏️ Tip: You can customize this high-level summary in your review settings.

Add persistent session storage to resume previous conversations. Sessions are stored per project with:
- Full conversation history (IContent[] format for API context)
- UI display state (HistoryItem[] for exact visual restoration)
- Project hash validation to prevent cross-project session loading
- Automatic save on turn completion, automatic restore on --continue flag

New SessionPersistenceService handles persistence logic. Sessions saved to:
~/.llxprt/tmp/{project-hash}/chats/persisted-session-{timestamp}.json

Closes vybestack#147
@sogoiii sogoiii requested a review from acoliver as a code owner January 4, 2026 02:29
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Walkthrough

Adds a CLI --continue flag and end-to-end session persistence: new persistence service, core config support, CLI/UI wiring to load a persisted session into the UI and AI context, restore/validate histories, and save sessions atomically on user-turn completion.

Changes

Cohort / File(s) Summary
CLI Argument Parsing & Config
packages/cli/src/config/config.ts, packages/cli/src/config/config.loadMemory.test.ts
Added --continue (-C) flag; extended CliArgs with continue?: boolean; map parsed value into Config via continueSession.
CLI Runtime / Startup
packages/cli/src/gemini.tsx
Construct SessionPersistenceService when continuing; call loadMostRecent() and pass restoredSession into the AppWrapper/start UI.
UI Props & Container
packages/cli/src/ui/App.tsx, packages/cli/src/ui/AppContainer.tsx
Added optional restoredSession?: PersistedSession prop; AppContainer validates/converts persisted UI history, restores core history into AI context, and triggers persistence saves on user-turn completion.
Core Config API
packages/core/src/config/config.ts
Added continueSession?: boolean to ConfigParameters; store in Config and expose isContinueSession(): boolean.
Session Persistence Service & Tests
packages/core/src/storage/SessionPersistenceService.ts, packages/core/src/storage/SessionPersistenceService.test.ts
New SessionPersistenceService and types (PersistedSession, PersistedUIHistoryItem, PersistedToolCall); atomic save/load, project-hash & version validation, corruption backup, timestamp management; comprehensive tests for many edge cases.
Core Public Exports
packages/core/src/index.ts
Exported SessionPersistenceService, PersistedSession, PersistedUIHistoryItem, and PersistedToolCall.
Docs / Plan
project-plans/.../SESSION-CONTINUE-IMPROVEMENTS.md
New planning document describing validation, restoration strategies, testing, and migration options for --continue improvements.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Entry
    participant ArgParser as Arg Parser (yargs)
    participant SessionSvc as SessionPersistenceService
    participant Storage as Disk Storage
    participant UI as AppContainer / UI
    participant AI as AI Context / HistoryService

    CLI->>ArgParser: Parse args (--continue)
    ArgParser-->>CLI: CliArgs{ continue: true }

    CLI->>SessionSvc: new SessionPersistenceService(storage, sessionId)
    CLI->>SessionSvc: loadMostRecent()
    activate SessionSvc
    SessionSvc->>Storage: readdir & readFile latest
    Storage-->>SessionSvc: persisted JSON
    SessionSvc->>SessionSvc: validate projectHash & version
    SessionSvc-->>CLI: PersistedSession | null
    deactivate SessionSvc

    CLI->>UI: render AppContainer(restoredSession)
    UI->>UI: validate/convert uiHistory
    UI->>AI: restore core history into AI context
    AI-->>UI: history restored / warning
    UI->>UI: show restoration status

    alt On user-turn completion (streaming -> idle)
        UI->>SessionSvc: save(history, metadata, uiHistory)
        activate SessionSvc
        SessionSvc->>Storage: write .tmp -> rename (atomic)
        Storage-->>SessionSvc: persisted
        deactivate SessionSvc
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

maintainer:e2e:ok

Suggested reviewers

  • acoliver

Poem

🐰 I found your session, tucked in a file,
I nibbled the crumbs and hopped back a mile.
With --continue I stitch the threads tight,
Chats wake anew beneath the screenlight. ✨

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature added: a --continue flag for session resumption.
Description check ✅ Passed The description includes all template sections: TLDR with clear overview, Dive Deeper with technical details, Reviewer Test Plan with usage examples, and Testing Matrix placeholder. However, the Testing Matrix is incomplete with only question marks.
Linked Issues check ✅ Passed The pull request fully implements issue #147's requirement: adds --continue flag to load the last conversation and enables session resumption per project. All coding requirements from the issue are met.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the --continue session resumption feature. No out-of-scope changes detected; all modifications support the core feature.
✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 4, 2026

LLxprt PR Review – PR #999

Issue Alignment

  • Implements issue add --continue #147 for --continue flag to resume previous sessions
  • Evidence: Added SessionPersistenceService with atomic writes and project hash validation
  • Evidence: Added CLI argument parsing for continue flag with alias -C
  • Evidence: UI restoration in AppContainer converts both core history and UI display state

Side Effects

  • SessionPersistenceService recreation on each config change leads to orphaned session files (memory/resource leak)
  • Silent AI context failure when core history restore fails - UI appears functional but AI has no memory
  • Type safety regression: uiHistory uses unknown[] requiring unsafe casting
  • Added 2900+ lines of complex session handling without comprehensive integration tests

Code Quality

  • SessionPersistenceService uses fs.existsSync() in async context (blocks event loop)
  • UI history validation lacks runtime checks - unsafe casting can cause crashes
  • Error handling gaps: core history restore fails silently without user notification
  • Project isolation using SHA-256 hashing is robust
  • Atomic write pattern (temp file + rename) prevents corruption

Tests & Coverage

  • Coverage impact: unknown – no coverage summary provided, but added 530 lines of unit tests for SessionPersistenceService
  • Tests cover core persistence service but lack integration tests for end-to-end --continue flow
  • No test coverage for UI history restoration validation logic
  • No tests for critical error paths (silent AI context failure)
  • Absent tests for the main session restoration workflow in AppContainer

Verdict

Copy link
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

🧹 Nitpick comments (4)
packages/core/src/storage/SessionPersistenceService.ts (2)

94-105: Potential timestamp collision in high-frequency scenarios.

The timestamp-based filename uses millisecond precision but replaces colons and dots, which could theoretically cause collisions if two instances are created within the same millisecond. This is unlikely in practice but worth noting.

🔎 Consider adding session ID to filename for uniqueness
-    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
-    this.sessionFilePath = path.join(
-      this.chatsDir,
-      `${PERSISTED_SESSION_PREFIX}${timestamp}.json`,
-    );
+    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+    this.sessionFilePath = path.join(
+      this.chatsDir,
+      `${PERSISTED_SESSION_PREFIX}${timestamp}-${sessionId.slice(0, 8)}.json`,
+    );

266-285: Consider handling backup failure more gracefully.

The backup operation catches and logs errors but doesn't propagate them. While this is intentional (to avoid blocking on backup failures), a corrupted file that fails to backup will remain in place and be retried on next load, potentially causing repeated errors.

The current behavior is acceptable since the backup is best-effort, but you could consider deleting the corrupted file if backup fails to prevent repeated errors.

packages/core/src/storage/SessionPersistenceService.test.ts (1)

226-246: Same issue - tests mock existsSync but implementation uses readdir.

These tests also mock existsSync which isn't used by the implementation. However, they do properly mock readdir, so the tests should pass - the existsSync mock is just redundant.

Remove the redundant vi.mocked(fs.existsSync).mockReturnValue(true) lines since the implementation doesn't use existsSync.

packages/cli/src/ui/AppContainer.tsx (1)

665-705: Polling interval should be cleaned up on success.

The interval is cleared when historyService is found, which is correct. However, consider adding a timeout to avoid infinite polling if historyService never becomes available.

🔎 Consider adding a timeout for core history restoration
 useEffect(() => {
   if (!restoredSession || coreHistoryRestoredRef.current) {
     return;
   }

+  const timeout = setTimeout(() => {
+    clearInterval(checkInterval);
+    debug.warn('Timed out waiting for history service to restore core history');
+    addItem(
+      {
+        type: 'warning',
+        text: 'Could not restore AI context - history service unavailable.',
+      },
+      Date.now(),
+    );
+  }, 30000); // 30 second timeout

   const checkInterval = setInterval(() => {
     // ... existing code ...
   }, 100);

   return () => {
     clearInterval(checkInterval);
+    clearTimeout(timeout);
   };
 }, [restoredSession, config, addItem]);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 623910c and 8e207e3.

📒 Files selected for processing (9)
  • packages/cli/src/config/config.loadMemory.test.ts
  • packages/cli/src/config/config.ts
  • packages/cli/src/gemini.tsx
  • packages/cli/src/ui/App.tsx
  • packages/cli/src/ui/AppContainer.tsx
  • packages/core/src/config/config.ts
  • packages/core/src/index.ts
  • packages/core/src/storage/SessionPersistenceService.test.ts
  • packages/core/src/storage/SessionPersistenceService.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Don't use any - Always specify proper types. Use unknown if the type is truly unknown and add proper type guards.
Do not use console.log or console.debug - Use the sophisticated logging system instead. Log files are written to ~/.llxprt/debug/
Fix all linting errors, including warnings about any types

Files:

  • packages/core/src/config/config.ts
  • packages/core/src/storage/SessionPersistenceService.test.ts
  • packages/cli/src/config/config.ts
  • packages/cli/src/ui/App.tsx
  • packages/cli/src/config/config.loadMemory.test.ts
  • packages/core/src/index.ts
  • packages/cli/src/gemini.tsx
  • packages/core/src/storage/SessionPersistenceService.ts
  • packages/cli/src/ui/AppContainer.tsx
🧬 Code graph analysis (4)
packages/core/src/storage/SessionPersistenceService.test.ts (1)
packages/core/src/storage/SessionPersistenceService.ts (3)
  • SessionPersistenceService (88-286)
  • PersistedSession (56-77)
  • getProjectHash (247-250)
packages/cli/src/ui/App.tsx (2)
packages/core/src/index.ts (1)
  • PersistedSession (359-359)
packages/core/src/storage/SessionPersistenceService.ts (1)
  • PersistedSession (56-77)
packages/cli/src/gemini.tsx (1)
packages/core/src/storage/SessionPersistenceService.ts (2)
  • PersistedSession (56-77)
  • SessionPersistenceService (88-286)
packages/cli/src/ui/AppContainer.tsx (2)
packages/core/src/storage/SessionPersistenceService.ts (2)
  • PersistedSession (56-77)
  • SessionPersistenceService (88-286)
packages/cli/src/ui/types.ts (2)
  • HistoryItem (246-246)
  • IndividualToolCallDisplay (47-56)
🔇 Additional comments (31)
packages/cli/src/config/config.loadMemory.test.ts (1)

328-328: LGTM!

The test correctly includes the new continue field to match the updated CliArgs interface.

packages/core/src/config/config.ts (2)

362-362: LGTM!

The continueSession parameter is properly defined as optional in the interface and stored as a private readonly field in the Config class, following the established patterns in this codebase.

Also applies to: 523-523


661-661: LGTM!

The initialization properly defaults to false when not provided, and the isContinueSession() accessor provides appropriate encapsulation. The implementation follows existing patterns in the Config class.

Also applies to: 862-864

packages/cli/src/config/config.ts (4)

170-170: LGTM!

The continue field is properly added to the CliArgs interface with the correct type matching other optional boolean CLI flags.


358-364: LGTM!

The CLI option is well-defined with a clear description and appropriate defaults. The ability to combine --continue with --prompt is explicitly documented in the help text, enabling users to resume a session and immediately send a new message.


651-651: LGTM!

The argument mapping correctly extracts the continue flag from the yargs result and assigns it to the CliArgs object, following the established pattern for other CLI arguments.


1329-1329: LGTM!

The continueSession parameter is correctly passed to the Config constructor with a safe default value of false, properly integrating the CLI flag with the core configuration system.

packages/core/src/storage/SessionPersistenceService.ts (7)

1-14: LGTM - License, imports, and logging setup are correct.

The file properly uses DebugLogger per coding guidelines instead of console.log. Imports are appropriate for the functionality.


16-51: LGTM - Type definitions are well-structured.

The PersistedToolCall and PersistedUIHistoryItem interfaces are properly documented with JSDoc comments and use appropriate types.


53-77: LGTM - PersistedSession interface is well-designed.

The schema includes versioning for future migrations, project hash for validation, and optional metadata. The version: 1 literal type is a good practice for schema evolution.


124-162: LGTM - Atomic write pattern is correctly implemented.

The save method properly:

  1. Creates directory recursively
  2. Writes to a temp file first
  3. Renames atomically to the final path
  4. Logs operations via DebugLogger
  5. Re-throws errors after logging

167-234: LGTM - Load logic with proper validation and error handling.

The loadMostRecent method correctly:

  1. Handles missing directory (ENOENT)
  2. Filters and sorts session files by timestamp
  3. Validates project hash to prevent cross-project session loading
  4. Validates schema version
  5. Backs up corrupted files gracefully

239-242: Verify locale-dependent date formatting behavior.

toLocaleString() produces different output based on system locale. If this string is used for anything other than display (e.g., parsing), it could cause issues.

Ensure this formatted time is only used for user-facing display and not for any date comparisons or parsing operations.


247-250: LGTM - Project hash implementation is secure.

Using SHA-256 to hash the project root path is appropriate for validation purposes. The hash is deterministic and collision-resistant.

packages/cli/src/ui/App.tsx (2)

8-8: LGTM - Type import is correct.

Using type import for PersistedSession is appropriate since it's only used for type annotations.


26-32: LGTM - AppProps interface properly extended.

The restoredSession prop is correctly typed as optional and uses the exported PersistedSession type from core.

packages/core/src/index.ts (1)

357-362: LGTM - Public API exports are correctly structured.

The exports properly expose the SessionPersistenceService class and the three related types (PersistedSession, PersistedUIHistoryItem, PersistedToolCall) for external consumption.

packages/cli/src/gemini.tsx (3)

77-79: LGTM - Imports are correctly structured.

The import of SessionPersistenceService class and PersistedSession type follows the pattern used elsewhere in the file.


223-265: Console output uses chalk for user-facing messaging - acceptable.

The console.log calls here are intentional user-facing CLI output (startup messages) using chalk for formatting, not debug logging. This is appropriate for CLI UX and doesn't violate the logging guidelines which prohibit console.log for debug purposes.

The session restoration logic properly:

  1. Creates the persistence service with storage and session ID
  2. Awaits the async load operation
  3. Provides clear user feedback for all scenarios (session found with/without prompt, no session found)

306-306: LGTM - Correctly converts null to undefined for prop.

The restoredSession ?? undefined conversion properly handles the null return from loadMostRecent() to match the optional prop type in AppWrapper.

packages/core/src/storage/SessionPersistenceService.test.ts (4)

1-33: LGTM - Test setup is well-structured.

The mock setup properly overrides node:fs promises API while preserving the actual module structure. The import of real crypto allows testing actual hash behavior.


374-397: LGTM - Error handling tests are comprehensive.

Tests properly verify that readdir and readFile failures are handled gracefully by returning null.


451-491: Testing private method via type casting is acceptable for thorough coverage.

While testing private methods is generally discouraged, verifying the project hash consistency and format is important for security. The as unknown as { getProjectHash(): string } pattern is a reasonable pragmatic choice.


493-537: LGTM - Edge case tests are valuable.

Testing empty arrays, large histories, and special characters in paths helps ensure robustness.

packages/cli/src/ui/AppContainer.tsx (7)

22-23: LGTM - Imports are correctly structured.

The new imports for IndividualToolCallDisplay, SessionPersistenceService, PersistedSession, IContent, ToolCallBlock, and ToolResponseBlock are all properly imported and used in the file.

Also applies to: 68-73


154-178: LGTM - Valid history types set is properly defined at module level.

Defining VALID_HISTORY_TYPES at module level ensures a stable reference for use in validation callbacks, avoiding unnecessary re-creation.


384-485: Well-structured conversion with appropriate status mapping.

The convertToUIHistory function properly:

  1. Collects tool responses into a map for lookup
  2. Handles human, AI text, and tool call blocks
  3. Maps tool response status correctly (Success if response exists, Pending otherwise)

One consideration: tools without responses are marked as Pending, which is accurate for conversion but may look odd if the session was interrupted mid-tool-execution.


496-567: LGTM - Validation logic is robust with good fallback behavior.

The validation:

  1. Uses duck typing for flexibility with schema changes
  2. Warns about unknown types but allows them (forward compatibility)
  3. Validates type-specific fields appropriately
  4. Returns both valid items and invalid count for error reporting

569-663: LGTM - UI restoration with graceful degradation.

The restoration effect properly:

  1. Uses ref to prevent double-execution
  2. Validates UI history before loading
  3. Falls back to conversion if all items are invalid
  4. Reports restoration status and any warnings to user
  5. Handles errors gracefully with user notification

1812-1861: Session persistence implementation is well-designed.

The persistence logic:

  1. Uses stable dependencies (storage, sessionId) to avoid recreating the service
  2. Detects turn completion via streaming state transition
  3. Saves both core history and UI history for exact restoration
  4. Uses fire-and-forget with .catch() to avoid blocking UI

One consideration: the history variable passed to save() on line 1856 is the UI history from useHistory, which is correct for display restoration.


1825-1834: This suggestion is not applicable—StreamingState.Active does not exist.

The StreamingState enum contains only three values: Idle, Responding, and WaitingForConfirmation. The current check correctly covers all active states by including both Responding and WaitingForConfirmation, which are the only non-idle streaming states in the codebase.

Likely an incorrect or invalid review comment.

…n and error handling

Add comprehensive improvements to session persistence for --continue flag:

- Add type-safe session interfaces (PersistedUIHistoryItem, PersistedToolCall)
- Add 30 unit tests for SessionPersistenceService covering all code paths
- Add runtime validation for persisted UI history with graceful fallback
- Add user notifications when core history restoration fails
- Fix memory leak in useMemo by using stable dependencies
- Add clear messaging when combining --continue with --prompt flags
- Replace sync fs.existsSync with async readdir (eliminates race condition)
- Update --continue help text to document --prompt compatibility

Fixes issue vybestack#147 session resumption concerns.
@sogoiii sogoiii force-pushed the feature/issue-147-continue branch from 8e207e3 to f4e97ee Compare January 4, 2026 07:44
Copy link
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: 0

🧹 Nitpick comments (2)
packages/cli/src/ui/AppContainer.tsx (1)

384-485: Consider extracting history conversion to a separate utility module.

The convertToUIHistory function is well-implemented but quite large (100+ lines). Extracting it to a separate utility module would improve:

  • Testability (easier to unit test in isolation)
  • Maintainability (clearer separation of concerns)
  • Reusability (could be used elsewhere if needed)
💡 Suggested extraction approach

Create a new file packages/cli/src/ui/utils/historyConversion.ts:

export function convertToUIHistory(history: IContent[]): HistoryItem[] {
  // Move the entire function body here
}

Then import and use in AppContainer:

+import { convertToUIHistory } from './utils/historyConversion.js';
+
 export const AppContainer = (props: AppContainerProps) => {
-  const convertToUIHistory = useCallback(
-    (history: IContent[]): HistoryItem[] => {
-      // ... 100 lines
-    },
-    [],
-  );
+  // Use the imported utility directly
packages/core/src/storage/SessionPersistenceService.ts (1)

177-244: Consider runtime schema validation for loaded sessions.

The method properly uses async filesystem operations and validates project hash and version. However, the JSON.parse at line 208 trusts the structure beyond basic version checking.

Consider adding runtime type guards for critical fields:

💡 Suggested validation approach
function isValidPersistedSession(obj: unknown): obj is PersistedSession {
  if (typeof obj !== 'object' || obj === null) return false;
  const session = obj as Record<string, unknown>;
  
  return (
    typeof session.version === 'number' &&
    typeof session.sessionId === 'string' &&
    typeof session.projectHash === 'string' &&
    typeof session.createdAt === 'string' &&
    typeof session.updatedAt === 'string' &&
    Array.isArray(session.history)
  );
}

// In loadMostRecent:
const parsed = JSON.parse(content);
if (!isValidPersistedSession(parsed)) {
  logger.warn('Session file has invalid structure');
  await this.backupCorruptedSession();
  return null;
}
const session = parsed;

This would catch structural corruption earlier and provide clearer error messages.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e207e3 and f4e97ee.

📒 Files selected for processing (7)
  • packages/cli/src/config/config.ts
  • packages/cli/src/gemini.tsx
  • packages/cli/src/ui/AppContainer.tsx
  • packages/core/src/index.ts
  • packages/core/src/storage/SessionPersistenceService.test.ts
  • packages/core/src/storage/SessionPersistenceService.ts
  • project-plans/20260103-continue-improvements/SESSION-CONTINUE-IMPROVEMENTS.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/index.ts
  • packages/core/src/storage/SessionPersistenceService.test.ts
  • packages/cli/src/gemini.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Don't use any - Always specify proper types. Use unknown if the type is truly unknown and add proper type guards.
Do not use console.log or console.debug - Use the sophisticated logging system instead. Log files are written to ~/.llxprt/debug/
Fix all linting errors, including warnings about any types

Files:

  • packages/cli/src/config/config.ts
  • packages/core/src/storage/SessionPersistenceService.ts
  • packages/cli/src/ui/AppContainer.tsx
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Documentation-only changes (*.md files, docs/) do NOT require build/test/lint cycle

Files:

  • project-plans/20260103-continue-improvements/SESSION-CONTINUE-IMPROVEMENTS.md
🧠 Learnings (1)
📚 Learning: 2025-12-12T23:31:19.937Z
Learnt from: acoliver
Repo: vybestack/llxprt-code PR: 779
File: project-plans/20251212issue488/PLAN.md:1-7
Timestamp: 2025-12-12T23:31:19.937Z
Learning: Do not validate or review plan documents in the vybestack/llxprt-code repository after implementation. Specifically, skip validation for files under project-plans (e.g., project-plans/**/PLAN.md) once the feature is implemented; these documents are not checked in reviews post-implementation.

Applied to files:

  • project-plans/20260103-continue-improvements/SESSION-CONTINUE-IMPROVEMENTS.md
🧬 Code graph analysis (1)
packages/core/src/storage/SessionPersistenceService.ts (2)
packages/core/src/tools/tools.ts (2)
  • ToolResultDisplay (538-538)
  • ToolCallConfirmationDetails (600-604)
packages/core/src/services/history/IContent.ts (1)
  • IContent (21-40)
🪛 markdownlint-cli2 (0.18.1)
project-plans/20260103-continue-improvements/SESSION-CONTINUE-IMPROVEMENTS.md

76-76: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


89-89: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


101-101: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


190-190: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


251-251: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


276-276: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


879-879: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


894-894: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


925-925: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

🔇 Additional comments (14)
packages/cli/src/config/config.ts (4)

170-170: LGTM: CLI argument type definition follows established patterns.

The continue field type boolean | undefined is consistent with other optional CLI flags in the interface.


651-651: LGTM: Argument parsing follows standard pattern.

The mapping from result.continue to the CliArgs structure is consistent with other CLI arguments.


1329-1329: LGTM: Config constructor receives the continue flag with proper defaulting.

The nullish coalescing operator (??) correctly handles undefined values and provides a sensible default of false.


358-364: The description references --prompt but the actual flag is --message.

The --continue option on lines 358-364 claims it "Can be combined with --prompt," but there is no --prompt option in the CLI. The correct flag name is --message. This should be corrected in the description to maintain consistency with the actual CLI interface.

Likely an incorrect or invalid review comment.

packages/cli/src/ui/AppContainer.tsx (6)

22-22: LGTM: Session persistence types properly imported and integrated.

The new imports from @vybestack/llxprt-code-core provide the necessary types for session restoration. The optional restoredSession prop in AppContainerProps maintains backward compatibility.

Also applies to: 68-72, 140-140


154-178: LGTM: Module-level Set provides stable validation reference.

Defining VALID_HISTORY_TYPES at module level ensures:

  • Stable reference across component re-renders
  • Efficient O(1) type validation lookups
  • Comprehensive coverage of known history item types

496-567: LGTM: Comprehensive validation with forward compatibility.

The validation functions properly check required fields and handle type-specific requirements. The decision to allow unknown types (lines 509-511) is good for forward compatibility—older versions can still load sessions created by newer versions without failing on new item types.


570-663: LGTM: Robust UI history restoration with graceful fallbacks.

The restoration logic properly:

  • Validates UI history items before use
  • Falls back to core history conversion when needed
  • Provides clear user feedback about restoration status
  • Handles partial corruption without failing entirely

The multi-layered approach (validate → use valid items → or fallback → notify user) ensures a good user experience even with corrupted data.


665-705: LGTM: Critical user notification prevents confusion from partial restoration.

The core history restoration properly handles the async availability of historyService with polling. Most importantly, the error handling (lines 689-698) warns users when AI context restoration fails, preventing the confusing state where the UI shows previous messages but the AI has no memory of them.


1812-1861: LGTM: Session persistence uses stable dependencies and proper turn completion detection.

The implementation correctly addresses potential issues:

  1. Lines 1814-1819: Using [storage, sessionId] as dependencies instead of [config] prevents unnecessary recreation of SessionPersistenceService (avoids memory leak and file path churn).

  2. Lines 1825-1830: The turn completion detection properly tracks when streaming goes from Active/WaitingForConfirmation to Idle, ensuring persistence at the right moments.

  3. Lines 1856: Saving both core history and UI history enables exact restoration of both AI context and display state.

  4. Lines 1858-1860: Error handling logs failures but doesn't crash the app, maintaining stability.

packages/core/src/storage/SessionPersistenceService.ts (4)

24-87: LGTM: Well-designed type definitions with versioning and flexibility.

The persisted types strike a good balance:

  • PersistedToolCall (lines 24-41): Comprehensive tool display information
  • PersistedUIHistoryItem (lines 48-61): Permissive structure accommodates different UI item types
  • PersistedSession (lines 66-87): Includes version: 1 for future schema migrations

The optional uiHistory field addresses type safety concerns while maintaining backward compatibility.


104-115: LGTM: Constructor creates project-scoped session files with sortable timestamps.

The implementation correctly:

  • Isolates sessions per project using storage.getProjectTempDir()
  • Uses ISO timestamp with filesystem-safe characters (line 110)
  • Enables "most recent" lookup via alphabetical sorting of filenames

When combined with stable dependencies in AppContainer's useMemo, this creates one file per session.


134-172: LGTM: Atomic write pattern ensures data integrity.

The save method implements best practices:

  • Line 141: Creates directory recursively (idempotent)
  • Lines 154-161: Atomic write pattern (write to temp, then rename) prevents corruption from crashes or kills
  • Line 158: Pretty-printed JSON aids debugging
  • Lines 169-171: Proper error handling with re-throw

249-296: LGTM: Well-designed helper methods with proper error handling.

The helper methods provide good utility:

  • Line 249-252: formatSessionTime uses locale-aware formatting
  • Lines 257-260: SHA-256 hash provides reliable project identification
  • Lines 265-271: Lazy initialization with caching for createdAt maintains timestamp stability
  • Lines 276-295: Corruption backup preserves data for debugging without blocking functionality

…st mocks

- Add 30s timeout to core history restoration polling to prevent
  infinite waits when historyService unavailable
- Fix test mock for directory-not-exist case to properly use readdir
  ENOENT error instead of unused existsSync
- Remove redundant existsSync mocks from all loadMostRecent tests
Copy link
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: 0

Caution

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

⚠️ Outside diff range comments (4)
packages/cli/src/ui/AppContainer.tsx (4)

753-753: Replace console.error with debug logger.

Per coding guidelines, use the sophisticated logging system instead of console.error. A DebugLogger is already available (line 130).

🔎 Proposed fix
+const rawModeLogger = new DebugLogger('llxprt:ui:rawmode');
+
 const restoreTerminalStateAfterEditor = useCallback(() => {
   const editorState = externalEditorStateRef.current;
   // ... existing code ...
   
   if (editorState?.rawModeManaged && setRawMode) {
     try {
       setRawMode(true);
     } catch (error) {
-      console.error('Failed to re-enable raw mode:', error);
+      rawModeLogger.error('Failed to re-enable raw mode:', error);
     }
   }
   // ... rest of function
 }, [setRawMode, stdin]);

 const handleExternalEditorOpen = useCallback(() => {
   // ... existing code ...
   
   if (setRawMode) {
     try {
       setRawMode(false);
       externalEditorStateRef.current.rawModeManaged = true;
     } catch (error) {
-      console.error('Failed to disable raw mode:', error);
+      rawModeLogger.error('Failed to disable raw mode:', error);
     }
   }
   // ... rest of function
 }, [setRawMode, stdin, stdout]);

Also applies to: 817-817


1100-1100: Replace console.error with debug logger.

Per coding guidelines, use the sophisticated logging system instead of console.error.

🔎 Proposed fix
+const modelLogger = new DebugLogger('llxprt:ui:model');
+const memoryLogger = new DebugLogger('llxprt:ui:memory');
+
 const openProviderModelDialog = useCallback(async () => {
   try {
     const models = await runtime.listAvailableModels();
     setProviderModels(models);
   } catch (e) {
-    console.error('Failed to load models:', e);
+    modelLogger.error('Failed to load models:', e);
     setProviderModels([]);
   }
   await openProviderModelDialogRaw();
 }, [openProviderModelDialogRaw, runtime]);

 const performMemoryRefresh = useCallback(async () => {
   // ... existing code ...
   } catch (error) {
     const errorMessage = getErrorMessage(error);
     addItem(
       {
         type: MessageType.ERROR,
         text: `Error refreshing memory: ${errorMessage}`,
       },
       Date.now(),
     );
-    console.error('Error refreshing memory:', error);
+    memoryLogger.error('Error refreshing memory:', error);
   }
 }, [config, addItem, settings.merged]);

Also applies to: 1231-1231


1117-1119: Replace console.debug with debug logger.

Per coding guidelines, use the sophisticated logging system instead of console.debug.

🔎 Proposed fix
+const modelLogger = new DebugLogger('llxprt:ui:model');
+
 const checkModelChange = () => {
   const configModel = config.getModel();
   const providerModel = runtime.getActiveModelName();
   const effectiveModel =
     providerModel && providerModel.trim() !== ''
       ? providerModel
       : configModel;

   if (effectiveModel !== currentModel) {
-    console.debug(
-      `[Model Update] Updating footer from ${currentModel} to ${effectiveModel}`,
-    );
+    modelLogger.debug(
+      () => `Updating footer from ${currentModel} to ${effectiveModel}`,
+    );
     setCurrentModel(effectiveModel);
   }
 };

1219-1219: Replace guarded console.log with debug logger.

Even though these are guarded by debug flags, per coding guidelines use the sophisticated logging system. Debug loggers support conditional logging via their log/debug methods with function arguments.

🔎 Proposed fix
+const memoryLogger = new DebugLogger('llxprt:ui:memory');
+
 const performMemoryRefresh = useCallback(async () => {
   // ... existing code ...
   
   addItem(
     {
       type: MessageType.INFO,
       text: `Memory refreshed successfully. ${/* ... */}`,
     },
     Date.now(),
   );
-  if (config.getDebugMode()) {
-    console.log(
-      `[DEBUG] Refreshed memory content in config: ${memoryContent.substring(0, 200)}...`,
-    );
-  }
+  memoryLogger.debug(
+    () => `Refreshed memory content in config: ${memoryContent.substring(0, 200)}...`,
+  );
 }, [config, addItem, settings.merged]);

 const handleGlobalKeypress = useCallback(
   (key: Key) => {
-    // Debug log keystrokes if enabled
-    if (settings.merged.debugKeystrokeLogging) {
-      console.log('[DEBUG] Keystroke:', JSON.stringify(key));
-    }
+    // Debug log keystrokes (controlled by logger's enabled state)
+    const keystrokeLogger = new DebugLogger('llxprt:ui:keystrokes');
+    keystrokeLogger.debug(() => `Keystroke: ${JSON.stringify(key)}`);
     
     // ... rest of handler
   },
   // ... dependencies
 );

Note: DebugLogger's debug() method only logs when the logger is enabled, so the conditional check is unnecessary.

Also applies to: 1571-1571

♻️ Duplicate comments (1)
packages/core/src/storage/SessionPersistenceService.test.ts (1)

217-225: Past issue resolved: Test now properly simulates missing directory.

The test correctly mocks readdir to reject with an ENOENT error, matching the implementation's error handling path. This addresses the previous review concern about mocking existsSync when the implementation uses readdir.

🧹 Nitpick comments (1)
packages/core/src/storage/SessionPersistenceService.test.ts (1)

443-483: Consider alternatives for testing private methods.

The tests access private getProjectHash() using type assertions (as unknown as { getProjectHash(): string }). While functional, consider these alternatives for cleaner test code:

  1. Make getProjectHash() package-private (internal visibility) for testing
  2. Test hash consistency indirectly through saved session data
  3. Export a test-only utility that exposes internal methods
Alternative approach using saved session data

Instead of accessing the private method directly, you could verify hash consistency by examining the projectHash field in saved sessions:

 it('should generate consistent hash for same project', async () => {
+  vi.mocked(fs.promises.mkdir).mockResolvedValue(undefined);
+  vi.mocked(fs.promises.writeFile).mockResolvedValue();
+  vi.mocked(fs.promises.rename).mockResolvedValue();
+  
   const service1 = new SessionPersistenceService(storage, 'session1');
   const service2 = new SessionPersistenceService(storage, 'session2');

-  // Access private method via any
-  const hash1 = (
-    service1 as unknown as { getProjectHash(): string }
-  ).getProjectHash();
-  const hash2 = (
-    service2 as unknown as { getProjectHash(): string }
-  ).getProjectHash();
+  let hash1: string = '';
+  let hash2: string = '';
+  
+  vi.mocked(fs.promises.writeFile).mockImplementation(async (_path, content) => {
+    const parsed = JSON.parse(content as string) as PersistedSession;
+    if (!hash1) hash1 = parsed.projectHash;
+    else hash2 = parsed.projectHash;
+  });
+  
+  await service1.save([], undefined, []);
+  await service2.save([], undefined, []);

   expect(hash1).toBe(hash2);
 });
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4e97ee and ddf5dc0.

📒 Files selected for processing (2)
  • packages/cli/src/ui/AppContainer.tsx
  • packages/core/src/storage/SessionPersistenceService.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Don't use any - Always specify proper types. Use unknown if the type is truly unknown and add proper type guards.
Do not use console.log or console.debug - Use the sophisticated logging system instead. Log files are written to ~/.llxprt/debug/
Fix all linting errors, including warnings about any types

Files:

  • packages/core/src/storage/SessionPersistenceService.test.ts
  • packages/cli/src/ui/AppContainer.tsx
🔇 Additional comments (10)
packages/core/src/storage/SessionPersistenceService.test.ts (4)

50-73: LGTM: Constructor tests are well-structured.

The tests properly verify path generation and timestamp uniqueness with appropriate timing delays.


75-211: LGTM: Comprehensive coverage of save() functionality.

The tests validate:

  • Atomic write operations (temp file → rename)
  • Complete data serialization with proper field validation
  • Timestamp preservation/update behavior
  • Error handling for all filesystem operations

227-390: LGTM: Thorough testing of session loading and validation.

Excellent coverage including:

  • File filtering and chronological sorting
  • Project hash and version validation
  • Corrupted JSON handling with backup creation
  • Graceful error handling for filesystem failures

485-529: LGTM: Good edge case coverage.

The tests verify handling of boundary conditions including empty histories, large datasets (1000 items), and special characters in paths.

packages/cli/src/ui/AppContainer.tsx (6)

154-178: LGTM: Well-designed validation constant.

The module-level VALID_HISTORY_TYPES Set provides a stable reference for validation across component renders. The comprehensive list of types ensures robust validation during session restoration.


384-485: LGTM: Well-structured history conversion with proper type guards.

The convertToUIHistory function correctly:

  • Maps tool responses by callId for efficient lookup
  • Converts IContent blocks to HistoryItem format
  • Handles multiple result formats (string, object with output field, generic object)
  • Uses type assertions followed by runtime type checks for safety

The two-pass approach (building toolResponseMap first) is a good optimization.


496-567: LGTM: Robust validation with forward compatibility.

The validation utilities provide:

  • Duck-typed validation for flexibility across schema versions
  • Forward compatibility by allowing unknown types with warnings (line 509-512)
  • Type-specific validation for critical fields (tool_group requires tools array, text types allow optional text)
  • Graceful degradation by filtering invalid items while preserving valid ones

569-663: LGTM: Well-designed UI history restoration with fallback.

The restoration logic provides:

  • Duplicate prevention via sessionRestoredRef
  • Validation of persisted UI history before use
  • Graceful fallback to core history conversion when UI history is invalid
  • Clear user feedback about restoration status and any data loss
  • Comprehensive error handling with user-facing warnings

The two-path approach (saved UI history vs. converted) ensures the best possible restoration experience.


665-723: LGTM: Robust core history restoration with timeout protection.

The core history restoration provides:

  • Polling for history service availability (appropriate for async initialization)
  • 30-second timeout to prevent infinite waits (as noted in commit messages)
  • Clear separation from UI restoration (allows UI to show while waiting for AI context)
  • Critical user warning when AI context cannot be restored (lines 709-715)

The timeout implementation addresses the concern mentioned in the commit messages about avoiding infinite waits when the history service is unavailable.


1830-1879: LGTM: Well-implemented automatic session persistence.

The persistence logic correctly:

  • Creates service with stable dependencies to avoid path changes (lines 1834-1837)
  • Detects turn completion by tracking streaming state transitions (lines 1844-1852)
  • Saves both core history and UI history for complete restoration (line 1874)
  • Includes session metadata (provider, model, token count) for diagnostics
  • Handles errors gracefully with debug logging (appropriate for automatic background operation)

The trigger condition (was active, now idle) ensures sessions are saved after each complete turn.

@acoliver acoliver merged commit f9d76e1 into vybestack:main Jan 4, 2026
19 checks passed
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.

add --continue

2 participants