@asaidimu/utils-workspace 1.0.1 → 2.0.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.ts CHANGED
@@ -9,8 +9,6 @@ type Result<T, E = WorkspaceError> = {
9
9
  ok: false;
10
10
  error: E;
11
11
  };
12
- declare function ok<T>(value: T): Result<T, never>;
13
- declare function err<E = WorkspaceError>(error: E): Result<never, E>;
14
12
  type WorkspaceError = {
15
13
  code: 'DUPLICATE_KEY';
16
14
  resource: string;
@@ -43,13 +41,12 @@ interface Project {
43
41
  type ImageMediaType = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
44
42
  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
43
  type BlobMediaType = ImageMediaType | DocumentMediaType;
46
- interface BlobRef {
47
- sha256: SHA256;
48
- mediaType: BlobMediaType;
49
- sizeBytes: number;
50
- filename?: string;
51
- previewUrl?: string;
52
- }
44
+ /**
45
+ * Lightweight pointer to a blob — the subset of BlobRecord that content
46
+ * blocks and context entries carry. Use this at call sites that only need
47
+ * to reference a blob, not inspect its registry metadata.
48
+ */
49
+ type BlobRef = Pick<BlobRecord, 'sha256' | 'mediaType' | 'sizeBytes' | 'filename' | 'previewUrl'>;
53
50
  type ResolvedBlob = {
54
51
  kind: 'inline';
55
52
  sha256: SHA256;
@@ -62,37 +59,36 @@ type ResolvedBlob = {
62
59
  fileId: string;
63
60
  providerId: string;
64
61
  };
62
+ /**
63
+ * Full registry entry for a blob. BlobRef is a Pick of this type, so
64
+ * a BlobRecord is always assignable to BlobRef. BlobSummary has been
65
+ * removed — callers that previously used BlobSummary use BlobRecord directly.
66
+ */
65
67
  interface BlobRecord {
66
68
  sha256: SHA256;
67
69
  mediaType: BlobMediaType;
68
70
  sizeBytes: number;
69
71
  filename?: string;
72
+ previewUrl?: string;
70
73
  refCount: number;
71
74
  remoteIds: Record<string, string>;
72
75
  createdAt: Timestamp;
73
76
  lastUsedAt: Timestamp;
74
77
  }
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
78
  interface TextBlock {
85
79
  type: 'text';
86
80
  text: string;
87
81
  }
88
82
  interface ImageBlock {
89
83
  type: 'image';
90
- blob: ResolvedBlob;
84
+ ref?: BlobRef;
85
+ blob?: ResolvedBlob;
91
86
  altText?: string;
92
87
  }
93
88
  interface DocumentBlock {
94
89
  type: 'document';
95
- blob: ResolvedBlob;
90
+ ref?: BlobRef;
91
+ blob?: ResolvedBlob;
96
92
  title?: string;
97
93
  }
98
94
  interface ToolUseBlock {
@@ -111,7 +107,16 @@ interface ThinkingBlock {
111
107
  type: 'thinking';
112
108
  thinking: string;
113
109
  }
114
- type ContentBlock = TextBlock | ImageBlock | DocumentBlock | ToolUseBlock | ToolResultBlock | ThinkingBlock;
110
+ interface SummaryBlock {
111
+ type: 'summary';
112
+ text: string;
113
+ }
114
+ interface RoleTransitionBlock {
115
+ type: 'role_transition';
116
+ previousRole: string | null;
117
+ newRole: string;
118
+ }
119
+ type ContentBlock = TextBlock | ImageBlock | DocumentBlock | ToolUseBlock | ToolResultBlock | SummaryBlock | RoleTransitionBlock | ThinkingBlock;
115
120
  type TurnRole = 'user' | 'assistant' | 'tool';
116
121
  interface Turn {
117
122
  id: UUID;
@@ -125,14 +130,31 @@ interface Turn {
125
130
  version: number;
126
131
  } | null;
127
132
  }
128
- interface SessionSummary {
133
+ interface TurnNode {
129
134
  id: UUID;
130
- label: string;
131
- role: string;
132
- topics: string[];
133
- created: Timestamp;
134
- updated: Timestamp;
135
- turns: number;
135
+ versions: Record<number, Turn>;
136
+ activeVersion: number;
137
+ role: TurnRole;
138
+ blocks: ContentBlock[];
139
+ timestamp: Timestamp;
140
+ roleSnapshot?: string;
141
+ parent: {
142
+ id: UUID;
143
+ version: number;
144
+ } | null;
145
+ children: Record<number, UUID[]>;
146
+ }
147
+ interface BranchInfo {
148
+ /** All sibling versions at this level, in insertion order. */
149
+ versions: number[];
150
+ /** Index of this turn within siblings (0-based). */
151
+ currentIndex: number;
152
+ /** Convenience: number of siblings (siblings.length). */
153
+ total: number;
154
+ /** True when there is a branch to the left. */
155
+ hasPrev: boolean;
156
+ /** True when there is a branch to the right. */
157
+ hasNext: boolean;
136
158
  }
137
159
  interface RoleSummary {
138
160
  name: string;
@@ -166,27 +188,19 @@ interface TopicIndex {
166
188
  entries?: number;
167
189
  };
168
190
  }
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
191
  interface Workspace {
177
192
  id: UUID;
178
193
  settings: Settings;
179
194
  project: Project;
180
- indexes: Indexes;
195
+ index: Index;
181
196
  }
182
- interface IndexState {
197
+ interface Index {
183
198
  format: string;
184
- workspace: Workspace;
185
199
  roles: Record<string, RoleSummary>;
186
200
  preferences: Record<UUID, PreferenceSummary>;
187
201
  context: Record<string, ContextSummary>;
188
202
  sessions: Record<UUID, SessionMeta>;
189
- blobs: Record<SHA256, BlobSummary>;
203
+ topics: Record<string, TopicIndex>;
190
204
  }
191
205
  interface SessionMeta {
192
206
  id: UUID;
@@ -220,13 +234,27 @@ interface Preference {
220
234
  }
221
235
  interface Context {
222
236
  key: string;
223
- content: string | object | URI;
237
+ content: ContextContent;
224
238
  topics: string[];
225
239
  timestamp: Timestamp;
226
240
  metadata?: Record<string, any>;
227
- attachments?: BlobRef[];
241
+ remoteIds?: Record<string, string>;
242
+ refCount?: number;
228
243
  }
229
- interface Session {
244
+ type ContextContent = {
245
+ kind: 'text';
246
+ value: string;
247
+ } | {
248
+ kind: 'json';
249
+ value: object;
250
+ } | {
251
+ kind: 'blob';
252
+ } & BlobRef | {
253
+ kind: 'remote';
254
+ uri: URI;
255
+ mediaType: BlobMediaType;
256
+ };
257
+ interface Session$1 {
230
258
  id: UUID;
231
259
  label: string;
232
260
  role: string;
@@ -244,7 +272,7 @@ interface WorkspaceBundle {
244
272
  roles: Record<string, Role>;
245
273
  preferences: Record<UUID, Preference>;
246
274
  context: Record<string, Context>;
247
- sessions: Record<UUID, Session>;
275
+ sessions: Record<UUID, Session$1>;
248
276
  blobs: Record<SHA256, BlobRecord>;
249
277
  }
250
278
  interface TranscriptWindow {
@@ -259,12 +287,12 @@ interface EffectiveSession {
259
287
  preferences: Preference[];
260
288
  context: Context[];
261
289
  transcript: Turn[];
262
- systemPrompt?: string;
290
+ instructions?: string;
263
291
  }
264
292
  interface CacheConfig {
265
293
  roles?: number;
266
294
  preferences?: number;
267
- contextEntries?: number;
295
+ context?: number;
268
296
  transcriptWindows?: number;
269
297
  }
270
298
  interface FlushConfig {
@@ -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: {
@@ -464,21 +485,35 @@ interface ContextRelevanceConfig {
464
485
  minScore?: number;
465
486
  freshnessHalfLifeDays?: number;
466
487
  }
467
- interface BuiltTurn {
468
- role: TurnRole;
469
- blocks: ContentBlock[];
470
- }
488
+ /**
489
+ * BuiltTurn has been removed. Prompt.transcript.turns is now Turn[].
490
+ * Synthetic turns produced by PromptAssembler (summary blocks, truncation
491
+ * notices, referential attachments) carry generated UUIDs and version 0,
492
+ * with parent: null. They are ephemeral — never stored, never patched.
493
+ */
471
494
  interface Prompt {
472
- system: string | null;
473
- turns: BuiltTurn[];
474
- blobs: BlobRef[];
495
+ system: {
496
+ instructions?: string;
497
+ persona: string;
498
+ preferences: Preference[];
499
+ context: Context[];
500
+ };
501
+ context: Context[];
502
+ transcript: {
503
+ turns: Turn[];
504
+ };
505
+ budget: {
506
+ total: number;
507
+ used: number;
508
+ breakdown: Record<string, number>;
509
+ };
475
510
  truncated: {
476
511
  preferences: number;
477
512
  interactions: number;
478
- contextEntries: number;
513
+ context: number;
479
514
  };
480
- conflicts: PreferenceConflict[];
481
515
  warnings: string[];
516
+ conflicts: PreferenceConflict[];
482
517
  }
483
518
 
484
519
  interface ContentStorage {
@@ -490,10 +525,12 @@ interface ContentStorage {
490
525
  deletePreference(id: UUID): Promise<void>;
491
526
  saveContext(context: Context): Promise<void>;
492
527
  loadContext(key: string): Promise<Context | null>;
528
+ loadContextBatch(keys: string[]): Promise<Map<string, Context>>;
493
529
  deleteContext(key: string): Promise<void>;
494
530
  saveTurn(sessionId: UUID, turn: Turn): Promise<void>;
495
531
  loadTurn(sessionId: UUID, turnId: UUID, version: number): Promise<Turn | null>;
496
- loadAllTurns(sessionId: UUID, limit?: number): Promise<Turn[]>;
532
+ loadAllTurns(sessionId: UUID): Promise<Turn[]>;
533
+ deleteTurn(sessionId: UUID, turnId: UUID, version: number): Promise<void>;
497
534
  setSessionHead(sessionId: UUID, head: {
498
535
  id: UUID;
499
536
  version: number;
@@ -502,45 +539,10 @@ interface ContentStorage {
502
539
  id: UUID;
503
540
  version: number;
504
541
  } | 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>;
509
- }
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
- };
542
+ storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
543
+ loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
544
+ deleteBytes(sha256: SHA256): Promise<void>;
545
+ exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
544
546
  }
545
547
 
546
548
  /**
@@ -556,6 +558,8 @@ type DeepPartial<T> = T extends object ? T extends readonly (infer U)[] ? readon
556
558
 
557
559
  declare function del<T>(): T;
558
560
  declare const merge: <T extends object>(original: T, changes: DeepPartial<T> | symbol) => T;
561
+ declare function ok<T>(value: T): Result<T, never>;
562
+ declare function err<E = WorkspaceError>(error: E): Result<never, E>;
559
563
 
560
564
  interface IndexedDBConfig {
561
565
  dbName?: string;
@@ -579,11 +583,12 @@ declare class IndexedDBStorage implements ContentStorage {
579
583
  deletePreference(id: UUID): Promise<void>;
580
584
  saveContext(context: Context): Promise<void>;
581
585
  loadContext(key: string): Promise<Context | null>;
586
+ loadContextBatch(keys: string[]): Promise<Map<string, Context>>;
582
587
  deleteContext(key: string): Promise<void>;
583
588
  saveTurn(sessionId: UUID, turn: Turn): Promise<void>;
584
- loadAllTurns(sessionId: UUID, limit?: number): Promise<Turn[]>;
585
- countChainedTurns(sessionId: UUID): Promise<number>;
586
589
  loadTurn(sessionId: UUID, turnId: UUID, version: number): Promise<Turn | null>;
590
+ loadAllTurns(sessionId: UUID): Promise<Turn[]>;
591
+ deleteTurn(sessionId: UUID, turnId: UUID, version: number): Promise<void>;
587
592
  setSessionHead(sessionId: UUID, head: {
588
593
  id: UUID;
589
594
  version: number;
@@ -592,24 +597,25 @@ declare class IndexedDBStorage implements ContentStorage {
592
597
  id: UUID;
593
598
  version: number;
594
599
  } | 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
+ storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
601
+ loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
602
+ deleteBytes(sha256: SHA256): Promise<void>;
603
+ exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
604
+ saveWorkspace(state: Workspace): Promise<void>;
605
+ loadWorkspace(): Promise<Workspace | null>;
600
606
  exportBundle(): Promise<{
601
607
  roles: Record<string, Role>;
602
608
  preferences: Record<UUID, Preference>;
603
609
  context: Record<string, Context>;
604
610
  transcripts: Record<UUID, Turn[]>;
605
- indexState: IndexState | null;
611
+ workspace: Workspace | null;
606
612
  }>;
607
613
  importBundle(data: {
608
614
  roles: Record<string, Role>;
609
615
  preferences: Record<UUID, Preference>;
610
616
  context: Record<string, Context>;
611
617
  transcripts: Record<UUID, Turn[]>;
612
- indexState?: IndexState;
618
+ workspace?: Workspace;
613
619
  }): Promise<void>;
614
620
  private getAllFromStore;
615
621
  }
@@ -652,65 +658,33 @@ interface BlobStorage {
652
658
  * Used for workspace export / migration.
653
659
  */
654
660
  exportAllBytes(): Promise<Array<[SHA256, Uint8Array]>>;
655
- }
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;
677
- }
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]>>;
708
661
  /**
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.
662
+ * Optional atomic operation: store bytes and save record in a single
663
+ * transaction. Backends that support this (e.g. IndexedDB) should
664
+ * implement it to avoid a window where bytes exist without a record.
665
+ * BlobStore will prefer this over calling storeBytes + saveRecord separately.
712
666
  */
713
- registerBlob(record: BlobRecord, data: Uint8Array): Promise<void>;
667
+ registerBlob?(record: BlobRecord, data: Uint8Array): Promise<void>;
668
+ }
669
+
670
+ interface TurnRef {
671
+ id: UUID;
672
+ version: number;
673
+ }
674
+ declare class TurnTree {
675
+ private readonly storage;
676
+ constructor(storage: ContentStorage);
677
+ append(sessionId: UUID, turn: Turn): Promise<Turn>;
678
+ appendBatch(sessionId: UUID, turns: Turn[]): Promise<void>;
679
+ replaceVersion(sessionId: UUID, newTurn: Turn): Promise<void>;
680
+ branch(sessionId: UUID, newTurn: Turn): Promise<void>;
681
+ getActiveChain(sessionId: UUID, dirtyBuffer?: readonly Turn[]): Promise<Turn[]>;
682
+ countChained(sessionId: UUID, dirtyBuffer?: readonly Turn[]): Promise<number>;
683
+ buildNodeGraph(sessionId: UUID, dirtyBuffer?: readonly Turn[]): Promise<Record<UUID, TurnNode>>;
684
+ deleteSubtree(sessionId: UUID, turnId: UUID, version: number, newHead: TurnRef | null): Promise<void>;
685
+ copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
686
+ getHead(sessionId: UUID): Promise<TurnRef | null>;
687
+ setHead(sessionId: UUID, head: TurnRef | null): Promise<void>;
714
688
  }
715
689
 
716
690
  declare function computeSHA256(data: Uint8Array): Promise<SHA256>;
@@ -718,13 +692,12 @@ interface BlobStoreConfig {
718
692
  /**
719
693
  * When true, blobs with refCount === 0 are deleted from the backend
720
694
  * 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.
695
+ * an explicit GC sweep via gc(). Lazy GC is safer for offline-first apps.
723
696
  */
724
697
  eagerEviction?: boolean;
725
698
  }
726
699
  declare class BlobStore {
727
- private readonly backend;
700
+ private readonly storage;
728
701
  private readonly config;
729
702
  /**
730
703
  * In-memory cache of BlobRecords. Kept consistent with the backend on
@@ -733,61 +706,22 @@ declare class BlobStore {
733
706
  private readonly recordCache;
734
707
  /**
735
708
  * 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.
709
+ * Passes the full BlobRecord (or null on deletion). The integration
710
+ * layer wires this to an Index patch that updates the blob index.
711
+ *
712
+ * BlobRecord is a superset of the former BlobSummary — callers
713
+ * that only need summary fields (sha256, mediaType, sizeBytes, etc.)
714
+ * can read them directly from BlobRecord without a separate type.
738
715
  */
739
- onRegistryChanged?: (sha256: SHA256, summary: BlobSummary | null) => void;
716
+ onRegistryChanged?: (sha256: SHA256, record: BlobRecord | null) => void;
740
717
  constructor(backend: BlobStorage, config?: BlobStoreConfig);
741
718
  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
719
  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
720
  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
721
  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
722
  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
723
  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
724
  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
725
  resolveRefs(refs: BlobRef[], providerId: string | null): Promise<{
792
726
  resolved: Map<SHA256, ResolvedBlob>;
793
727
  errors: Array<{
@@ -795,74 +729,52 @@ declare class BlobStore {
795
729
  error: WorkspaceError;
796
730
  }>;
797
731
  }>;
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
732
  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
- */
733
+ gcFull(): Promise<number>;
811
734
  purge(sha256: SHA256): Promise<Result<void, WorkspaceError>>;
812
735
  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>;
736
+ /**
737
+ * All records — used to seed Index.blobs on init.
738
+ * Returns BlobRecord directly (BlobSummary has been removed).
739
+ */
740
+ getAllRecords(): Record<SHA256, BlobRecord>;
816
741
  }
817
742
 
818
743
  declare class ContentStore {
819
744
  private readonly storage;
745
+ private readonly tree;
820
746
  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;
747
+ constructor(storage: ContentStorage, config?: ContentStoreConfig);
748
+ getTurnTree(): TurnTree;
828
749
  getRole(name: string): Promise<Result<Role, WorkspaceError>>;
829
750
  getPreference(id: UUID): Promise<Result<Preference, WorkspaceError>>;
830
751
  getContext(key: string): Promise<Result<Context, WorkspaceError>>;
831
- getContextByTopics(indexState: IndexState, topics: string[]): Promise<Context[]>;
832
- getTranscriptWindow(sessionId: UUID, windowSize?: number): Promise<TranscriptWindow>;
752
+ getContextByTopics(indexState: Index, topics: string[]): Promise<Context[]>;
833
753
  saveRole(role: Role): Promise<void>;
834
754
  savePreference(preference: Preference): Promise<void>;
835
755
  saveContext(context: Context): Promise<void>;
836
- copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
837
756
  deleteRole(name: string): Promise<void>;
838
757
  deletePreference(id: UUID): Promise<void>;
839
758
  deleteContext(key: string): Promise<void>;
840
759
  recordTurn(sessionId: UUID, turn: Turn): Promise<Result<void, WorkspaceError>>;
841
760
  editTurn(sessionId: UUID, turnId: UUID, newBlocks: ContentBlock[], newVersion: number, roleSnapshot?: string): Promise<Result<void, WorkspaceError>>;
842
761
  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>;
762
+ deleteTurnSubtree(sessionId: UUID, turnId: UUID, version: number, newHead: TurnRef | null): Promise<Result<void, WorkspaceError>>;
763
+ copyTranscript(sourceSessionId: UUID, targetSessionId: UUID): Promise<void>;
764
+ storeBytes(sha256: SHA256, data: Uint8Array): Promise<void>;
765
+ loadBytes(sha256: SHA256): Promise<Uint8Array | null>;
766
+ deleteBytes(sha256: SHA256): Promise<void>;
767
+ resolveSession(workspace: Workspace, sessionId: UUID, transcript?: Turn[]): Promise<Result<EffectiveSession, WorkspaceError>>;
852
768
  }
853
769
 
854
- declare function workspaceReducer(state: IndexState, command: Command): Result<DeepPartial<IndexState>, WorkspaceError>;
770
+ declare function workspaceReducer({ index: state }: Workspace, command: Command): Result<DeepPartial<Workspace>, WorkspaceError>;
855
771
 
856
772
  declare class WorkspaceManager {
857
773
  private readonly contentStore;
858
774
  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>;
775
+ reduce(workspace: Workspace, command: Command): Result<DeepPartial<Workspace>, WorkspaceError>;
776
+ dispatch(workspace: Workspace, command: Command): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
777
+ resolveSession(workspace: Workspace, sessionId: UUID): Promise<Result<EffectiveSession, WorkspaceError>>;
866
778
  private handleContentSideEffects;
867
779
  }
868
780
 
@@ -873,27 +785,147 @@ interface Summarizer {
873
785
  }>;
874
786
  }
875
787
 
788
+ interface ContextRankingInput {
789
+ entries: Context[];
790
+ recentMessages: string[];
791
+ config: ContextRelevanceConfig;
792
+ }
793
+ interface ContextRetriever {
794
+ rank(input: ContextRankingInput): Context[];
795
+ }
796
+ interface PlanningInput {
797
+ systemInstructions?: string;
798
+ persona: string;
799
+ preferences: Preference[];
800
+ context: Context[];
801
+ transcript: Turn[];
802
+ budget?: TokenBudget;
803
+ }
804
+ interface PlanningOutput {
805
+ preferences: Preference[];
806
+ context: Context[];
807
+ transcript: Turn[];
808
+ truncated: {
809
+ preferences: number;
810
+ interactions: number;
811
+ context: number;
812
+ };
813
+ breakdown: Record<string, number>;
814
+ totalUsed: number;
815
+ lastRoleBeforeTruncation: string | null;
816
+ }
817
+ interface TokenPlanner {
818
+ plan(input: PlanningInput): PlanningOutput;
819
+ }
820
+ interface AssemblyInput {
821
+ session: EffectiveSession;
822
+ plan: PlanningOutput;
823
+ resolvedBlobs: Map<SHA256, ResolvedBlob>;
824
+ summaryBlockText: string | null;
825
+ warnings: string[];
826
+ conflicts: PreferenceConflict[];
827
+ budgetTotal: number;
828
+ }
829
+ declare class PromptAssembler {
830
+ assemble(input: AssemblyInput): Prompt;
831
+ private buildReferentialBlock;
832
+ private resolveTurnBlocks;
833
+ }
876
834
  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>;
835
+ private retriever;
836
+ private planner;
837
+ private assembler;
838
+ private summarizer?;
839
+ private blobResolver;
840
+ constructor(dependencies: {
841
+ blobResolver: BlobStore['resolveRefs'];
842
+ retriever?: ContextRetriever;
843
+ planner?: TokenPlanner;
844
+ assembler?: PromptAssembler;
845
+ summarizer?: Summarizer;
846
+ });
847
+ build(session: EffectiveSession, options?: {
894
848
  tokenBudget?: TokenBudget;
895
849
  relevanceConfig?: ContextRelevanceConfig;
850
+ providerId?: string;
896
851
  }): Promise<Prompt>;
852
+ private collectUsedBlobRefs;
853
+ private deduplicateBlobRefs;
854
+ private extractRecentUserQueries;
855
+ private resolvePreferenceConflicts;
856
+ }
857
+
858
+ declare class Session {
859
+ /** The session ID this object manages. */
860
+ readonly sessionId: UUID;
861
+ /**
862
+ * The turn DAG. Keys are turn IDs. Mutated in-place on every write.
863
+ * Callers read this directly for rendering — no copy is made.
864
+ */
865
+ readonly nodes: Record<UUID, TurnNode>;
866
+ /**
867
+ * Current head of the active branch. Mirrors what is in
868
+ * workspace.index.sessions[sessionId].head after each patch is applied.
869
+ * Session keeps its own copy so reads don't require workspace.
870
+ */
871
+ private head;
872
+ /** Turns not yet flushed to storage. */
873
+ private readonly dirtyBuffer;
874
+ private flushTimer;
875
+ private flushChain;
876
+ private readonly flushConfig;
877
+ private readonly tree;
878
+ private readonly contentStore;
879
+ constructor(sessionId: UUID, nodes: Record<UUID, TurnNode>, head: TurnRef | null, tree: TurnTree, contentStore: ContentStore, flushConfig?: FlushConfig);
880
+ activeChain(): TurnNode[];
881
+ siblings(turnId: UUID): TurnNode[];
882
+ branchInfo(turnId: UUID): BranchInfo;
883
+ addTurn(workspace: Workspace, turn: Turn): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
884
+ editTurn(workspace: Workspace, turnId: UUID, newBlocks: ContentBlock[], roleSnapshot?: string): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
885
+ branchFrom(workspace: Workspace, newTurn: Turn): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
886
+ deleteTurn(workspace: Workspace, turnId: UUID, version: number, newHead: TurnRef | null): Promise<Result<DeepPartial<Workspace>, WorkspaceError>>;
887
+ switchVersionLeft(_: Workspace, turnId: UUID): Result<DeepPartial<Workspace>, WorkspaceError>;
888
+ switchVersionRight(_: Workspace, turnId: UUID): Result<DeepPartial<Workspace>, WorkspaceError>;
889
+ private switchVersion;
890
+ resolve(workspace: Workspace): Promise<Result<EffectiveSession, WorkspaceError>>;
891
+ flush(): Promise<void>;
892
+ dispose(): Promise<void>;
893
+ private findSubtreeTipForVersion;
894
+ private collectSubtreeIds;
895
+ private upsertNode;
896
+ private scheduleFlush;
897
+ private cancelFlushTimer;
898
+ private doFlush;
899
+ }
900
+ declare function buildTurnNode(turn: Turn, children?: UUID[]): TurnNode;
901
+
902
+ interface SessionManagerConfig {
903
+ flush?: Partial<FlushConfig>;
904
+ }
905
+ /**
906
+ * Returned by SessionManager.open().
907
+ *
908
+ * session — the live Session instance. Store this in your component or
909
+ * module; pass it to every subsequent session.* call.
910
+ *
911
+ * patch — a DeepPartial<Workspace> that the caller should merge into
912
+ * their Workspace immediately after open(). Currently empty
913
+ * (the Workspace index already holds SessionMeta), but present
914
+ * for forward compatibility — future versions may update head
915
+ * or flushedTurnCount as part of opening.
916
+ */
917
+ interface OpenResult {
918
+ session: Session;
919
+ patch: DeepPartial<Workspace>;
920
+ }
921
+ declare class SessionManager {
922
+ private readonly workspaceManager;
923
+ private readonly contentStore;
924
+ private readonly flushConfig;
925
+ constructor(workspaceManager: WorkspaceManager, contentStore: ContentStore, config?: SessionManagerConfig);
926
+ open(workspace: Workspace, sessionId: UUID): Promise<Result<OpenResult, WorkspaceError>>;
927
+ close(session: Session): Promise<void>;
928
+ get workspace(): WorkspaceManager;
897
929
  }
898
930
 
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 };
931
+ export { type AddContext, type AddPreference, type AddRole, type AddSessionTopics, type AddTurn, type BaseCommand, type BlobMediaType, type BlobRecord, type BlobRef, type BlobStorage, BlobStore, type BlobStoreConfig, type BranchInfo, type BranchTurn, type CacheConfig, type Command, type ContentBlock, type ContentStorage, 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 IndexedDBConfig, IndexedDBStorage, type OpenResult, 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 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 WorkspaceError, WorkspaceManager, buildTurnNode, computeSHA256, del, err, merge, ok, workspaceReducer };