Skip to content

fix(pi-plugin): support pi-web multi-session and RPC hosts - #350

Merged
ualtinok merged 8 commits into
cortexkit:masterfrom
elrond298:fix/pi-web-compat
Aug 29, 2026
Merged

fix(pi-plugin): support pi-web multi-session and RPC hosts#350
ualtinok merged 8 commits into
cortexkit:masterfrom
elrond298:fix/pi-web-compat

Conversation

@elrond298

@elrond298 elrond298 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adapts the Pi plugin for pi-web, where multiple Pi sessions share one persistent Node.js process and commands run through Pi’s RPC mode.

It addresses shared-process session isolation, duplicate startup work, unsafe subagent CLI detection, RPC command feedback, and /ctx-dream failures before the first model turn.

Changes

Session isolation

  • Replace the process-global child-session flag with an AsyncLocalStorage<boolean> marker.
  • Update the marker through the public subagents:child:session-created and subagents:child:disposed events.
  • Suppress initialization only for in-process child subagents.
  • Allow independent sessions in the same pi-web process to initialize normally.
  • Remove child lifecycle listeners during session_shutdown to prevent stale handlers after reloads.

Process-wide startup maintenance

  • Claim deferred startup maintenance once per process.
  • Schedule the legacy-memory identity rekey introduced by database migration v22 only once per process.
  • Schedule the session-to-project mapping backfill only once per process.
  • Load session history only after acquiring the durable backfill lease, avoiding redundant JSONL scans.

Safer subagent CLI detection

  • Reuse process.argv[1] only when it identifies a supported Pi CLI; otherwise use the packaged executable, bundled CLI, or PATH fallback as appropriate.
  • This prevents embedded hosts such as Next.js from accidentally launching another web server when creating a subagent.

RPC command presentation

  • Preserve model-invisible command entries while presenting them through the RPC UI.
  • Show short progress updates through ctx.ui.notify.
  • Show formatted results in modal dialogs through ctx.ui.custom.
  • Apply this behavior to /ctx-status, /ctx-embed, /ctx-recomp, /ctx-session-upgrade, and /ctx-dream.

Dreamer registration

  • Track Dreamer registration ownership per Pi extension instance.
  • Prevent one session’s shutdown from deregistering a same-project sibling.
  • Transfer the active registration to a remaining worktree owner when necessary.
  • Synchronize the current project’s Dreamer registration immediately before a manual /ctx-dream run.
  • This allows /ctx-dream to work before the first before_agent_start event.

Verification

  • bun run --cwd packages/pi-plugin build
  • bun run --cwd packages/pi-plugin test804 passed, 0 failed
  • bun test packages/plugin/src/features/magic-context/session-project-backfill.test.ts9 passed, 0 failed
  • git diff --check

Regression coverage includes:

  • independent same-process sessions versus child-session suppression
  • lifecycle listener cleanup during shutdown
  • process-wide startup maintenance claimed through full runtime initialization
  • lazy backfill lease gating without session-history reads
  • owner-aware Dreamer registration, shutdown isolation, and ownership transfer
  • single-timer Dreamer handoff when remaining owners repeat a worktree directory
  • embedded-host Pi CLI discrimination
  • RPC notification and modal-dialog routing
  • pre-execution /ctx-dream registration synchronization

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Supports packages/pi-plugin in pi-web multi-session and RPC hosts. Old behavior suppressed all same-process inits and drained all jobs on shutdown; new behavior suppresses only in-process child subagents, drains only the shutting-down session's historian/recomp and the active owner's Dreamer jobs (cancels after ~5s), routes command output through the live RPC UI, and isolates the shared Dreamer lifecycle across plugin instances and worktrees.

New Features

  • Scopes child-session detection to lifecycle AsyncLocalStorage, unregisters lifecycle listeners on session_shutdown, and keeps agent_end synchronous.
  • Captures a session snapshot at command start, threads cancellation through recomp and memory migration, blocks late UI writes, and aborts long drains safely.
  • Shows short progress via notifications and detailed results in dialogs, with a safe fallback to notifications; applied to /ctx-status, /ctx-embed, /ctx-recomp, /ctx-session-upgrade, /ctx-wrapup, /ctx-flush, and /ctx-dream.
  • Claims process-wide startup tasks once and defers session-history reads until the backfill lease is acquired.
  • Shares Dreamer registration per project with an active owner requirement for manual runs, transfers ownership on exit, rejects stale/ownerless requests, prevents stale runs across worktrees, and stops shared timers when idle.
  • Tracks and cancels in-flight recomp/upgrade runs per session; waits only the matching session during shutdown and aborts remaining work after a short grace period.
  • Resolves the subagent CLI safely in embedded hosts by reusing process.argv[1] only when the host is the Pi CLI, preferring packaged/bundled binaries otherwise, and never spawning with a shell.

