@rool-dev/extension 0.3.7 → 0.3.8-dev.b3f8671

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.
@@ -1,10 +1,17 @@
1
1
  /**
2
- * Reactive Svelte wrapper around Channel (bridge client).
2
+ * Extension channel bridge client with Svelte 5 reactivity.
3
3
  *
4
- * Mirrors the @rool-dev/svelte ReactiveChannel API: same $state properties,
5
- * same methods, same reactive primitives (object(), watch()).
6
- * The underlying transport is the postMessage bridge, not the SDK.
4
+ * Handles the postMessage bridge to the host and provides reactive $state
5
+ * properties, matching the @rool-dev/svelte ReactiveChannel API.
7
6
  */
7
+ import { isBridgeMessage } from './protocol.js';
8
+ // ---------------------------------------------------------------------------
9
+ // Helpers
10
+ // ---------------------------------------------------------------------------
11
+ let _nextId = 0;
12
+ function nextRequestId() {
13
+ return `req-${++_nextId}-${Date.now().toString(36)}`;
14
+ }
8
15
  // ---------------------------------------------------------------------------
9
16
  // ReactiveWatch
10
17
  // ---------------------------------------------------------------------------
@@ -160,125 +167,324 @@ class ReactiveObjectImpl {
160
167
  // ReactiveChannel
161
168
  // ---------------------------------------------------------------------------
162
169
  class ReactiveChannelImpl {
163
- #channel;
170
+ _pending = new Map();
171
+ _listeners = new Map();
172
+ _schema;
173
+ _metadata;
174
+ #unsubscribers = [];
164
175
  #closed = false;
176
+ // Metadata from handshake
177
+ channelId;
178
+ spaceId;
179
+ spaceName;
180
+ role;
181
+ linkAccess;
182
+ userId;
183
+ /** Current user info (id, name, email). */
184
+ user;
165
185
  // Reactive state
166
186
  interactions = $state([]);
167
187
  objectIds = $state([]);
168
188
  collections = $state([]);
169
189
  conversations = $state([]);
170
- #unsubscribers = [];
171
- constructor(channel) {
172
- this.#channel = channel;
190
+ constructor(init) {
191
+ this.channelId = init.channelId;
192
+ this.spaceId = init.spaceId;
193
+ this.spaceName = init.spaceName;
194
+ this.role = init.role;
195
+ this.linkAccess = init.linkAccess;
196
+ this.userId = init.userId;
197
+ this.user = init.user;
198
+ this._schema = init.schema;
199
+ this._metadata = init.metadata;
200
+ window.addEventListener('message', this._onMessage);
173
201
  // Load initial data
174
- channel.getInteractions().then((list) => { this.interactions = list; });
175
- channel.getObjectIds().then((ids) => { this.objectIds = ids; });
176
- channel.getConversations().then((list) => { this.conversations = list; });
177
- this.collections = Object.keys(channel.getSchema());
202
+ this.getInteractions().then((list) => { this.interactions = list; });
203
+ this._call('getObjectIds').then((ids) => { this.objectIds = ids; });
204
+ this.getConversations().then((list) => { this.conversations = list; });
205
+ this.collections = Object.keys(this._schema);
178
206
  // Subscribe to channel updates → refresh interactions
179
207
  const onChannelUpdated = () => {
180
- channel.getInteractions().then((list) => { this.interactions = list; });
208
+ this.getInteractions().then((list) => { this.interactions = list; });
181
209
  };
182
- channel.on('channelUpdated', onChannelUpdated);
183
- this.#unsubscribers.push(() => channel.off('channelUpdated', onChannelUpdated));
210
+ this.on('channelUpdated', onChannelUpdated);
211
+ this.#unsubscribers.push(() => this.off('channelUpdated', onChannelUpdated));
184
212
  // Subscribe to conversation updates → refresh conversations
185
213
  const onConversationUpdated = () => {
186
- channel.getConversations().then((list) => { this.conversations = list; });
214
+ this.getConversations().then((list) => { this.conversations = list; });
187
215
  };
188
- channel.on('conversationUpdated', onConversationUpdated);
189
- this.#unsubscribers.push(() => channel.off('conversationUpdated', onConversationUpdated));
216
+ this.on('conversationUpdated', onConversationUpdated);
217
+ this.#unsubscribers.push(() => this.off('conversationUpdated', onConversationUpdated));
190
218
  // Subscribe to object events → refresh objectIds
191
219
  const refreshObjectIds = () => {
192
- channel.getObjectIds().then((ids) => { this.objectIds = ids; });
220
+ this._call('getObjectIds').then((ids) => { this.objectIds = ids; });
193
221
  };
194
- channel.on('objectCreated', refreshObjectIds);
195
- this.#unsubscribers.push(() => channel.off('objectCreated', refreshObjectIds));
196
- channel.on('objectDeleted', refreshObjectIds);
197
- this.#unsubscribers.push(() => channel.off('objectDeleted', refreshObjectIds));
222
+ this.on('objectCreated', refreshObjectIds);
223
+ this.#unsubscribers.push(() => this.off('objectCreated', refreshObjectIds));
224
+ this.on('objectDeleted', refreshObjectIds);
225
+ this.#unsubscribers.push(() => this.off('objectDeleted', refreshObjectIds));
198
226
  // Subscribe to schema updates → refresh collections
199
227
  const onSchemaUpdated = () => {
200
- this.collections = Object.keys(channel.getSchema());
228
+ this.collections = Object.keys(this._schema);
201
229
  };
202
- channel.on('schemaUpdated', onSchemaUpdated);
203
- this.#unsubscribers.push(() => channel.off('schemaUpdated', onSchemaUpdated));
230
+ this.on('schemaUpdated', onSchemaUpdated);
231
+ this.#unsubscribers.push(() => this.off('schemaUpdated', onSchemaUpdated));
204
232
  // Full resets
205
233
  const onReset = () => {
206
- channel.getInteractions().then((list) => { this.interactions = list; });
207
- channel.getObjectIds().then((ids) => { this.objectIds = ids; });
208
- channel.getConversations().then((list) => { this.conversations = list; });
209
- this.collections = Object.keys(channel.getSchema());
234
+ this.getInteractions().then((list) => { this.interactions = list; });
235
+ this._call('getObjectIds').then((ids) => { this.objectIds = ids; });
236
+ this.getConversations().then((list) => { this.conversations = list; });
237
+ this.collections = Object.keys(this._schema);
210
238
  };
211
- channel.on('reset', onReset);
212
- this.#unsubscribers.push(() => channel.off('reset', onReset));
239
+ this.on('reset', onReset);
240
+ this.#unsubscribers.push(() => this.off('reset', onReset));
241
+ }
242
+ get isReadOnly() {
243
+ return this.role === 'viewer';
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // Event emitter
247
+ // ---------------------------------------------------------------------------
248
+ on(event, callback) {
249
+ let set = this._listeners.get(event);
250
+ if (!set) {
251
+ set = new Set();
252
+ this._listeners.set(event, set);
253
+ }
254
+ set.add(callback);
255
+ }
256
+ off(event, callback) {
257
+ this._listeners.get(event)?.delete(callback);
258
+ }
259
+ _emit(event, data) {
260
+ const set = this._listeners.get(event);
261
+ if (set) {
262
+ for (const cb of set) {
263
+ try {
264
+ cb(data);
265
+ }
266
+ catch (e) {
267
+ console.error(`[Channel] Error in ${event} listener:`, e);
268
+ }
269
+ }
270
+ }
271
+ }
272
+ // ---------------------------------------------------------------------------
273
+ // postMessage transport
274
+ // ---------------------------------------------------------------------------
275
+ _call(method, ...args) {
276
+ return this._callScoped(method, args);
277
+ }
278
+ /**
279
+ * Send a bridge request, optionally scoped to a conversation.
280
+ * @internal
281
+ */
282
+ _callScoped(method, args, conversationId) {
283
+ return new Promise((resolve, reject) => {
284
+ const id = nextRequestId();
285
+ this._pending.set(id, { resolve, reject });
286
+ const msg = { type: 'rool:request', id, method, args };
287
+ if (conversationId !== undefined)
288
+ msg.conversationId = conversationId;
289
+ window.parent.postMessage(msg, '*');
290
+ });
213
291
  }
214
- // Proxied properties
215
- get channelId() { return this.#channel.channelId; }
216
- get spaceId() { return this.#channel.spaceId; }
217
- get spaceName() { return this.#channel.spaceName; }
218
- get role() { return this.#channel.role; }
219
- get linkAccess() { return this.#channel.linkAccess; }
220
- get userId() { return this.#channel.userId; }
221
- get isReadOnly() { return this.#channel.isReadOnly; }
292
+ _onMessage = (event) => {
293
+ if (!isBridgeMessage(event.data))
294
+ return;
295
+ if (event.data.type === 'rool:response') {
296
+ const msg = event.data;
297
+ const pending = this._pending.get(msg.id);
298
+ if (pending) {
299
+ this._pending.delete(msg.id);
300
+ if (msg.error) {
301
+ pending.reject(new Error(msg.error));
302
+ }
303
+ else {
304
+ pending.resolve(msg.result);
305
+ }
306
+ }
307
+ return;
308
+ }
309
+ if (event.data.type === 'rool:event') {
310
+ const msg = event.data;
311
+ // Update local caches before emitting so listeners see fresh data
312
+ if (msg.name === 'metadataUpdated') {
313
+ const payload = msg.data;
314
+ this._metadata = payload.metadata;
315
+ }
316
+ else if (msg.name === 'schemaUpdated') {
317
+ const payload = msg.data;
318
+ this._schema = payload.schema;
319
+ }
320
+ else if (msg.name === 'reset') {
321
+ // Full reload happened on the host — refresh cached schema and metadata
322
+ Promise.all([
323
+ this._call('getSchema'),
324
+ this._call('getAllMetadata'),
325
+ ]).then(([schema, metadata]) => {
326
+ this._schema = schema;
327
+ this._metadata = metadata;
328
+ });
329
+ }
330
+ this._emit(msg.name, msg.data);
331
+ return;
332
+ }
333
+ };
334
+ // ---------------------------------------------------------------------------
222
335
  // Object operations
223
- getObject(objectId) { return this.#channel.getObject(objectId); }
224
- stat(objectId) { return this.#channel.stat(objectId); }
225
- findObjects(options) { return this.#channel.findObjects(options); }
226
- getObjectIds(options) { return this.#channel.getObjectIds(options); }
227
- createObject(options) { return this.#channel.createObject(options); }
228
- updateObject(objectId, options) { return this.#channel.updateObject(objectId, options); }
229
- deleteObjects(objectIds) { return this.#channel.deleteObjects(objectIds); }
230
- // AI
231
- prompt(text, options) { return this.#channel.prompt(text, options); }
232
- // Undo/redo
233
- checkpoint(label) { return this.#channel.checkpoint(label); }
234
- canUndo() { return this.#channel.canUndo(); }
235
- canRedo() { return this.#channel.canRedo(); }
236
- undo() { return this.#channel.undo(); }
237
- redo() { return this.#channel.redo(); }
238
- clearHistory() { return this.#channel.clearHistory(); }
239
- // Metadata
240
- setMetadata(key, value) { return this.#channel.setMetadata(key, value); }
241
- getMetadata(key) { return this.#channel.getMetadata(key); }
242
- getAllMetadata() { return this.#channel.getAllMetadata(); }
243
- // History
244
- getInteractions() { return this.#channel.getInteractions(); }
245
- getSystemInstruction() { return this.#channel.getSystemInstruction(); }
246
- setSystemInstruction(instruction) { return this.#channel.setSystemInstruction(instruction); }
247
- getConversations() { return this.#channel.getConversations(); }
248
- deleteConversation(conversationId) { return this.#channel.deleteConversation(conversationId); }
249
- renameConversation(name) { return this.#channel.renameConversation(name); }
336
+ // ---------------------------------------------------------------------------
337
+ async getObject(objectId) {
338
+ return this._call('getObject', objectId);
339
+ }
340
+ async stat(objectId) {
341
+ return this._call('stat', objectId);
342
+ }
343
+ async findObjects(options) {
344
+ return this._call('findObjects', options);
345
+ }
346
+ async getObjectIds(options) {
347
+ return this._call('getObjectIds', options);
348
+ }
349
+ async createObject(options) {
350
+ return this._call('createObject', options);
351
+ }
352
+ async updateObject(objectId, options) {
353
+ return this._call('updateObject', objectId, options);
354
+ }
355
+ async deleteObjects(objectIds) {
356
+ await this._call('deleteObjects', objectIds);
357
+ }
358
+ // ---------------------------------------------------------------------------
250
359
  // Schema
251
- getSchema() { return this.#channel.getSchema(); }
252
- createCollection(name, fields) { return this.#channel.createCollection(name, fields); }
253
- alterCollection(name, fields) { return this.#channel.alterCollection(name, fields); }
254
- dropCollection(name) { return this.#channel.dropCollection(name); }
360
+ // ---------------------------------------------------------------------------
361
+ getSchema() {
362
+ return this._schema;
363
+ }
364
+ async createCollection(name, fields) {
365
+ const result = await this._call('createCollection', name, fields);
366
+ this._schema[name] = result;
367
+ return result;
368
+ }
369
+ async alterCollection(name, fields) {
370
+ const result = await this._call('alterCollection', name, fields);
371
+ this._schema[name] = result;
372
+ return result;
373
+ }
374
+ async dropCollection(name) {
375
+ await this._call('dropCollection', name);
376
+ delete this._schema[name];
377
+ }
378
+ // ---------------------------------------------------------------------------
379
+ // Interactions & system instruction
380
+ // ---------------------------------------------------------------------------
381
+ async getInteractions() {
382
+ return this._call('getInteractions');
383
+ }
384
+ async getTree() {
385
+ return this._call('getTree');
386
+ }
387
+ async getActiveLeafId() {
388
+ return this._call('getActiveLeafId');
389
+ }
390
+ async setActiveLeaf(interactionId) {
391
+ await this._call('setActiveLeaf', interactionId);
392
+ }
393
+ async getSystemInstruction() {
394
+ return this._call('getSystemInstruction');
395
+ }
396
+ async setSystemInstruction(instruction) {
397
+ await this._call('setSystemInstruction', instruction);
398
+ }
399
+ async getConversations() {
400
+ return this._call('getConversations');
401
+ }
402
+ async deleteConversation(conversationId) {
403
+ await this._call('deleteConversation', conversationId);
404
+ }
405
+ async renameConversation(name) {
406
+ await this._call('renameConversation', name);
407
+ }
408
+ // ---------------------------------------------------------------------------
255
409
  // Conversations
410
+ // ---------------------------------------------------------------------------
411
+ /**
412
+ * Get a reactive handle for a specific conversation within this channel.
413
+ * Scopes AI and mutation operations to that conversation's interaction history.
414
+ * Conversations are auto-created on first interaction.
415
+ */
256
416
  conversation(conversationId) {
257
417
  if (this.#closed)
258
418
  throw new Error('Cannot create reactive conversation: channel is closed');
259
- return new ReactiveConversationHandleImpl(this.#channel, conversationId);
419
+ return new ReactiveConversationHandleImpl(this, conversationId);
420
+ }
421
+ // ---------------------------------------------------------------------------
422
+ // Metadata
423
+ // ---------------------------------------------------------------------------
424
+ async setMetadata(key, value) {
425
+ await this._call('setMetadata', key, value);
426
+ this._metadata[key] = value;
260
427
  }
261
- // Events
262
- on(...args) { return this.#channel.on(...args); }
263
- off(...args) { return this.#channel.off(...args); }
428
+ getMetadata(key) {
429
+ return this._metadata[key];
430
+ }
431
+ getAllMetadata() {
432
+ return { ...this._metadata };
433
+ }
434
+ // ---------------------------------------------------------------------------
435
+ // AI
436
+ // ---------------------------------------------------------------------------
437
+ async prompt(text, options) {
438
+ return this._call('prompt', text, options);
439
+ }
440
+ // ---------------------------------------------------------------------------
441
+ // Undo/redo
442
+ // ---------------------------------------------------------------------------
443
+ async checkpoint(label) {
444
+ return this._call('checkpoint', label);
445
+ }
446
+ async canUndo() {
447
+ return this._call('canUndo');
448
+ }
449
+ async canRedo() {
450
+ return this._call('canRedo');
451
+ }
452
+ async undo() {
453
+ return this._call('undo');
454
+ }
455
+ async redo() {
456
+ return this._call('redo');
457
+ }
458
+ async clearHistory() {
459
+ await this._call('clearHistory');
460
+ }
461
+ // ---------------------------------------------------------------------------
264
462
  // Reactive primitives
463
+ // ---------------------------------------------------------------------------
265
464
  object(objectId) {
266
465
  if (this.#closed)
267
466
  throw new Error('Cannot create reactive object: channel is closed');
268
- return new ReactiveObjectImpl(this.#channel, objectId);
467
+ return new ReactiveObjectImpl(this, objectId);
269
468
  }
270
469
  watch(options) {
271
470
  if (this.#closed)
272
471
  throw new Error('Cannot create reactive watch: channel is closed');
273
- return new ReactiveWatchImpl(this.#channel, options);
472
+ return new ReactiveWatchImpl(this, options);
274
473
  }
474
+ // ---------------------------------------------------------------------------
275
475
  // Cleanup
476
+ // ---------------------------------------------------------------------------
276
477
  destroy() {
277
478
  this.#closed = true;
278
479
  for (const unsub of this.#unsubscribers)
279
480
  unsub();
280
481
  this.#unsubscribers.length = 0;
281
- this.#channel.destroy();
482
+ window.removeEventListener('message', this._onMessage);
483
+ for (const { reject } of this._pending.values()) {
484
+ reject(new Error('Channel destroyed'));
485
+ }
486
+ this._pending.clear();
487
+ this._listeners.clear();
282
488
  }
283
489
  }
284
490
  // ---------------------------------------------------------------------------
@@ -286,56 +492,91 @@ class ReactiveChannelImpl {
286
492
  // ---------------------------------------------------------------------------
287
493
  /**
288
494
  * A reactive conversation handle for the extension bridge.
289
- * Wraps ConversationHandle with $state interactions that auto-update
290
- * when the conversation changes via SSE events.
495
+ * Scopes AI and mutation operations to a specific conversation's
496
+ * interaction history, while sharing the channel's bridge connection.
291
497
  *
292
498
  * Call `close()` when done to stop listening for updates.
293
499
  */
294
500
  class ReactiveConversationHandleImpl {
295
- #handle;
501
+ #channel;
296
502
  #conversationId;
297
503
  #unsubscribers = [];
298
504
  // Reactive state
299
505
  interactions = $state([]);
300
506
  constructor(channel, conversationId) {
507
+ this.#channel = channel;
301
508
  this.#conversationId = conversationId;
302
- this.#handle = channel.conversation(conversationId);
303
509
  // Initial load
304
- this.#handle.getInteractions().then((list) => { this.interactions = list; });
510
+ this.getInteractions().then((list) => { this.interactions = list; });
305
511
  // Listen for updates to this conversation
306
512
  const onConversationUpdated = ({ conversationId: cid }) => {
307
513
  if (cid === this.#conversationId) {
308
- this.#handle.getInteractions().then((list) => { this.interactions = list; });
514
+ this.getInteractions().then((list) => { this.interactions = list; });
309
515
  }
310
516
  };
311
517
  channel.on('conversationUpdated', onConversationUpdated);
312
518
  this.#unsubscribers.push(() => channel.off('conversationUpdated', onConversationUpdated));
313
519
  // Handle full resets
314
520
  const onReset = () => {
315
- this.#handle.getInteractions().then((list) => { this.interactions = list; });
521
+ this.getInteractions().then((list) => { this.interactions = list; });
316
522
  };
317
523
  channel.on('reset', onReset);
318
524
  this.#unsubscribers.push(() => channel.off('reset', onReset));
319
525
  }
320
526
  get conversationId() { return this.#conversationId; }
321
527
  // Conversation history
322
- getInteractions() { return this.#handle.getInteractions(); }
323
- getSystemInstruction() { return this.#handle.getSystemInstruction(); }
324
- setSystemInstruction(instruction) { return this.#handle.setSystemInstruction(instruction); }
325
- rename(name) { return this.#handle.rename(name); }
528
+ async getInteractions() {
529
+ return this.#channel._callScoped('getInteractions', [], this.#conversationId);
530
+ }
531
+ async getTree() {
532
+ return this.#channel._callScoped('getTree', [], this.#conversationId);
533
+ }
534
+ async getActiveLeafId() {
535
+ return this.#channel._callScoped('getActiveLeafId', [], this.#conversationId);
536
+ }
537
+ async setActiveLeaf(interactionId) {
538
+ await this.#channel._callScoped('setActiveLeaf', [interactionId], this.#conversationId);
539
+ }
540
+ async getSystemInstruction() {
541
+ return this.#channel._callScoped('getSystemInstruction', [], this.#conversationId);
542
+ }
543
+ async setSystemInstruction(instruction) {
544
+ await this.#channel._callScoped('setSystemInstruction', [instruction], this.#conversationId);
545
+ }
546
+ async rename(name) {
547
+ await this.#channel._callScoped('renameConversation', [name], this.#conversationId);
548
+ }
326
549
  // Object operations
327
- findObjects(options) { return this.#handle.findObjects(options); }
328
- createObject(options) { return this.#handle.createObject(options); }
329
- updateObject(objectId, options) { return this.#handle.updateObject(objectId, options); }
330
- deleteObjects(objectIds) { return this.#handle.deleteObjects(objectIds); }
550
+ async findObjects(options) {
551
+ return this.#channel._callScoped('findObjects', [options], this.#conversationId);
552
+ }
553
+ async createObject(options) {
554
+ return this.#channel._callScoped('createObject', [options], this.#conversationId);
555
+ }
556
+ async updateObject(objectId, options) {
557
+ return this.#channel._callScoped('updateObject', [objectId, options], this.#conversationId);
558
+ }
559
+ async deleteObjects(objectIds) {
560
+ await this.#channel._callScoped('deleteObjects', [objectIds], this.#conversationId);
561
+ }
331
562
  // AI
332
- prompt(text, options) { return this.#handle.prompt(text, options); }
563
+ async prompt(text, options) {
564
+ return this.#channel._callScoped('prompt', [text, options], this.#conversationId);
565
+ }
333
566
  // Schema
334
- createCollection(name, fields) { return this.#handle.createCollection(name, fields); }
335
- alterCollection(name, fields) { return this.#handle.alterCollection(name, fields); }
336
- dropCollection(name) { return this.#handle.dropCollection(name); }
567
+ async createCollection(name, fields) {
568
+ return this.#channel._callScoped('createCollection', [name, fields], this.#conversationId);
569
+ }
570
+ async alterCollection(name, fields) {
571
+ return this.#channel._callScoped('alterCollection', [name, fields], this.#conversationId);
572
+ }
573
+ async dropCollection(name) {
574
+ await this.#channel._callScoped('dropCollection', [name], this.#conversationId);
575
+ }
337
576
  // Metadata
338
- setMetadata(key, value) { return this.#handle.setMetadata(key, value); }
577
+ async setMetadata(key, value) {
578
+ await this.#channel._callScoped('setMetadata', [key, value], this.#conversationId);
579
+ }
339
580
  /**
340
581
  * Stop listening for updates and clean up.
341
582
  */
@@ -346,17 +587,47 @@ class ReactiveConversationHandleImpl {
346
587
  }
347
588
  }
348
589
  // ---------------------------------------------------------------------------
349
- // initExtension (reactive version)
590
+ // initExtension
350
591
  // ---------------------------------------------------------------------------
351
- import { initExtension as initBridge } from './client.js';
352
592
  /**
353
593
  * Initialize the extension and return a reactive channel.
354
594
  *
355
595
  * Sends `rool:ready` to the host, waits for the handshake, and returns
356
596
  * a reactive channel with $state properties (interactions, objectIds)
357
597
  * and reactive primitives (object(), watch()).
598
+ *
599
+ * If the extension is opened directly (not in an iframe), redirects to the Rool
600
+ * console with `?openExtension={extensionId}` so the user can install or navigate to it.
601
+ *
602
+ * @param timeout - How long to wait for the handshake (ms). Default: 10000.
358
603
  */
359
- export async function initExtension(timeout) {
360
- const bridge = await initBridge(timeout);
361
- return new ReactiveChannelImpl(bridge);
604
+ export function initExtension(timeout = 10000) {
605
+ // Deep link: if not in an iframe, redirect to the Rool console
606
+ if (window.self === window.top) {
607
+ const host = window.location.hostname;
608
+ const dot = host.indexOf('.');
609
+ if (dot > 0) {
610
+ const extensionId = host.slice(0, dot);
611
+ const domain = host.slice(dot + 1);
612
+ window.location.href = `https://${domain}/?openExtension=${extensionId}`;
613
+ }
614
+ // Never resolve — the redirect will unload the page
615
+ return new Promise(() => { });
616
+ }
617
+ return new Promise((resolve, reject) => {
618
+ const timer = setTimeout(() => {
619
+ window.removeEventListener('message', onMessage);
620
+ reject(new Error('Extension handshake timed out — is this running inside a Rool host?'));
621
+ }, timeout);
622
+ function onMessage(event) {
623
+ if (!isBridgeMessage(event.data) || event.data.type !== 'rool:init')
624
+ return;
625
+ clearTimeout(timer);
626
+ window.removeEventListener('message', onMessage);
627
+ resolve(new ReactiveChannelImpl(event.data));
628
+ }
629
+ window.addEventListener('message', onMessage);
630
+ // Signal to the host that we're ready
631
+ window.parent.postMessage({ type: 'rool:ready' }, '*');
632
+ });
362
633
  }
package/dist/types.d.ts CHANGED
@@ -50,6 +50,8 @@ export interface ToolCall {
50
50
  export type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
51
51
  export interface Interaction {
52
52
  id: string;
53
+ /** Parent interaction in the conversation tree. null = root message. */
54
+ parentId: string | null;
53
55
  timestamp: number;
54
56
  userId: string;
55
57
  userName?: string | null;
@@ -69,6 +71,8 @@ export interface PromptOptions {
69
71
  effort?: PromptEffort;
70
72
  ephemeral?: boolean;
71
73
  readOnly?: boolean;
74
+ /** Parent interaction in the conversation tree. Omit to auto-continue from the active leaf. */
75
+ parentInteractionId?: string | null;
72
76
  }
73
77
  export interface FindObjectsOptions {
74
78
  where?: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rool-dev/extension",
3
- "version": "0.3.7",
3
+ "version": "0.3.8-dev.b3f8671",
4
4
  "description": "Svelte-first extension SDK for Rool — reactive channel over iframe bridge",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -57,7 +57,7 @@
57
57
  "archiver": "^7.0.1",
58
58
  "tailwindcss": "^4.2.2",
59
59
  "vite": "^8.0.0",
60
- "@rool-dev/sdk": "0.3.7"
60
+ "@rool-dev/sdk": "0.3.8-dev.b3f8671"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@sveltejs/package": "^2.5.7",