feat: add context sharing and storage consistency (P0)

Session runtime mutex:
- Add session_runtime_locks table with TTL-based locking via SQLite
  single-writer guarantee (transaction: DELETE expired + INSERT, PK
  conflict = already locked → 409 SESSION_BUSY)
- POST /api/chat acquires lock before processing, releases in
  collectStreamResponse finally block and error catch
- Add runtime_status/runtime_updated_at/runtime_error columns to
  chat_sessions for tracking session state

Permission request persistence:
- Add permission_requests table with full lifecycle tracking
  (pending → allow/deny/timeout/aborted)
- Dual-write in permission-registry: in-memory Map for real-time
  resolution + DB for audit and recovery
- claude-client canUseTool now persists to DB before SSE event
- permission/route.ts validates against DB before resolving:
  404 (not found), 409 (already resolved), 410 (waiter gone)

Immutable sdk_cwd:
- Add sdk_cwd column, snapshot from working_directory at session
  creation time, never changes afterward
- route.ts uses sdk_cwd for SDK cwd to prevent resume breakage
  when user modifies working_directory
- Backfill existing sessions via migration

Startup recovery (migrateDb):
- Reset running/waiting_permission sessions to idle with error msg
- Clear all runtime locks (stale from previous process)
- Abort pending permission requests (no in-memory waiter exists)

All DB writes are try/catch guarded — failure degrades gracefully
without breaking the in-memory hot path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-27 12:20:01 +08:00
1 parent 09908cfcb7
commit 2a7dbd45c2
6 files changed
+364 -12

No files matched your search

+5
View File
@@ -17,6 +17,10 @@ export interface ChatSession {
needs_approval?: boolean;
provider_name: string;
provider_id: string;
sdk_cwd: string;
runtime_status: string;
runtime_updated_at: string;
runtime_error: string;
}
// ==========================================
@@ -599,4 +603,5 @@ export interface ClaudeStreamOptions {
provider?: ApiProvider;
/** Recent conversation history from DB — used as fallback context when SDK resume is unavailable or fails */
conversationHistory?: Array<{ role: 'user' | 'assistant'; content: string }>;
onRuntimeStatusChange?: (status: string) => void;
}