Written for commit fd89658. Summary will update on new commits.

Review in cubic

Greptile Summary

The PR adapts the Pi integration for multiple sessions sharing one RPC host process.

  • Isolates in-process child initialization while allowing independent sessions to initialize.
  • Coordinates process-wide startup maintenance and session-scoped background-job shutdown.
  • Adds owner-aware Dreamer registration, manual execution, and worktree handoff.
  • Routes command status through live RPC notifications and dialogs.
  • Safely resolves the Pi CLI when running inside embedded hosts.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/pi-plugin/src/index.ts Introduces child-context isolation, process-wide startup claims, per-session lifecycle cleanup, and owner-aware Dreamer wiring.
packages/pi-plugin/src/dreamer/index.ts Adds process-shared project registrations with explicit owners, safe timer replacement, owner-bound manual runs, and scoped in-flight drainage.
packages/plugin/src/plugin/dream-timer.ts Makes shared timer registration cleanup identity-aware so stale cleanup cannot remove a replacement registration.
packages/pi-plugin/src/pi-recomp-runner.ts Tracks detached recomp and upgrade work per session with cancellation and guarded cleanup.
packages/pi-plugin/src/commands/pi-command-utils.ts Preserves model-invisible command entries while presenting status through live RPC notifications and modal dialogs.
packages/pi-plugin/src/subagent-runner.ts Hardens executable resolution so embedded hosts do not accidentally relaunch their own server entrypoint.
packages/pi-plugin/src/commands/ctx-recomp.ts Captures session state before detached execution, propagates cancellation, and defers marker and context refresh effects safely.
packages/pi-plugin/src/commands/ctx-session-upgrade.ts Applies the same snapshot and cancellation lifecycle to recompilation and memory migration during session upgrades.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  P[Persistent Pi RPC host] --> S1[Session A extension]
  P --> S2[Session B extension]
  P --> C[In-process child session]
  C --> G[AsyncLocalStorage child guard]
  G -->|Child context| N[Suppress full initialization]
  S1 --> M[Shared process maintenance]
  S2 --> M
  M --> O[Run startup work once]
  S1 --> D[Shared Dreamer project registration]
  S2 --> D
  D --> T[Active owner timer]
  S1 --> J1[Session-scoped detached jobs]
  S2 --> J2[Session-scoped detached jobs]
  J1 --> U1[Session A RPC UI]
  J2 --> U2[Session B RPC UI]
Loading

Reviews (10): Last reviewed commit: "Merge branch 'master' into fix/pi-web-co..." | Re-trigger Greptile

Context used (5)

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/pi-plugin/src/dreamer/index.ts Outdated
ualtinok added a commit that referenced this pull request Aug 21, 2026
…amer registry, session-scoped drains, presenter ctx capture; NOT a security blocker — rpc-server untouched)

Co-Authored-By: Alfonso <alfonso@cortexkit.io>

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/pi-plugin/src/dreamer/index.ts

@alfonso-magic-context alfonso-magic-context left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this — especially as a first contribution. The diagnosis is right: the #247 process-global latch is what makes the second pi-web session skip Magic Context entirely, and routing child suppression through subagents:child:session-created/disposed plus AsyncLocalStorage is the correct seam. Once-per-process startup maintenance, not reusing a non-Pi argv[1], Dreamer sibling ownership, and keeping ctx-status entries model-invisible while presenting them in RPC are all the right instincts. And noted that you already pushed the Dreamer owner-handoff stabilization mid-review — that resolves one of the items we had flagged, and that kind of responsiveness makes this easy to shepherd.

Two clarifications so we don't talk past each other:

  1. "RPC hosts" here is Pi RPC mode (ctx.ui.notify/ctx.ui.custom). It does not change Magic Context's RPC server, which must stay on 127.0.0.1 with a bearer token. We checked; this PR does not touch that.
  2. The old "second init in the process is a no-op" test should change — that contract is the bug for pi-web. Please keep the child-only skip test (you did).

