Skip to content

Replace CLI in-memory store with Turso-backed @cyrus/database - #18

Merged
soorya-u merged 5 commits into
mainfrom
feat/replace-cli-store-with-turso
Jul 8, 2026
Merged

soorya-u merged 5 commits into
mainfrom
feat/replace-cli-store-with-turso

Conversation

@soorya-u

@soorya-u soorya-u commented Jul 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add @cyrus/database with Turso/SQLite persistence for projects, threads, and conversations under CYRUS_HOME, replacing the CLI's in-memory Map stores
  • Expose monotonic seq on conversation entries, optional afterSeq cursor reads, and persist-before-broadcast ordering in chat
  • Return Result from repositories and map errors to ORPC in handlers; align wire schemas with optionalString for nullable DB columns

Closes #13

Test plan

  • Start cyrusd worker, create a project and thread, send a multi-turn chat
  • Restart the worker and confirm listProjects, listThreads, and getConversations return prior data
  • Call getConversations with afterSeq mid-conversation and confirm only later entries are returned
  • Verify streaming chat still broadcasts deltas live while completed messages persist once
  • Run bun check and bun check:types

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added persistent local storage for projects, threads, and conversation history.
    • Chat streams now support completed message/reasoning events and improved transcript stitching.
    • The web app now tracks per-thread streaming state for better live status feedback.
  • Bug Fixes

    • Improved reliability when loading, creating, renaming, and deleting projects/threads.
    • Chat sessions now handle interruptions and persistence more consistently.
    • Updated identity handling and storage for controller info.

Persist projects, threads, and conversations under CYRUS_HOME so worker restarts retain history. Add monotonic seq, afterSeq reads, persist-before-broadcast, and Result-based repository errors mapped to ORPC.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cyrus Ready Ready Preview, Comment Jul 8, 2026 5:45pm

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CLI's in-memory project/thread stores are replaced with a new shared Turso/Drizzle database package providing schema models and repository functions for projects, threads, and conversations. CLI handlers, coordinator, and chat streaming logic migrate to these async repositories with centralized error mapping. Web identity storage moves to a persisted Zustand store, streaming UI state is tracked per-thread, and server database setup, migration snapshots, and dependency versions are updated.

Changes

Turso persistence migration

