@asaidimu/utils-workspace 1.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.mts CHANGED
@@ -1,3 +1,6 @@
1
+ import { QueryFilter, PaginationOptions } from '@asaidimu/query';
2
+ import { SchemaDefinition, SchemaChange, DataTransform } from '@asaidimu/anansi';
3
+
1
4
  type UUID = string;
2
5
  type Timestamp = string;
3
6
  type URI = string;
@@ -9,8 +12,6 @@ type Result<T, E = WorkspaceError> = {
9
12
  ok: false;
10
13
  error: E;
11
14
  };
12
- declare function ok<T>(value: T): Result<T, never>;
13
- declare function err<E = WorkspaceError>(error: E): Result<never, E>;
14
15
  type WorkspaceError = {
15
16
  code: 'DUPLICATE_KEY';
16
17
  resource: string;
@@ -31,7 +32,7 @@ type WorkspaceError = {
31
32
  };
32
33
  interface Settings {
33
34
  language: string;
34
- defaultRole: string;
35
+ defaultRole?: string;
35
36
  prompt?: string;
36
37
  }
37
38
  interface Project {
@@ -43,13 +44,12 @@ interface Project {
43
44
  type ImageMediaType = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
44
45
  type DocumentMediaType = 'application/pdf' | 'application/json' | 'text/plain' | 'text/html' | 'text/markdown' | 'text/csv' | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
45
46
  type BlobMediaType = ImageMediaType | DocumentMediaType;
46
- interface BlobRef {
47
- sha256: SHA256;
48
- mediaType: BlobMediaType;
49
- sizeBytes: number;
50
- filename?: string;
51
- previewUrl?: string;
52
- }
47
+ /**
48
+ * Lightweight pointer to a blob — the subset of BlobRecord that content
49
+ * blocks and context entries carry. Use this at call sites that only need
50
+ * to reference a blob, not inspect its registry metadata.
51
+ */
52
+ type BlobRef = Pick<BlobRecord, 'sha256' | 'mediaType' | 'sizeBytes' | 'filename' | 'previewUrl'>;
53
53
  type ResolvedBlob = {
54
54
  kind: 'inline';
55
55
  sha256: SHA256;
@@ -62,37 +62,35 @@ type ResolvedBlob = {
62
62
  fileId: string;
63
63
  providerId: string;
64
64
  };
65
+ /**
66
+ * Full registry entry for a blob. BlobRef is a Pick of this type, so
67
+ * a BlobRecord is always assignable to BlobRef.
68
+ */
65
69
  interface BlobRecord {
66
70
  sha256: SHA256;
67
71
  mediaType: BlobMediaType;
68
72
  sizeBytes: number;
69
73
  filename?: string;
74
+ previewUrl?: string;
70
75
  refCount: number;
71
76
  remoteIds: Record<string, string>;
72
77
  createdAt: Timestamp;
73
78
  lastUsedAt: Timestamp;
74
79
  }
75
- interface BlobSummary {
76
- sha256: SHA256;
77
- mediaType: BlobMediaType;
78
- sizeBytes: number;
79
- filename?: string;
80
- refCount: number;
81
- createdAt: Timestamp;
82
- lastUsedAt: Timestamp;
83
- }
84
80
  interface TextBlock {
85
81
  type: 'text';
86
82
  text: string;
87
83
  }
88
84
  interface ImageBlock {
89
85
  type: 'image';
90
- blob: ResolvedBlob;
86
+ ref?: BlobRef;
87
+ blob?: ResolvedBlob;
91
88
  altText?: string;
92
89
  }
93
90
  interface DocumentBlock {
94
91
  type: 'document';
95
- blob: ResolvedBlob;
92
+ ref?: BlobRef;
93
+ blob?: ResolvedBlob;
96
94
  title?: string;
97
95
  }
98
96
  interface ToolUseBlock {
@@ -111,7 +109,16 @@ interface ThinkingBlock {
111
109
  type: 'thinking';
112
110
  thinking: string;
113
111
  }
114
- type ContentBlock = TextBlock | ImageBlock | DocumentBlock | ToolUseBlock | ToolResultBlock | ThinkingBlock;
112
+ interface SummaryBlock {
113
+ type: 'summary';
114
+ text: string;
115
+ }
116
+ interface RoleTransitionBlock {
117
+ type: 'role_transition';
118
+ previousRole: string | null;
119
+ newRole: string;
120
+ }
121
+ type ContentBlock = TextBlock | ImageBlock | DocumentBlock | ToolUseBlock | ToolResultBlock | SummaryBlock | RoleTransitionBlock | ThinkingBlock;
115
122
  type TurnRole = 'user' | 'assistant' | 'tool';
116
123
  interface Turn {
117
124
  id: UUID;
@@ -119,75 +126,89 @@ interface Turn {
119
126
  role: TurnRole;
120
127
  blocks: ContentBlock[];
121
128
  timestamp: Timestamp;
129
+ /**
130
+ * The name of the role active when this turn was recorded.
131
+ * Snapshot so history is stable even if the role is later renamed.
132
+ */
122
133
  roleSnapshot?: string;
134
+ /**
135
+ * Parent pointer. Null for root turns.
136
+ * This is plain data stored on the document — the DAG is reconstructed
137
+ * in memory by TurnTree.buildNodeGraph() at session open time.
138
+ */
123
139
  parent: {
124
140
  id: UUID;
125
141
  version: number;
126
142
  } | null;
143
+ /**
144
+ * Partition key. Stored on the Turn document so Collection.filter
145
+ * can retrieve all turns for a session in a single query.
146
+ * Not present on Turn objects used purely in memory (e.g. dirty buffer).
147
+ */
148
+ sessionId?: UUID;
127
149
  }
128
- interface SessionSummary {
150
+ interface TurnNode {
129
151
  id: UUID;
130
- label: string;
131
- role: string;
132
- topics: string[];
133
- created: Timestamp;
134
- updated: Timestamp;
135
- turns: number;
152
+ versions: Record<number, Turn>;
153
+ activeVersion: number;
154
+ role: TurnRole;
155
+ blocks: ContentBlock[];
156
+ timestamp: Timestamp;
157
+ roleSnapshot?: string;
158
+ parent: {
159
+ id: UUID;
160
+ version: number;
161
+ } | null;
162
+ children: Record<number, UUID[]>;
136
163
  }
137
- interface RoleSummary {
164
+ interface BranchInfo {
165
+ versions: number[];
166
+ currentIndex: number;
167
+ total: number;
168
+ hasPrev: boolean;
169
+ hasNext: boolean;
170
+ }
171
+ interface Role {
138
172
  name: string;
139
173
  label: string;
140
174
  description?: string;
141
- preferences: number;
175
+ persona: string;
176
+ preferences: UUID[];
142
177
  }
143
- interface PreferenceSummary {
178
+ interface Preference {
144
179
  id: UUID;
180
+ content: string;
145
181
  topics: string[];
146
182
  timestamp: Timestamp;
147
- snippet?: string;
148
183
  }
149
- interface ContextSummary {
184
+ type ContextContent = {
185
+ kind: 'text';
186
+ value: string;
187
+ } | {
188
+ kind: 'json';
189
+ value: unknown;
190
+ } | {
191
+ kind: 'blob';
192
+ sha256: SHA256;
193
+ mediaType: BlobMediaType;
194
+ sizeBytes: number;
195
+ filename?: string;
196
+ } | {
197
+ kind: 'remote';
198
+ uri: URI;
199
+ mediaType?: BlobMediaType;
200
+ };
201
+ interface Context {
150
202
  key: string;
151
203
  topics: string[];
204
+ content: ContextContent;
152
205
  timestamp: Timestamp;
153
- mime?: string;
154
- size?: number;
155
- preview?: string;
156
- source?: string;
157
206
  metadata?: Record<string, any>;
158
207
  }
159
- interface TopicIndex {
160
- topic: string;
161
- contextKeys: string[];
162
- preferences: UUID[];
163
- metadata?: {
164
- created?: Timestamp;
165
- updated?: Timestamp;
166
- entries?: number;
167
- };
168
- }
169
- interface Indexes {
170
- sessions: Record<UUID, SessionSummary>;
171
- roles: Record<string, RoleSummary>;
172
- preferences: Record<UUID, PreferenceSummary>;
173
- context: Record<string, ContextSummary>;
174
- topics: Record<string, TopicIndex>;
175
- }
176
- interface Workspace {
177
- id: UUID;
178
- settings: Settings;
179
- project: Project;
180
- indexes: Indexes;
181
- }
182
- interface IndexState {
183
- format: string;
184
- workspace: Workspace;
185
- roles: Record<string, RoleSummary>;
186
- preferences: Record<UUID, PreferenceSummary>;
187
- context: Record<string, ContextSummary>;
188
- sessions: Record<UUID, SessionMeta>;
189
- blobs: Record<SHA256, BlobSummary>;
190
- }
208
+ /**
209
+ * SessionMeta is stored as a document in the 'session' collection.
210
+ * The head pointer lives here — no separate session_heads store needed.
211
+ */
191
212
  interface SessionMeta {
192
213
  id: UUID;
193
214
  label: string;
@@ -204,68 +225,76 @@ interface SessionMeta {
204
225
  version: number;
205
226
  } | null;
206
227
  }
207
- interface Role {
208
- id: UUID;
228
+ interface RoleSummary {
209
229
  name: string;
210
230
  label: string;
211
- persona: string;
212
231
  description?: string;
213
- preferences: UUID[];
232
+ /** Number of preference IDs listed on the role. */
233
+ preferences: number;
214
234
  }
215
- interface Preference {
235
+ interface PreferenceSummary {
216
236
  id: UUID;
217
- content: string;
218
237
  topics: string[];
219
238
  timestamp: Timestamp;
239
+ snippet?: string;
220
240
  }
221
- interface Context {
241
+ interface ContextSummary {
222
242
  key: string;
223
- content: string | object | URI;
224
243
  topics: string[];
225
244
  timestamp: Timestamp;
245
+ mime?: string;
246
+ size?: number;
247
+ preview?: string;
248
+ source?: string;
226
249
  metadata?: Record<string, any>;
227
- attachments?: BlobRef[];
228
250
  }
229
- interface Session {
230
- id: UUID;
231
- label: string;
232
- role: string;
233
- topics: string[];
251
+ interface TopicIndex {
252
+ topic: string;
253
+ contextKeys: string[];
234
254
  preferences: UUID[];
235
- turns: Turn[];
236
- metadata: {
255
+ metadata?: {
237
256
  created?: Timestamp;
238
257
  updated?: Timestamp;
258
+ entries?: number;
239
259
  };
240
260
  }
261
+ interface Index {
262
+ roles: Record<string, RoleSummary>;
263
+ preferences: Record<UUID, PreferenceSummary>;
264
+ context: Record<string, ContextSummary>;
265
+ sessions: Record<UUID, SessionMeta>;
266
+ topics: Record<string, TopicIndex>;
267
+ blobs: Record<SHA256, BlobRecord>;
268
+ }
269
+ interface Workspace {
270
+ id: UUID;
271
+ settings: Settings;
272
+ project: Project;
273
+ index: Index;
274
+ }
241
275
  interface WorkspaceBundle {
242
276
  format: 'aiworkspace/4.0';
243
277
  workspace: Workspace;
244
278
  roles: Record<string, Role>;
245
279
  preferences: Record<UUID, Preference>;
246
280
  context: Record<string, Context>;
247
- sessions: Record<UUID, Session>;
281
+ sessions: Record<UUID, SessionMeta & {
282
+ turns: Turn[];
283
+ }>;
248
284
  blobs: Record<SHA256, BlobRecord>;
249
285
  }
250
- interface TranscriptWindow {
251
- sessionId: UUID;
252
- turns: Turn[];
253
- flushedCount: number;
254
- hasMore: boolean;
255
- }
256
286
  interface EffectiveSession {
257
287
  session: SessionMeta;
258
288
  role: Role;
259
289
  preferences: Preference[];
260
290
  context: Context[];
261
291
  transcript: Turn[];
262
- systemPrompt?: string;
292
+ instructions?: string;
263
293
  }
264
294
  interface CacheConfig {
265
295
  roles?: number;
266
296
  preferences?: number;
267
- contextEntries?: number;
268
- transcriptWindows?: number;
297
+ context?: number;
269
298
  }
270
299
  interface FlushConfig {
271
300
  maxBufferSize: number;
@@ -274,7 +303,6 @@ interface FlushConfig {
274
303
  interface ContentStoreConfig {
275
304
  cache?: CacheConfig;
276
305
  flush?: FlushConfig;
277
- transcriptWindowSize?: number;
278
306
  }
279
307
  interface BaseCommand {
280
308
  type: string;
@@ -385,13 +413,6 @@ interface DeleteTurn extends BaseCommand {
385
413
  } | null;
386
414
  };
387
415
  }
388
- interface AddInteraction extends BaseCommand {
389
- type: 'turn:add';
390
- payload: {
391
- sessionId: UUID;
392
- turn: Turn;
393
- };
394
- }
395
416
  interface SwitchRole extends BaseCommand {
396
417
  type: 'session:role:switch';
397
418
  payload: {
@@ -431,14 +452,16 @@ interface DeleteSession extends BaseCommand {
431
452
  }
432
453
  interface RegisterBlob extends BaseCommand {
433
454
  type: 'blob:register';
434
- payload: BlobRecord;
455
+ payload: {
456
+ data: Uint8Array;
457
+ mediaType: BlobMediaType;
458
+ filename?: string;
459
+ };
435
460
  }
436
- interface RecordBlobRemoteId extends BaseCommand {
437
- type: 'blob:remote_id';
461
+ interface RetainBlob extends BaseCommand {
462
+ type: 'blob:retain';
438
463
  payload: {
439
464
  sha256: SHA256;
440
- providerId: string;
441
- fileId: string;
442
465
  };
443
466
  }
444
467
  interface ReleaseBlob extends BaseCommand {
@@ -447,7 +470,22 @@ interface ReleaseBlob extends BaseCommand {
447
470
  sha256: SHA256;
448
471
  };
449
472
  }
450
- type Command = CreateWorkspace | AddRole | UpdateRole | DeleteRole | AddPreference | UpdatePreference | DeletePreference | CreateSession | AddContext | UpdateContext | DeleteContext | AddTurn | EditTurn | BranchTurn | DeleteTurn | SwitchRole | AddSessionTopics | OverrideSessionPreferences | ForkSession | DeleteSession | RegisterBlob | RecordBlobRemoteId | ReleaseBlob;
473
+ interface PurgeBlob extends BaseCommand {
474
+ type: 'blob:purge';
475
+ payload: {
476
+ sha256: SHA256;
477
+ };
478
+ }
479
+ interface RecordBlobRemoteId extends BaseCommand {
480
+ type: 'blob:record_remote_id';
481
+ payload: {
482
+ sha256: SHA256;
483
+ providerId: string;
484
+ fileId: string;
485
+ };
486
+ }
487
+ type BlobCommand = RegisterBlob | RetainBlob | ReleaseBlob | PurgeBlob | RecordBlobRemoteId;
488
+ type Command = CreateWorkspace | AddRole | UpdateRole | DeleteRole | AddPreference | UpdatePreference | DeletePreference | CreateSession | AddContext | UpdateContext | DeleteContext | AddTurn | EditTurn | BranchTurn | DeleteTurn | SwitchRole | AddSessionTopics | OverrideSessionPreferences | ForkSession | DeleteSession | BlobCommand;
451
489
  interface TokenBudget {
452
490
  total: number;
453
491
  estimator?: (text: string) => number;
@@ -464,170 +502,524 @@ interface ContextRelevanceConfig {
464
502
  minScore?: number;
465
503
  freshnessHalfLifeDays?: number;
466
504
  }
467
- interface BuiltTurn {
468
- role: TurnRole;
469
- blocks: ContentBlock[];
470
- }
505
+ /**
506
+ * Prompt.transcript.turns is Turn[].
507
+ * Synthetic turns produced by PromptAssembler (summary blocks, truncation
508
+ * notices, referential attachments) carry generated UUIDs and version 0,
509
+ * with parent: null. They are ephemeral — never stored, never patched.
510
+ */
471
511
  interface Prompt {
472
- system: string | null;
473
- turns: BuiltTurn[];
474
- blobs: BlobRef[];
512
+ system: {
513
+ instructions?: string;
514
+ persona: string;
515
+ preferences: Preference[];
516
+ context: Context[];
517
+ };
518
+ context: Context[];
519
+ transcript: {
520
+ turns: Turn[];
521
+ };
522
+ budget: {
523
+ total: number;
524
+ used: number;
525
+ breakdown: Record<string, number>;
526
+ };
475
527
  truncated: {
476
528
  preferences: number;
477
529
  interactions: number;
478
- contextEntries: number;
530
+ context: number;
479
531
  };
480
- conflicts: PreferenceConflict[];
481
532
  warnings: string[];
533
+ conflicts: PreferenceConflict[];
482
534
  }
483
-
484
- interface ContentStorage {
485
- saveRole(role: Role): Promise<void>;
486
- loadRole(name: string): Promise<Role | null>;
487
- deleteRole(name: string): Promise<void>;
488
- savePreference(preference: Preference): Promise<void>;
489
- loadPreference(id: UUID): Promise<Preference | null>;
490
- deletePreference(id: UUID): Promise<void>;
491
- saveContext(context: Context): Promise<void>;
492
- loadContext(key: string): Promise<Context | null>;
493
- deleteContext(key: string): Promise<void>;
494
- saveTurn(sessionId: UUID, turn: Turn): Promise<void>;
495
- loadTurn(sessionId: UUID, turnId: UUID, version: number): Promise<Turn | null>;
496
- loadAllTurns(sessionId: UUID, limit?: number): Promise<Turn[]>;
497
- setSessionHead(sessionId: UUID, head: {
498
- id: UUID;
499
- version: number;
500
- } | null): Promise<void>;
501
- getSessionHead(sessionId: UUID): Promise<{
502
- id: UUID;
503
- version: number;
504
- } | null>;
505
- deleteTurnSubtree(sessionId: UUID, turnId: UUID, version: number): Promise<void>;
506
- getActiveChain(sessionId: UUID): Promise<Turn[]>;
507
- countChainedTurns(sessionId: UUID): Promise<number>;
508
- copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
535
+ interface TranscriptWindow {
536
+ sessionId: UUID;
537
+ turns: Turn[];
538
+ flushedCount: number;
539
+ hasMore: boolean;
509
540
  }
510
- declare class MemoryStorage implements ContentStorage {
511
- private readonly roles;
512
- private readonly preferences;
513
- private readonly contexts;
514
- private readonly turnVersions;
515
- private readonly sessionHeads;
516
- saveRole(role: Role): Promise<void>;
517
- loadRole(name: string): Promise<Role | null>;
518
- deleteRole(name: string): Promise<void>;
519
- savePreference(preference: Preference): Promise<void>;
520
- loadPreference(id: UUID): Promise<Preference | null>;
521
- deletePreference(id: UUID): Promise<void>;
522
- saveContext(context: Context): Promise<void>;
523
- loadContext(key: string): Promise<Context | null>;
524
- deleteContext(key: string): Promise<void>;
525
- saveTurn(sessionId: UUID, turn: Turn): Promise<void>;
526
- loadTurn(sessionId: UUID, turnId: UUID, version: number): Promise<Turn | null>;
527
- loadAllTurns(sessionId: UUID, limit?: number): Promise<Turn[]>;
528
- setSessionHead(sessionId: UUID, head: {
529
- id: UUID;
530
- version: number;
531
- } | null): Promise<void>;
532
- getSessionHead(sessionId: UUID): Promise<{
533
- id: UUID;
534
- version: number;
535
- } | null>;
536
- getActiveChain(sessionId: UUID): Promise<Turn[]>;
537
- deleteTurnSubtree(sessionId: UUID, turnId: UUID, version: number): Promise<void>;
538
- countChainedTurns(sessionId: UUID): Promise<number>;
539
- copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
540
- static fromBundle(bundle: WorkspaceBundle): {
541
- backend: MemoryStorage;
542
- indexState: IndexState;
543
- };
541
+
542
+ declare class TransactionContext {
543
+ readonly id: string;
544
+ private buffer;
545
+ private committed;
546
+ constructor();
547
+ addOp(store: Store<any>, type: "put" | "delete", data: any): void;
548
+ commit(): Promise<void>;
549
+ rollback(): void;
544
550
  }
545
551
 
552
+ interface CursorCallbackResult<T> {
553
+ value: T | null;
554
+ done: boolean;
555
+ offset?: number;
556
+ }
546
557
  /**
547
- * Utility type for representing partial updates to the state, allowing deep nesting.
548
- * It makes all properties optional and applies the same transformation recursively
549
- * to nested objects and array elements, allowing for selective updates while
550
- * preserving the original structure. It also includes the original type T and
551
- * undefined as possibilities for the top level and nested values.
558
+ * Callback function for cursor iteration over store records.
559
+ *
560
+ * @template T - The type of records stored.
561
+ * @param value - The current record value (cloned, not a live reference).
562
+ * @param key - The key (ID) of the current record.
563
+ * @param cursor - The underlying cursor object (implementation‑specific; may be `null` in memory adapters).
564
+ * @returns A promise that resolves to an object indicating whether iteration should stop, and an optional offset to advance.
552
565
  */
553
- type DeepPartial<T> = T extends object ? T extends readonly (infer U)[] ? readonly (DeepPartial<U> | undefined)[] | undefined | T : T extends (infer U)[] ? (DeepPartial<U> | undefined)[] | undefined | T : {
554
- [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> | undefined : T[K] | undefined;
555
- } | undefined | T : T | undefined;
556
-
557
- declare function del<T>(): T;
558
- declare const merge: <T extends object>(original: T, changes: DeepPartial<T> | symbol) => T;
559
-
560
- interface IndexedDBConfig {
561
- dbName?: string;
562
- }
563
- declare class IndexedDBStorage implements ContentStorage {
564
- private readonly dbName;
565
- private db;
566
- constructor(config?: IndexedDBConfig);
567
- open(): Promise<void>;
568
- private createSchema;
569
- close(): void;
570
- deleteDatabase(): Promise<void>;
571
- private getDB;
572
- private readTx;
573
- private writeTx;
574
- saveRole(role: Role): Promise<void>;
575
- loadRole(name: string): Promise<Role | null>;
576
- deleteRole(name: string): Promise<void>;
577
- savePreference(preference: Preference): Promise<void>;
578
- loadPreference(id: UUID): Promise<Preference | null>;
579
- deletePreference(id: UUID): Promise<void>;
580
- saveContext(context: Context): Promise<void>;
581
- loadContext(key: string): Promise<Context | null>;
582
- deleteContext(key: string): Promise<void>;
583
- saveTurn(sessionId: UUID, turn: Turn): Promise<void>;
584
- loadAllTurns(sessionId: UUID, limit?: number): Promise<Turn[]>;
585
- countChainedTurns(sessionId: UUID): Promise<number>;
586
- loadTurn(sessionId: UUID, turnId: UUID, version: number): Promise<Turn | null>;
587
- setSessionHead(sessionId: UUID, head: {
588
- id: UUID;
589
- version: number;
590
- } | null): Promise<void>;
591
- getSessionHead(sessionId: UUID): Promise<{
592
- id: UUID;
593
- version: number;
594
- } | null>;
595
- getActiveChain(sessionId: UUID): Promise<Turn[]>;
596
- deleteTurnSubtree(sessionId: UUID, turnId: UUID, version: number): Promise<void>;
597
- copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
598
- saveIndexState(state: IndexState): Promise<void>;
599
- loadIndexState(): Promise<IndexState | null>;
600
- exportBundle(): Promise<{
601
- roles: Record<string, Role>;
602
- preferences: Record<UUID, Preference>;
603
- context: Record<string, Context>;
604
- transcripts: Record<UUID, Turn[]>;
605
- indexState: IndexState | null;
606
- }>;
607
- importBundle(data: {
608
- roles: Record<string, Role>;
609
- preferences: Record<UUID, Preference>;
610
- context: Record<string, Context>;
611
- transcripts: Record<UUID, Turn[]>;
612
- indexState?: IndexState;
613
- }): Promise<void>;
614
- private getAllFromStore;
566
+ type CursorCallback<T> = (value: T, key: string | number, cursor: any) => Promise<CursorCallbackResult<T>>;
567
+ /**
568
+ * A generic representation of a key range, replacing the browser-specific IDBKeyRange.
569
+ */
570
+ interface StoreKeyRange {
571
+ lower?: any;
572
+ upper?: any;
573
+ lowerOpen?: boolean;
574
+ upperOpen?: boolean;
615
575
  }
616
-
617
576
  /**
618
- * Persistence contract for binary blob content and blob registry records.
577
+ * Storage adapter interface for a single object store (collection).
619
578
  *
620
- * Two concerns live here together deliberately:
621
- * 1. Raw bytes stored and retrieved by SHA-256.
622
- * 2. BlobRecord the registry entry that tracks ref counts and remote IDs.
579
+ * This interface abstracts all low‑level persistence operations, allowing
580
+ * different backends (IndexedDB, memory, remote, etc.) to be used interchangeably.
581
+ * All methods return promises and operate on clones of data to prevent
582
+ * unintended mutations.
623
583
  *
624
- * Keeping them in the same backend ensures atomicity: registering a blob and
625
- * storing its bytes happen in the same transaction boundary.
584
+ * @template T - The type of objects stored in this store. Must include the key path property.
626
585
  */
627
- interface BlobStorage {
586
+ interface Store<T = any> {
628
587
  /**
629
- * Store raw bytes for a blob. Idempotent — if the SHA-256 already exists
630
- * the bytes are not re-written (content-addressed, so they are identical).
588
+ * Adds one or more records to the store.
589
+ *
590
+ * If a record does not have a value for the store's key path, an automatic
591
+ * key (e.g., auto‑incremented number) may be assigned. The store's key path
592
+ * property is then updated on the added record(s).
593
+ *
594
+ * @param data - A single record or an array of records to add.
595
+ * @returns A promise that resolves to:
596
+ * - the key(s) of the added record(s) – a single key if `data` was a single record,
597
+ * or an array of keys if `data` was an array.
598
+ * @throws {Error} If any record lacks the key path property and auto‑keying is not supported,
599
+ * or if a record with the same key already exists.
600
+ */
601
+ add(data: T | T[]): Promise<string | number | (string | number)[]>;
602
+ /**
603
+ * Removes all records from the store.
604
+ *
605
+ * @returns A promise that resolves when the store is cleared.
606
+ * @throws {Error} If the operation fails (e.g., store is closed).
607
+ */
608
+ clear(): Promise<void>;
609
+ /**
610
+ * Returns the total number of records in the store.
611
+ *
612
+ * @returns A promise that resolves to the record count.
613
+ * @throws {Error} If the operation fails.
614
+ */
615
+ count(): Promise<number>;
616
+ /**
617
+ * Deletes one or more records by their keys.
618
+ *
619
+ * @param id - A single key or an array of keys to delete.
620
+ * @returns A promise that resolves when the records are deleted.
621
+ * @throws {Error} If any key is `undefined` or the operation fails.
622
+ */
623
+ delete(id: string | number | (string | number)[]): Promise<void>;
624
+ /**
625
+ * Retrieves a single record by its primary key.
626
+ *
627
+ * @param id - The key of the record to retrieve.
628
+ * @returns A promise that resolves to the record (cloned) if found, otherwise `undefined`.
629
+ * @throws {Error} If the key is `undefined` or the operation fails.
630
+ */
631
+ getById(id: string | number): Promise<T | undefined>;
632
+ /**
633
+ * Retrieves a single record by an index and index key.
634
+ *
635
+ * @param index - The name of the index to query.
636
+ * @param key - The exact key value to look up in the index.
637
+ * @returns A promise that resolves to the first matching record (cloned), or `undefined` if none.
638
+ * @throws {Error} If the index does not exist, the key is `undefined`, or the operation fails.
639
+ */
640
+ getByIndex(index: string, key: any): Promise<T | undefined>;
641
+ /**
642
+ * Retrieves multiple records from an index, optionally within a key range.
643
+ *
644
+ * @param index - The name of the index to query.
645
+ * @param keyRange - Optional `StoreKeyRange` to filter results. If omitted, all records are returned.
646
+ * @returns A promise that resolves to an array of matching records (each cloned).
647
+ * @throws {Error} If the index does not exist or the operation fails.
648
+ */
649
+ getByKeyRange(index: string, keyRange?: StoreKeyRange): Promise<T[]>;
650
+ /**
651
+ * Retrieves all records from the store.
652
+ *
653
+ * @returns A promise that resolves to an array of all records (each cloned).
654
+ * @throws {Error} If the operation fails.
655
+ */
656
+ getAll(): Promise<T[]>;
657
+ /**
658
+ * Inserts or replaces a record.
659
+ *
660
+ * If a record with the same key already exists, it is replaced.
661
+ * The record must contain the store's key path property.
662
+ *
663
+ * @param data - The record to store.
664
+ * @returns A promise that resolves to the key of the stored record.
665
+ * @throws {Error} If the record lacks the key path property or the operation fails.
666
+ */
667
+ put(data: T): Promise<string | number>;
668
+ /**
669
+ * Iterates over records using a cursor, allowing early termination and skipping.
670
+ *
671
+ * The callback is invoked for each record in iteration order. The callback
672
+ * can control the iteration by returning `{ done: true }` to stop, or
673
+ * `{ offset: n }` to skip ahead `n` records.
674
+ *
675
+ * @param callback - Function called for each record.
676
+ * @param direction - Iteration direction: `"forward"` (ascending keys) or `"backward"` (descending keys).
677
+ * @param keyRange - An optional StoreKeyRange to start from specific points.
678
+ * @returns A promise that resolves to the last record processed (or `null` if none).
679
+ * @throws {Error} If the callback throws or the operation fails.
680
+ */
681
+ cursor(callback: CursorCallback<T>, direction?: "forward" | "backward", keyRange?: StoreKeyRange): Promise<T | null>;
682
+ /**
683
+ * Executes a batch of write operations atomically.
684
+ *
685
+ * All operations in the batch succeed or fail together. This is useful for
686
+ * maintaining consistency when multiple writes are required.
687
+ *
688
+ * @param operations - An array of operations. Each operation can be:
689
+ * - `{ type: "add" | "put", data: T | T[] }`
690
+ * - `{ type: "delete", data: string | number | (string | number)[] }`
691
+ * @returns A promise that resolves when the batch is committed.
692
+ * @throws {Error} If any operation fails or the batch cannot be completed.
693
+ */
694
+ batch(operations: Array<{
695
+ type: "add" | "put";
696
+ data: T | T[];
697
+ } | {
698
+ type: "delete";
699
+ data: string | number | (string | number)[];
700
+ }>): Promise<void>;
701
+ open(): Promise<void>;
702
+ }
703
+ interface Collection<T> {
704
+ /**
705
+ * Finds a single document matching the query.
706
+ * @param query - The query to execute.
707
+ * @returns A promise resolving to the matching document or `null` if not found.
708
+ */
709
+ find: (query: QueryFilter<T>) => Promise<Document<T> | null>;
710
+ /**
711
+ * Lists documents based on the provided query.
712
+ * @param query - The query to list documents (supports pagination and sorting).
713
+ * @returns A promise resolving to an array of documents.
714
+ */
715
+ list: (query: PaginationOptions) => Promise<AsyncIterator<Document<T>[]>>;
716
+ /**
717
+ * Filters documents based on the provided query.
718
+ * @param query - The query to filter documents.
719
+ * @returns A promise resolving to an array of matching documents.
720
+ */
721
+ filter: (query: QueryFilter<T>) => Promise<Document<T>[]>;
722
+ /**
723
+ * Creates a new document in the schema.
724
+ * @param initial - The initial data for the document.
725
+ * @returns A promise resolving to the created document.
726
+ */
727
+ create: (initial: T) => Promise<Document<T>>;
728
+ /**
729
+ * Subscribes to schema-level events (e.g., "create", "update", "delete", "access").
730
+ * @param event - The event type to subscribe to.
731
+ * @param callback - The function to call when the event occurs.
732
+ * @returns A promise resolving to an unsubscribe function.
733
+ */
734
+ subscribe: (event: CollectionEventType | TelemetryEventType, callback: (event: CollectionEvent<T> | TelemetryEvent) => void) => Promise<() => void>;
735
+ /**
736
+ * Validate data
737
+ * @param data - The data to validate
738
+ * @returns An object containing validation results
739
+ */
740
+ validate(data: Record<string, any>): Promise<{
741
+ value?: any;
742
+ issues: Array<{
743
+ message: string;
744
+ path: Array<string>;
745
+ }>;
746
+ }>;
747
+ }
748
+ /**
749
+ * Event payload for DocumentCursor events.
750
+ */
751
+ type CollectionEventType = "document:create" | "collection:read" | "migration:start" | "migration:end";
752
+ type CollectionEvent<T> = {
753
+ type: CollectionEventType;
754
+ document?: T;
755
+ model?: string;
756
+ method?: keyof Collection<T>;
757
+ metadata?: Record<string, unknown>;
758
+ timestamp: number;
759
+ };
760
+ interface Database {
761
+ /**
762
+ * Accesses a schema model by name.
763
+ * @param schemaName - The name of the schema to access.
764
+ * @returns A promise resolving to the schema's DocumentCursor.
765
+ * @throws DatabaseError
766
+ */
767
+ collection: <T>(schemaName: string) => Promise<Collection<T>>;
768
+ /**
769
+ * Creates a new schema model.
770
+ * @param schema - The schema definition.
771
+ * @returns A promise resolving to the created schema's DocumentCursor.
772
+ * @throws DatabaseError
773
+ */
774
+ createCollection: <T>(schema: SchemaDefinition) => Promise<Collection<T>>;
775
+ /**
776
+ * Deletes a schema by name.
777
+ * @param schemaName - The name of the schema to delete.
778
+ * @returns A promise resolving to `true` if successful, or `false` if an error occurs.
779
+ * @throws DatabaseError
780
+ */
781
+ deleteCollection: (schemaName: string) => Promise<boolean>;
782
+ /**
783
+ * Updates an existing schema.
784
+ * @param schema - The updated schema definition.
785
+ * @returns A promise resolving to `true` if successful, or `false` if an error occurs.
786
+ * @throws DatabaseError
787
+ */
788
+ updateCollection: (schema: SchemaDefinition) => Promise<boolean>;
789
+ /**
790
+ * Migrates an existing collection's data and updates its schema definition metadata.
791
+ * This function processes data in a streaming fashion to prevent loading
792
+ * the entire collection into memory.
793
+ *
794
+ * It will:
795
+ * 1. Verify the target collection exists.
796
+ * 2. Retrieve the collection's current schema definition from a metadata store ($index).
797
+ * 3. Initialize a `MigrationEngine` with this schema.
798
+ * 4. Allow a callback to define specific data transformations using the `MigrationEngine`.
799
+ * 5. **Crucially, it uses `migrationEngine.dryRun()` to get the `newSchema` that results**
800
+ * **from the transformations defined in the callback.**
801
+ * 6. Execute these transformations by streaming data from the collection,
802
+ * through the `MigrationEngine`, and back into the same collection.
803
+ * 7. Finally, update the schema definition for the collection in the `$index` metadata store
804
+ * to reflect this `newSchema`.
805
+ * All these steps for data and metadata updates happen within a single atomic IndexedDB transaction.
806
+ *
807
+ * Note: This function focuses solely on *data transformation* and *metadata updates*.
808
+ * It does NOT handle structural IndexedDB changes like adding/removing physical indexes or object stores,
809
+ * which still require an `onupgradeneeded` event (i.e., a database version upgrade).
810
+ *
811
+ * @param name - The name of the collection (IndexedDB object store) to migrate.
812
+ * @param {Object} opts - Options for the new migration
813
+ * @param {SchemaChange<any>[]} opts.changes - Array of schema changes
814
+ * @param {string} opts.description - Description of the migration
815
+ * @param {SchemaChange<any>[]} [opts.rollback] - Optional rollback changes
816
+ * @param {DataTransform<any, any>} [opts.transform] - Optional data transform
817
+ * @returns A Promise resolving to `true` if the migration completes successfully,
818
+ * @throws {DatabaseError} If the collection does not exist, its schema metadata is missing,
819
+ * or any IndexedDB operation/streaming fails critically.
820
+ */
821
+ migrateCollection: (name: string, opts: CollectionMigrationOptions, batchSize?: number) => Promise<boolean>;
822
+ /**
823
+ * Subscribes to database-level events (e.g., "schemaAdded", "schemaDeleted", "schemaAccessed", "migrate").
824
+ * @param event - The event type to subscribe to.
825
+ * @param callback - The function to call when the event occurs.
826
+ * @returns A promise resolving to an unsubscribe function.
827
+ */
828
+ subscribe: (event: DatabaseEventType | "telemetry", callback: (event: DatabaseEvent | TelemetryEvent) => void) => Promise<() => void>;
829
+ /**
830
+ * Closes the connection to the database
831
+ */
832
+ close: () => void;
833
+ /**
834
+ * Ensures a collection exists; creates it if it doesn't.
835
+ * Idempotent – safe to call multiple times.
836
+ * @param schema - The schema definition for the collection.
837
+ * @returns A promise that resolves when the collection exists.
838
+ * @throws {DatabaseError} If validation fails or an unexpected error occurs.
839
+ */
840
+ ensureCollection: (schema: SchemaDefinition) => Promise<void>;
841
+ /**
842
+ * Ensures multiple collections exist; creates any that don't.
843
+ * Idempotent – safe to call multiple times.
844
+ * @param schemas - An array of schema definitions.
845
+ * @returns A promise that resolves when all collections exist.
846
+ * @throws {DatabaseError} If any schema validation fails or an unexpected error occurs.
847
+ */
848
+ setupCollections: (schemas: SchemaDefinition[]) => Promise<void>;
849
+ }
850
+ type CollectionMigrationOptions = {
851
+ changes: SchemaChange<any>[];
852
+ description: string;
853
+ rollback?: SchemaChange<any>[];
854
+ transform?: string | DataTransform<any, any>;
855
+ };
856
+ /**
857
+ * Event payload for Database events.
858
+ */
859
+ type DatabaseEventType = "collection:create" | "collection:delete" | "collection:update" | "collection:read" | "migrate";
860
+ type DatabaseEvent = {
861
+ type: DatabaseEventType;
862
+ schema?: SchemaDefinition | Partial<SchemaDefinition>;
863
+ timestamp: number;
864
+ };
865
+ type Document<T> = {
866
+ readonly [K in keyof T]: T[K];
867
+ } & {
868
+ /**
869
+ * Unique identifier for the document (assigned automatically if not provided).
870
+ */
871
+ $id?: string;
872
+ /**
873
+ * Timestamp of document creation (ISO string or Date).
874
+ */
875
+ $created?: string | Date;
876
+ /**
877
+ * Timestamp of last document update (ISO string or Date).
878
+ */
879
+ $updated?: string | Date;
880
+ /**
881
+ * Version number incremented on each change (used for optimistic concurrency control).
882
+ */
883
+ $version?: number;
884
+ /**
885
+ * Fetches the latest data from the database and updates the document instance.
886
+ * @returns A promise resolving to `true` if successful, or `false` if an error occurs.
887
+ */
888
+ read: () => Promise<boolean>;
889
+ /**
890
+ * Saves the current document state to the database.
891
+ * Normally called automatically by `update()` or `delete()`, but can be used manually.
892
+ * @returns A promise resolving to `true` if successful, or `false` if an error occurs.
893
+ */
894
+ save: (tx?: TransactionContext) => Promise<boolean>;
895
+ /**
896
+ * Updates the document with the provided properties.
897
+ * @param props - Partial object containing the fields to update.
898
+ * @returns A promise resolving to `true` if successful, or `false` if an error occurs.
899
+ */
900
+ update: (props: Partial<T>) => Promise<boolean>;
901
+ /**
902
+ * Deletes the document from the database.
903
+ * @returns A promise resolving to `true` if successful, or `false` if an error occurs.
904
+ */
905
+ delete: () => Promise<boolean>;
906
+ /**
907
+ * Subscribes to document events (e.g., "update", "delete", "access").
908
+ * @param event - The event type to subscribe to.
909
+ * @param callback - The function to call when the event occurs.
910
+ * @returns An unsubscribe function.
911
+ */
912
+ subscribe: (event: DocumentEventType | TelemetryEventType, callback: (event: DocumentEvent<T> | TelemetryEvent) => void) => () => void;
913
+ /**
914
+ * Returns a plain object containing only the user-defined data (without system fields like $id, $version, etc.).
915
+ * @returns The current user data.
916
+ */
917
+ state(): T;
918
+ };
919
+ /**
920
+ * Event payload for DocumentModel events.
921
+ */
922
+ type DocumentEventType = "document:create" | "document:write" | "document:update" | "document:delete" | "document:read";
923
+ type DocumentEvent<T> = {
924
+ type: DocumentEventType;
925
+ data?: Partial<T>;
926
+ timestamp: number;
927
+ };
928
+ type TelemetryEventType = "telemetry";
929
+ type TelemetryEvent = {
930
+ type: TelemetryEventType;
931
+ method: string;
932
+ timestamp: number;
933
+ source: any;
934
+ metadata: {
935
+ args: any[];
936
+ performance: {
937
+ durationMs: number;
938
+ };
939
+ source: {
940
+ level: "database" | "collection" | "document";
941
+ collection?: string;
942
+ document?: string;
943
+ };
944
+ result?: {
945
+ type: 'array' | string;
946
+ size?: number;
947
+ };
948
+ error: {
949
+ message: string;
950
+ name: string;
951
+ stack?: string;
952
+ } | null;
953
+ };
954
+ };
955
+
956
+ interface WorkspaceDatabase {
957
+ /**
958
+ * Open the database. Registers core schemas followed by any extension
959
+ * schemas supplied by the caller (domain plugins).
960
+ *
961
+ * Idempotent — safe to call multiple times; subsequent calls are no-ops
962
+ * if the database is already open.
963
+ */
964
+ open(extensionSchemas?: SchemaDefinition[]): Promise<void>;
965
+ /**
966
+ * Access a collection by schema name.
967
+ * Throws if the schema has not been registered.
968
+ */
969
+ collection<T>(schemaName: string): Promise<Collection<T>>;
970
+ /**
971
+ * Close the database connection.
972
+ */
973
+ close(): void;
974
+ }
975
+ declare function createWorkspaceDatabase(db: Database): WorkspaceDatabase;
976
+ declare const COLLECTIONS: {
977
+ readonly ROLE: "role";
978
+ readonly PREFERENCE: "preference";
979
+ readonly CONTEXT: "context";
980
+ readonly SESSION: "session";
981
+ readonly TURN: "turn";
982
+ };
983
+ type CollectionName = typeof COLLECTIONS[keyof typeof COLLECTIONS];
984
+
985
+ /**
986
+ * Utility type for representing partial updates to the state, allowing deep nesting.
987
+ * It makes all properties optional and applies the same transformation recursively
988
+ * to nested objects and array elements, allowing for selective updates while
989
+ * preserving the original structure. It also includes the original type T and
990
+ * undefined as possibilities for the top level and nested values.
991
+ */
992
+ type DeepPartial<T> = T extends object ? T extends readonly (infer U)[] ? readonly (DeepPartial<U> | undefined)[] | undefined | T : T extends (infer U)[] ? (DeepPartial<U> | undefined)[] | undefined | T : {
993
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> | undefined : T[K] | undefined;
994
+ } | undefined | T : T | undefined;
995
+
996
+ declare function del<T>(): T;
997
+ declare const merge: <T extends object>(original: T, changes: DeepPartial<T> | symbol) => T;
998
+ declare function ok<T>(value: T): Result<T, never>;
999
+ declare function err<E = WorkspaceError>(error: E): Result<never, E>;
1000
+ declare function omitNullUndefined<T extends Record<string, any>>(obj: T): Partial<T>;
1001
+ declare function createSimpleWorkspace({ name, owner, language }: {
1002
+ name: string;
1003
+ language: string;
1004
+ owner: string;
1005
+ }): Workspace;
1006
+ declare function extractBlobRecord(patch: DeepPartial<Workspace>): BlobRecord | null;
1007
+ declare function extractBlobRef(record: BlobRecord): BlobRef;
1008
+
1009
+ /**
1010
+ * Persistence contract for binary blob content and blob registry records.
1011
+ *
1012
+ * Two concerns live here together deliberately:
1013
+ * 1. Raw bytes — stored and retrieved by SHA-256.
1014
+ * 2. BlobRecord — the registry entry that tracks ref counts and remote IDs.
1015
+ *
1016
+ * Keeping them in the same backend ensures atomicity: registering a blob and
1017
+ * storing its bytes happen in the same transaction boundary.
1018
+ */
1019
+ interface BlobStorage {
1020
+ /**
1021
+ * Store raw bytes for a blob. Idempotent — if the SHA-256 already exists
1022
+ * the bytes are not re-written (content-addressed, so they are identical).
631
1023
  */
632
1024
  storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
633
1025
  /** Load raw bytes. Returns null if the blob is not stored locally. */
@@ -652,65 +1044,40 @@ interface BlobStorage {
652
1044
  * Used for workspace export / migration.
653
1045
  */
654
1046
  exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
1047
+ /**
1048
+ * Optional atomic operation: store bytes and save record in a single
1049
+ * transaction. Backends that support this (e.g. IndexedDB) should
1050
+ * implement it to avoid a window where bytes exist without a record.
1051
+ * BlobStore will prefer this over calling storeBytes + saveRecord separately.
1052
+ */
1053
+ registerBlob?(record: BlobRecord, data: Uint8Array): Promise<void>;
655
1054
  }
656
- /**
657
- * In-process implementation backed by plain Maps.
658
- * Suitable for development, testing, and server-side runtimes without
659
- * access to IndexedDB.
660
- */
661
- declare class MemoryBlobStorage implements BlobStorage {
662
- private readonly bytes;
663
- private readonly records;
664
- storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
665
- loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
666
- hasBytes(sha256: SHA256): Promise<boolean>;
667
- deleteBytes(sha256: SHA256): Promise<void>;
668
- saveRecord(record: BlobRecord): Promise<void>;
669
- loadRecord(sha256: SHA256): Promise<BlobRecord | null>;
670
- deleteRecord(sha256: SHA256): Promise<void>;
671
- listRecords(): Promise<BlobRecord[]>;
672
- exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
673
- }
674
- interface IndexedDBBlobConfig {
675
- /** Name of the IndexedDB database. Defaults to 'aiworkspace-blobs'. */
676
- dbName?: string;
1055
+
1056
+ interface TurnRef {
1057
+ id: UUID;
1058
+ version: number;
677
1059
  }
678
- /**
679
- * IndexedDB-backed blob storage.
680
- *
681
- * Bytes are stored as raw Uint8Array — IndexedDB handles binary natively,
682
- * no base64 encoding at rest. This is important for large files.
683
- *
684
- * Transaction discipline matches IndexedDBBlobStorage: never await inside an
685
- * active transaction. All multi-step operations chain synchronous IDB
686
- * request handlers inside a single Promise.
687
- */
688
- declare class IndexedDBBlobStorage implements BlobStorage {
689
- private readonly dbName;
690
- private db;
691
- constructor(config?: IndexedDBBlobConfig);
692
- open(): Promise<void>;
693
- private createSchema;
694
- close(): void;
695
- deleteDatabase(): Promise<void>;
696
- private getDB;
697
- private readTx;
698
- private writeTx;
699
- storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
700
- loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
701
- hasBytes(sha256: SHA256): Promise<boolean>;
702
- deleteBytes(sha256: SHA256): Promise<void>;
703
- saveRecord(record: BlobRecord): Promise<void>;
704
- loadRecord(sha256: SHA256): Promise<BlobRecord | null>;
705
- deleteRecord(sha256: SHA256): Promise<void>;
706
- listRecords(): Promise<BlobRecord[]>;
707
- exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
1060
+ declare class TurnTree {
1061
+ private readonly db;
1062
+ constructor(db: WorkspaceDatabase);
1063
+ private turns;
1064
+ private sessions;
708
1065
  /**
709
- * Atomically stores bytes and saves a record together.
710
- * Preferred over calling storeBytes + saveRecord separately when both
711
- * are new — avoids a window where bytes exist without a record.
1066
+ * Compound filter for a specific (sessionId, id, version) tuple.
1067
+ * Uses only declared schema fields never $id.
712
1068
  */
713
- registerBlob(record: BlobRecord, data: Uint8Array): Promise<void>;
1069
+ private turnFilter;
1070
+ getHead(sessionId: UUID): Promise<TurnRef | null>;
1071
+ setHead(sessionId: UUID, head: TurnRef | null): Promise<void>;
1072
+ append(sessionId: UUID, turn: Turn): Promise<Turn>;
1073
+ appendBatch(sessionId: UUID, turns: Turn[], finalHead: TurnRef | null): Promise<void>;
1074
+ replaceVersion(sessionId: UUID, newTurn: Turn): Promise<void>;
1075
+ branch(sessionId: UUID, newTurn: Turn): Promise<void>;
1076
+ loadAllTurns(sessionId: UUID): Promise<Turn[]>;
1077
+ getActiveChain(sessionId: UUID, dirtyBuffer?: readonly Turn[]): Promise<Turn[]>;
1078
+ buildNodeGraph(sessionId: UUID, dirtyBuffer?: readonly Turn[]): Promise<Record<UUID, TurnNode>>;
1079
+ deleteSubtree(sessionId: UUID, turnId: UUID, version: number, newHead: TurnRef | null): Promise<void>;
1080
+ copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
714
1081
  }
715
1082
 
716
1083
  declare function computeSHA256(data: Uint8Array): Promise<SHA256>;
@@ -718,13 +1085,12 @@ interface BlobStoreConfig {
718
1085
  /**
719
1086
  * When true, blobs with refCount === 0 are deleted from the backend
720
1087
  * immediately on release. When false (default), they are retained until
721
- * an explicit GC sweep via gc(). Lazy GC is safer for offline-first apps
722
- * that may need blobs for re-upload after a crash.
1088
+ * an explicit GC sweep via gc(). Lazy GC is safer for offline-first apps.
723
1089
  */
724
1090
  eagerEviction?: boolean;
725
1091
  }
726
1092
  declare class BlobStore {
727
- private readonly backend;
1093
+ private readonly storage;
728
1094
  private readonly config;
729
1095
  /**
730
1096
  * In-memory cache of BlobRecords. Kept consistent with the backend on
@@ -733,61 +1099,22 @@ declare class BlobStore {
733
1099
  private readonly recordCache;
734
1100
  /**
735
1101
  * Called after any operation that changes the blob registry.
736
- * The integration layer wires this to an IndexState patch that updates
737
- * IndexState.blobs with the new BlobSummary.
1102
+ * Passes the full BlobRecord (or null on deletion). The integration
1103
+ * layer wires this to an Index patch that updates the blob index.
1104
+ *
1105
+ * BlobRecord is a superset of the former BlobSummary — callers
1106
+ * that only need summary fields (sha256, mediaType, sizeBytes, etc.)
1107
+ * can read them directly from BlobRecord without a separate type.
738
1108
  */
739
- onRegistryChanged?: (sha256: SHA256, summary: BlobSummary | null) => void;
1109
+ onRegistryChanged?: (sha256: SHA256, record: BlobRecord | null) => void;
740
1110
  constructor(backend: BlobStorage, config?: BlobStoreConfig);
741
1111
  init(): Promise<void>;
742
- /**
743
- * Register a binary blob and return a BlobRef.
744
- *
745
- * If the same bytes have been registered before (same SHA-256), only the
746
- * ref count is incremented — bytes are not written again.
747
- *
748
- * @param data Raw binary content.
749
- * @param mediaType MIME type of the content.
750
- * @param filename Optional original filename for UI display.
751
- */
752
1112
  register(data: Uint8Array, mediaType: BlobMediaType, filename?: string): Promise<Result<BlobRef, WorkspaceError>>;
753
- /**
754
- * Increment the ref count for a blob that is being referenced by a new
755
- * turn or context entry. Safe to call multiple times for the same sha256.
756
- */
757
1113
  retain(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
758
- /**
759
- * Decrement the ref count. When it reaches 0, the blob is eligible for
760
- * eviction. With eagerEviction=true, bytes are deleted immediately.
761
- * With eagerEviction=false, bytes are retained until gc() is called.
762
- */
763
1114
  release(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
764
- /**
765
- * Record that a blob has been uploaded to a provider and assigned a file ID.
766
- * Idempotent — calling with the same providerId + fileId has no effect.
767
- */
768
1115
  recordRemoteId(sha256: SHA256, providerId: string, fileId: string): Promise<Result<void, WorkspaceError>>;
769
- /**
770
- * Get the remote file ID for a blob on a specific provider, if known.
771
- */
772
1116
  getRemoteId(sha256: SHA256, providerId: string): string | null;
773
- /**
774
- * Resolve a BlobRef to a ResolvedBlob.
775
- *
776
- * If a remote file ID is known for the given providerId, returns
777
- * { kind: 'remote' } — no bytes transferred to the model call.
778
- * Otherwise loads bytes from the backend and returns { kind: 'inline' }.
779
- *
780
- * @param ref The BlobRef to resolve.
781
- * @param providerId The provider the resolved blob will be sent to
782
- * (e.g. 'anthropic', 'openai'). Used to look up
783
- * known remote file IDs. Pass null for local-only use.
784
- */
785
1117
  resolveRef(ref: BlobRef, providerId: string | null): Promise<Result<ResolvedBlob, WorkspaceError>>;
786
- /**
787
- * Resolve multiple BlobRefs, collecting all results.
788
- * Returns a map of sha256 → ResolvedBlob for successes and a list of
789
- * errors for failures, so partial failures don't block the whole build.
790
- */
791
1118
  resolveRefs(refs: BlobRef[], providerId: string | null): Promise<{
792
1119
  resolved: Map<SHA256, ResolvedBlob>;
793
1120
  errors: Array<{
@@ -795,74 +1122,84 @@ declare class BlobStore {
795
1122
  error: WorkspaceError;
796
1123
  }>;
797
1124
  }>;
798
- /**
799
- * Delete bytes for all blobs with refCount === 0.
800
- * Records are kept so re-registration is cheap (no re-hash needed).
801
- * Call this periodically or on app startup to reclaim storage.
802
- *
803
- * Returns the number of blobs whose bytes were evicted.
804
- */
805
1125
  gc(): Promise<number>;
806
- /**
807
- * Fully purge a blob — delete both bytes and record regardless of refCount.
808
- * Only use this for explicit user-initiated deletion or during workspace
809
- * destruction. In normal operation, use release() and let GC handle it.
810
- */
1126
+ gcFull(): Promise<number>;
811
1127
  purge(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
812
1128
  getRecord(sha256: SHA256): BlobRecord | null;
813
- getSummary(sha256: SHA256): BlobSummary | null;
814
- /** All summaries — used to seed IndexState.blobs on init. */
815
- getAllSummaries(): Record<SHA256, BlobSummary>;
1129
+ /**
1130
+ * All records — used to seed Index.blobs on init.
1131
+ * Returns BlobRecord directly (BlobSummary has been removed).
1132
+ */
1133
+ getAllRecords(): Record<SHA256, BlobRecord>;
816
1134
  }
817
1135
 
818
1136
  declare class ContentStore {
819
- private readonly storage;
820
- private readonly cache;
821
- private readonly config;
822
- private activeSession;
823
- private pinnedPreferenceIds;
824
- onFlushed?: (sessionId: UUID, flushedCount: number) => void;
825
- onSessionInvalidated?: (sessionId: UUID) => void;
826
- constructor(backend: ContentStorage, config?: ContentStoreConfig);
827
- getActiveSessionId(): UUID | null;
1137
+ private readonly db;
1138
+ readonly tree: TurnTree;
1139
+ readonly blobs: BlobStore;
1140
+ private readonly roleCache;
1141
+ private readonly preferenceCache;
1142
+ private readonly contextCache;
1143
+ /**
1144
+ * Called after any blob registry change with the sha256 and updated
1145
+ * BlobRecord (null on deletion). Wire this to your state manager to
1146
+ * keep Index.blobs current.
1147
+ *
1148
+ * ContentStore sets this on BlobStore.onRegistryChanged internally.
1149
+ * Consumers set this property to receive the forwarded notifications.
1150
+ */
1151
+ onBlobRegistryChanged?: (sha256: SHA256, record: BlobRecord | null) => void;
1152
+ private constructor();
1153
+ static create(db: WorkspaceDatabase, blobStorage: BlobStorage, config?: ContentStoreConfig): Promise<ContentStore>;
1154
+ private init;
1155
+ getTurnTree(): TurnTree;
828
1156
  getRole(name: string): Promise<Result<Role, WorkspaceError>>;
829
- getPreference(id: UUID): Promise<Result<Preference, WorkspaceError>>;
830
- getContext(key: string): Promise<Result<Context, WorkspaceError>>;
831
- getContextByTopics(indexState: IndexState, topics: string[]): Promise<Context[]>;
832
- getTranscriptWindow(sessionId: UUID, windowSize?: number): Promise<TranscriptWindow>;
833
1157
  saveRole(role: Role): Promise<void>;
834
- savePreference(preference: Preference): Promise<void>;
835
- saveContext(context: Context): Promise<void>;
836
- copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
837
1158
  deleteRole(name: string): Promise<void>;
1159
+ getPreference(id: UUID): Promise<Result<Preference, WorkspaceError>>;
1160
+ savePreference(preference: Preference): Promise<void>;
838
1161
  deletePreference(id: UUID): Promise<void>;
1162
+ getContext(key: string): Promise<Result<Context, WorkspaceError>>;
1163
+ saveContext(context: Context): Promise<void>;
839
1164
  deleteContext(key: string): Promise<void>;
1165
+ getContextByTopics(indexState: Index, topics: string[]): Promise<Context[]>;
1166
+ saveSession(meta: SessionMeta): Promise<void>;
1167
+ updateSessionMeta(sessionId: UUID, patch: DeepPartial<SessionMeta>): Promise<void>;
1168
+ deleteSession(sessionId: UUID): Promise<void>;
1169
+ registerBlob(data: Uint8Array, mediaType: BlobMediaType, filename?: string): Promise<Result<BlobRef, WorkspaceError>>;
1170
+ retainBlob(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
1171
+ releaseBlob(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
1172
+ purgeBlob(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
1173
+ recordBlobRemoteId(sha256: SHA256, providerId: string, fileId: string): Promise<Result<void, WorkspaceError>>;
1174
+ getBlobRecord(sha256: SHA256): BlobRecord | null;
1175
+ getAllBlobRecords(): Record<SHA256, BlobRecord>;
1176
+ /**
1177
+ * Returns the blob resolver function expected by PromptBuilder.
1178
+ * Closes over the internal BlobStore — no BlobStore reference leaks out.
1179
+ */
1180
+ getBlobResolver(): BlobStore['resolveRefs'];
840
1181
  recordTurn(sessionId: UUID, turn: Turn): Promise<Result<void, WorkspaceError>>;
841
1182
  editTurn(sessionId: UUID, turnId: UUID, newBlocks: ContentBlock[], newVersion: number, roleSnapshot?: string): Promise<Result<void, WorkspaceError>>;
842
1183
  branchTurn(sessionId: UUID, newTurn: Turn): Promise<Result<void, WorkspaceError>>;
843
- deleteTurnSubtree(sessionId: UUID, turnId: UUID, version: number, newHead: {
844
- id: UUID;
845
- version: number;
846
- } | null): Promise<Result<void, WorkspaceError>>;
847
- activateSession(meta: SessionMeta): Promise<Result<void, WorkspaceError>>;
848
- deactivateSession(meta?: SessionMeta): Promise<void>;
849
- resolveSession(indexState: IndexState, sessionId: UUID): Promise<Result<EffectiveSession, WorkspaceError>>;
850
- flush(): Promise<void>;
851
- dispose(): Promise<void>;
1184
+ deleteTurnSubtree(sessionId: UUID, turnId: UUID, version: number, newHead: TurnRef | null): Promise<Result<void, WorkspaceError>>;
1185
+ copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
1186
+ resolveSession(workspace: Workspace, sessionId: UUID, transcript?: Turn[]): Promise<Result<EffectiveSession, WorkspaceError>>;
852
1187
  }
853
1188
 
854
- declare function workspaceReducer(state: IndexState, command: Command): Result<DeepPartial<IndexState>, WorkspaceError>;
1189
+ declare function workspaceReducer({ index: state }: Workspace, command: Command): Result<DeepPartial<Workspace>, WorkspaceError>;
855
1190
 
856
1191
  declare class WorkspaceManager {
857
1192
  private readonly contentStore;
1193
+ /**
1194
+ * Called when BlobStore triggers a registry change outside of a dispatch()
1195
+ * call — e.g. eagerEviction deleting a blob on release. The caller should
1196
+ * merge this patch into their Workspace.
1197
+ */
1198
+ onWorkspacePatch?: (patch: DeepPartial<Workspace>) => void;
858
1199
  constructor(contentStore: ContentStore);
859
- reduce(indexState: IndexState, command: Command): Result<DeepPartial<IndexState>, WorkspaceError>;
860
- dispatch(indexState: IndexState, command: Command): Promise<Result<DeepPartial<IndexState>, WorkspaceError>>;
861
- resolveSession(indexState: IndexState, sessionId: UUID): Promise<Result<EffectiveSession, WorkspaceError>>;
862
- activateSession(indexState: IndexState, sessionId: UUID): Promise<Result<void, WorkspaceError>>;
863
- deactivateSession(indexState?: IndexState): Promise<void>;
864
- flush(): Promise<void>;
865
- dispose(): Promise<void>;
1200
+ reduce(workspace: Workspace, command: Command): Result<DeepPartial<Workspace>, WorkspaceError>;
1201
+ dispatch(workspace: Workspace, command: Command): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
1202
+ resolveSession(workspace: Workspace, sessionId: UUID): Promise<Result<EffectiveSession, WorkspaceError>>;
866
1203
  private handleContentSideEffects;
867
1204
  }
868
1205
 
@@ -873,27 +1210,202 @@ interface Summarizer {
873
1210
  }>;
874
1211
  }
875
1212
 
1213
+ interface ContextRankingInput {
1214
+ entries: Context[];
1215
+ recentMessages: string[];
1216
+ config: ContextRelevanceConfig;
1217
+ }
1218
+ interface ContextRetriever {
1219
+ rank(input: ContextRankingInput): Context[];
1220
+ }
1221
+ interface PlanningInput {
1222
+ systemInstructions?: string;
1223
+ persona: string;
1224
+ preferences: Preference[];
1225
+ context: Context[];
1226
+ transcript: Turn[];
1227
+ budget?: TokenBudget;
1228
+ }
1229
+ interface PlanningOutput {
1230
+ preferences: Preference[];
1231
+ context: Context[];
1232
+ transcript: Turn[];
1233
+ truncated: {
1234
+ preferences: number;
1235
+ interactions: number;
1236
+ context: number;
1237
+ };
1238
+ breakdown: Record<string, number>;
1239
+ totalUsed: number;
1240
+ lastRoleBeforeTruncation: string | null;
1241
+ }
1242
+ interface TokenPlanner {
1243
+ plan(input: PlanningInput): PlanningOutput;
1244
+ }
1245
+ interface AssemblyInput {
1246
+ session: EffectiveSession;
1247
+ plan: PlanningOutput;
1248
+ resolvedBlobs: Map<SHA256, ResolvedBlob>;
1249
+ summaryBlockText: string | null;
1250
+ warnings: string[];
1251
+ conflicts: PreferenceConflict[];
1252
+ budgetTotal: number;
1253
+ }
1254
+ declare class PromptAssembler {
1255
+ assemble(input: AssemblyInput): Prompt;
1256
+ private buildReferentialBlock;
1257
+ private resolveTurnBlocks;
1258
+ }
876
1259
  declare class PromptBuilder {
877
- private readonly summarizer?;
878
- constructor(summarizer?: Summarizer);
879
- /**
880
- * Build a provider-agnostic BuildResult from an EffectiveSession.
881
- *
882
- * IMPORTANT: blobs in the session's turns must be pre-resolved before
883
- * calling build(). Pass the resolved map via options.resolvedBlobs.
884
- * Any blob whose SHA-256 is not in resolvedBlobs will be omitted from
885
- * the output with a warning.
886
- *
887
- * @param effectiveSession The resolved session (from ContentStore.resolveSession).
888
- * @param options.resolvedBlobs Map of sha256 → ResolvedBlob from BlobStore.resolveRefs().
889
- * @param options.tokenBudget Optional token budget for truncation.
890
- * @param options.relevanceConfig Optional context ranking config.
891
- */
892
- build(effectiveSession: EffectiveSession, options?: {
893
- resolvedBlobs?: Map<SHA256, ResolvedBlob>;
1260
+ private retriever;
1261
+ private planner;
1262
+ private assembler;
1263
+ private summarizer?;
1264
+ private blobResolver;
1265
+ constructor(dependencies: {
1266
+ blobResolver: BlobStore['resolveRefs'];
1267
+ retriever?: ContextRetriever;
1268
+ planner?: TokenPlanner;
1269
+ assembler?: PromptAssembler;
1270
+ summarizer?: Summarizer;
1271
+ });
1272
+ build(session: EffectiveSession, options?: {
894
1273
  tokenBudget?: TokenBudget;
895
1274
  relevanceConfig?: ContextRelevanceConfig;
1275
+ providerId?: string;
896
1276
  }): Promise<Prompt>;
1277
+ private collectUsedBlobRefs;
1278
+ private deduplicateBlobRefs;
1279
+ private extractRecentUserQueries;
1280
+ private resolvePreferenceConflicts;
1281
+ }
1282
+
1283
+ declare class Session {
1284
+ /** The session ID this object manages. */
1285
+ readonly sessionId: UUID;
1286
+ /**
1287
+ * The turn DAG. Keys are turn IDs. Mutated in-place on every write.
1288
+ * Callers read this directly for rendering — no copy is made.
1289
+ */
1290
+ readonly nodes: Record<UUID, TurnNode>;
1291
+ /**
1292
+ * Current head of the active branch. Mirrors what is in
1293
+ * workspace.index.sessions[sessionId].head after each patch is applied.
1294
+ * Session keeps its own copy so reads don't require workspace.
1295
+ */
1296
+ private head;
1297
+ /** Turns not yet flushed to storage. */
1298
+ private readonly dirtyBuffer;
1299
+ private flushTimer;
1300
+ private flushChain;
1301
+ private readonly flushConfig;
1302
+ private readonly tree;
1303
+ private readonly contentStore;
1304
+ constructor(sessionId: UUID, nodes: Record<UUID, TurnNode>, head: TurnRef | null, tree: TurnTree, contentStore: ContentStore, flushConfig?: FlushConfig);
1305
+ activeChain(): TurnNode[];
1306
+ siblings(turnId: UUID): TurnNode[];
1307
+ branchInfo(turnId: UUID): BranchInfo;
1308
+ addTurn(workspace: Workspace, turn: Turn): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
1309
+ editTurn(workspace: Workspace, turnId: UUID, newBlocks: ContentBlock[], roleSnapshot?: string): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
1310
+ branchFrom(workspace: Workspace, newTurn: Turn): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
1311
+ deleteTurn(workspace: Workspace, turnId: UUID, version: number, newHead: TurnRef | null): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
1312
+ switchVersionLeft(_: Workspace, turnId: UUID): Result<DeepPartial<Workspace>, WorkspaceError>;
1313
+ switchVersionRight(_: Workspace, turnId: UUID): Result<DeepPartial<Workspace>, WorkspaceError>;
1314
+ private switchVersion;
1315
+ resolve(workspace: Workspace): Promise<Result<EffectiveSession, WorkspaceError>>;
1316
+ flush(): Promise<void>;
1317
+ dispose(): Promise<void>;
1318
+ private findSubtreeTipForVersion;
1319
+ private collectSubtreeIds;
1320
+ private upsertNode;
1321
+ private scheduleFlush;
1322
+ private cancelFlushTimer;
1323
+ private doFlush;
1324
+ }
1325
+ declare function buildTurnNode(turn: Turn, children?: UUID[]): TurnNode;
1326
+
1327
+ interface SessionManagerConfig {
1328
+ flush?: Partial<FlushConfig>;
1329
+ }
1330
+ interface OpenResult {
1331
+ session: Session;
1332
+ patch: DeepPartial<Workspace>;
1333
+ }
1334
+ declare class SessionManager {
1335
+ private readonly workspaceManager;
1336
+ private readonly contentStore;
1337
+ private readonly flushConfig;
1338
+ constructor(workspaceManager: WorkspaceManager, contentStore: ContentStore, config?: SessionManagerConfig);
1339
+ open(workspace: Workspace, sessionId: UUID): Promise<Result<OpenResult, WorkspaceError>>;
1340
+ close(session: Session): Promise<void>;
1341
+ get workspace(): WorkspaceManager;
1342
+ }
1343
+
1344
+ /**
1345
+ * In-process implementation backed by plain Maps.
1346
+ * Suitable for development, testing, and server-side runtimes without
1347
+ * access to IndexedDB.
1348
+ */
1349
+ declare class MemoryBlobStorage implements BlobStorage {
1350
+ private readonly bytes;
1351
+ private readonly records;
1352
+ storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
1353
+ loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
1354
+ hasBytes(sha256: SHA256): Promise<boolean>;
1355
+ deleteBytes(sha256: SHA256): Promise<void>;
1356
+ saveRecord(record: BlobRecord): Promise<void>;
1357
+ loadRecord(sha256: SHA256): Promise<BlobRecord | null>;
1358
+ deleteRecord(sha256: SHA256): Promise<void>;
1359
+ listRecords(): Promise<BlobRecord[]>;
1360
+ exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
1361
+ /**
1362
+ * Atomic register for MemoryBlobStorage — sequential ops are atomic
1363
+ * in a single-threaded JS runtime, so this is equivalent to two separate
1364
+ * calls, but we implement it for interface consistency.
1365
+ */
1366
+ registerBlob(record: BlobRecord, data: Uint8Array): Promise<void>;
1367
+ }
1368
+
1369
+ interface IndexedDBBlobConfig {
1370
+ /** Name of the IndexedDB database. Defaults to 'aiworkspace-blobs'. */
1371
+ dbName?: string;
1372
+ }
1373
+ /**
1374
+ * IndexedDB-backed blob storage.
1375
+ *
1376
+ * Bytes are stored as raw Uint8Array — IndexedDB handles binary natively,
1377
+ * no base64 encoding at rest. This is important for large files.
1378
+ *
1379
+ * Transaction discipline: never await inside an active transaction.
1380
+ * All multi-step operations chain synchronous IDB request handlers
1381
+ * inside a single Promise.
1382
+ */
1383
+ declare class IndexedDBBlobStorage implements BlobStorage {
1384
+ private readonly dbName;
1385
+ private db;
1386
+ constructor(config?: IndexedDBBlobConfig);
1387
+ open(): Promise<void>;
1388
+ private createSchema;
1389
+ close(): void;
1390
+ deleteDatabase(): Promise<void>;
1391
+ private getDB;
1392
+ private readTx;
1393
+ private writeTx;
1394
+ storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
1395
+ loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
1396
+ hasBytes(sha256: SHA256): Promise<boolean>;
1397
+ deleteBytes(sha256: SHA256): Promise<void>;
1398
+ saveRecord(record: BlobRecord): Promise<void>;
1399
+ loadRecord(sha256: SHA256): Promise<BlobRecord | null>;
1400
+ deleteRecord(sha256: SHA256): Promise<void>;
1401
+ listRecords(): Promise<BlobRecord[]>;
1402
+ exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
1403
+ /**
1404
+ * Atomically stores bytes and saves a record together.
1405
+ * Preferred over calling storeBytes + saveRecord separately when both
1406
+ * are new — avoids a window where bytes exist without a record.
1407
+ */
1408
+ registerBlob(record: BlobRecord, data: Uint8Array): Promise<void>;
897
1409
  }
898
1410
 
899
- export { type AddContext, type AddInteraction, type AddPreference, type AddRole, type AddSessionTopics, type AddTurn, type BaseCommand, type BlobMediaType, type BlobRecord, type BlobRef, type BlobStorage, BlobStore, type BlobStoreConfig, type BlobSummary, type BranchTurn, type BuiltTurn, type CacheConfig, type Command, type ContentBlock, type ContentStorage, ContentStore, type ContentStoreConfig, type Context, type ContextRelevanceConfig, type ContextSummary, type CreateSession, type CreateWorkspace, type DeleteContext, type DeletePreference, type DeleteRole, type DeleteSession, type DeleteTurn, type DocumentBlock, type DocumentMediaType, type EditTurn, type EffectiveSession, type FlushConfig, type ForkSession, type ImageBlock, type ImageMediaType, type IndexState, type IndexedDBBlobConfig, IndexedDBBlobStorage, type IndexedDBConfig, IndexedDBStorage, type Indexes, MemoryBlobStorage, MemoryStorage, type OverrideSessionPreferences, type Preference, type PreferenceConflict, type PreferenceSummary, type Project, type Prompt, PromptBuilder, type RecordBlobRemoteId, type RegisterBlob, type ReleaseBlob, type ResolvedBlob, type Result, type Role, type RoleSummary, type SHA256, type Session, type SessionMeta, type SessionSummary, type Settings, type SwitchRole, type TextBlock, type ThinkingBlock, type Timestamp, type TokenBudget, type ToolResultBlock, type ToolUseBlock, type TopicIndex, type TranscriptWindow, type Turn, type TurnRole, type URI, type UUID, type UpdateContext, type UpdatePreference, type UpdateRole, type Workspace, type WorkspaceBundle, type WorkspaceError, WorkspaceManager, computeSHA256, del, err, merge, ok, workspaceReducer };
1411
+ export { type AddContext, type AddPreference, type AddRole, type AddSessionTopics, type AddTurn, type BaseCommand, type BlobCommand, type BlobMediaType, type BlobRecord, type BlobRef, type BlobStorage, BlobStore, type BlobStoreConfig, type BranchInfo, type BranchTurn, COLLECTIONS, type CacheConfig, type CollectionName, type Command, type ContentBlock, ContentStore, type ContentStoreConfig, type Context, type ContextContent, type ContextRelevanceConfig, type ContextSummary, type CreateSession, type CreateWorkspace, type DeleteContext, type DeletePreference, type DeleteRole, type DeleteSession, type DeleteTurn, type DocumentBlock, type DocumentMediaType, type EditTurn, type EffectiveSession, type FlushConfig, type ForkSession, type ImageBlock, type ImageMediaType, type Index, type IndexedDBBlobConfig, IndexedDBBlobStorage, MemoryBlobStorage, type OpenResult, type OverrideSessionPreferences, type Preference, type PreferenceConflict, type PreferenceSummary, type Project, type Prompt, PromptBuilder, type PurgeBlob, type RecordBlobRemoteId, type RegisterBlob, type ReleaseBlob, type ResolvedBlob, type Result, type RetainBlob, type Role, type RoleSummary, type RoleTransitionBlock, type SHA256, Session, SessionManager, type SessionManagerConfig, type SessionMeta, type Settings, type SummaryBlock, type SwitchRole, type TextBlock, type ThinkingBlock, type Timestamp, type TokenBudget, type ToolResultBlock, type ToolUseBlock, type TopicIndex, type TranscriptWindow, type Turn, type TurnNode, type TurnRef, type TurnRole, TurnTree, type URI, type UUID, type UpdateContext, type UpdatePreference, type UpdateRole, type Workspace, type WorkspaceBundle, type WorkspaceDatabase, type WorkspaceError, WorkspaceManager, buildTurnNode, computeSHA256, createSimpleWorkspace, createWorkspaceDatabase, del, err, extractBlobRecord, extractBlobRef, merge, ok, omitNullUndefined, workspaceReducer };