Before we can merge:

  • Dreamer registeredProjects on globalThis (same jiti moduleCache:false reason as the child marker), so two sessions in one repo don't start two timers.
  • session_shutdown draining only that session's in-flight work — in pi-web, shutdown is not process exit.
  • RPC presentation using the command's live ctx, not a session_start closure.
  • The #177 "never spawn bare pi" test kept alongside the new embedded-host test.
  • packages/pi-plugin/PARITY.md updated for RPC dialogs, the multi-session process model, and the latch → ALS change.

We've approved CI for this PR so your next push gets the full check suite. Really solid work — happy to re-review quickly.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 21 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/pi-plugin/src/dreamer/index.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/pi-plugin/src/dreamer/index.ts Outdated
@elrond298

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest master (52e650c6) and force-pushed the updated branch. No functional changes were made as part of the rebase.

LZHcode1986 pushed a commit to LZHcode1986/magic-context-pi-web that referenced this pull request Aug 21, 2026
Self-review against the upstream six-axis standard (calibrated on the
PR cortexkit#350 review) found and fixes:

- dreamer project registry moves to a globalThis Symbol.for holder so
  jiti moduleCache:false re-imports share one timer per project
  (duplicate-timer class, cortexkit#350-review must-fix 1); runtimeKeys
  ref-counting now actually spans module instances (+ test)
- session_shutdown drains historian/recomp scoped to the shutting-down
  sessionId (awaitInFlightHistoriansFor/awaitInFlightRecompsFor),
  keeping process-wide variants as fallback; comments no longer assume
  shutdown == process exit; dream drain stays process-wide (project-
  scoped work, bounded wait)
- PARITY.md §7a documents the embedded host model, adapter trust tier,
  init-gating split, and the known in-process-child limitation (F1);
  §13 points at scoped drains

Known limitations documented rather than fixed: embedded-mode third-
party in-process children still fully initialize (needs lifecycle-event
marking, coordinate with upstream PR cortexkit#350). Test-depth gaps G1/G2
recorded in the self-review report (.cortexkit/alfonso/task-outputs/,
untracked).

Verification: pi-plugin typecheck clean, 809/809 tests passing.

@alfonso-magic-context alfonso-magic-context left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the rework — the quality jump since the last round is real, and most of it verified clean under a full re-review (all five prior items confirmed addressed at source; ALS suppression held up under a four-concurrent-child probe with an async hop; both suites green on the PR merged onto current master; no public adapter surface rides in, which matters because the API question is deliberately deferred to #353).

Four items block the merge, two of them reproduced by probe rather than read off the diff:

  1. Stale sibling-worktree dreamer timer. registerPiDreamerProject keeps the old owner in the shared owners map when a different owner re-registers the same project identity from another directory (src/dreamer/index.ts:157-185), and the retired timer's predicate checks only that old owner's entry and directory (:190-194) — not the current registration generation/activeOwner. A probe with owner A/worktree A then owner B/worktree B showed the old A client still prompts. The committed test (dreamer/index.test.ts:428-470) covers one owner changing directories and misses this. Bind scheduled clients to the current registration generation (or activeOwner+projectDir) and add the sibling-owner regression.

  2. Shutdown timeout abandons rather than cancels. src/index.ts:2337-2373 stops waiting after 5s while timeout.ts:1-14 leaves the underlying promise alive; detached recomp/upgrade retains ctx (commands/ctx-recomp.ts:177-278) and pi-recomp-runner.ts:69-84 calls onStatusChange from finally without a stale-context guard. Pi 0.83 invalidates command contexts after disposal, so long recomp work can outlive shutdown and throw on dead ctx/UI. Capture immutable session data up front, fence or cancel on shutdown, and guard post-await presentation.

  3. Native Pi 0.83 RPC dialogs are silently vacuous. pi-command-utils.ts:180-190 assumes ctx.ui.custom renders or rejects, but Pi 0.83 rpc-mode implements it as Promise.resolve(undefined) — the catch fallback never runs, so native/external RPC hosts lose the detailed results entirely. It works in your host because pi-web supplies its own custom UI. Add a capability check with a portable notification fallback (and a test against the real 0.83 shape, not a functional fake), or scope the PARITY.md claim explicitly to hosts that provide ui.custom.

  4. Missing #247 storm regression. The process-global latch you removed originally existed for the four-child in-process init storm. Our probe of your ALS scoping passed it — the implementation looks right — but no committed test reproduces the storm. Please add the parallel multi-child regression asserting every child registers no tools/events/background scans.

Nothing else stands between this and merge — the internals-first direction is settled on our side, and #353 tracks the public API question separately.

@elrond298

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. I addressed the four remaining items, rebased the branch onto master at 7d6fda35, and force-pushed the updated commits.

  1. Dreamer timers and results from an old registration

    Each Dreamer registration now has a version number. A scheduled Dreamer job checks that version before creating a session, sending a prompt, reading messages, and accepting the final result. If another worktree or a reloaded plugin has replaced the registration, the old job stops and its result is ignored.

    I also covered the asynchronous A → B → A case. If the first A timer finishes starting after the final A timer has already been registered, cleanup from the first timer cannot remove the final timer entry stored for that project directory.

  2. Recompilation and upgrade tasks after session shutdown

    Recompilation and session upgrade now copy the required session, directory, branch, and model data when the command starts. Their background tasks no longer read from the command context after it may have become invalid.

    During session_shutdown, the plugin waits up to five seconds for that session’s background tasks to finish. Tasks that are still running then receive a cancellation signal. After cancellation or shutdown, their results cannot update compaction markers, migration state, memories, or the session’s UI.

    agent_end still returns immediately. Waiting and cancellation happen only during session_shutdown.

  3. Dialog fallback in native Pi 0.83

    Native Pi 0.83 can resolve ui.custom() with undefined without displaying anything or throwing an error. The plugin now records whether the rendering callback passed to ui.custom() was actually called.

    pi-web continues to display the custom dialog. If native Pi does not call the rendering callback, the command displays the same information as a notification instead.

    All commands that display status also receive the session shutdown signal. If ui.custom() resolves or rejects after shutdown, the command does not use the old UI context to display a notification.

  4. Four child sessions initializing concurrently

    I added a behavioral test that starts four child sessions at the same time and closes them in different orders, including while other child sessions are still initializing.

    The test verifies that:

    • each child session keeps its own child-session state;
    • child sessions do not register primary-session tools, event handlers, entry renderers, or startup maintenance;
    • closing one child session does not change how the other child sessions are handled;
    • the parent session still performs its normal initialization.

    This keeps the current AsyncLocalStorage design and verifies that concurrently running child sessions do not overwrite one another’s state.

Additional points from the earlier review remain covered:

  • All plugin instances in the same process use the same Dreamer registration map, preventing duplicate timers for the same project.
  • Shutdown waits only for work owned by the session and Dreamer registration being closed.
  • RPC commands receive the current command context when invoked instead of retaining a context from plugin startup. Their status output is displayed in the UI and is not added to the model conversation.
  • The [pi] Historian error spawning pi (Windows Powershell) #177 regression remains a separate test. It creates a valid Pi CLI path and verifies that the child process uses process.execPath with that cli.js, does not execute bare pi, and does not use a shell. The adjacent embedded-host test separately verifies that the plugin does not re-run an unrelated host program from process.argv[1].
  • PARITY.md documents the shared process behavior, RPC presentation behavior, and how AsyncLocalStorage keeps concurrently initializing child sessions separate.
  • No implementation of the Host Adapter API proposed in [pi-plugin] pi-web embedded multi-session: field data + proposal for a public Host Adapter API (coordination with #350) #353, and no dashboard changes, are included in this PR.

After rebasing onto 7d6fda35, the Pi-plugin suite passes 848 tests. Another 59 tests covering the changed Dreamer and shared code also pass. The build, workspace type checking, formatting check, and Git whitespace check pass.

The new CI and Smoke runs are currently waiting for workflow approval. Cubic reports that it skipped automatic review because the force-push rewrote the branch history, so it will need to be triggered manually.

Scope in-process child detection to lifecycle AsyncLocalStorage so independent sessions initialize normally, and release lifecycle subscriptions on shutdown. Run startup maintenance once per process and defer session-history reads until the backfill lease is acquired.

Resolve the child Pi CLI independently from embedded host argv, present command output through RPC notifications and dialogs, and ensure Dreamer registration before manual runs.
Handle multiple Pi sessions running in the same embedded host.

- share Dreamer registration across plugin instances and hand its timer to
  the most recently registered remaining session
- wait only for each session's historian, recomp, and Dreamer jobs during
  session_shutdown
- keep agent_end synchronous so it does not delay turn completion
- use the current command context to display status in Pi RPC mode
- keep standalone Pi and embedded-host subagent launch regression tests
- document the behavior in PARITY.md

Tests:
- NODE_ENV=test bun test src (813 passed)
- NODE_ENV=test bun test session-project-backfill.test.ts (9 passed)
- bun run build
- bun run typecheck
- bun run format:check
- reject Dreamer prompts after their registration owner is removed or switches worktrees
- track complete manual runs, including domain lease waits, during shutdown
- unregister the session owner before draining its Dreamer work
- notify every registered owner after successful adjunct updates
- add regression coverage for shutdown and multi-worktree races
- resolve manual runs through the active registration owner's options
- reject ownerless and deregistered-owner requests
- preserve process-shared argument order across extension reloads
- cover refreshed owners, stale owners, and lease-wait draining
- fence Dreamer timers and late results across reloads and worktree handoffs
- wait up to five seconds for recomp and upgrade tasks before canceling them
- capture session state at command start and block late UI updates
- fall back to notifications when RPC dialogs are unavailable
- add regression coverage for concurrent child initialization
@ualtinok

Copy link
Copy Markdown
Contributor

Reviewed the lifecycle rework (head 1425715) — strong pass. Three of the four requested changes are cleanly addressed and well-tested: the RPC dialog's factory-invocation tracking with the notify fallback, the AsyncLocalStorage child marker replacing the process-global latch (independent sessions now initialize normally — nice resolution of the #247 tension), and the fenced per-session shutdown drain with owner-scoped awaits.

One item still open before this can merge, on blocker 1: the stale-timer fix fences the client by generation, but timer cleanup identity is still directory-only. In the A→B→A sequence, the first A registration's canceled timer sees the final registration also at directory A, skips timerCleanup, and the old timer resource stays registered even though its client is fenced — and the new test currently pins that skip as expected. Cleanup needs the same generation identity the client checks (drop the resource when generation !== current, not when the directory differs), and the A→B→A test should assert the first timer's cleanup runs.

Two smaller asks alongside: a shutdown integration test with two live sessions (retire A, assert B's historian/dreamer continue working), and a fresh CI run on the current head (the force-push left the checks neutral/empty). With those, this is mergeable — the rework's shape is exactly right, and the new owner/generation surfaces read well.

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/plugin/dream-timer.test.ts">

<violation number="1" location="packages/plugin/src/plugin/dream-timer.test.ts:69">
P3: The first test mocks setInterval/clearInterval but leaves setTimeout real. startDreamScheduleTimer's second registration (after activeTimer is already set) takes the `else if (...) scheduleInitialProjectRun` path, which calls scheduleAfterBootQuiet → real setTimeout with a bootQuietRemainingMs delay (~30s). The test then asserts stale-cleanup behavior against a non-hermetic timer that is unref'd and merely cleared in the finally block. Mock setTimeout (as the second test does) so the code under test is deterministic and the real ~30s timers aren't created during the run.</violation>

<violation number="2" location="packages/plugin/src/plugin/dream-timer.test.ts:69">
P3: Both new tests call startDreamScheduleTimer, which internally calls the real openDatabase() with no dbPath (via openTimerDatabaseOrNull). That opens/creates the process-wide global context.db — getMagicContextStorageDir() → real data dir unless MAGIC_CONTEXT_TEST_DATA_DIR is set or NODE_ENV=test reaches the backstop — running migrations on shared real user storage. The tests create a mkdtemp dir but never point the DB there (no dbPath is passed; openDatabase has no dbPath parameter exposed here). Mock openDatabase or set MAGIC_CONTEXT_TEST_DATA_DIR so these tests are isolated and can't touch the real shared database.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

let cleanupReplacement: (() => void) | undefined;

try {
const cleanupStale = await startDreamScheduleTimer({ ...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.

P3: The first test mocks setInterval/clearInterval but leaves setTimeout real. startDreamScheduleTimer's second registration (after activeTimer is already set) takes the else if (...) scheduleInitialProjectRun path, which calls scheduleAfterBootQuiet → real setTimeout with a bootQuietRemainingMs delay (~30s). The test then asserts stale-cleanup behavior against a non-hermetic timer that is unref'd and merely cleared in the finally block. Mock setTimeout (as the second test does) so the code under test is deterministic and the real ~30s timers aren't created during the run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/plugin/dream-timer.test.ts, line 69:

<comment>The first test mocks setInterval/clearInterval but leaves setTimeout real. startDreamScheduleTimer's second registration (after activeTimer is already set) takes the `else if (...) scheduleInitialProjectRun` path, which calls scheduleAfterBootQuiet → real setTimeout with a bootQuietRemainingMs delay (~30s). The test then asserts stale-cleanup behavior against a non-hermetic timer that is unref'd and merely cleared in the finally block. Mock setTimeout (as the second test does) so the code under test is deterministic and the real ~30s timers aren't created during the run.</comment>

<file context>
@@ -43,6 +44,111 @@ describe("schema-fence null-DB contract", () => {
+        let cleanupReplacement: (() => void) | undefined;
+
+        try {
+            const cleanupStale = await startDreamScheduleTimer({ ...base });
+            cleanupReplacement = await startDreamScheduleTimer({ ...base });
+            expect(cleanupStale).toBeFunction();
</file context>

let cleanupReplacement: (() => void) | undefined;

try {
const cleanupStale = await startDreamScheduleTimer({ ...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.

P3: Both new tests call startDreamScheduleTimer, which internally calls the real openDatabase() with no dbPath (via openTimerDatabaseOrNull). That opens/creates the process-wide global context.db — getMagicContextStorageDir() → real data dir unless MAGIC_CONTEXT_TEST_DATA_DIR is set or NODE_ENV=test reaches the backstop — running migrations on shared real user storage. The tests create a mkdtemp dir but never point the DB there (no dbPath is passed; openDatabase has no dbPath parameter exposed here). Mock openDatabase or set MAGIC_CONTEXT_TEST_DATA_DIR so these tests are isolated and can't touch the real shared database.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/plugin/dream-timer.test.ts, line 69:

<comment>Both new tests call startDreamScheduleTimer, which internally calls the real openDatabase() with no dbPath (via openTimerDatabaseOrNull). That opens/creates the process-wide global context.db — getMagicContextStorageDir() → real data dir unless MAGIC_CONTEXT_TEST_DATA_DIR is set or NODE_ENV=test reaches the backstop — running migrations on shared real user storage. The tests create a mkdtemp dir but never point the DB there (no dbPath is passed; openDatabase has no dbPath parameter exposed here). Mock openDatabase or set MAGIC_CONTEXT_TEST_DATA_DIR so these tests are isolated and can't touch the real shared database.</comment>

<file context>
@@ -43,6 +44,111 @@ describe("schema-fence null-DB contract", () => {
+        let cleanupReplacement: (() => void) | undefined;
+
+        try {
+            const cleanupStale = await startDreamScheduleTimer({ ...base });
+            cleanupReplacement = await startDreamScheduleTimer({ ...base });
+            expect(cleanupStale).toBeFunction();
</file context>

@magic-alfonso

magic-alfonso Bot commented Aug 25, 2026

Copy link
Copy Markdown

Verified the lifecycle round (0c0c029) — the remaining blocker is closed: cleanup now fences on generation identity (and the A→B→A test asserts the first timer's cleanup actually runs), and the two-live-session shutdown test covers the retire-A/B-continues path properly. The added dream-timer singleton lifecycle handling reads well too. I've approved the blocked CI workflows on your head; once they come back green this is mergeable from my side — it will land after the v0.40.1 patch currently in flight so your change rides a clean base.

@magic-alfonso

magic-alfonso Bot commented Aug 29, 2026

Copy link
Copy Markdown

Final verification on the refreshed head (post master merge): Pi suite 881/0, plugin suite 4184/0, typecheck clean. All review rounds resolved — the generation-fenced timer cleanup closed the last blocker, and the master refresh integrates cleanly with this week's parity and cache work. Merging with the boundary we discussed intact: the multi-session host plumbing lands as internal wiring; the public adapter surface stays experimental until the pi-web contract settles. Thank you for the persistence across the review rounds — the A→B→A lifecycle test and the two-live-session shutdown coverage made this landable.

@ualtinok
ualtinok merged commit 4e429da into cortexkit:master Aug 29, 2026
5 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.

3 participants