Skip to content

fix(core): preserve REPL bindings across sessions#230

Merged
ian-pascoe merged 2 commits into
mainfrom
fix/code-mode-repl-session-bindings
Jul 19, 2026
Merged

fix(core): preserve REPL bindings across sessions#230
ian-pascoe merged 2 commits into
mainfrom
fix/code-mode-repl-session-bindings

Conversation

@ian-pascoe

@ian-pascoe ian-pascoe commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • persist top-level let, const, class, enum, namespace, and TypeScript declarations across reused Code Mode sessions
  • preserve live closures, enforce lexical redeclaration rules, and retain completed state after ordinary runtime errors
  • keep diagnostics synchronized with runtime session state, including Caplet handle types and type/interface declarations
  • isolate Host Node storage in concurrent tests and packaged-runtime smoke checks

Verification

  • pnpm verify
  • 2,427 tests passed; 15 skipped
  • local end-to-end Code Mode session smoke covered const handles, closures, classes, failed-cell state, duplicate declarations, and type reuse

Release

  • patch changeset for @caplets/core

Summary by CodeRabbit

  • New Features

    • Improved Code Mode session reuse, preserving top-level JavaScript and TypeScript declarations across compatible sessions.
    • Preserved completed assignments and object mutations when a cell encounters an ordinary runtime error.
    • Added clearer handling for redeclaration conflicts, failed declarations, and unavailable sessions.
  • Bug Fixes

    • Prevented failed or conflicting declarations from corrupting reusable session state.
    • Improved session persistence and isolation across subsequent code executions.
  • Documentation

    • Updated Code Mode guidance to explain session reuse and preserved state.

Persist lexical and TypeScript declarations across Code Mode cells, retain completed failed-cell state, and isolate Host storage in concurrent verification fixtures.
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ian-pascoe, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 32da101d-3a60-4aa8-bc9a-a4c88be5509f

📥 Commits

Reviewing files that changed from the base of the PR and between bfad541 and 1a14835.

📒 Files selected for processing (5)
  • packages/core/src/code-mode/diagnostics.ts
  • packages/core/src/code-mode/runner.ts
  • packages/core/src/code-mode/sandbox.ts
  • packages/core/src/code-mode/sessions.ts
  • packages/core/test/code-mode-sessions.test.ts
📝 Walkthrough

Walkthrough

Code Mode reusable sessions now preserve broader JavaScript and TypeScript declarations, retain completed mutations after ordinary runtime errors, detect cross-cell declaration conflicts, and dispose sessions when persistence integrity fails. Session diagnostics, guidance, tests, and test storage configuration were updated accordingly.

Changes

Code Mode reusable sessions

