@rool-dev/sdk 0.11.3-dev.5dfa2e5 → 0.11.3-dev.7c57c94

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.
Files changed (49) hide show
  1. package/README.md +156 -105
  2. package/dist/auth-base.d.ts +100 -0
  3. package/dist/auth-base.d.ts.map +1 -0
  4. package/dist/auth-base.js +377 -0
  5. package/dist/auth-base.js.map +1 -0
  6. package/dist/auth-browser.d.ts +6 -77
  7. package/dist/auth-browser.d.ts.map +1 -1
  8. package/dist/auth-browser.js +32 -328
  9. package/dist/auth-browser.js.map +1 -1
  10. package/dist/auth-native.d.ts +72 -0
  11. package/dist/auth-native.d.ts.map +1 -0
  12. package/dist/auth-native.js +260 -0
  13. package/dist/auth-native.js.map +1 -0
  14. package/dist/auth.d.ts +17 -1
  15. package/dist/auth.d.ts.map +1 -1
  16. package/dist/auth.js +36 -5
  17. package/dist/auth.js.map +1 -1
  18. package/dist/client.d.ts +27 -8
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +52 -26
  21. package/dist/client.js.map +1 -1
  22. package/dist/graphql.d.ts +13 -19
  23. package/dist/graphql.d.ts.map +1 -1
  24. package/dist/graphql.js +35 -70
  25. package/dist/graphql.js.map +1 -1
  26. package/dist/index.d.ts +4 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/reroute.d.ts.map +1 -1
  31. package/dist/reroute.js +9 -0
  32. package/dist/reroute.js.map +1 -1
  33. package/dist/{channel.d.ts → space-session.d.ts} +44 -139
  34. package/dist/space-session.d.ts.map +1 -0
  35. package/dist/{channel.js → space-session.js} +111 -325
  36. package/dist/space-session.js.map +1 -0
  37. package/dist/space.d.ts +18 -48
  38. package/dist/space.d.ts.map +1 -1
  39. package/dist/space.js +84 -223
  40. package/dist/space.js.map +1 -1
  41. package/dist/subscription.d.ts +2 -2
  42. package/dist/subscription.d.ts.map +1 -1
  43. package/dist/subscription.js +6 -30
  44. package/dist/subscription.js.map +1 -1
  45. package/dist/types.d.ts +58 -83
  46. package/dist/types.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/channel.d.ts.map +0 -1
  49. package/dist/channel.js.map +0 -1
@@ -156,34 +156,29 @@ function base64Body(data) {
156
156
  return bytes.buffer;
157
157
  }
158
158
  /**
159
- * A channel is a space + channelId pair.
159
+ * Operational handle for a space.
160
160
  *
161
- * All object operations go through a channel. The channelId is fixed
162
- * at open time and cannot be changed. To use a different channel,
163
- * open a second one.
164
- *
165
- * Objects are addressed by machine path (`/space/.../*.json`).
166
- * Only schema, metadata, object stats, and the channel's own history are cached
167
- * locally. Object bodies are fetched on demand. Object/file reactivity is
168
- * exposed at the space level via WebDAV sync notifications.
161
+ * Objects are addressed by machine path (`/space/.../*.json`). Conversation handles
162
+ * control attribution and AI history. Only schema, metadata,
163
+ * object stats, and conversation history are cached locally. Object bodies are
164
+ * fetched on demand. Object/file reactivity is exposed at the space level via
165
+ * WebDAV sync notifications.
169
166
  */