Layer / File(s) Summary
Database package, models, and repository errors
shared/database/package.json, shared/database/tsconfig.json, shared/database/drizzle.config.ts, shared/database/.env.example, shared/database/src/connection.ts, shared/database/src/models/*, shared/database/src/utils/error.ts
New @cyrus/database package with Drizzle/Turso connection wrapper, projects/threads/conversations table models, and RepositoryError/tryRepo/notFound helpers.
RTC schemas and repositories
shared/connections/src/schemas/rtc/*, shared/database/src/repositories/*
New Zod schemas for completion events, seq/afterSeq fields, optionalString; new projects, threads, conversations repository functions returning Result.
CLI worker database bootstrap
apps/cli/src/constants/file.ts, apps/cli/src/store/database.ts, apps/cli/src/commands/service/worker.ts, apps/cli/package.json
Adds DATABASE_FILE/initDatabase(), wires worker startup to open the database and shutdown to close it.
CLI controller handlers and coordinator
apps/cli/src/handlers/controller/projects.ts, .../threads.ts, apps/cli/src/core/threads/coordinator.ts, apps/cli/src/utils/error.ts
Handlers and coordinator switch to async repository calls with .match()/error handling via throwOrpcFromRepositoryError.
Streaming chat completion events
apps/cli/src/core/acp/events.ts, apps/cli/src/handlers/controller/chat.ts, apps/cli/src/utils/streams.ts
Maps message.completed/reasoning.completed events, buffers streaming deltas, and persists coalesced conversation entries before broadcasting.
Web streaming UI and turn derivation
apps/web/src/stores/chat-ui.ts, apps/web/src/components/chat/main/thread-workspace.tsx, apps/web/src/hooks/use-controller-threads.ts, apps/web/src/utils/conversation-cache.ts, shared/hooks/src/derive-thread.ts
Adds per-thread streaming flag, busy-state wiring, cached seq, and message_completed-aware turn state inference.
Identity helpers and dependencies
shared/utils/src/identity.ts, shared/utils/src/time.ts, apps/web/src/stores/identity.ts, apps/web/src/constants/storage-keys.ts, apps/web/src/lib/orpc.ts, apps/cli/src/commands/auth/login.ts, package.json, turbo.json, apps/web/package.json, shared/utils/package.json
Renames generateId→randomId, adds nowISO, migrates web identity to a persisted store, and updates dependency/tool versions.
Conversation persistence spec and tasks
openspec/specs/conversation-persistence/spec.md, openspec/specs/acp-session-router/spec.md, openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/tasks.md
New spec for Turso-backed persistence and completed checklist marking the migration done.

Server database config and snapshots

Layer / File(s) Summary
Server DB client and auth import
apps/server/src/db/index.ts, apps/server/src/db/models/auth.ts, apps/server/package.json
Switches to drizzle-orm/neon-http direct construction, updates relations import, moves drizzle-kit to catalog.
Server migration snapshots
apps/server/src/db/migrations/*/snapshot.json, apps/server/src/db/migrations/meta/*
Adds new snapshot files and removes prior snapshot/journal metadata.
Signaling handler formatting
apps/server/src/handlers/signaling.ts
Reformats loop and guard clauses with no behavior change.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Runtime
  participant ChatHandler
  participant StreamUtils
  participant ConversationsRepository
  participant WebClient

  Runtime->>ChatHandler: token/thought event
  ChatHandler->>StreamUtils: trackDelta(event, buffers)
  ChatHandler-->>WebClient: broadcast streaming chunk

  Runtime->>ChatHandler: message_completed event
  ChatHandler->>StreamUtils: resolvePersistEvent(event, buffers)
  ChatHandler->>ConversationsRepository: appendConversation(threadId, chunk)
  ConversationsRepository-->>ChatHandler: persisted entry with seq
  ChatHandler-->>WebClient: broadcast persisted chunk
Loading
sequenceDiagram
  participant CLIWorker
  participant DatabaseConnection
  participant Turso
  participant ProjectsRepository

  CLIWorker->>DatabaseConnection: initDatabase()
  DatabaseConnection->>Turso: connect + push schema
  DatabaseConnection->>Turso: configure pragmas
  DatabaseConnection-->>CLIWorker: opened worker db
  CLIWorker->>ProjectsRepository: resolveProjectCwd(projectId)
  ProjectsRepository->>Turso: query projects
  ProjectsRepository-->>CLIWorker: Result<cwd, RepositoryError>
Loading

Possibly related PRs

  • soorya-u/cyrus#5: Both PRs touch apps/cli/src/commands/auth/login.ts—the retrieved PR adds the device-code OAuth login implementation, while this PR adjusts the generateName import used by that command.
  • soorya-u/cyrus#6: Both PRs touch apps/cli/src/commands/service/worker.ts, with this PR's persistence/shutdown changes building on the retrieved PR's signaling worker daemon.
  • soorya-u/cyrus#8: Both PRs modify apps/cli/src/core/threads/coordinator.ts and related controller/router integration for agent/thread management.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR also includes web identity/chat UI and server DB/auth changes that are not required by #13's CLI store replacement scope. Split the web/server refactors and migration/auth updates into separate PRs, keeping this change focused on CLI persistence.
✅ 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 states the main change: replacing the CLI in-memory store with Turso-backed @cyrus/database.
Linked Issues check ✅ Passed The PR replaces the CLI's in-memory project, thread, and conversation stores with persistent Turso-backed repositories under CYRUS_HOME.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/replace-cli-store-with-turso

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

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
shared/database/src/repositories/conversations.ts (1)

27-46: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider wrapping the insert and thread update in a transaction.

appendConversation performs an insert into conversations followed by an update to threads.updatedAt as two independent operations. If the update fails, the conversation entry is already persisted but the thread's updatedAt is stale, causing it to appear less recently active in listThreads ordering. Wrapping both in connection.db.transaction() would make them atomic.

♻️ Proposed refactor
 	return tryRepo(async () => {
 		const id = randomId();
 		const createdAt = nowISO();
-		const [row] = await connection.db
-			.insert(conversations)
-			.values({
-				id,
-				threadId,
-				chunk: JSON.stringify(chunk),
-				createdAt,
-			})
-			.returning();
-		if (!row) {
-			throw {
-				type: "persist_failed",
-				message: `failed to persist conversation entry for thread ${threadId}`,
-			} satisfies RepositoryError;
-		}
-
-		await connection.db
-			.update(threads)
-			.set({ updatedAt: createdAt })
-			.where(eq(threads.id, threadId));
-
-		return parseConversationEntry(row);
+		return await connection.db.transaction(async (tx) => {
+			const [row] = await tx
+				.insert(conversations)
+				.values({
+					id,
+					threadId,
+					chunk: JSON.stringify(chunk),
+					createdAt,
+				})
+				.returning();
+			if (!row) {
+				throw {
+					type: "persist_failed",
+					message: `failed to persist conversation entry for thread ${threadId}`,
+				} satisfies RepositoryError;
+			}
+
+			await tx
+				.update(threads)
+				.set({ updatedAt: createdAt })
+				.where(eq(threads.id, threadId));
+
+			return parseConversationEntry(row);
+		});
 	});
🤖 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 `@shared/database/src/repositories/conversations.ts` around lines 27 - 46, The
appendConversation flow currently does the conversation insert and
threads.updatedAt update as separate operations, so a failure in the second step
leaves the data inconsistent. Update the appendConversation logic in
conversations.ts to run both the insert into conversations and the update of
threads inside connection.db.transaction() so they succeed or fail together;
keep the existing persist_failed handling for the insert result and preserve the
use of id, threadId, createdAt, and eq(threads.id, threadId) within the
transactional block.
shared/database/src/connection.ts (1)

10-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding a close/dispose method for resource cleanup.

The singleton DatabaseConnection has no way to release the native client. While the CLI process exit handles cleanup in practice, a close method would improve testability and prevent resource leaks if open is ever called more than once.

♻️ Suggested addition
 	get db(): DrizzleDb {
 		if (!this.drizzleDb)
 			throw new Error("DatabaseConnection is not initialized");

 		return this.drizzleDb;
 	}

+	async close(): Promise<void> {
+		if (this.nativeClient) {
+			await this.nativeClient.close?.();
+			this.nativeClient = null;
+		}
+		this.drizzleDb = null;
+	}
+
 	open(
🤖 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 `@shared/database/src/connection.ts` around lines 10 - 68, The
DatabaseConnection singleton has no cleanup path for the native client, which
can leave resources open and makes repeated open/setup calls harder to manage.
Add a close/dispose method to DatabaseConnection that safely releases
nativeClient and resets drizzleDb, and have it guard against double-closing so
callers can invoke it during tests or shutdown. Keep the change localized to the
DatabaseConnection class and preserve the existing client/db getters’
initialization behavior.

Source: Linters/SAST tools

shared/utils/src/time.ts (1)

14-16: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider using new Date().toISOString() for UTC consistency.

formatISO(new Date()) returns a local-timezone string with offset (e.g., 2024-01-15T10:30:00+02:00) and no milliseconds, while new Date().toISOString() returns UTC (2024-01-15T08:30:00.000Z) with millisecond precision. For database timestamps, UTC is generally preferred for consistency — especially if the machine's timezone changes (DST transitions). Since date-fns is already a dependency, this is a style/consistency choice rather than a correctness issue for a single-machine CLI.

♻️ Optional refactor for UTC timestamps
 export function nowISO(): string {
-	return formatISO(new Date());
+	return new Date().toISOString();
 }

If you keep formatISO, consider adding { representation: "complete" } for millisecond precision to avoid identical timestamps on rapid sequential inserts.

🤖 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 `@shared/utils/src/time.ts` around lines 14 - 16, The nowISO helper in time.ts
currently uses formatISO(new Date()), which produces a local-time offset string
and omits milliseconds; update nowISO to return a UTC timestamp using new
Date().toISOString(), or if you keep formatISO, adjust the formatting for
complete precision. Keep the change scoped to the nowISO function so database
timestamp generation stays consistent across timezone changes.
🤖 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 `@apps/cli/src/commands/service/worker.ts`:
- Around line 30-32: Add explicit error handling around initDatabase() in
worker.ts so a database startup failure does not leave the worker running in a
broken state. Wrap the initDatabase() call near createWorkerRuntime() in a
try-catch, log a clear user-facing initialization failure message with the
underlying error, and then terminate cleanly by calling shutdown() and exiting
the process instead of relying on the unhandledRejection path. Keep the fix
localized to the worker startup flow so the worker never proceeds without a
usable database.

In `@apps/cli/src/utils/streams.ts`:
- Around line 20-23: The delta accumulation in trackDelta is reading from
messageBuffers even when the selected target is thoughtBuffers, so
thought/reasoning text gets overwritten instead of appended. Update trackDelta
to read the existing value from the same buffer it is writing to, using the
event.messageId key consistently, and keep resolvePersistEvent relying on the
fully accumulated thought text for reasoning_completed events.

In `@apps/web/src/stores/identity.ts`:
- Line 33: The controller identity store currently writes only
CONTROLLER_IDENTITY, so legacy users with CONTROLLER_ID/CONTROLLER_NAME lose
their previous controller identity on first load. Update the identity
initialization logic in the identity store to check for the old keys before
creating a new value, migrate the existing controller data into
CONTROLLER_IDENTITY when present, and then clear the legacy entries so the
migration runs only once. Use the existing identity store initialization path
and the CONTROLLER_IDENTITY constant as the anchor points for the change.

In `@package.json`:
- Around line 23-24: The catalog update in package.json should keep date-fns
because apps/web/package.json still resolves it through catalog:, and removing
it would break that workspace; update the catalog entry list to preserve
date-fns and remove only the unused random-word-slugs entry. Use the catalog
section in package.json as the target for this change.

In `@shared/database/src/repositories/threads.ts`:
- Around line 31-60: The ensureThread update path is matching threads only by id
and then returning the passed-in projectId, which can update or report a thread
from the wrong project. Update the lookup in ensureThread to filter by both
threads.id and projectId, and when building the ThreadSchema result use
existing.projectId instead of the input parameter so the returned object always
reflects the database row.

In `@shared/database/src/utils/error.ts`:
- Around line 19-21: `fromUnknown` and `repositoryErrorMessage` currently allow
unexpected objects with a `type` field to slip through, which can make the
default branch return a non-string value. Update `fromUnknown` in error.ts to
validate that `type` is one of the known RepositoryError variants before
casting, and make `repositoryErrorMessage`’s default path return a safe string
fallback instead of the raw `error` object. Use the existing `RepositoryError`,
`fromUnknown`, and `repositoryErrorMessage` symbols to keep the narrowing
consistent for callers like coordinator.ts.

---

Nitpick comments:
In `@shared/database/src/connection.ts`:
- Around line 10-68: The DatabaseConnection singleton has no cleanup path for
the native client, which can leave resources open and makes repeated open/setup
calls harder to manage. Add a close/dispose method to DatabaseConnection that
safely releases nativeClient and resets drizzleDb, and have it guard against
double-closing so callers can invoke it during tests or shutdown. Keep the
change localized to the DatabaseConnection class and preserve the existing
client/db getters’ initialization behavior.

In `@shared/database/src/repositories/conversations.ts`:
- Around line 27-46: The appendConversation flow currently does the conversation
insert and threads.updatedAt update as separate operations, so a failure in the
second step leaves the data inconsistent. Update the appendConversation logic in
conversations.ts to run both the insert into conversations and the update of
threads inside connection.db.transaction() so they succeed or fail together;
keep the existing persist_failed handling for the insert result and preserve the
use of id, threadId, createdAt, and eq(threads.id, threadId) within the
transactional block.

In `@shared/utils/src/time.ts`:
- Around line 14-16: The nowISO helper in time.ts currently uses formatISO(new
Date()), which produces a local-time offset string and omits milliseconds;
update nowISO to return a UTC timestamp using new Date().toISOString(), or if
you keep formatISO, adjust the formatting for complete precision. Keep the
change scoped to the nowISO function so database timestamp generation stays
consistent across timezone changes.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 220972e2-518c-44bc-85e3-c311322cc097

📥 Commits

Reviewing files that changed from the base of the PR and between 87d5a95 and e13ec33.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (45)
  • apps/cli/package.json
  • apps/cli/src/commands/auth/login.ts
  • apps/cli/src/commands/service/worker.ts
  • apps/cli/src/constants/file.ts
  • apps/cli/src/core/acp/events.ts
  • apps/cli/src/core/threads/coordinator.ts
  • apps/cli/src/handlers/controller/chat.ts
  • apps/cli/src/handlers/controller/projects.ts
  • apps/cli/src/handlers/controller/threads.ts
  • apps/cli/src/store/database.ts
  • apps/cli/src/store/projects.ts
  • apps/cli/src/store/threads.ts
  • apps/cli/src/utils/error.ts
  • apps/cli/src/utils/streams.ts
  • apps/server/package.json
  • apps/server/src/db/index.ts
  • apps/server/src/db/models/auth.ts
  • apps/web/package.json
  • apps/web/src/constants/storage-keys.ts
  • apps/web/src/lib/orpc.ts
  • apps/web/src/stores/identity.ts
  • apps/web/src/utils/conversation-cache.ts
  • apps/web/src/utils/identity.ts
  • openspec/changes/replace-cli-store-with-turso/tasks.md
  • package.json
  • shared/connections/src/schemas/rtc/chat.ts
  • shared/connections/src/schemas/rtc/common.ts
  • shared/connections/src/schemas/rtc/projects.ts
  • shared/connections/src/schemas/rtc/threads.ts
  • shared/database/drizzle.config.ts
  • shared/database/package.json
  • shared/database/src/connection.ts
  • shared/database/src/models/conversations.ts
  • shared/database/src/models/index.ts
  • shared/database/src/models/projects.ts
  • shared/database/src/models/threads.ts
  • shared/database/src/repositories/conversations.ts
  • shared/database/src/repositories/projects.ts
  • shared/database/src/repositories/threads.ts
  • shared/database/src/utils/error.ts
  • shared/database/tsconfig.json
  • shared/hooks/src/derive-thread.ts
  • shared/utils/package.json
  • shared/utils/src/identity.ts
  • shared/utils/src/time.ts
💤 Files with no reviewable changes (4)
  • apps/web/package.json
  • apps/web/src/utils/identity.ts
  • apps/cli/src/store/projects.ts
  • apps/cli/src/store/threads.ts

Comment thread apps/cli/src/commands/service/worker.ts Outdated
Comment thread apps/cli/src/utils/streams.ts Outdated
Comment thread apps/web/src/stores/identity.ts
Comment thread package.json
Comment thread shared/database/src/repositories/threads.ts
Comment thread shared/database/src/utils/error.ts Outdated
Reopen the database after pushSchema so Drizzle writes flush to disk, accept
only a connect factory in DatabaseConnection, close the DB on worker shutdown,
and add db:file:studio plus Drizzle Kit 1.0 server migration layout.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
shared/database/src/connection.ts (1)

67-82: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider wrapping batch handler in a transaction.

The batch handler executes statements sequentially via client.exec(). If a statement fails midway, previously executed statements are already committed, potentially leaving the schema in a partially-applied state. If pushSchema expects atomicity from batch, this could cause data integrity issues during schema migration.

If the Turso client supports it, wrapping in an explicit transaction would provide rollback safety:

🔒️ Suggested transactional batch handler
 		batch: async (statements) => {
-			for (const statement of statements) await client.exec(statement);
+			await client.exec("BEGIN");
+			try {
+				for (const statement of statements) await client.exec(statement);
+				await client.exec("COMMIT");
+			} catch (error) {
+				await client.exec("ROLLBACK");
+				throw error;
+			}
 		},

If pushSchema already handles atomicity at a higher level or schema statements are idempotent, this can be skipped.

🤖 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 `@shared/database/src/connection.ts` around lines 67 - 82, The batch handler in
push currently runs statements one by one with client.exec, which can leave a
partially applied schema if one statement fails. Update the push method’s batch
implementation to execute the statements inside an explicit transaction when the
DatabasePromise client supports it, so the schema migration is atomic and can
roll back on failure; if pushSchema already guarantees atomicity, keep the
handler as-is only after confirming that behavior.
🤖 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.

Nitpick comments:
In `@shared/database/src/connection.ts`:
- Around line 67-82: The batch handler in push currently runs statements one by
one with client.exec, which can leave a partially applied schema if one
statement fails. Update the push method’s batch implementation to execute the
statements inside an explicit transaction when the DatabasePromise client
supports it, so the schema migration is atomic and can roll back on failure; if
pushSchema already guarantees atomicity, keep the handler as-is only after
confirming that behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5cd103ab-3f83-45f9-9e21-ebb7a74cd44d

📥 Commits

Reviewing files that changed from the base of the PR and between e13ec33 and a2b077b.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • apps/cli/package.json
  • apps/cli/src/commands/service/worker.ts
  • apps/cli/src/store/database.ts
  • apps/server/src/db/migrations/20260624170920_db_init/migration.sql
  • apps/server/src/db/migrations/20260624170920_db_init/snapshot.json
  • apps/server/src/db/migrations/20260628062857_device_authorization/migration.sql
  • apps/server/src/db/migrations/20260628062857_device_authorization/snapshot.json
  • apps/server/src/db/migrations/meta/0000_snapshot.json
  • apps/server/src/db/migrations/meta/0001_snapshot.json
  • apps/server/src/db/migrations/meta/_journal.json
  • apps/server/src/handlers/signaling.ts
  • apps/web/package.json
  • apps/web/src/hooks/use-controller-threads.ts
  • package.json
  • shared/database/.env.example
  • shared/database/drizzle.config.ts
  • shared/database/package.json
  • shared/database/src/connection.ts
💤 Files with no reviewable changes (4)
  • apps/server/src/db/migrations/meta/0001_snapshot.json
  • apps/server/src/db/migrations/meta/0000_snapshot.json
  • apps/server/src/db/migrations/meta/_journal.json
  • apps/web/package.json
✅ Files skipped from review due to trivial changes (3)
  • shared/database/.env.example
  • apps/server/src/handlers/signaling.ts
  • apps/server/src/db/migrations/20260628062857_device_authorization/snapshot.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • shared/database/drizzle.config.ts
  • apps/cli/src/commands/service/worker.ts
  • package.json
  • shared/database/package.json
  • apps/cli/src/store/database.ts
  • apps/cli/package.json

Relax thread ID validation to accept v4 UUIDs, emit turn completion events from the worker, and derive turn state correctly so the composer returns to send once a response finishes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Exit cleanly when DB init fails, fix thought delta buffering, scope ensureThread to project ownership, and tighten repository error narrowing.

Co-authored-by: Cursor <cursoragent@cursor.com>
@soorya-u
soorya-u merged commit 6bce0dd into main Jul 8, 2026
4 of 5 checks passed
@soorya-u
soorya-u deleted the feat/replace-cli-store-with-turso branch July 8, 2026 17:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/cli/src/commands/service/worker.ts (1)

60-66: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard connection.close() to ensure shutdown always exits.

If connection.close() throws, process.exit(0) on line 65 is never reached. The unhandledRejection handler at line 70 only logs — it doesn't exit — so the process could hang on shutdown. Consider wrapping the cleanup in a try-finally to guarantee exit.

🛡️ Proposed fix for shutdown reliability
 	const shutdown = async () => {
-		await runtime.agentPool.shutdown();
-		device.close();
-		signalingSession.close();
-		await connection.close();
-		process.exit(0);
+		try {
+			await runtime.agentPool.shutdown();
+			device.close();
+			signalingSession.close();
+			await connection.close();
+		} finally {
+			process.exit(0);
+		}
 	};
🤖 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 `@apps/cli/src/commands/service/worker.ts` around lines 60 - 66, The shutdown
flow in shutdown currently awaits connection.close() before calling
process.exit(0), so a thrown error can prevent the process from exiting. Update
the cleanup sequence to guarantee exit even if connection.close() fails,
preferably by wrapping the shutdown steps in a try-finally and placing
process.exit(0) in the finally path. Keep the existing cleanup for
runtime.agentPool.shutdown, device.close, and signalingSession.close, and make
sure the fix is applied in the shutdown function so the worker always terminates
cleanly.
🤖 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 `@openspec/specs/conversation-persistence/spec.md`:
- Around line 64-72: The ordering requirement is too broad and conflicts with
the live-delta flow in chat handling; scope it to persisted ConversationEntry
rows only. Update the spec around the persist-before-broadcast behavior to state
that only durable entries must be written to Turso before their corresponding
ChatChunk is broadcast, while streaming deltas in chat controller paths like
chat.ts may still broadcast immediately with a placeholder seq and persist later
on completion.

---

Outside diff comments:
In `@apps/cli/src/commands/service/worker.ts`:
- Around line 60-66: The shutdown flow in shutdown currently awaits
connection.close() before calling process.exit(0), so a thrown error can prevent
the process from exiting. Update the cleanup sequence to guarantee exit even if
connection.close() fails, preferably by wrapping the shutdown steps in a
try-finally and placing process.exit(0) in the finally path. Keep the existing
cleanup for runtime.agentPool.shutdown, device.close, and
signalingSession.close, and make sure the fix is applied in the shutdown
function so the worker always terminates cleanly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4d7e7a8-77bc-46e5-b4c1-f4be37388bdf

📥 Commits

Reviewing files that changed from the base of the PR and between a2b077b and 2055a10.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • apps/cli/src/commands/service/worker.ts
  • apps/cli/src/handlers/controller/chat.ts
  • apps/cli/src/utils/streams.ts
  • apps/web/src/components/chat/main/thread-workspace.tsx
  • apps/web/src/hooks/use-controller-threads.ts
  • apps/web/src/stores/chat-ui.ts
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/.openspec.yaml
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/design.md
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/proposal.md
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/specs/acp-session-router/spec.md
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/specs/conversation-persistence/spec.md
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/tasks.md
  • openspec/specs/acp-session-router/spec.md
  • openspec/specs/conversation-persistence/spec.md
  • package.json
  • shared/connections/src/schemas/rtc/chat.ts
  • shared/connections/src/schemas/rtc/common.ts
  • shared/database/src/repositories/threads.ts
  • shared/database/src/utils/error.ts
  • shared/hooks/src/derive-thread.ts
  • turbo.json
💤 Files with no reviewable changes (1)
  • openspec/changes/archive/2026-07-08-replace-cli-store-with-turso/tasks.md
✅ Files skipped from review due to trivial changes (1)
  • turbo.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • shared/connections/src/schemas/rtc/common.ts
  • package.json
  • shared/database/src/utils/error.ts
  • apps/cli/src/handlers/controller/chat.ts
  • apps/cli/src/utils/streams.ts
  • shared/database/src/repositories/threads.ts

Comment on lines +64 to +72
### Requirement: Persist-before-broadcast ordering

The worker SHALL persist a conversation entry and obtain its assigned `seq` before broadcasting the corresponding `ChatChunk` to connected peers.

#### Scenario: Broadcast carries a durable sequence

- **WHEN** an event is emitted during a chat turn
- **THEN** the entry is written to Turso first, and the `ChatChunk` broadcast to peers includes the `seq` returned by that write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Scope the ordering guarantee to persisted entries.

As written, this conflicts with the live-delta path in apps/cli/src/handlers/controller/chat.ts:29-72, which broadcasts streaming deltas immediately with seq: 0 and only persists the coalesced completion event first. The requirement should say persisted ConversationEntry rows are written before broadcast, not every ChatChunk.

Proposed wording
-The worker SHALL persist a conversation entry and obtain its assigned seq before broadcasting the corresponding ChatChunk to connected peers.
+For persisted conversation entries, the worker SHALL persist the entry and obtain its assigned seq before broadcasting the corresponding ChatChunk to connected peers.
+Streaming deltas may still be broadcast immediately with a placeholder seq.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Requirement: Persist-before-broadcast ordering
The worker SHALL persist a conversation entry and obtain its assigned `seq` before broadcasting the corresponding `ChatChunk` to connected peers.
#### Scenario: Broadcast carries a durable sequence
- **WHEN** an event is emitted during a chat turn
- **THEN** the entry is written to Turso first, and the `ChatChunk` broadcast to peers includes the `seq` returned by that write
### Requirement: Persist-before-broadcast ordering
For persisted conversation entries, the worker SHALL persist the entry and obtain its assigned `seq` before broadcasting the corresponding `ChatChunk` to connected peers.
Streaming deltas may still be broadcast immediately with a placeholder `seq`.
#### Scenario: Broadcast carries a durable sequence
- **WHEN** an event is emitted during a chat turn
- **THEN** the entry is written to Turso first, and the `ChatChunk` broadcast to peers includes the `seq` returned by that write
🤖 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 `@openspec/specs/conversation-persistence/spec.md` around lines 64 - 72, The
ordering requirement is too broad and conflicts with the live-delta flow in chat
handling; scope it to persisted ConversationEntry rows only. Update the spec
around the persist-before-broadcast behavior to state that only durable entries
must be written to Turso before their corresponding ChatChunk is broadcast,
while streaming deltas in chat controller paths like chat.ts may still broadcast
immediately with a placeholder seq and persist later on completion.

This branch was successfully deployed

1 active deployment
Preview — 2055a10b Deployed Jul 8, 2026 by vercel[bot]
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.

Replace CLI's in-memory project/thread store with Turso

1 participant