Layer / File(s) Summary
Binding-based session persistence
packages/core/src/code-mode/sandbox.ts
Persistent bindings now use scoped session APIs, AST-derived declaration splitting, initialization helpers, and failed-declaration settlement.
Executed-cell lifecycle and disposal
packages/core/src/code-mode/sandbox.ts, packages/core/src/code-mode/sessions.ts, packages/core/src/code-mode/runner.ts
Executed cells are recorded even after ordinary failures, while timeout, taint, descriptor, and persistence checks determine whether a session remains usable.
Cross-cell declaration diagnostics
packages/core/src/code-mode/diagnostics.ts
Diagnostics track value declarations, type declarations, and runtime binding kinds to report redeclaration and TypeScript type conflicts.
Persistence and diagnostics validation
packages/core/test/code-mode-session.test.ts, packages/core/test/code-mode-sessions.test.ts
Tests cover declaration reuse, mutations after failures, closures, TypeScript declarations, Promise hardening, descriptor poisoning, redeclarations, and disposal.
Guidance and test harness updates
.changeset/clever-repls-share.md, packages/core/src/code-mode/declarations.ts, packages/core/src/native/tools.ts, packages/core/test/*, scripts/check-package-runtime.mjs
REPL reuse guidance and release metadata describe the new behavior, while test imports and SQLite configurations are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant runCodeMode
  participant CodeModeSessionManager
  participant QuickJsCodeModeReplSession
  participant CodeModeDiagnosticsSession
  Client->>runCodeMode: submit code with sessionId
  runCodeMode->>CodeModeSessionManager: run cell
  CodeModeSessionManager->>QuickJsCodeModeReplSession: execute scoped cell
  QuickJsCodeModeReplSession-->>CodeModeSessionManager: result and session status
  CodeModeSessionManager->>CodeModeDiagnosticsSession: record executed cell
  CodeModeSessionManager-->>Client: result and session metadata
Loading

Possibly related PRs

  • spiritledsoftware/caplets#129: Earlier reusable-session and recovery-journal work that connects to the session persistence and diagnostics changes here.

Poem

I’m a rabbit with bindings tucked tight,
Through failed little cells I still hop right.
Let and const stand, types bloom anew,
Conflicts cry “no!” before code can chew.
Sessions persist with a wiggle and cheer—
Unless poisoned state makes them disappear.

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: preserving REPL bindings across reused sessions.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/code-mode-repl-session-bindings

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/code-mode/diagnostics.ts (1)

419-447: 🚀 Performance & Scalability | 🔵 Trivial

Type-conflict diagnostics recompile the full accumulated declaration set on every diagnose call.

typeDeclarationDiagnostics (via declarationDiagnostics) creates a fresh ts.createProgram over [...previousDeclarations, ...currentDeclarations] on each cell, and this set grows monotonically with the session's lifetime. Combined with the main-program compilation in diagnoseCodeModeTypeScript and the per-cell program in recordCell, long-lived REPL sessions will see diagnose latency scale with the number of accumulated type declarations. If sessions are expected to be long, consider bounding retained declarations or caching the compiled declaration program across cells and invalidating only when #typeDeclarations changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/code-mode/diagnostics.ts` around lines 419 - 447, Update
typeDeclarationDiagnostics and its declarationDiagnostics caller to avoid
rebuilding the full accumulated declaration set on every diagnose call. Cache
the compiled declaration program or diagnostics across cells, invalidating the
cache only when `#typeDeclarations` changes; alternatively bound retained
declarations if that is the established session policy. Preserve conflict
deduplication and current diagnostic results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/code-mode/sandbox.ts`:
- Around line 1292-1301: Update the runner’s diagnosticsSession.recordCell
handling to use the settled binding state after
__caplets_settle_failed_declarations runs. Ensure failed const/class
declarations are recorded as let in later-cell diagnostics, while preserving the
original declaration kind for bindings that successfully initialize.

---

Nitpick comments:
In `@packages/core/src/code-mode/diagnostics.ts`:
- Around line 419-447: Update typeDeclarationDiagnostics and its
declarationDiagnostics caller to avoid rebuilding the full accumulated
declaration set on every diagnose call. Cache the compiled declaration program
or diagnostics across cells, invalidating the cache only when `#typeDeclarations`
changes; alternatively bound retained declarations if that is the established
session policy. Preserve conflict deduplication and current diagnostic results.
🪄 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 Plus

Run ID: 4db20b12-7c71-47c6-b0ba-f2069cae8114

📥 Commits

Reviewing files that changed from the base of the PR and between d241ab4 and bfad541.

📒 Files selected for processing (14)
  • .changeset/clever-repls-share.md
  • packages/core/src/code-mode/declarations.ts
  • packages/core/src/code-mode/diagnostics.ts
  • packages/core/src/code-mode/runner.ts
  • packages/core/src/code-mode/sandbox.ts
  • packages/core/src/code-mode/sessions.ts
  • packages/core/src/native/tools.ts
  • packages/core/test/attach-service-wiring.test.ts
  • packages/core/test/code-mode-cli.test.ts
  • packages/core/test/code-mode-session.test.ts
  • packages/core/test/code-mode-sessions.test.ts
  • packages/core/test/native.test.ts
  • packages/core/test/remote-control-dispatch.test.ts
  • scripts/check-package-runtime.mjs

Comment thread packages/core/src/code-mode/sandbox.ts
@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends Code Mode REPL session reuse from var/function declarations to all binding forms — let, const, class, enum, namespace, and TypeScript type/interface declarations. It also relaxes the prior policy of disposing sessions on runtime errors, instead retaining completed assignments and object mutations while settling failed uninitialized declarations (mirroring Node.js REPL semantics).

  • Runtime: Replaces the previous snapshot-and-restore approach with a predeclare + scopeProxy + with-statement system. New helpers (__caplets_predeclare, __caplets_initialize_binding, __caplets_settle_failed_declarations) enforce lexical redeclaration rules inside QuickJS and persist bindings across cells via non-configurable accessor properties on globalThis.
  • Diagnostics: CodeModeDiagnosticsSession now tracks #valueDeclarations, #typeDeclarations, and #runtimeKinds separately; pre-flight declarationDiagnostics blocks conflicting redeclarations before execution, and recordCell updates all three maps for both successful and failed cells using the settledBindingNames list from the sandbox.
  • Test isolation: All test fixtures gain a storage: { type: \"sqlite\", path: ... } config field to prevent concurrent test interference with the Host Node store.

Confidence Score: 5/5

Safe to merge. The new session-persistence logic is well-structured, thoroughly tested with 2,427 passing tests, and no correctness defects were found.

The predeclare/scopeProxy/with-statement architecture is coherent and internally consistent: the diagnostic runtimeKinds map stays synchronized with the runtime persistBacking through every execution path (success, ordinary error, timeout, corruption). Destructuring rewriting in initializationAssignmentTarget correctly uses the string-returning bindingNames helper from sandbox.ts. The only findings are non-blocking performance and growth notes in the type-declaration tracking path.

packages/core/src/code-mode/diagnostics.ts — two minor performance/growth concerns: projectedTypeDeclarations is invoked twice per cell for type-declaration cells, and interface merge declarations accumulate as a concatenated string in #typeDeclarations across cells. Neither affects correctness in typical sessions.

Important Files Changed

Filename Overview
packages/core/src/code-mode/sandbox.ts Major rewrite replacing snapshot-and-restore with predeclare + scopeProxy + with-statement; adds settleFailedDeclarations for REPL-style error recovery; narrows session-disposal conditions significantly. Well-exercised by the expanded test suite.
packages/core/src/code-mode/diagnostics.ts Tracks valueDeclarations, typeDeclarations, and runtimeKinds separately; adds pre-flight declarationDiagnostics and recordCell for both ok and failed cells. Two minor P2 concerns: projectedTypeDeclarations double-invocation and unbounded interface-merge string growth.
packages/core/src/code-mode/sessions.ts onSuccessfulCell renamed to onExecutedCell; recordExecutedCell now passes settledBindingNames to the diagnostics session. Clean, minimal change.
packages/core/test/code-mode-session.test.ts Large expansion of sandbox-level tests covering const/let/class/enum/namespace persistence, destructured bindings, settled failed declarations, and reversed disposal assertions.
packages/core/test/code-mode-sessions.test.ts New integration tests for the full diagnostics+runtime path with sqlite storage isolation per session.
scripts/check-package-runtime.mjs Adds sqlite storage config to the packaged-runtime smoke check for Host Node storage isolation.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Caller
    participant Runner
    participant DiagSession as CodeModeDiagnosticsSession
    participant SessionMgr as CodeModeSessionManager
    participant QuickJS as QuickJS Sandbox

    Caller->>Runner: runCodeMode(code, sessionId)
    Runner->>DiagSession: declarationDiagnostics(code)
    Note over DiagSession: Check runtimeKinds for block-scoped redeclarations
    DiagSession-->>Runner: diagnostics list

    alt Diagnostics block execution
        Runner-->>Caller: "ok=false, diagnostic_blocked"
    else Diagnostics pass
        Runner->>SessionMgr: session.run(code)
        SessionMgr->>QuickJS: predeclare(declarations)
        Note over QuickJS: Checks conflicts, adds to persistBacking, exposes globalThis accessors
        QuickJS->>QuickJS: with(scopeProxy) async IIFE with initializeBinding calls
        alt Cell succeeds or ordinary runtime error
            QuickJS->>QuickJS: settleFailedDeclarations
            QuickJS-->>SessionMgr: result with settledBindingNames
            SessionMgr->>DiagSession: recordCell(code, declaration, settledBindingNames)
            SessionMgr-->>Caller: ok, meta.sessionId preserved
        else Timeout or global corruption
            QuickJS-->>SessionMgr: session disposed
            SessionMgr-->>Caller: "ok=false"
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Caller
    participant Runner
    participant DiagSession as CodeModeDiagnosticsSession
    participant SessionMgr as CodeModeSessionManager
    participant QuickJS as QuickJS Sandbox

    Caller->>Runner: runCodeMode(code, sessionId)
    Runner->>DiagSession: declarationDiagnostics(code)
    Note over DiagSession: Check runtimeKinds for block-scoped redeclarations
    DiagSession-->>Runner: diagnostics list

    alt Diagnostics block execution
        Runner-->>Caller: "ok=false, diagnostic_blocked"
    else Diagnostics pass
        Runner->>SessionMgr: session.run(code)
        SessionMgr->>QuickJS: predeclare(declarations)
        Note over QuickJS: Checks conflicts, adds to persistBacking, exposes globalThis accessors
        QuickJS->>QuickJS: with(scopeProxy) async IIFE with initializeBinding calls
        alt Cell succeeds or ordinary runtime error
            QuickJS->>QuickJS: settleFailedDeclarations
            QuickJS-->>SessionMgr: result with settledBindingNames
            SessionMgr->>DiagSession: recordCell(code, declaration, settledBindingNames)
            SessionMgr-->>Caller: ok, meta.sessionId preserved
        else Timeout or global corruption
            QuickJS-->>SessionMgr: session disposed
            SessionMgr-->>Caller: "ok=false"
        end
    end
Loading

Reviews (2): Last reviewed commit: "fix(core): align failed bindings with di..." | Re-trigger Greptile

Comment thread packages/core/src/code-mode/diagnostics.ts
Comment thread packages/core/src/code-mode/diagnostics.ts Outdated
@ian-pascoe

Copy link
Copy Markdown
Contributor Author

Review follow-up on the declaration-diagnostics performance nit: no cache change is needed. typeDeclarationDiagnostics returns before creating a TypeScript program whenever the current cell adds no type declarations. When the current cell does add one, the candidate declaration set has necessarily changed and must be rechecked against accumulated declarations, so a cache invalidated on #typeDeclarations changes would miss every relevant call. Bounding declarations would change the REPL contract. Kept the existing fast path and verified the full gate.

@ian-pascoe
ian-pascoe merged commit 10bb0c2 into main Jul 19, 2026
8 checks passed
@ian-pascoe
ian-pascoe deleted the fix/code-mode-repl-session-bindings branch July 19, 2026 14:16
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