170
- export class RoolChannel extends EventEmitter {
167
+ export class SpaceOperations extends EventEmitter {
171
168
  _id;
172
169
  _name;
173
170
  _role;
174
171
  _userId;
175
- _channelId;
176
- _conversationId;
177
172
  _closed = false;
178
- graphqlClient;
179
- restClient;
180
- webdav;
181
- onCloseCallback;
182
- logger;
183
- // Local cache for bounded data (schema, metadata, own channel, object stats)
173
+ _graphqlClient;
174
+ _restClient;
175
+ _webdav;
176
+ _onCloseCallback;
177
+ _logger;
178
+ // Local cache for bounded data (schema, metadata, conversations, object stats)
184
179
  _meta;
185
180
  _schema;
186
- _channel;
181
+ _conversations;
187
182
  _objectStats;
188
183
  // Active leaf per conversation (client-side tree cursor)
189
184
  _activeLeaves = new Map();
@@ -194,17 +189,15 @@ export class RoolChannel extends EventEmitter {
194
189
  this._role = config.role;
195
190
  this._userId = config.userId;
196
191
  this._emitterLogger = config.logger;
197
- this._channelId = config.channelId;
198
- this._conversationId = 'default';
199
- this.graphqlClient = config.graphqlClient;
200
- this.restClient = config.restClient;
201
- this.webdav = config.webdav;
202
- this.logger = config.logger;
203
- this.onCloseCallback = config.onClose;
192
+ this._graphqlClient = config.graphqlClient;
193
+ this._restClient = config.restClient;
194
+ this._webdav = config.webdav;
195
+ this._logger = config.logger;
196
+ this._onCloseCallback = config.onClose;
204
197
  // Initialize local cache from server data
205
198
  this._meta = config.meta;
206
199
  this._schema = config.schema;
207
- this._channel = config.channel ?? undefined;
200
+ this._conversations = config.conversations;
208
201
  this._objectStats = new Map(Object.entries(config.objectStats));
209
202
  }
210
203
  /**
@@ -213,11 +206,11 @@ export class RoolChannel extends EventEmitter {
213
206
  * @internal
214
207
  */
215
208
  _handleEvent(event) {
216
- this.handleChannelEvent(event);
209
+ this.applySpaceContentEvent(event);
217
210
  }
218
211
  /**
219
212
  * Apply resync data after reconnection. Called by the client, which
220
- * fetches space data once and distributes to all channels.
213
+ * fetches space data once and applies it.
221
214
  * @internal
222
215
  */
223
216
  _applyResyncData(data) {
@@ -226,8 +219,7 @@ export class RoolChannel extends EventEmitter {
226
219
  this._meta = data.meta;
227
220
  this._schema = data.schema;
228
221
  this._objectStats = new Map(Object.entries(data.objectStats));
229
- if (data.channel)
230
- this._channel = data.channel;
222
+ this._conversations = data.conversations;
231
223
  this._activeLeaves.clear();
232
224
  this.emit('reset', { source: 'system' });
233
225
  }
@@ -244,39 +236,12 @@ export class RoolChannel extends EventEmitter {
244
236
  get userId() {
245
237
  return this._userId;
246
238
  }
247
- /**
248
- * Get the channel's display name, or null if not set.
249
- */
250
- get channelName() {
251
- return this._channel?.name ?? null;
252
- }
253
- /**
254
- * Get the channel ID for this channel.
255
- * Fixed at open time — cannot be changed.
256
- */
257
- get channelId() {
258
- return this._channelId;
259
- }
260
- /**
261
- * Get the conversation ID for this channel.
262
- * Defaults to 'default' for most apps.
263
- */
264
- get conversationId() {
265
- return this._conversationId;
266
- }
267
239
  get isReadOnly() {
268
240
  return this._role === 'viewer';
269
241
  }
270
- /**
271
- * Get the active branch of the current conversation as a flat array (root → leaf).
272
- * Walks from the active leaf up through parentId pointers.
273
- */
274
- getInteractions() {
275
- return this._getInteractionsImpl(this._conversationId);
276
- }
277
242
  /** @internal */
278
243
  _getInteractionsImpl(conversationId) {
279
- const interactions = this._channel?.conversations[conversationId]?.interactions;
244
+ const interactions = this._conversations[conversationId]?.interactions;
280
245
  if (!interactions)
281
246
  return [];
282
247
  // Handle legacy array format
@@ -287,59 +252,35 @@ export class RoolChannel extends EventEmitter {
287
252
  return [];
288
253
  return walkBranch(interactions, leafId);
289
254
  }
290
- /**
291
- * Get the full interaction tree for a conversation as a record.
292
- * For clients that need to render branch navigation UI.
293
- */
294
- getTree() {
295
- return this._getTreeImpl(this._conversationId);
296
- }
297
255
  /** @internal */
298
256
  _getTreeImpl(conversationId) {
299
- const interactions = this._channel?.conversations[conversationId]?.interactions;
257
+ const interactions = this._conversations[conversationId]?.interactions;
300
258
  if (!interactions || Array.isArray(interactions))
301
259
  return {};
302
260
  return interactions;
303
261
  }
304
- /**
305
- * Get the active leaf interaction ID for a conversation.
306
- * Returns undefined if the conversation has no interactions.
307
- */
308
- get activeLeafId() {
309
- return this._getActiveLeafImpl(this._conversationId);
310
- }
311
262
  /** @internal */
312
263
  _getActiveLeafImpl(conversationId) {
313
- return this._activeLeaves.get(conversationId) ?? findDefaultLeaf(this._channel?.conversations[conversationId]?.interactions);
314
- }
315
- /**
316
- * Set the active leaf for a conversation (switch branches).
317
- * Emits a conversationUpdated event so reactive wrappers refresh.
318
- */
319
- setActiveLeaf(interactionId) {
320
- this._setActiveLeafImpl(interactionId, this._conversationId);
264
+ return this._activeLeaves.get(conversationId) ?? findDefaultLeaf(this._conversations[conversationId]?.interactions);
321
265
  }
322
266
  /** @internal */
323
267
  _setActiveLeafImpl(interactionId, conversationId) {
324
- const interactions = this._channel?.conversations[conversationId]?.interactions;
268
+ const interactions = this._conversations[conversationId]?.interactions;
325
269
  if (!interactions || Array.isArray(interactions) || !interactions[interactionId]) {
326
270
  throw new Error(`Interaction "${interactionId}" not found in conversation "${conversationId}"`);
327
271
  }
328
272
  this._activeLeaves.set(conversationId, interactionId);
329
273
  this.emit('conversationUpdated', {
330
274
  conversationId,
331
- channelId: this._channelId,
332
275
  source: 'local_user',
333
276
  });
334
277
  }
335
278
  /**
336
- * Get all conversations in this channel.
279
+ * Get all conversations in this space.
337
280
  * Returns summary info (no full interaction data) for each conversation.
338
281
  */
339
282
  getConversations() {
340
- if (!this._channel)
341
- return [];
342
- return Object.entries(this._channel.conversations).map(([id, conv]) => ({
283
+ return Object.entries(this._conversations).map(([id, conv]) => ({
343
284
  id,
344
285
  name: conv.name ?? null,
345
286
  systemInstruction: conv.systemInstruction ?? null,
@@ -349,74 +290,67 @@ export class RoolChannel extends EventEmitter {
349
290
  }));
350
291
  }
351
292
  /**
352
- * Delete a conversation from this channel.
353
- * Cannot delete the conversation you are currently using.
293
+ * Delete a conversation from this space.
354
294
  */
355
295
  async deleteConversation(conversationId) {
356
- if (conversationId === this._conversationId) {
357
- throw new Error('Cannot delete the active conversation');
358
- }
359
- await this.graphqlClient.deleteConversation(this._id, this._channelId, conversationId);
296
+ await this._graphqlClient.deleteConversation(this._id, conversationId);
360
297
  // Optimistic local update — remove from cache and emit event
361
298
  // in case the server doesn't send a conversation_updated event for deletes
362
- if (this._channel?.conversations[conversationId]) {
363
- delete this._channel.conversations[conversationId];
299
+ if (this._conversations[conversationId]) {
300
+ delete this._conversations[conversationId];
364
301
  this.emit('conversationUpdated', {
365
302
  conversationId,
366
- channelId: this._channelId,
367
303
  source: 'local_user',
368
304
  });
369
305
  }
370
306
  }
371
307
  /**
372
- * Get a handle for a specific conversation within this channel.
308
+ * Get a handle for a specific conversation.
373
309
  */
374
310
  conversation(conversationId) {
375
311
  return new ConversationHandle(this, conversationId);
376
312
  }
377
313
  /**
378
- * Close this channel and clean up resources.
379
- * Stops real-time subscription and unregisters from client.
314
+ * Close this space session and clean up resources.
380
315
  */
381
316
  close() {
382
317
  this._closed = true;
383
- this.onCloseCallback();
318
+ this._onCloseCallback();
384
319
  this.removeAllListeners();
385
320
  }
386
321
  /**
387
322
  * Create a checkpoint of the current space state.
388
323
  */
389
324
  async checkpoint(label = 'Change') {
390
- const result = await this.graphqlClient.checkpoint(this._id, label, this._channelId);
325
+ const result = await this._graphqlClient.checkpoint(this._id, label);
391
326
  return result.checkpointId;
392
327
  }
393
328
  /** Check if undo is available for this space. */
394
329
  async canUndo() {
395
- const status = await this.graphqlClient.checkpointStatus(this._id, this._channelId);
330
+ const status = await this._graphqlClient.checkpointStatus(this._id);
396
331
  return status.canUndo;
397
332
  }
398
333
  /** Check if redo is available for this space. */
399
334
  async canRedo() {
400
- const status = await this.graphqlClient.checkpointStatus(this._id, this._channelId);
335
+ const status = await this._graphqlClient.checkpointStatus(this._id);
401
336
  return status.canRedo;
402
337
  }
403
338
  /** Restore the space to the most recent checkpoint. */
404
339
  async undo() {
405
- const result = await this.graphqlClient.undo(this._id, this._channelId);
340
+ const result = await this._graphqlClient.undo(this._id);
406
341
  return result.success;
407
342
  }
408
343
  /** Reapply the most recently undone checkpoint. */
409
344
  async redo() {
410
- const result = await this.graphqlClient.redo(this._id, this._channelId);
345
+ const result = await this._graphqlClient.redo(this._id);
411
346
  return result.success;
412
347
  }
413
348
  /** Clear the space's checkpoint history. */
414
349
  async clearHistory() {
415
- await this.graphqlClient.clearCheckpointHistory(this._id, this._channelId);
350
+ await this._graphqlClient.clearCheckpointHistory(this._id);
416
351
  }
417
352
  davHeaders(conversationId, interactionId) {
418
353
  const headers = new Headers({
419
- 'X-Rool-Channel-Id': this._channelId,
420
354
  'X-Rool-Conversation-Id': conversationId,
421
355
  });
422
356
  if (interactionId)
@@ -426,7 +360,7 @@ export class RoolChannel extends EventEmitter {
426
360
  async readObject(path) {
427
361
  const canonical = objectPath(path);
428
362
  try {
429
- const response = await this.webdav.get(canonical);
363
+ const response = await this._webdav.get(canonical);
430
364
  const body = jsonObject(await response.json(), `Object ${canonical}`);
431
365
  return { object: objectFromBody(canonical, body), etag: response.headers.get('ETag') };
432
366
  }
@@ -456,7 +390,7 @@ export class RoolChannel extends EventEmitter {
456
390
  const result = { objects: [], missing: [] };
457
391
  for (let i = 0; i < canonical.length; i += GET_OBJECTS_CHUNK_SIZE) {
458
392
  const chunk = canonical.slice(i, i + GET_OBJECTS_CHUNK_SIZE);
459
- const partial = await this.restClient.getObjects(this._id, chunk);
393
+ const partial = await this._restClient.getObjects(this._id, chunk);
460
394
  result.objects.push(...partial.objects);
461
395
  result.missing.push(...partial.missing);
462
396
  }
@@ -466,17 +400,13 @@ export class RoolChannel extends EventEmitter {
466
400
  stat(path) {
467
401
  return this._objectStats.get(objectPath(path));
468
402
  }
469
- /** Create or replace an object JSON file at an exact machine path. */
470
- async putObject(path, body) {
471
- return this._putObjectImpl(path, body, this._conversationId);
472
- }
473
403
  /** @internal */
474
404
  async _putObjectImpl(path, body, conversationId) {
475
405
  const canonical = objectPath(path);
476
406
  const optimistic = objectFromBody(canonical, body);
477
407
  try {
478
408
  const interactionId = generateEntityId();
479
- await this.webdav.put(canonical, JSON.stringify(body), {
409
+ await this._webdav.put(canonical, JSON.stringify(body), {
480
410
  contentType: 'application/json',
481
411
  headers: this.davHeaders(conversationId, interactionId),
482
412
  });
@@ -484,15 +414,11 @@ export class RoolChannel extends EventEmitter {
484
414
  return { object: fresh, message: `Put ${canonical}` };
485
415
  }
486
416
  catch (error) {
487
- this.logger.error('[RoolChannel] Failed to put object:', error);
417
+ this._logger.error('[RoolSpace] Failed to put object:', error);
488
418
  this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
489
419
  throw error;
490
420
  }
491
421
  }
492
- /** Patch an existing object. Null or undefined deletes a field. */
493
- async patchObject(path, options) {
494
- return this._patchObjectImpl(path, options, this._conversationId);
495
- }
496
422
  /** @internal */
497
423
  async _patchObjectImpl(path, options, conversationId) {
498
424
  const canonical = objectPath(path);
@@ -504,7 +430,7 @@ export class RoolChannel extends EventEmitter {
504
430
  const optimistic = objectFromBody(canonical, body);
505
431
  try {
506
432
  const interactionId = generateEntityId();
507
- await this.webdav.put(canonical, JSON.stringify(body), {
433
+ await this._webdav.put(canonical, JSON.stringify(body), {
508
434
  contentType: 'application/json',
509
435
  ifMatch: current.etag ?? undefined,
510
436
  headers: this.davHeaders(conversationId, interactionId),
@@ -513,15 +439,11 @@ export class RoolChannel extends EventEmitter {
513
439
  return { object: fresh, message: `Patched ${canonical}` };
514
440
  }
515
441
  catch (error) {
516
- this.logger.error('[RoolChannel] Failed to patch object:', error);
442
+ this._logger.error('[RoolSpace] Failed to patch object:', error);
517
443
  this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
518
444
  throw error;
519
445
  }
520
446
  }
521
- /** Move an object JSON file to a new machine path, optionally replacing its body. */
522
- async moveObject(from, to, options) {
523
- return this._moveObjectImpl(from, to, options, this._conversationId);
524
- }
525
447
  /** @internal */
526
448
  async _moveObjectImpl(from, to, options, conversationId) {
527
449
  const fromPath = objectPath(from);
@@ -529,11 +451,11 @@ export class RoolChannel extends EventEmitter {
529
451
  const optimistic = objectFromBody(toPath, options?.body ?? {});
530
452
  try {
531
453
  const interactionId = generateEntityId();
532
- await this.webdav.move(fromPath, toPath, {
454
+ await this._webdav.move(fromPath, toPath, {
533
455
  headers: this.davHeaders(conversationId, interactionId),
534
456
  });
535
457
  if (options?.body) {
536
- await this.webdav.put(toPath, JSON.stringify(options.body), {
458
+ await this._webdav.put(toPath, JSON.stringify(options.body), {
537
459
  contentType: 'application/json',
538
460
  headers: this.davHeaders(conversationId, interactionId),
539
461
  });
@@ -543,19 +465,11 @@ export class RoolChannel extends EventEmitter {
543
465
  return { object: fresh, message: `Moved ${fromPath} to ${toPath}` };
544
466
  }
545
467
  catch (error) {
546
- this.logger.error('[RoolChannel] Failed to move object:', error);
468
+ this._logger.error('[RoolSpace] Failed to move object:', error);
547
469
  this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
548
470
  throw error;
549
471
  }
550
472
  }
551
- /** Delete object JSON files by machine path. */
552
- async deleteObjects(paths) {
553
- return this._deleteObjectsImpl(paths, this._conversationId);
554
- }
555
- /** @deprecated Use deleteObjects instead. */
556
- async deletePaths(paths) {
557
- return this.deleteObjects(paths);
558
- }
559
473
  /** @internal */
560
474
  async _deleteObjectsImpl(paths, conversationId) {
561
475
  if (paths.length === 0)
@@ -564,14 +478,14 @@ export class RoolChannel extends EventEmitter {
564
478
  try {
565
479
  const interactionId = generateEntityId();
566
480
  for (const path of canonical) {
567
- await this.webdav.delete(path, {
481
+ await this._webdav.delete(path, {
568
482
  headers: this.davHeaders(conversationId, interactionId),
569
483
  });
570
484
  this._objectStats.delete(path);
571
485
  }
572
486
  }
573
487
  catch (error) {
574
- this.logger.error('[RoolChannel] Failed to delete paths:', error);
488
+ this._logger.error('[RoolSpace] Failed to delete paths:', error);
575
489
  this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
576
490
  throw error;
577
491
  }
@@ -580,10 +494,6 @@ export class RoolChannel extends EventEmitter {
580
494
  getSchema() {
581
495
  return this._schema;
582
496
  }
583
- /** Create a new collection schema. */
584
- async createCollection(name, fields, options) {
585
- return this._createCollectionImpl(name, fields, options, this._conversationId);
586
- }
587
497
  /** @internal */
588
498
  async _createCollectionImpl(name, fields, options, conversationId) {
589
499
  if (this._schema[name]) {
@@ -593,8 +503,8 @@ export class RoolChannel extends EventEmitter {
593
503
  const optimisticDef = collectionDef(fields, options);
594
504
  this._schema[name] = optimisticDef;
595
505
  try {
596
- await this.webdav.mkcol(collectionPath(name), { headers: this.davHeaders(conversationId, generateEntityId()) });
597
- await this.webdav.put(schemaPath(name), JSON.stringify(optimisticDef), {
506
+ await this._webdav.mkcol(collectionPath(name), { headers: this.davHeaders(conversationId, generateEntityId()) });
507
+ await this._webdav.put(schemaPath(name), JSON.stringify(optimisticDef), {
598
508
  contentType: 'application/json',
599
509
  headers: this.davHeaders(conversationId, generateEntityId()),
600
510
  });
@@ -602,15 +512,11 @@ export class RoolChannel extends EventEmitter {
602
512
  return optimisticDef;
603
513
  }
604
514
  catch (error) {
605
- this.logger.error('[RoolChannel] Failed to create collection:', error);
515
+ this._logger.error('[RoolSpace] Failed to create collection:', error);
606
516
  delete this._schema[name];
607
517
  throw error;
608
518
  }
609
519
  }
610
- /** Alter an existing collection schema, replacing its field definitions. */
611
- async alterCollection(name, fields, options) {
612
- return this._alterCollectionImpl(name, fields, options, this._conversationId);
613
- }
614
520
  /** @internal */
615
521
  async _alterCollectionImpl(name, fields, options, conversationId) {
616
522
  if (!this._schema[name]) {
@@ -620,7 +526,7 @@ export class RoolChannel extends EventEmitter {
620
526
  this._schema[name] = collectionDef(fields, options);
621
527
  try {
622
528
  const updated = this._schema[name];
623
- await this.webdav.put(schemaPath(name), JSON.stringify(updated), {
529
+ await this._webdav.put(schemaPath(name), JSON.stringify(updated), {
624
530
  contentType: 'application/json',
625
531
  headers: this.davHeaders(conversationId, generateEntityId()),
626
532
  });
@@ -628,15 +534,11 @@ export class RoolChannel extends EventEmitter {
628
534
  return updated;
629
535
  }
630
536
  catch (error) {
631
- this.logger.error('[RoolChannel] Failed to alter collection:', error);
537
+ this._logger.error('[RoolSpace] Failed to alter collection:', error);
632
538
  this._schema[name] = previous;
633
539
  throw error;
634
540
  }
635
541
  }
636
- /** Drop a collection schema. */
637
- async dropCollection(name) {
638
- return this._dropCollectionImpl(name, this._conversationId);
639
- }
640
542
  /** @internal */
641
543
  async _dropCollectionImpl(name, conversationId) {
642
544
  if (!this._schema[name]) {
@@ -645,33 +547,23 @@ export class RoolChannel extends EventEmitter {
645
547
  const previous = this._schema[name];
646
548
  delete this._schema[name];
647
549
  try {
648
- await this.webdav.delete(collectionPath(name), { collection: true, headers: this.davHeaders(conversationId, generateEntityId()) });
550
+ await this._webdav.delete(collectionPath(name), { collection: true, headers: this.davHeaders(conversationId, generateEntityId()) });
649
551
  this.emit('schemaUpdated', { schema: this._schema, source: 'local_user' });
650
552
  }
651
553
  catch (error) {
652
- this.logger.error('[RoolChannel] Failed to drop collection:', error);
554
+ this._logger.error('[RoolSpace] Failed to drop collection:', error);
653
555
  this._schema[name] = previous;
654
556
  throw error;
655
557
  }
656
558
  }
657
- /**
658
- * Get the system instruction for the current conversation.
659
- */
660
- getSystemInstruction() {
661
- return this._getSystemInstructionImpl(this._conversationId);
662
- }
663
559
  /** @internal */
664
560
  _getSystemInstructionImpl(conversationId) {
665
- return this._channel?.conversations[conversationId]?.systemInstruction;
666
- }
667
- /** Set the system instruction for the current conversation. */
668
- async setSystemInstruction(instruction) {
669
- return this._setSystemInstructionImpl(instruction, this._conversationId);
561
+ return this._conversations[conversationId]?.systemInstruction;
670
562
  }
671
563
  /** @internal */
672
564
  async _setSystemInstructionImpl(instruction, conversationId) {
673
565
  this._ensureConversationImpl(conversationId);
674
- const conv = this._channel.conversations[conversationId];
566
+ const conv = this._conversations[conversationId];
675
567
  const previousInstruction = conv.systemInstruction;
676
568
  if (instruction === null) {
677
569
  delete conv.systemInstruction;
@@ -681,20 +573,13 @@ export class RoolChannel extends EventEmitter {
681
573
  }
682
574
  this.emit('conversationUpdated', {
683
575
  conversationId,
684
- channelId: this._channelId,
685
576
  source: 'local_user',
686
577
  });
687
- if (conversationId === this._conversationId) {
688
- this.emit('channelUpdated', {
689
- channelId: this._channelId,
690
- source: 'local_user',
691
- });
692
- }
693
578
  try {
694
- await this.graphqlClient.updateConversation(this._id, this._channelId, conversationId, { systemInstruction: instruction });
579
+ await this._graphqlClient.updateConversation(this._id, conversationId, { systemInstruction: instruction });
695
580
  }
696
581
  catch (error) {
697
- this.logger.error('[RoolChannel] Failed to set system instruction:', error);
582
+ this._logger.error('[RoolSpace] Failed to set system instruction:', error);
698
583
  if (previousInstruction === undefined) {
699
584
  delete conv.systemInstruction;
700
585
  }
@@ -704,65 +589,43 @@ export class RoolChannel extends EventEmitter {
704
589
  throw error;
705
590
  }
706
591
  }
707
- /** Rename the current conversation. */
708
- async renameConversation(name) {
709
- return this._renameConversationImpl(name, this._conversationId);
710
- }
711
592
  /** @internal */
712
593
  async _renameConversationImpl(name, conversationId) {
713
594
  this._ensureConversationImpl(conversationId);
714
- const conv = this._channel.conversations[conversationId];
595
+ const conv = this._conversations[conversationId];
715
596
  const previousName = conv.name;
716
597
  conv.name = name;
717
598
  this.emit('conversationUpdated', {
718
599
  conversationId,
719
- channelId: this._channelId,
720
600
  source: 'local_user',
721
601
  });
722
- if (conversationId === this._conversationId) {
723
- this.emit('channelUpdated', {
724
- channelId: this._channelId,
725
- source: 'local_user',
726
- });
727
- }
728
602
  try {
729
- await this.graphqlClient.updateConversation(this._id, this._channelId, conversationId, { name });
603
+ await this._graphqlClient.updateConversation(this._id, conversationId, { name });
730
604
  }
731
605
  catch (error) {
732
- this.logger.error('[RoolChannel] Failed to rename conversation:', error);
606
+ this._logger.error('[RoolSpace] Failed to rename conversation:', error);
733
607
  conv.name = previousName;
734
608
  throw error;
735
609
  }
736
610
  }
737
611
  /** @internal */
738
612
  _ensureConversationImpl(conversationId) {
739
- if (!this._channel) {
740
- this._channel = {
741
- createdAt: Date.now(),
742
- createdBy: this._userId,
743
- conversations: {},
744
- };
745
- }
746
- if (!this._channel.conversations[conversationId]) {
747
- this._channel.conversations[conversationId] = {
613
+ if (!this._conversations[conversationId]) {
614
+ this._conversations[conversationId] = {
748
615
  createdAt: Date.now(),
749
616
  createdBy: this._userId,
750
617
  interactions: {},
751
618
  };
752
619
  }
753
620
  }
754
- /** Set a space-level metadata value. */
755
- setMetadata(key, value) {
756
- this._setMetadataImpl(key, value, this._conversationId);
757
- }
758
621
  /** @internal */
759
622
  _setMetadataImpl(key, value, conversationId) {
760
623
  this._meta[key] = value;
761
624
  this.emit('metadataUpdated', { metadata: this._meta, source: 'local_user' });
762
625
  // Fire-and-forget server call
763
- this.graphqlClient.setSpaceMeta(this._id, this._meta, this._channelId, conversationId)
626
+ this._graphqlClient.setSpaceMeta(this._id, this._meta, conversationId)
764
627
  .catch((error) => {
765
- this.logger.error('[RoolChannel] Failed to set meta:', error);
628
+ this._logger.error('[RoolSpace] Failed to set meta:', error);
766
629
  });
767
630
  }
768
631
  /** Get a space-level metadata value. */
@@ -773,13 +636,6 @@ export class RoolChannel extends EventEmitter {
773
636
  getAllMetadata() {
774
637
  return this._meta;
775
638
  }
776
- /**
777
- * Send a prompt to the AI agent for space manipulation.
778
- * @returns The message from the AI and the list of objects that were created or modified.
779
- */
780
- async prompt(prompt, options) {
781
- return this._promptImpl(prompt, options, this._conversationId);
782
- }
783
639
  /** @internal */
784
640
  async _promptImpl(prompt, options, conversationId) {
785
641
  const { attachments, parentInteractionId: explicitParent, signal, ...rest } = options ?? {};
@@ -811,7 +667,7 @@ export class RoolChannel extends EventEmitter {
811
667
  }
812
668
  let result;
813
669
  try {
814
- result = await this.graphqlClient.prompt(this._id, prompt, this._channelId, conversationId, {
670
+ result = await this._graphqlClient.prompt(this._id, prompt, conversationId, {
815
671
  ...rest,
816
672
  attachmentRefs,
817
673
  interactionId,
@@ -833,32 +689,21 @@ export class RoolChannel extends EventEmitter {
833
689
  objects,
834
690
  };
835
691
  }
836
- /**
837
- * Stop the in-flight interaction on the default conversation, if any.
838
- *
839
- * No-op returning `false` when the active leaf is already finished or the
840
- * conversation has no interactions. Stopping is best-effort: the server
841
- * halts the agent loop and closes the stream, but an LLM turn already in
842
- * flight keeps generating server-side and is billed.
843
- */
844
- async stop() {
845
- return this._stopImpl(this._conversationId);
846
- }
847
692
  /**
848
693
  * Request that the server stop a specific in-flight interaction by ID.
849
694
  *
850
695
  * Returns whether the server stopped an interaction (`false` if it had
851
- * already finished). Stopping is best-effort — see {@link stop}.
696
+ * already finished). Stopping is best-effort — see {@link ConversationHandle.stop}.
852
697
  */
853
698
  async stopInteraction(interactionId) {
854
- return this.graphqlClient.stopInteraction(this._id, interactionId);
699
+ return this._graphqlClient.stopInteraction(this._id, interactionId);
855
700
  }
856
701
  /** @internal */
857
702
  async _stopImpl(conversationId) {
858
703
  const leafId = this._getActiveLeafImpl(conversationId);
859
704
  if (!leafId)
860
705
  return false;
861
- const interactions = this._channel?.conversations[conversationId]?.interactions;
706
+ const interactions = this._conversations[conversationId]?.interactions;
862
707
  const interaction = interactions && !Array.isArray(interactions) ? interactions[leafId] : undefined;
863
708
  // Skip the round trip when we already know the interaction has settled.
864
709
  if (interaction && (interaction.status === 'done' || interaction.status === 'error')) {
@@ -866,29 +711,11 @@ export class RoolChannel extends EventEmitter {
866
711
  }
867
712
  return this.stopInteraction(leafId);
868
713
  }
869
- /** Rename this channel. */
870
- async rename(newName) {
871
- const previousName = this._channel?.name;
872
- if (this._channel) {
873
- this._channel.name = newName;
874
- }
875
- this.emit('channelUpdated', { channelId: this._channelId, source: 'local_user' });
876
- try {
877
- await this.graphqlClient.renameChannel(this._id, this._channelId, newName);
878
- }
879
- catch (error) {
880
- this.logger.error('[RoolChannel] Failed to rename channel:', error);
881
- if (this._channel) {
882
- this._channel.name = previousName;
883
- }
884
- throw error;
885
- }
886
- }
887
714
  /**
888
715
  * Fetch an external URL via the server proxy, bypassing CORS restrictions.
889
716
  */
890
717
  async fetch(url, init) {
891
- return this.restClient.proxyFetch(this._id, url, init);
718
+ return this._restClient.proxyFetch(this._id, url, init);
892
719
  }
893
720
  async uploadAttachment(file, conversationId) {
894
721
  await this.ensureCollection('/rool-drive/attachments');
@@ -896,70 +723,32 @@ export class RoolChannel extends EventEmitter {
896
723
  await this.ensureCollection(directory);
897
724
  const attachment = attachmentBody(file);
898
725
  const path = `${directory}/${attachment.filename}`;
899
- await this.webdav.put(path, attachment.body, { contentType: attachment.contentType });
726
+ await this._webdav.put(path, attachment.body, { contentType: attachment.contentType });
900
727
  return path;
901
728
  }
902
729
  async ensureCollection(path) {
903
- const response = await this.webdav.request('MKCOL', path, { collection: true });
730
+ const response = await this._webdav.request('MKCOL', path, { collection: true });
904
731
  if (response.status === 201 || response.status === 405)
905
732
  return;
906
733
  throw new Error(`Failed to create collection ${path}: ${response.status} ${await response.text()}`);
907
734
  }
908
735
  /**
909
- * Handle a channel event from the subscription.
736
+ * Handle a space event from the subscription.
910
737
  * @internal
911
738
  */
912
- handleChannelEvent(event) {
739
+ applySpaceContentEvent(event) {
913
740
  if (this._closed)
914
741
  return;
915
742
  const changeSource = event.source === 'agent' ? 'remote_agent' : 'remote_user';
916
743
  switch (event.type) {
917
- case 'connected':
918
- // Resync is handled by the client via _applyResyncData.
919
- break;
920
- case 'schema_updated':
921
- if (event.schema) {
922
- this._schema = event.schema;
923
- this.emit('schemaUpdated', { schema: this._schema, source: changeSource });
924
- }
925
- break;
926
- case 'metadata_updated':
927
- if (event.metadata) {
928
- this._meta = event.metadata;
929
- this.emit('metadataUpdated', { metadata: this._meta, source: changeSource });
930
- }
931
- break;
932
- case 'channel_updated':
933
- if (event.channelId === this._channelId && event.channel) {
934
- const changed = JSON.stringify(this._channel) !== JSON.stringify(event.channel);
935
- this._channel = event.channel;
936
- if (changed) {
937
- this.emit('channelUpdated', { channelId: event.channelId, source: changeSource });
938
- }
939
- }
940
- break;
941
- case 'channel_deleted':
942
- if (event.channelId === this._channelId) {
943
- this._channel = undefined;
944
- this._activeLeaves.clear();
945
- this.emit('reset', { source: changeSource });
946
- }
947
- break;
948
744
  case 'conversation_updated':
949
- if (event.channelId === this._channelId && event.conversationId) {
950
- if (!this._channel) {
951
- this._channel = {
952
- createdAt: Date.now(),
953
- createdBy: this._userId,
954
- conversations: {},
955
- };
956
- }
957
- const prev = this._channel.conversations[event.conversationId];
745
+ if (event.conversationId) {
746
+ const prev = this._conversations[event.conversationId];
958
747
  if (event.conversation) {
959
- this._channel.conversations[event.conversationId] = event.conversation;
748
+ this._conversations[event.conversationId] = event.conversation;
960
749
  }
961
750
  else {
962
- delete this._channel.conversations[event.conversationId];
751
+ delete this._conversations[event.conversationId];
963
752
  }
964
753
  if (JSON.stringify(prev) === JSON.stringify(event.conversation))
965
754
  break;
@@ -976,77 +765,74 @@ export class RoolChannel extends EventEmitter {
976
765
  }
977
766
  this.emit('conversationUpdated', {
978
767
  conversationId: event.conversationId,
979
- channelId: event.channelId,
980
768
  source: changeSource,
981
769
  });
982
- if (event.conversationId === this._conversationId) {
983
- this.emit('channelUpdated', { channelId: event.channelId, source: changeSource });
984
- }
985
770
  }
986
771
  break;
987
- case 'space_changed':
988
- // Resync is handled by the client via _applyResyncData.
989
- break;
990
772
  }
991
773
  }
992
774
  }
993
775
  /**
994
- * A lightweight handle for a specific conversation within a channel.
776
+ * A lightweight handle for a specific conversation.
995
777
  */
996
778
  export class ConversationHandle {
997
779
  /** @internal */
998
- _channel;
780
+ _space;
999
781
  _conversationId;
1000
782
  /** @internal */
1001
- constructor(channel, conversationId) {
1002
- this._channel = channel;
783
+ constructor(space, conversationId) {
784
+ this._space = space;
1003
785
  this._conversationId = conversationId;
1004
786
  }
1005
787
  /** The conversation ID this handle is scoped to. */
1006
788
  get conversationId() { return this._conversationId; }
1007
789
  /** Get the active branch of this conversation as a flat array (root → leaf). */
1008
790
  getInteractions() {
1009
- return this._channel._getInteractionsImpl(this._conversationId);
791
+ return this._space._getInteractionsImpl(this._conversationId);
1010
792
  }
1011
793
  /** Get the full interaction tree as a record. */
1012
794
  getTree() {
1013
- return this._channel._getTreeImpl(this._conversationId);
795
+ return this._space._getTreeImpl(this._conversationId);
1014
796
  }
1015
797
  /** Get the active leaf interaction ID, or undefined if empty. */
1016
798
  get activeLeafId() {
1017
- return this._channel._getActiveLeafImpl(this._conversationId);
799
+ return this._space._getActiveLeafImpl(this._conversationId);
1018
800
  }
1019
801
  /** Switch to a different branch by setting the active leaf. */
1020
802
  setActiveLeaf(interactionId) {
1021
- this._channel._setActiveLeafImpl(interactionId, this._conversationId);
803
+ this._space._setActiveLeafImpl(interactionId, this._conversationId);
1022
804
  }
1023
805
  /** Get the system instruction for this conversation. */
1024
806
  getSystemInstruction() {
1025
- return this._channel._getSystemInstructionImpl(this._conversationId);
807
+ return this._space._getSystemInstructionImpl(this._conversationId);
1026
808
  }
1027
809
  /** Set the system instruction for this conversation. Pass null to clear. */
1028
810
  async setSystemInstruction(instruction) {
1029
- return this._channel._setSystemInstructionImpl(instruction, this._conversationId);
811
+ return this._space._setSystemInstructionImpl(instruction, this._conversationId);
1030
812
  }
1031
813
  /** Rename this conversation. */
1032
814
  async rename(name) {
1033
- return this._channel._renameConversationImpl(name, this._conversationId);
815
+ return this._space._renameConversationImpl(name, this._conversationId);
816
+ }
817
+ /** Delete this conversation. */
818
+ async delete() {
819
+ return this._space.deleteConversation(this._conversationId);
1034
820
  }
1035
821
  /** Create or replace an object JSON file. */
1036
822
  async putObject(path, body) {
1037
- return this._channel._putObjectImpl(path, body, this._conversationId);
823
+ return this._space._putObjectImpl(path, body, this._conversationId);
1038
824
  }
1039
825
  /** Patch an existing object JSON file. */
1040
826
  async patchObject(path, options) {
1041
- return this._channel._patchObjectImpl(path, options, this._conversationId);
827
+ return this._space._patchObjectImpl(path, options, this._conversationId);
1042
828
  }
1043
829
  /** Move (rename/relocate) an object. */
1044
830
  async moveObject(from, to, options) {
1045
- return this._channel._moveObjectImpl(from, to, options, this._conversationId);
831
+ return this._space._moveObjectImpl(from, to, options, this._conversationId);
1046
832
  }
1047
833
  /** Delete object JSON files by path. */
1048
834
  async deleteObjects(paths) {
1049
- return this._channel._deleteObjectsImpl(paths, this._conversationId);
835
+ return this._space._deleteObjectsImpl(paths, this._conversationId);
1050
836
  }
1051
837
  /** @deprecated Use deleteObjects instead. */
1052
838
  async deletePaths(paths) {
@@ -1054,30 +840,30 @@ export class ConversationHandle {
1054
840
  }
1055
841
  /** Send a prompt to the AI agent, scoped to this conversation's history. */
1056
842
  async prompt(text, options) {
1057
- return this._channel._promptImpl(text, options, this._conversationId);
843
+ return this._space._promptImpl(text, options, this._conversationId);
1058
844
  }
1059
845
  /**
1060
846
  * Stop this conversation's in-flight interaction, if any. No-op returning
1061
847
  * `false` when nothing is running. Stopping is best-effort — see
1062
- * {@link RoolChannel.stop}.
848
+ * {@link RoolSpace.stopInteraction}.
1063
849
  */
1064
850
  async stop() {
1065
- return this._channel._stopImpl(this._conversationId);
851
+ return this._space._stopImpl(this._conversationId);
1066
852
  }
1067
853
  /** Create a new collection schema. */
1068
854
  async createCollection(name, fields, options) {
1069
- return this._channel._createCollectionImpl(name, fields, options, this._conversationId);
855
+ return this._space._createCollectionImpl(name, fields, options, this._conversationId);
1070
856
  }
1071
857
  /** Alter an existing collection schema. */
1072
858
  async alterCollection(name, fields, options) {
1073
- return this._channel._alterCollectionImpl(name, fields, options, this._conversationId);
859
+ return this._space._alterCollectionImpl(name, fields, options, this._conversationId);
1074
860
  }
1075
861
  /** Drop a collection schema. */
1076
862
  async dropCollection(name) {
1077
- return this._channel._dropCollectionImpl(name, this._conversationId);
863
+ return this._space._dropCollectionImpl(name, this._conversationId);
1078
864
  }
1079
865
  setMetadata(key, value) {
1080
- return this._channel._setMetadataImpl(key, value, this._conversationId);
866
+ return this._space._setMetadataImpl(key, value, this._conversationId);
1081
867
  }
1082
868
  }
1083
- //# sourceMappingURL=channel.js.map
869
+ //# sourceMappingURL=space-session.js.map