@rool-dev/svelte 0.11.15 → 0.12.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/README.md CHANGED
@@ -59,8 +59,9 @@ The Svelte wrapper adds reactive state on top of the SDK:
59
59
  | `space.fileTree` | Canonical reactive WebDAV tree for `/`, including `/space` objects and `/rool-drive` user files |
60
60
  | `space.objectPaths` | Object paths derived from `space.fileTree` |
61
61
  | `space.collections` | Collection directories derived from `space.fileTree` |
62
- | `space.conversations` | Conversations in this space (auto-updates on create/delete/rename) |
63
- | `thread.interactions` | Interactions for a specific conversation (auto-updates) |
62
+ | `space.conversations` | Lightweight conversation roster, kept current from `openSpace` and SSE |
63
+ | `thread.interactions` | Active branch for one lazily opened conversation |
64
+ | `thread.loading` / `thread.error` | Load state for that conversation only |
64
65
  | `watch.objects` | Objects matching a filter (auto-updates) |
65
66
  | `watch.loading` | Whether watch is loading |
66
67
 
@@ -148,7 +149,7 @@ Reactive cross-device storage for user preferences. Synced from server on `init(
148
149
 
149
150
  ### Spaces & Conversations
150
151
 
151
- Every space has its own SSE subscription. Open a space, then get explicit conversation handles from it. Call `space.close()` when done to stop the subscription.
152
+ Every space has its own SSE subscription. Conversation metadata is reactive at the space level; full contents are loaded only for conversation handles requested through `space.conversation(id)`. Repeated calls for the same ID return the same handle. Call `space.close()` when done to stop the subscription and close its conversation handles.
152
153
 
153
154
  ```typescript
154
155
  // Open a space — reactive, with SSE
@@ -330,7 +331,7 @@ articles.close() // Stop listening for updates
330
331
 
331
332
  ### Reactive Conversation Handle
332
333
 
333
- For apps with multiple independent interaction threads (e.g., chat with threads), use `space.conversation()` to get a handle with reactive interactions:
334
+ For apps with multiple independent interaction threads, use `space.conversation()` to lazily load and retain reactive state for the threads currently in use:
334
335
 
335
336
  ```svelte
336
337
  <script lang="ts">
@@ -358,21 +359,21 @@ For apps with multiple independent interaction threads (e.g., chat with threads)
358
359
  ```
359
360
 
360
361
  ```typescript
361
- // Reactive state
362
- thread.interactions // $state<Interaction[]> updates from space events
362
+ // State retained for this conversation only
363
+ thread.data // Conversation | null | undefined
364
+ thread.interactions // active branch; updates from space events
365
+ thread.activeLeafId
366
+ thread.loading
367
+ thread.error
363
368
 
364
- // Conversation-scoped methods
365
369
  await thread.prompt('Hello')
366
- await thread.stop() // Stop this thread's running work (false if nothing was running)
367
- await thread.putObject('/space/note/welcome.json', { text: 'Note' })
368
- await thread.patchObject('/space/note/welcome.json', { data: { text: 'Updated' } })
370
+ await thread.stop()
369
371
  await thread.setSystemInstruction('Respond in haiku')
370
372
  await thread.rename('Research Thread')
371
- thread.getInteractions() // Manual read
372
- thread.getSystemInstruction()
373
+ thread.setActiveLeaf(interactionId)
373
374
 
374
- // Cleanup
375
- thread.close() // Stop listening for updates
375
+ // Evicts this handle from the space; requesting the ID again loads a new one.
376
+ thread.close()
376
377
  ```
377
378
 
378
379
  Conversations are auto-created when you first write history or settings. Real-time events are owned by the space subscription; conversation handles subscribe to space events locally.
@@ -403,25 +404,24 @@ space.role
403
404
  const path = '/space/note/welcome.json';
404
405
  await space.getObject(path)
405
406
 
406
- // Conversation-scoped writes and AI
407
- const conversation = space.conversation('thread-42');
408
- await conversation.putObject(path, { text: 'Hello' })
409
- await conversation.patchObject(path, { data: { text: 'Updated' } })
410
- await conversation.moveObject(path, '/space/note/renamed.json')
411
- await conversation.deleteObjects(['/space/note/renamed.json'])
412
- await conversation.prompt('Summarize everything')
413
- await conversation.stop()
414
-
415
- // Schema/metadata writes are also attributed to a conversation
416
- space.getSchema()
417
- await conversation.createCollection('article', [
407
+ // Object and schema APIs live on the space
408
+ await space.putObject(path, { text: 'Hello' })
409
+ await space.patchObject(path, { data: { text: 'Updated' } })
410
+ await space.moveObject(path, '/space/note/renamed.json')
411
+ await space.deleteObjects(['/space/note/renamed.json'])
412
+ await space.createCollection('article', [
418
413
  { name: 'title', type: { kind: 'string' } },
419
414
  { name: 'status', type: { kind: 'enum', values: ['draft', 'published'] } },
420
415
  ])
421
- await conversation.alterCollection('article', updatedFields)
422
- await conversation.dropCollection('article')
416
+ await space.alterCollection('article', updatedFields)
417
+ await space.dropCollection('article')
418
+
419
+ // Conversation state and operations are scoped to a lazy reactive handle
420
+ const conversation = space.conversation('thread-42');
421
+ await conversation.prompt('Summarize everything')
422
+ await conversation.stop()
423
423
  await conversation.setSystemInstruction('You are helpful')
424
- conversation.getInteractions()
424
+ conversation.interactions
425
425
 
426
426
  // Space history/admin
427
427
  await space.undo()
@@ -1 +1 @@
1
- {"version":3,"file":"rool.svelte.d.ts","sourceRoot":"","sources":["../src/rool.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rool.svelte.d.ts","sourceRoot":"","sources":["../src/rool.svelte.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,8 @@ import { wrapSpace } from './space.svelte.js';
12
12
  class RoolImpl {
13
13
  #client;
14
14
  #unsubscribers = [];
15
- #openSpaces = new Set();
15
+ #openSpaces = new Map();
16
+ #openingSpaces = new Map();
16
17
  // Reactive state
17
18
  authenticated = $state(null); // null = checking, false = not auth, true = auth
18
19
  spaces = $state(undefined);
@@ -150,10 +151,10 @@ class RoolImpl {
150
151
  * Log out and close all open spaces.
151
152
  */
152
153
  logout() {
153
- for (const space of this.#openSpaces) {
154
+ for (const space of this.#openSpaces.values())
154
155
  space.close();
155
- }
156
156
  this.#openSpaces.clear();
157
+ this.#openingSpaces.clear();
157
158
  this.#client.logout();
158
159
  }
159
160
  /**
@@ -161,31 +162,29 @@ class RoolImpl {
161
162
  * Call `space.close()` when done to stop the subscription.
162
163
  */
163
164
  async openSpace(spaceId) {
164
- const raw = await this.#client.openSpace(spaceId);
165
- const reactive = wrapSpace(raw);
166
- await reactive.ready();
167
- this.#openSpaces.add(reactive);
168
- return reactive;
165
+ const existing = this.#openSpaces.get(spaceId);
166
+ if (existing && !existing.isClosed)
167
+ return existing;
168
+ const opening = this.#openingSpaces.get(spaceId);
169
+ if (opening)
170
+ return opening;
171
+ const pending = this.#client.openSpace(spaceId)
172
+ .then((space) => this.#wrapSpace(space))
173
+ .finally(() => this.#openingSpaces.delete(spaceId));
174
+ this.#openingSpaces.set(spaceId, pending);
175
+ return pending;
169
176
  }
170
177
  /**
171
178
  * Create a new space. Returns a ReactiveSpace.
172
179
  */
173
180
  async createSpace(name) {
174
- const raw = await this.#client.createSpace(name);
175
- const reactive = wrapSpace(raw);
176
- await reactive.ready();
177
- this.#openSpaces.add(reactive);
178
- return reactive;
181
+ return this.#wrapSpace(await this.#client.createSpace(name));
179
182
  }
180
183
  /**
181
184
  * Duplicate an existing space. Returns a ReactiveSpace.
182
185
  */
183
186
  async duplicateSpace(sourceSpaceId, name) {
184
- const raw = await this.#client.duplicateSpace(sourceSpaceId, name);
185
- const reactive = wrapSpace(raw);
186
- await reactive.ready();
187
- this.#openSpaces.add(reactive);
188
- return reactive;
187
+ return this.#wrapSpace(await this.#client.duplicateSpace(sourceSpaceId, name));
189
188
  }
190
189
  /**
191
190
  * Manually refresh the spaces list.
@@ -196,8 +195,10 @@ class RoolImpl {
196
195
  /**
197
196
  * Delete a space.
198
197
  */
199
- deleteSpace(spaceId) {
200
- return this.#client.deleteSpace(spaceId);
198
+ async deleteSpace(spaceId) {
199
+ await this.#client.deleteSpace(spaceId);
200
+ this.#openSpaces.get(spaceId)?.close();
201
+ this.#openSpaces.delete(spaceId);
201
202
  }
202
203
  /**
203
204
  * Mark the current user for deletion, then log out locally.
@@ -258,11 +259,7 @@ class RoolImpl {
258
259
  * Import a space from a zip archive. Returns a ReactiveSpace.
259
260
  */
260
261
  async importArchive(name, archive) {
261
- const raw = await this.#client.importArchive(name, archive);
262
- const reactive = wrapSpace(raw);
263
- await reactive.ready();
264
- this.#openSpaces.add(reactive);
265
- return reactive;
262
+ return this.#wrapSpace(await this.#client.importArchive(name, archive));
266
263
  }
267
264
  /**
268
265
  * Get auth user info from JWT token.
@@ -286,14 +283,32 @@ class RoolImpl {
286
283
  this.currentUser = user;
287
284
  return user;
288
285
  }
286
+ async #wrapSpace(space) {
287
+ const existing = this.#openSpaces.get(space.id);
288
+ if (existing && !existing.isClosed)
289
+ return existing;
290
+ const reactive = wrapSpace(space, () => {
291
+ if (this.#openSpaces.get(space.id) === reactive)
292
+ this.#openSpaces.delete(space.id);
293
+ });
294
+ try {
295
+ await reactive.ready();
296
+ }
297
+ catch (error) {
298
+ reactive.close();
299
+ throw error;
300
+ }
301
+ this.#openSpaces.set(space.id, reactive);
302
+ return reactive;
303
+ }
289
304
  /**
290
305
  * Clean up resources.
291
306
  */
292
307
  destroy() {
293
- for (const space of this.#openSpaces) {
308
+ for (const space of this.#openSpaces.values())
294
309
  space.close();
295
- }
296
310
  this.#openSpaces.clear();
311
+ this.#openingSpaces.clear();
297
312
  for (const unsub of this.#unsubscribers) {
298
313
  unsub();
299
314
  }
@@ -1,4 +1,4 @@
1
- import type { RoolSpace, Interaction, RoolObject, ConversationHandle } from '@rool-dev/sdk';
1
+ import type { RoolSpace, Interaction, Conversation, RoolObject, ConversationHandle } from '@rool-dev/sdk';
2
2
  import { ReactiveFileTree } from './file-tree.svelte.js';
3
3
  /**
4
4
  * Options for creating a reactive watch.
@@ -41,22 +41,22 @@ export declare class ReactiveObjectImpl {
41
41
  export type ReactiveObject = ReactiveObjectImpl;
42
42
  export declare class ReactiveConversationHandleImpl {
43
43
  #private;
44
+ data: Conversation | null | undefined;
44
45
  interactions: Interaction[];
45
- constructor(space: RoolSpace, conversationId: string);
46
+ loading: boolean;
47
+ error: Error | null;
48
+ constructor(space: RoolSpace, conversationId: string, onClose: () => void);
46
49
  get conversationId(): string;
50
+ get activeLeafId(): string | undefined;
51
+ refresh(): Promise<void>;
47
52
  getInteractions(): Interaction[];
48
53
  getTree(): Record<string, Interaction>;
49
- get activeLeafId(): string | undefined;
50
54
  setActiveLeaf(interactionId: string): void;
51
55
  getSystemInstruction(): string | undefined;
52
56
  setSystemInstruction(...args: Parameters<ConversationHandle['setSystemInstruction']>): Promise<void>;
53
57
  rename(...args: Parameters<ConversationHandle['rename']>): Promise<void>;
54
58
  delete(): Promise<void>;
55
- prompt(...args: Parameters<ConversationHandle['prompt']>): Promise<{
56
- message: string;
57
- objects: RoolObject[];
58
- creditsUsed: number;
59
- }>;
59
+ prompt(text: string, options?: Parameters<ConversationHandle['prompt']>[1]): ReturnType<ConversationHandle['prompt']>;
60
60
  stop(): Promise<boolean>;
61
61
  close(): void;
62
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"space-session.svelte.d.ts","sourceRoot":"","sources":["../src/space-session.svelte.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAqD,MAAM,uBAAuB,CAAC;AAE5G;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AA8CD;;GAEG;AACH,qBAAa,iBAAiB;;IAO5B,OAAO,eAA4B;IACnC,OAAO,UAAgB;gBAEX,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY;IAgB/E,yEAAyE;IACnE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,KAAK,IAAI,IAAI;CAId;AAED,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAE9C;;GAEG;AACH,qBAAa,kBAAkB;;IAO7B,IAAI,yBAA6C;IACjD,OAAO,UAAgB;gBAEX,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM;IAoBhE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,KAAK,IAAI,IAAI;CAId;AAED,MAAM,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAMhD,qBAAa,8BAA8B;;IAMzC,YAAY,gBAA6B;gBAE7B,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM;IAiCpD,IAAI,cAAc,IAAI,MAAM,CAAiC;IAG7D,eAAe;IACf,OAAO;IACP,IAAI,YAAY,uBAAwC;IACxD,aAAa,CAAC,aAAa,EAAE,MAAM;IAInC,oBAAoB;IACpB,oBAAoB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;IACpF,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM;IAGN,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;;;;;IACxD,IAAI;IAEJ,KAAK,IAAI,IAAI;CAId;AAED,MAAM,MAAM,0BAA0B,GAAG,8BAA8B,CAAC"}
1
+ {"version":3,"file":"space-session.svelte.d.ts","sourceRoot":"","sources":["../src/space-session.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAqD,MAAM,uBAAuB,CAAC;AAE5G;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AA8CD;;GAEG;AACH,qBAAa,iBAAiB;;IAO5B,OAAO,eAA4B;IACnC,OAAO,UAAgB;gBAEX,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY;IAgB/E,yEAAyE;IACnE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,KAAK,IAAI,IAAI;CAId;AAED,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAE9C;;GAEG;AACH,qBAAa,kBAAkB;;IAO7B,IAAI,yBAA6C;IACjD,OAAO,UAAgB;gBAEX,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM;IAoBhE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B,KAAK,IAAI,IAAI;CAId;AAED,MAAM,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAMhD,qBAAa,8BAA8B;;IASzC,IAAI,kCAAsD;IAC1D,YAAY,gBAA6B;IACzC,OAAO,UAAgB;IACvB,KAAK,eAA8B;gBAEvB,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI;IAezE,IAAI,cAAc,IAAI,MAAM,CAAiC;IAC7D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAA+B;IAE/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B,eAAe,IAAI,WAAW,EAAE;IAChC,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IAEtC,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAS1C,oBAAoB,IAAI,MAAM,GAAG,SAAS;IAC1C,oBAAoB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,kBAAkB,CAAC,sBAAsB,CAAC,CAAC;IACpF,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM;IAEA,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GACpD,UAAU,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAmB3C,IAAI;IAEJ,KAAK,IAAI,IAAI;CA0Bd;AAED,MAAM,MAAM,0BAA0B,GAAG,8BAA8B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { isObjectPath, machinePath } from '@rool-dev/sdk';
1
+ import { conversationBranch, defaultConversationLeaf, generateEntityId, isObjectPath, machinePath, } from '@rool-dev/sdk';
2
2
  function objectCollection(path) {
3
3
  if (!isObjectPath(path))
4
4
  return undefined;
@@ -137,60 +137,109 @@ export class ReactiveObjectImpl {
137
137
  export class ReactiveConversationHandleImpl {
138
138
  #handle;
139
139
  #conversationId;
140
+ #activeLeafId = $state(undefined);
141
+ #revision = 0;
142
+ #closed = false;
140
143
  #unsubscribers = [];
141
- // Reactive state
144
+ #onClose;
145
+ data = $state(undefined);
142
146
  interactions = $state([]);
143
- constructor(space, conversationId) {
147
+ loading = $state(true);
148
+ error = $state(null);
149
+ constructor(space, conversationId, onClose) {
144
150
  this.#conversationId = conversationId;
145
151
  this.#handle = space.conversation(conversationId);
146
- // Cold-open: fetch the conversation contents if not already loaded (SSE may
147
- // have filled it if this conversation received a recent event).
148
- if (this.#handle.loaded) {
149
- this.interactions = this.#handle.getInteractions();
150
- }
151
- else {
152
- void this.#handle.load().then(() => {
153
- this.interactions = this.#handle.getInteractions();
154
- });
155
- }
152
+ this.#onClose = onClose;
153
+ void this.refresh();
156
154
  const onConversationUpdated = (event) => {
157
155
  if (event.conversationId !== this.#conversationId)
158
156
  return;
159
- this.#handle.applyUpdate(event.conversation);
160
- this.interactions = this.#handle.getInteractions();
157
+ this.#revision += 1;
158
+ this.#apply(event.conversation);
161
159
  };
162
160
  space.on('conversationUpdated', onConversationUpdated);
163
161
  this.#unsubscribers.push(() => space.off('conversationUpdated', onConversationUpdated));
164
- // Reconnect reset: force-reload the conversation (we may have missed SSE).
165
- const onReset = () => {
166
- if (!this.#handle.loaded)
167
- return;
168
- void this.#handle.load(true).then(() => {
169
- this.interactions = this.#handle.getInteractions();
170
- });
171
- };
172
- space.on('reset', onReset);
173
- this.#unsubscribers.push(() => space.off('reset', onReset));
174
162
  }
175
163
  get conversationId() { return this.#conversationId; }
176
- // Conversation history
177
- getInteractions() { return this.#handle.getInteractions(); }
178
- getTree() { return this.#handle.getTree(); }
179
- get activeLeafId() { return this.#handle.activeLeafId; }
164
+ get activeLeafId() { return this.#activeLeafId; }
165
+ async refresh() {
166
+ const revision = this.#revision;
167
+ this.loading = true;
168
+ this.error = null;
169
+ try {
170
+ const conversation = await this.#handle.get();
171
+ if (!this.#closed && revision === this.#revision)
172
+ this.#apply(conversation);
173
+ }
174
+ catch (error) {
175
+ if (!this.#closed)
176
+ this.error = error instanceof Error ? error : new Error(String(error));
177
+ }
178
+ finally {
179
+ if (!this.#closed)
180
+ this.loading = false;
181
+ }
182
+ }
183
+ getInteractions() { return this.interactions; }
184
+ getTree() { return this.data?.interactions ?? {}; }
180
185
  setActiveLeaf(interactionId) {
181
- this.#handle.setActiveLeaf(interactionId);
182
- this.interactions = this.#handle.getInteractions();
186
+ if (!this.data?.interactions[interactionId]) {
187
+ throw new Error(`Interaction "${interactionId}" not found in conversation "${this.#conversationId}"`);
188
+ }
189
+ this.#revision += 1;
190
+ this.#activeLeafId = interactionId;
191
+ this.interactions = conversationBranch(this.data, interactionId);
183
192
  }
184
- getSystemInstruction() { return this.#handle.getSystemInstruction(); }
193
+ getSystemInstruction() { return this.data?.systemInstruction; }
185
194
  setSystemInstruction(...args) { return this.#handle.setSystemInstruction(...args); }
186
195
  rename(...args) { return this.#handle.rename(...args); }
187
196
  delete() { return this.#handle.delete(); }
188
- // AI
189
- prompt(...args) { return this.#handle.prompt(...args); }
197
+ async prompt(text, options) {
198
+ const previousLeaf = this.#activeLeafId;
199
+ const interactionId = options?.interactionId ?? generateEntityId();
200
+ this.#revision += 1;
201
+ this.#activeLeafId = interactionId;
202
+ try {
203
+ return await this.#handle.prompt(text, {
204
+ ...options,
205
+ interactionId,
206
+ parentInteractionId: options?.parentInteractionId === undefined
207
+ ? previousLeaf ?? null
208
+ : options.parentInteractionId,
209
+ });
210
+ }
211
+ catch (error) {
212
+ if (this.#activeLeafId === interactionId)
213
+ this.#activeLeafId = previousLeaf;
214
+ throw error;
215
+ }
216
+ }
190
217
  stop() { return this.#handle.stop(); }
191
218
  close() {
219
+ if (this.#closed)
220
+ return;
221
+ this.#closed = true;
192
222
  for (const unsub of this.#unsubscribers)
193
223
  unsub();
194
224
  this.#unsubscribers.length = 0;
225
+ this.#onClose();
226
+ }
227
+ #apply(conversation) {
228
+ this.data = conversation;
229
+ if (!conversation) {
230
+ this.#activeLeafId = undefined;
231
+ this.interactions = [];
232
+ return;
233
+ }
234
+ const currentLeaf = this.#activeLeafId;
235
+ if (currentLeaf) {
236
+ const child = Object.values(conversation.interactions).find((interaction) => interaction.parentId === currentLeaf);
237
+ if (child)
238
+ this.#activeLeafId = child.id;
239
+ }
240
+ if (!this.#activeLeafId || !conversation.interactions[this.#activeLeafId]) {
241
+ this.#activeLeafId = defaultConversationLeaf(conversation);
242
+ }
243
+ this.interactions = conversationBranch(conversation, this.#activeLeafId);
195
244
  }
196
245
  }
@@ -17,9 +17,10 @@ import { ReactiveFileTree } from './file-tree.svelte.js';
17
17
  declare class ReactiveSpaceImpl {
18
18
  #private;
19
19
  connectionState: ConnectionState;
20
+ openSpaceError: Error | null;
20
21
  meta: Record<string, unknown>;
21
22
  schema: SpaceSchema;
22
- constructor(space: RoolSpace);
23
+ constructor(space: RoolSpace, onClose: () => void);
23
24
  /** Resolve when the initial meta/schema fetch has settled. */
24
25
  ready(): Promise<void>;
25
26
  /**
@@ -29,7 +30,7 @@ declare class ReactiveSpaceImpl {
29
30
  */
30
31
  setMeta(key: string, value: unknown): Promise<void>;
31
32
  get isClosed(): boolean;
32
- /** Get a reactive handle for a conversation in this space. */
33
+ /** Get the shared reactive handle for one lazily loaded conversation. */
33
34
  conversation(conversationId: string): ReactiveConversationHandleImpl;
34
35
  /**
35
36
  * Close this space and stop the SSE subscription. Idempotent.
@@ -62,16 +63,11 @@ declare class ReactiveSpaceImpl {
62
63
  dropCollection(...args: Parameters<RoolSpace['dropCollection']>): Promise<void>;
63
64
  object(path: string): ReactiveObjectImpl;
64
65
  watch(options: WatchOptions): ReactiveWatchImpl;
65
- /** @deprecated Use {@link stopConversation}. */
66
- stopInteraction(...args: Parameters<RoolSpace['stopInteraction']>): Promise<boolean>;
67
66
  stopConversation(...args: Parameters<RoolSpace['stopConversation']>): Promise<boolean>;
68
- checkpoint(...args: Parameters<RoolSpace['checkpoint']>): Promise<string>;
69
67
  canUndo(...args: Parameters<RoolSpace['canUndo']>): Promise<boolean>;
70
68
  canRedo(...args: Parameters<RoolSpace['canRedo']>): Promise<boolean>;
71
69
  undo(...args: Parameters<RoolSpace['undo']>): Promise<boolean>;
72
70
  redo(...args: Parameters<RoolSpace['redo']>): Promise<boolean>;
73
- clearHistory(...args: Parameters<RoolSpace['clearHistory']>): Promise<void>;
74
- getConversations(...args: Parameters<RoolSpace['getConversations']>): ConversationMeta[];
75
71
  createConversation(...args: Parameters<RoolSpace['createConversation']>): Promise<string>;
76
72
  deleteConversation(...args: Parameters<RoolSpace['deleteConversation']>): Promise<void>;
77
73
  listAgents(...args: Parameters<RoolSpace['listAgents']>): Promise<string[]>;
@@ -94,7 +90,7 @@ declare class ReactiveSpaceImpl {
94
90
  on(...args: Parameters<RoolSpace['on']>): () => void;
95
91
  off(...args: Parameters<RoolSpace['off']>): void;
96
92
  }
97
- export declare function wrapSpace(space: RoolSpace): ReactiveSpace;
93
+ export declare function wrapSpace(space: RoolSpace, onClose?: () => void): ReactiveSpace;
98
94
  export type ReactiveSpace = ReactiveSpaceImpl;
99
95
  export {};
100
96
  //# sourceMappingURL=space.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"space.svelte.d.ts","sourceRoot":"","sources":["../src/space.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"space.svelte.d.ts","sourceRoot":"","sources":["../src/space.svelte.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,18 @@
1
1
  import { ReactiveConversationHandleImpl, ReactiveObjectImpl, ReactiveWatchImpl } from './space-session.svelte.js';
2
2
  import { ReactiveFileTree } from './file-tree.svelte.js';
3
+ function conversationMeta(id, conversation, updatedAt) {
4
+ return {
5
+ id,
6
+ agent: conversation.agent ?? 'rool',
7
+ visibility: conversation.visibility ?? 'shared',
8
+ name: conversation.name ?? null,
9
+ systemInstruction: conversation.systemInstruction ?? null,
10
+ createdAt: conversation.createdAt,
11
+ createdBy: conversation.createdBy,
12
+ interactionCount: Object.keys(conversation.interactions).length,
13
+ updatedAt,
14
+ };
15
+ }
3
16
  /**
4
17
  * A reactive wrapper around a RoolSpace. Exposes reactive `conversations`,
5
18
  * `connectionState`, `meta`, and `schema`, plus space-level
@@ -17,10 +30,16 @@ class ReactiveSpaceImpl {
17
30
  #space;
18
31
  #unsubscribers = [];
19
32
  #fileTree;
33
+ #conversationHandles = new Map();
34
+ #onClose;
20
35
  #closed = false;
21
- // Reactive state mirroring the underlying space
36
+ #name = $state('');
37
+ #role = $state('viewer');
38
+ #memberCount = $state(0);
22
39
  #conversationList = $state([]);
40
+ #conversationRevision = 0;
23
41
  connectionState = $state('reconnecting');
42
+ openSpaceError = $state(null);
24
43
  // Reactive space content read from the WebDAV filesystem.
25
44
  meta = $state({});
26
45
  schema = $state({});
@@ -28,20 +47,39 @@ class ReactiveSpaceImpl {
28
47
  // meta/schema already populated (avoids a default-theme flash on switch).
29
48
  #metaReady = Promise.resolve();
30
49
  #schemaReady = Promise.resolve();
31
- constructor(space) {
50
+ constructor(space, onClose) {
32
51
  this.#space = space;
52
+ this.#onClose = onClose;
33
53
  this.#fileTree = new ReactiveFileTree(space);
34
- this.#conversationList = [...space.conversations];
35
- const refreshConversations = () => { this.#conversationList = [...space.conversations]; };
36
- space.on('conversationUpdated', refreshConversations);
37
- this.#unsubscribers.push(() => space.off('conversationUpdated', refreshConversations));
38
- space.on('reset', refreshConversations);
39
- this.#unsubscribers.push(() => space.off('reset', refreshConversations));
54
+ this.connectionState = space.connectionState;
55
+ this.#applyOpenSpace(space.openSpaceResult, true);
56
+ const updateConversation = (event) => {
57
+ this.#conversationRevision += 1;
58
+ if (event.conversation === null) {
59
+ this.#conversationList = this.#conversationList.filter((conversation) => conversation.id !== event.conversationId);
60
+ return;
61
+ }
62
+ const next = conversationMeta(event.conversationId, event.conversation, event.timestamp);
63
+ const index = this.#conversationList.findIndex((conversation) => conversation.id === event.conversationId);
64
+ this.#conversationList = index === -1
65
+ ? [next, ...this.#conversationList]
66
+ : [
67
+ ...this.#conversationList.slice(0, index),
68
+ next,
69
+ ...this.#conversationList.slice(index + 1),
70
+ ];
71
+ };
72
+ space.on('conversationUpdated', updateConversation);
73
+ this.#unsubscribers.push(() => space.off('conversationUpdated', updateConversation));
40
74
  const onConnectionStateChanged = (state) => {
41
75
  this.connectionState = state;
76
+ if (state === 'connected')
77
+ this.#reloadAfterConnection();
42
78
  };
43
79
  space.on('connectionStateChanged', onConnectionStateChanged);
44
80
  this.#unsubscribers.push(() => space.off('connectionStateChanged', onConnectionStateChanged));
81
+ if (this.connectionState === 'connected')
82
+ this.#reloadAfterConnection();
45
83
  // Seed meta/schema from the filesystem, and re-fetch the relevant one when
46
84
  // the file tree reports its node changed. Remote writes (another tab, the
47
85
  // agent) land here the same as local ones.
@@ -66,6 +104,26 @@ class ReactiveSpaceImpl {
66
104
  return;
67
105
  this.schema = await this.#space.readSchema();
68
106
  }
107
+ #applyOpenSpace(result, replaceConversations) {
108
+ this.#name = result.name;
109
+ this.#role = result.role;
110
+ this.#memberCount = result.memberCount;
111
+ if (replaceConversations)
112
+ this.#conversationList = [...result.conversationMeta];
113
+ this.openSpaceError = null;
114
+ }
115
+ async #refreshOpenSpace() {
116
+ const conversationRevision = this.#conversationRevision;
117
+ const result = await this.#space.refresh();
118
+ this.#applyOpenSpace(result, conversationRevision === this.#conversationRevision);
119
+ }
120
+ #reloadAfterConnection() {
121
+ void this.#refreshOpenSpace().catch((error) => {
122
+ this.openSpaceError = error instanceof Error ? error : new Error(String(error));
123
+ });
124
+ for (const conversation of this.#conversationHandles.values())
125
+ void conversation.refresh();
126
+ }
69
127
  /** Resolve when the initial meta/schema fetch has settled. */
70
128
  async ready() {
71
129
  await Promise.all([this.#metaReady, this.#schemaReady]);
@@ -85,9 +143,18 @@ class ReactiveSpaceImpl {
85
143
  await this.#space.writeMeta(next);
86
144
  }
87
145
  get isClosed() { return this.#closed; }
88
- /** Get a reactive handle for a conversation in this space. */
146
+ /** Get the shared reactive handle for one lazily loaded conversation. */
89
147
  conversation(conversationId) {
90
- return new ReactiveConversationHandleImpl(this.#space, conversationId);
148
+ const existing = this.#conversationHandles.get(conversationId);
149
+ if (existing)
150
+ return existing;
151
+ const conversation = new ReactiveConversationHandleImpl(this.#space, conversationId, () => {
152
+ if (this.#conversationHandles.get(conversationId) === conversation) {
153
+ this.#conversationHandles.delete(conversationId);
154
+ }
155
+ });
156
+ this.#conversationHandles.set(conversationId, conversation);
157
+ return conversation;
91
158
  }
92
159
  /**
93
160
  * Close this space and stop the SSE subscription. Idempotent.
@@ -99,16 +166,20 @@ class ReactiveSpaceImpl {
99
166
  for (const unsub of this.#unsubscribers)
100
167
  unsub();
101
168
  this.#unsubscribers.length = 0;
169
+ for (const conversation of this.#conversationHandles.values())
170
+ conversation.close();
171
+ this.#conversationHandles.clear();
102
172
  this.#fileTree.close();
103
173
  this.#space.close();
174
+ this.#onClose();
104
175
  }
105
176
  // Reactive getters
106
177
  get conversations() { return this.#conversationList; }
107
178
  // Proxy read-only properties
108
179
  get id() { return this.#space.id; }
109
- get name() { return this.#space.name; }
110
- get role() { return this.#space.role; }
111
- get memberCount() { return this.#space.memberCount; }
180
+ get name() { return this.#name; }
181
+ get role() { return this.#role; }
182
+ get memberCount() { return this.#memberCount; }
112
183
  get webdav() { return this.#space.webdav; }
113
184
  get fileTree() { return this.#fileTree; }
114
185
  // Space-level methods
@@ -123,16 +194,11 @@ class ReactiveSpaceImpl {
123
194
  dropCollection(...args) { return this.#space.dropCollection(...args); }
124
195
  object(path) { return new ReactiveObjectImpl(this.#space, this.#fileTree, path); }
125
196
  watch(options) { return new ReactiveWatchImpl(this.#space, this.#fileTree, options); }
126
- /** @deprecated Use {@link stopConversation}. */
127
- stopInteraction(...args) { return this.#space.stopInteraction(...args); }
128
197
  stopConversation(...args) { return this.#space.stopConversation(...args); }
129
- checkpoint(...args) { return this.#space.checkpoint(...args); }
130
198
  canUndo(...args) { return this.#space.canUndo(...args); }
131
199
  canRedo(...args) { return this.#space.canRedo(...args); }
132
200
  undo(...args) { return this.#space.undo(...args); }
133
201
  redo(...args) { return this.#space.redo(...args); }
134
- clearHistory(...args) { return this.#space.clearHistory(...args); }
135
- getConversations(...args) { return this.#space.getConversations(...args); }
136
202
  createConversation(...args) { return this.#space.createConversation(...args); }
137
203
  deleteConversation(...args) { return this.#space.deleteConversation(...args); }
138
204
  listAgents(...args) { return this.#space.listAgents(...args); }
@@ -146,8 +212,14 @@ class ReactiveSpaceImpl {
146
212
  getStorageUsage(...args) { return this.#space.getStorageUsage(...args); }
147
213
  fetchPath(...args) { return this.#space.fetchPath(...args); }
148
214
  // Proxy admin methods
149
- rename(newName) { return this.#space.rename(newName); }
150
- delete() { return this.#space.delete(); }
215
+ async rename(newName) {
216
+ await this.#space.rename(newName);
217
+ this.#name = newName;
218
+ }
219
+ async delete() {
220
+ await this.#space.delete();
221
+ this.close();
222
+ }
151
223
  listUsers() { return this.#space.listUsers(); }
152
224
  setUserRole(...args) { return this.#space.setUserRole(...args); }
153
225
  removeUser(userId) { return this.#space.removeUser(userId); }
@@ -155,11 +227,11 @@ class ReactiveSpaceImpl {
155
227
  listInvites(...args) { return this.#space.listInvites(...args); }
156
228
  revokeInvite(...args) { return this.#space.revokeInvite(...args); }
157
229
  exportArchive() { return this.#space.exportArchive(); }
158
- refresh() { return this.#space.refresh(); }
230
+ refresh() { return this.#refreshOpenSpace(); }
159
231
  // Events on the underlying space (conversationUpdated, filesChanged, connectionStateChanged)
160
232
  on(...args) { return this.#space.on(...args); }
161
233
  off(...args) { return this.#space.off(...args); }
162
234
  }
163
- export function wrapSpace(space) {
164
- return new ReactiveSpaceImpl(space);
235
+ export function wrapSpace(space, onClose = () => { }) {
236
+ return new ReactiveSpaceImpl(space, onClose);
165
237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rool-dev/svelte",
3
- "version": "0.11.15",
3
+ "version": "0.12.0",
4
4
  "description": "Svelte 5 runes for Rool Spaces",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "license": "MIT",
43
43
  "dependencies": {
44
- "@rool-dev/sdk": "0.11.15"
44
+ "@rool-dev/sdk": "0.12.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "svelte": "^5.0.0"