@rool-dev/extension 0.3.7 → 0.3.8-dev.8872f4d

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
@@ -183,6 +183,7 @@ These are Svelte 5 `$state` properties — use them directly in templates or `$e
183
183
  | `role` | `RoolUserRole` | User's role (`owner`, `admin`, `editor`, `viewer`) |
184
184
  | `linkAccess` | `LinkAccess` | URL sharing level |
185
185
  | `userId` | `string` | Current user's ID |
186
+ | `user` | `BridgeUser` | Current user info (`{ id, name, email }`) |
186
187
  | `isReadOnly` | `boolean` | True if viewer role |
187
188
 
188
189
  ### Object Operations
@@ -409,10 +410,20 @@ Extensions run in a sandboxed iframe (`allow-scripts allow-same-origin`). The ho
409
410
 
410
411
  The bridge protocol:
411
412
  1. Extension sends `rool:ready`
412
- 2. Host responds with `rool:init` (channel metadata, schema, space info)
413
+ 2. Host responds with `rool:init` (channel metadata, schema, space info, user identity)
413
414
  3. Extension calls channel methods → `rool:request` → host executes → `rool:response`
414
415
  4. Host pushes real-time events → `rool:event` → extension updates reactive state
415
416
 
417
+ When creating a bridge host, pass `user` so the extension can display the current user's name:
418
+
419
+ ```typescript
420
+ const host = createBridgeHost({
421
+ channel,
422
+ iframe,
423
+ user: { id: currentUser.id, name: currentUser.name, email: currentUser.email },
424
+ });
425
+ ```
426
+
416
427
  ## CLI Commands
417
428
 
418
429
  | Command | Description |
@@ -431,6 +442,7 @@ import type {
431
442
  ReactiveObject,
432
443
  ReactiveWatch,
433
444
  WatchOptions,
445
+ BridgeUser,
434
446
  RoolObject,
435
447
  RoolObjectStat,
436
448
  SpaceSchema,
@@ -71,6 +71,7 @@ export declare class DevHostController {
71
71
  registerIframe(tabId: string, el: HTMLIFrameElement): void;
72
72
  unregisterIframe(tabId: string): void;
73
73
  logout(): void;
74
+ private get _bridgeUser();
74
75
  private _bindBridge;
75
76
  private _bindAllBridges;
76
77
  private _destroyTab;
@@ -1 +1 @@
1
- {"version":3,"file":"DevHostController.d.ts","sourceRoot":"","sources":["../../src/dev/DevHostController.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAe,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAO5D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC;AAqBnD,qBAAa,iBAAiB;IAE5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAGtC,MAAM,EAAG,UAAU,CAAC;IAGpB,MAAM,EAAE,aAAa,EAAE,CAAM;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IACrC,UAAU,EAAE,MAAM,CAAqB;IACvC,WAAW,EAAE,WAAW,CAAS;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAuB;IACrD,GAAG,EAAE,WAAW,CAAC;IACjB,mBAAmB,EAAE,sBAAsB,EAAE,CAAM;IACnD,qBAAqB,EAAE,MAAM,EAAE,CAAM;IACrC,gBAAgB,EAAE,OAAO,CAAS;IAClC,YAAY,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAU;IAC5E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IACrC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAGjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,WAAW,CAAkC;IAGrD,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,KAAK,CAAsB;IAGnC,OAAO,CAAC,SAAS,CAAS;gBAGxB,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;QAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,EACD,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;IAoB3B,IAAI,IAAI,IAAI,YAAY,EAAE,CAoBzB;IAMK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+DrB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0DjD;;;;;;OAMG;IACG,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB1D;;;OAGG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAiBpC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkExB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnD,aAAa,IAAI,IAAI;IAUrB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,iBAAiB,GAAG,IAAI;IAK1D,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQrC,MAAM,IAAI,IAAI;IASd,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,6BAA6B;IAYrC;;;OAGG;YACW,aAAa;IA4B3B,OAAO,CAAC,YAAY;CAKrB"}
1
+ {"version":3,"file":"DevHostController.d.ts","sourceRoot":"","sources":["../../src/dev/DevHostController.ts"],"names":[],"mappings":"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"}
@@ -358,11 +358,15 @@ export class DevHostController {
358
358
  // ---------------------------------------------------------------------------
359
359
  // Private helpers
360
360
  // ---------------------------------------------------------------------------
361
+ get _bridgeUser() {
362
+ const cu = this.client.currentUser; // Always available after boot() authenticates
363
+ return { id: cu.id, name: cu.name, email: cu.email };
364
+ }
361
365
  _bindBridge(tabId) {
362
366
  const el = this.iframeEls[tabId];
363
367
  const ch = this.channels[tabId];
364
368
  if (el && ch && !this.bridgeHosts[tabId]) {
365
- this.bridgeHosts[tabId] = createBridgeHost({ channel: ch, iframe: el });
369
+ this.bridgeHosts[tabId] = createBridgeHost({ channel: ch, iframe: el, user: this._bridgeUser });
366
370
  }
367
371
  }
368
372
  _bindAllBridges() {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { onMount, tick } from 'svelte';
2
+ import { getContext, onMount, tick } from 'svelte';
3
3
  import { DevHostController } from './DevHostController.js';
4
4
  import type { ExtensionTab } from './DevHostController.js';
5
5
  import type { Manifest } from '../manifest.js';
@@ -8,15 +8,15 @@
8
8
  import Sidebar from './Sidebar.svelte';
9
9
  import AppGrid from './AppGrid.svelte';
10
10
 
11
- // Props injected from the mount entry
12
- interface Props {
11
+ // Static config injected via mount() context — not reactive, never changes
12
+ interface HostConfig {
13
13
  channelId: string;
14
14
  extensionUrl: string;
15
15
  manifest: Manifest | null;
16
16
  manifestError: string | null;
17
17
  }
18
18
 
19
- const props: Props = $props();
19
+ const { channelId, extensionUrl, manifest, manifestError } = getContext<HostConfig>('hostConfig');
20
20
 
21
21
  // ---------------------------------------------------------------------------
22
22
  // Controller + reactive state mirror
@@ -40,7 +40,7 @@
40
40
  let dropdownOpen: boolean = $state(false);
41
41
 
42
42
  const controller = new DevHostController(
43
- props,
43
+ { channelId, extensionUrl, manifest, manifestError },
44
44
  syncState,
45
45
  tick,
46
46
  );
@@ -63,7 +63,7 @@
63
63
 
64
64
  // Derived: published apps not yet installed (excluding the local dev app)
65
65
  let uninstalledExtensions = $derived(
66
- publishedExtensions.filter((ext) => ext.extensionId !== props.channelId && !installedExtensionIds.includes(ext.extensionId)),
66
+ publishedExtensions.filter((ext) => ext.extensionId !== channelId && !installedExtensionIds.includes(ext.extensionId)),
67
67
  );
68
68
 
69
69
  // Initial sync
@@ -89,8 +89,8 @@
89
89
  <!-- Sidebar -->
90
90
  <Sidebar
91
91
  {controller}
92
- manifest={props.manifest}
93
- manifestError={props.manifestError}
92
+ {manifest}
93
+ {manifestError}
94
94
  {spaces}
95
95
  {currentSpaceId}
96
96
  {env}
@@ -1,11 +1,4 @@
1
- import type { Manifest } from '../manifest.js';
2
- interface Props {
3
- channelId: string;
4
- extensionUrl: string;
5
- manifest: Manifest | null;
6
- manifestError: string | null;
7
- }
8
- declare const HostShell: import("svelte").Component<Props, {}, "">;
1
+ declare const HostShell: import("svelte").Component<Record<string, never>, {}, "">;
9
2
  type HostShell = ReturnType<typeof HostShell>;
10
3
  export default HostShell;
11
4
  //# sourceMappingURL=HostShell.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HostShell.svelte.d.ts","sourceRoot":"","sources":["../../src/dev/HostShell.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAQ7C,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgGH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"HostShell.svelte.d.ts","sourceRoot":"","sources":["../../src/dev/HostShell.svelte.ts"],"names":[],"mappings":"AAkHA,QAAA,MAAM,SAAS,2DAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -313,6 +313,19 @@ function set_component_context(context) {
313
313
  component_context = context;
314
314
  }
315
315
  /**
316
+ * Retrieves the context that belongs to the closest parent component with the specified `key`.
317
+ * Must be called during component initialisation.
318
+ *
319
+ * [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) is a type-safe alternative.
320
+ *
321
+ * @template T
322
+ * @param {any} key
323
+ * @returns {T}
324
+ */
325
+ function getContext(key) {
326
+ return get_or_init_context_map("getContext").get(key);
327
+ }
328
+ /**
316
329
  * @param {Record<string, unknown>} props
317
330
  * @param {any} runes
318
331
  * @param {Function} [fn]
@@ -355,6 +368,27 @@ function pop(component) {
355
368
  function is_runes() {
356
369
  return !legacy_mode_flag || component_context !== null && component_context.l === null;
357
370
  }
371
+ /**
372
+ * @param {string} name
373
+ * @returns {Map<unknown, unknown>}
374
+ */
375
+ function get_or_init_context_map(name) {
376
+ if (component_context === null) lifecycle_outside_component(name);
377
+ return component_context.c ??= new Map(get_parent_context(component_context) || void 0);
378
+ }
379
+ /**
380
+ * @param {ComponentContext} component_context
381
+ * @returns {Map<unknown, unknown> | null}
382
+ */
383
+ function get_parent_context(component_context) {
384
+ let parent = component_context.p;
385
+ while (parent !== null) {
386
+ const context_map = parent.c;
387
+ if (context_map !== null) return context_map;
388
+ parent = parent.p;
389
+ }
390
+ return null;
391
+ }
358
392
  //#endregion
359
393
  //#region ../../node_modules/.pnpm/svelte@5.54.0/node_modules/svelte/src/internal/client/dom/task.js
360
394
  /** @type {Array<() => void>} */
@@ -4119,48 +4153,6 @@ function bind_this(element_or_component = {}, update, get_value, get_parts) {
4119
4153
  //#region ../../node_modules/.pnpm/svelte@5.54.0/node_modules/svelte/src/internal/client/reactivity/props.js
4120
4154
  /** @import { Effect, Source } from './types.js' */
4121
4155
  /**
4122
- * The proxy handler for rest props (i.e. `const { x, ...rest } = $props()`).
4123
- * Is passed the full `$$props` object and excludes the named props.
4124
- * @type {ProxyHandler<{ props: Record<string | symbol, unknown>, exclude: Array<string | symbol>, name?: string }>}}
4125
- */
4126
- var rest_props_handler = {
4127
- get(target, key) {
4128
- if (target.exclude.includes(key)) return;
4129
- return target.props[key];
4130
- },
4131
- set(target, key) {
4132
- return false;
4133
- },
4134
- getOwnPropertyDescriptor(target, key) {
4135
- if (target.exclude.includes(key)) return;
4136
- if (key in target.props) return {
4137
- enumerable: true,
4138
- configurable: true,
4139
- value: target.props[key]
4140
- };
4141
- },
4142
- has(target, key) {
4143
- if (target.exclude.includes(key)) return false;
4144
- return key in target.props;
4145
- },
4146
- ownKeys(target) {
4147
- return Reflect.ownKeys(target.props).filter((key) => !target.exclude.includes(key));
4148
- }
4149
- };
4150
- /**
4151
- * @param {Record<string, unknown>} props
4152
- * @param {string[]} exclude
4153
- * @param {string} [name]
4154
- * @returns {Record<string, unknown>}
4155
- */
4156
- /* @__NO_SIDE_EFFECTS__ */
4157
- function rest_props(props, exclude, name) {
4158
- return new Proxy({
4159
- props,
4160
- exclude
4161
- }, rest_props_handler);
4162
- }
4163
- /**
4164
4156
  * This function is responsible for synchronizing a possibly bound prop with the inner component state.
4165
4157
  * It is used whenever the compiler sees that the component writes to the prop, or when it has a default prop_value.
4166
4158
  * @template V
@@ -5638,8 +5630,8 @@ var GraphQLClient = class {
5638
5630
  }
5639
5631
  async prompt(spaceId, prompt, channelId, conversationId, options) {
5640
5632
  return (await this.request(`
5641
- mutation Prompt($spaceId: String!, $prompt: String!, $objectIds: [String!], $responseSchema: JSON, $channelId: String!, $conversationId: String!, $effort: PromptEffort, $ephemeral: Boolean, $readOnly: Boolean, $attachments: [String!], $interactionId: String!) {
5642
- prompt(spaceId: $spaceId, prompt: $prompt, objectIds: $objectIds, responseSchema: $responseSchema, channelId: $channelId, conversationId: $conversationId, effort: $effort, ephemeral: $ephemeral, readOnly: $readOnly, attachments: $attachments, interactionId: $interactionId) {
5633
+ mutation Prompt($spaceId: String!, $prompt: String!, $objectIds: [String!], $responseSchema: JSON, $channelId: String!, $conversationId: String!, $effort: PromptEffort, $ephemeral: Boolean, $readOnly: Boolean, $attachments: [String!], $interactionId: String!, $parentInteractionId: String) {
5634
+ prompt(spaceId: $spaceId, prompt: $prompt, objectIds: $objectIds, responseSchema: $responseSchema, channelId: $channelId, conversationId: $conversationId, effort: $effort, ephemeral: $ephemeral, readOnly: $readOnly, attachments: $attachments, interactionId: $interactionId, parentInteractionId: $parentInteractionId) {
5643
5635
  message
5644
5636
  modifiedObjectIds
5645
5637
  }
@@ -5655,7 +5647,8 @@ var GraphQLClient = class {
5655
5647
  ephemeral: options.ephemeral,
5656
5648
  readOnly: options.readOnly,
5657
5649
  attachments: options.attachmentUrls,
5658
- interactionId: options.interactionId
5650
+ interactionId: options.interactionId,
5651
+ parentInteractionId: options.parentInteractionId
5659
5652
  })).prompt;
5660
5653
  }
5661
5654
  async getAccount() {
@@ -7220,6 +7213,29 @@ function generateEntityId() {
7220
7213
  for (let i = 0; i < 6; i++) result += ID_CHARS[Math.floor(Math.random() * 62)];
7221
7214
  return result;
7222
7215
  }
7216
+ /** Walk from a leaf interaction up through parentId to root, return in root→leaf order. */
7217
+ function walkBranch(interactions, leafId) {
7218
+ const path = [];
7219
+ let currentId = leafId;
7220
+ while (currentId) {
7221
+ const ix = interactions[currentId];
7222
+ if (!ix) break;
7223
+ path.push(ix);
7224
+ currentId = ix.parentId;
7225
+ }
7226
+ return path.reverse();
7227
+ }
7228
+ /** Find the default leaf: the most recent interaction by timestamp that has no children. */
7229
+ function findDefaultLeaf(interactions) {
7230
+ if (!interactions || Array.isArray(interactions)) return void 0;
7231
+ const childSet = /* @__PURE__ */ new Set();
7232
+ for (const ix of Object.values(interactions)) if (ix.parentId) childSet.add(ix.parentId);
7233
+ let best;
7234
+ for (const ix of Object.values(interactions)) if (!childSet.has(ix.id)) {
7235
+ if (!best || ix.timestamp > best.timestamp) best = ix;
7236
+ }
7237
+ return best?.id;
7238
+ }
7223
7239
  var OBJECT_COLLECT_TIMEOUT = 3e4;
7224
7240
  /**
7225
7241
  * A channel is a space + channelId pair.
@@ -7260,6 +7276,7 @@ var RoolChannel = class extends EventEmitter {
7260
7276
  _objectIds;
7261
7277
  _objectStats;
7262
7278
  _hasConnected = false;
7279
+ _activeLeaves = /* @__PURE__ */ new Map();
7263
7280
  _pendingMutations = /* @__PURE__ */ new Map();
7264
7281
  _objectResolvers = /* @__PURE__ */ new Map();
7265
7282
  _objectBuffer = /* @__PURE__ */ new Map();
@@ -7350,14 +7367,62 @@ var RoolChannel = class extends EventEmitter {
7350
7367
  return this._channel?.extensionUrl ?? null;
7351
7368
  }
7352
7369
  /**
7353
- * Get interactions for the current conversation.
7370
+ * Get the active branch of the current conversation as a flat array (root → leaf).
7371
+ * Walks from the active leaf up through parentId pointers.
7354
7372
  */
7355
7373
  getInteractions() {
7356
7374
  return this._getInteractionsImpl(this._conversationId);
7357
7375
  }
7358
7376
  /** @internal */
7359
7377
  _getInteractionsImpl(conversationId) {
7360
- return this._channel?.conversations[conversationId]?.interactions ?? [];
7378
+ const interactions = this._channel?.conversations[conversationId]?.interactions;
7379
+ if (!interactions) return [];
7380
+ if (Array.isArray(interactions)) return interactions;
7381
+ const leafId = this._getActiveLeafImpl(conversationId);
7382
+ if (!leafId) return [];
7383
+ return walkBranch(interactions, leafId);
7384
+ }
7385
+ /**
7386
+ * Get the full interaction tree for a conversation as a record.
7387
+ * For clients that need to render branch navigation UI.
7388
+ */
7389
+ getTree() {
7390
+ return this._getTreeImpl(this._conversationId);
7391
+ }
7392
+ /** @internal */
7393
+ _getTreeImpl(conversationId) {
7394
+ const interactions = this._channel?.conversations[conversationId]?.interactions;
7395
+ if (!interactions || Array.isArray(interactions)) return {};
7396
+ return interactions;
7397
+ }
7398
+ /**
7399
+ * Get the active leaf interaction ID for a conversation.
7400
+ * Returns undefined if the conversation has no interactions.
7401
+ */
7402
+ get activeLeafId() {
7403
+ return this._getActiveLeafImpl(this._conversationId);
7404
+ }
7405
+ /** @internal */
7406
+ _getActiveLeafImpl(conversationId) {
7407
+ return this._activeLeaves.get(conversationId) ?? findDefaultLeaf(this._channel?.conversations[conversationId]?.interactions);
7408
+ }
7409
+ /**
7410
+ * Set the active leaf for a conversation (switch branches).
7411
+ * Emits a conversationUpdated event so reactive wrappers refresh.
7412
+ */
7413
+ setActiveLeaf(interactionId) {
7414
+ this._setActiveLeafImpl(interactionId, this._conversationId);
7415
+ }
7416
+ /** @internal */
7417
+ _setActiveLeafImpl(interactionId, conversationId) {
7418
+ const interactions = this._channel?.conversations[conversationId]?.interactions;
7419
+ if (!interactions || Array.isArray(interactions) || !interactions[interactionId]) throw new Error(`Interaction "${interactionId}" not found in conversation "${conversationId}"`);
7420
+ this._activeLeaves.set(conversationId, interactionId);
7421
+ this.emit("conversationUpdated", {
7422
+ conversationId,
7423
+ channelId: this._channelId,
7424
+ source: "local_user"
7425
+ });
7361
7426
  }
7362
7427
  /**
7363
7428
  * Get all conversations in this channel.
@@ -7371,7 +7436,7 @@ var RoolChannel = class extends EventEmitter {
7371
7436
  systemInstruction: conv.systemInstruction ?? null,
7372
7437
  createdAt: conv.createdAt,
7373
7438
  createdBy: conv.createdBy,
7374
- interactionCount: conv.interactions.length
7439
+ interactionCount: Object.keys(conv.interactions).length
7375
7440
  }));
7376
7441
  }
7377
7442
  /**
@@ -7780,7 +7845,7 @@ var RoolChannel = class extends EventEmitter {
7780
7845
  if (!this._channel.conversations[conversationId]) this._channel.conversations[conversationId] = {
7781
7846
  createdAt: Date.now(),
7782
7847
  createdBy: this._userId,
7783
- interactions: []
7848
+ interactions: {}
7784
7849
  };
7785
7850
  }
7786
7851
  /**
@@ -7822,14 +7887,17 @@ var RoolChannel = class extends EventEmitter {
7822
7887
  }
7823
7888
  /** @internal */
7824
7889
  async _promptImpl(prompt, options, conversationId) {
7825
- const { attachments, ...rest } = options ?? {};
7890
+ const { attachments, parentInteractionId: explicitParent, ...rest } = options ?? {};
7826
7891
  let attachmentUrls;
7827
7892
  if (attachments?.length) attachmentUrls = await Promise.all(attachments.map((file) => this.mediaClient.upload(this._id, file)));
7893
+ const parentInteractionId = explicitParent !== void 0 ? explicitParent : this._getActiveLeafImpl(conversationId) ?? null;
7828
7894
  const interactionId = generateEntityId();
7895
+ this._activeLeaves.set(conversationId, interactionId);
7829
7896
  const result = await this.graphqlClient.prompt(this._id, prompt, this._channelId, conversationId, {
7830
7897
  ...rest,
7831
7898
  attachmentUrls,
7832
- interactionId
7899
+ interactionId,
7900
+ parentInteractionId
7833
7901
  });
7834
7902
  const objects = [];
7835
7903
  const missing = [];
@@ -8015,6 +8083,15 @@ var RoolChannel = class extends EventEmitter {
8015
8083
  if (event.conversation) this._channel.conversations[event.conversationId] = event.conversation;
8016
8084
  else delete this._channel.conversations[event.conversationId];
8017
8085
  if (JSON.stringify(prev) === JSON.stringify(event.conversation)) break;
8086
+ if (event.conversation && !Array.isArray(event.conversation.interactions)) {
8087
+ const currentLeaf = this._getActiveLeafImpl(event.conversationId);
8088
+ if (currentLeaf) {
8089
+ for (const ix of Object.values(event.conversation.interactions)) if (ix.parentId === currentLeaf && ix.id !== currentLeaf) {
8090
+ this._activeLeaves.set(event.conversationId, ix.id);
8091
+ break;
8092
+ }
8093
+ }
8094
+ }
8018
8095
  this.emit("conversationUpdated", {
8019
8096
  conversationId: event.conversationId,
8020
8097
  channelId: event.channelId,
@@ -8123,10 +8200,22 @@ var ConversationHandle = class {
8123
8200
  get conversationId() {
8124
8201
  return this._conversationId;
8125
8202
  }
8126
- /** Get interactions for this conversation. */
8203
+ /** Get the active branch of this conversation as a flat array (root → leaf). */
8127
8204
  getInteractions() {
8128
8205
  return this._channel._getInteractionsImpl(this._conversationId);
8129
8206
  }
8207
+ /** Get the full interaction tree as a record. */
8208
+ getTree() {
8209
+ return this._channel._getTreeImpl(this._conversationId);
8210
+ }
8211
+ /** Get the active leaf interaction ID, or undefined if empty. */
8212
+ get activeLeafId() {
8213
+ return this._channel._getActiveLeafImpl(this._conversationId);
8214
+ }
8215
+ /** Switch to a different branch by setting the active leaf. */
8216
+ setActiveLeaf(interactionId) {
8217
+ this._channel._setActiveLeafImpl(interactionId, this._conversationId);
8218
+ }
8130
8219
  /** Get the system instruction for this conversation. */
8131
8220
  getSystemInstruction() {
8132
8221
  return this._channel._getSystemInstructionImpl(this._conversationId);
@@ -8871,6 +8960,9 @@ var ALLOWED_METHODS = new Set([
8871
8960
  "alterCollection",
8872
8961
  "dropCollection",
8873
8962
  "getInteractions",
8963
+ "getTree",
8964
+ "setActiveLeaf",
8965
+ "getActiveLeafId",
8874
8966
  "getConversations",
8875
8967
  "getSystemInstruction",
8876
8968
  "setSystemInstruction",
@@ -8889,6 +8981,9 @@ var ALLOWED_METHODS = new Set([
8889
8981
  ]);
8890
8982
  var CONVERSATION_METHODS = new Set([
8891
8983
  "getInteractions",
8984
+ "getTree",
8985
+ "setActiveLeaf",
8986
+ "getActiveLeafId",
8892
8987
  "getSystemInstruction",
8893
8988
  "setSystemInstruction",
8894
8989
  "renameConversation",
@@ -8903,14 +8998,17 @@ var CONVERSATION_METHODS = new Set([
8903
8998
  "setMetadata"
8904
8999
  ]);
8905
9000
  var CONVERSATION_METHOD_MAP = { "renameConversation": "rename" };
9001
+ var GETTER_MAP = { "getActiveLeafId": "activeLeafId" };
8906
9002
  var BridgeHost = class {
8907
9003
  channel;
8908
9004
  iframe;
9005
+ user;
8909
9006
  eventCleanups = [];
8910
9007
  _destroyed = false;
8911
9008
  constructor(options) {
8912
9009
  this.channel = options.channel;
8913
9010
  this.iframe = options.iframe;
9011
+ this.user = options.user;
8914
9012
  window.addEventListener("message", this._onMessage);
8915
9013
  for (const eventName of FORWARDED_EVENTS) {
8916
9014
  const handler = (data) => {
@@ -8935,6 +9033,7 @@ var BridgeHost = class {
8935
9033
  role: this.channel.role,
8936
9034
  linkAccess: this.channel.linkAccess,
8937
9035
  userId: this.channel.userId,
9036
+ user: this.user,
8938
9037
  schema: this.channel.getSchema(),
8939
9038
  metadata: this.channel.getAllMetadata()
8940
9039
  };
@@ -8969,6 +9068,7 @@ var BridgeHost = class {
8969
9068
  target = this.channel.conversation(conversationId);
8970
9069
  methodName = CONVERSATION_METHOD_MAP[method] ?? method;
8971
9070
  }
9071
+ if (GETTER_MAP[methodName]) methodName = GETTER_MAP[methodName];
8972
9072
  const fn = target[methodName];
8973
9073
  let result;
8974
9074
  if (typeof fn === "function") {
@@ -9307,12 +9407,21 @@ var DevHostController = class {
9307
9407
  this.client.logout();
9308
9408
  window.location.reload();
9309
9409
  }
9410
+ get _bridgeUser() {
9411
+ const cu = this.client.currentUser;
9412
+ return {
9413
+ id: cu.id,
9414
+ name: cu.name,
9415
+ email: cu.email
9416
+ };
9417
+ }
9310
9418
  _bindBridge(tabId) {
9311
9419
  const el = this.iframeEls[tabId];
9312
9420
  const ch = this.channels[tabId];
9313
9421
  if (el && ch && !this.bridgeHosts[tabId]) this.bridgeHosts[tabId] = createBridgeHost({
9314
9422
  channel: ch,
9315
- iframe: el
9423
+ iframe: el,
9424
+ user: this._bridgeUser
9316
9425
  });
9317
9426
  }
9318
9427
  _bindAllBridges() {
@@ -15127,11 +15236,7 @@ var root_1 = /* @__PURE__ */ from_html(`<div class="flex items-center justify-ce
15127
15236
  var root$1 = /* @__PURE__ */ from_html(`<!> <div class="flex-1 min-w-0 flex flex-col"><!></div>`, 1);
15128
15237
  function HostShell($$anchor, $$props) {
15129
15238
  push($$props, true);
15130
- const props = /* @__PURE__ */ rest_props($$props, [
15131
- "$$slots",
15132
- "$$events",
15133
- "$$legacy"
15134
- ]);
15239
+ const { channelId, extensionUrl, manifest, manifestError } = getContext("hostConfig");
15135
15240
  let spaces = /* @__PURE__ */ state(proxy([]));
15136
15241
  let currentSpaceId = /* @__PURE__ */ state(null);
15137
15242
  let statusText = /* @__PURE__ */ state("Initializing...");
@@ -15146,7 +15251,12 @@ function HostShell($$anchor, $$props) {
15146
15251
  let publishMessage = /* @__PURE__ */ state(null);
15147
15252
  let publishUrl = /* @__PURE__ */ state(null);
15148
15253
  let dropdownOpen = /* @__PURE__ */ state(false);
15149
- const controller = new DevHostController(props, syncState, tick);
15254
+ const controller = new DevHostController({
15255
+ channelId,
15256
+ extensionUrl,
15257
+ manifest,
15258
+ manifestError
15259
+ }, syncState, tick);
15150
15260
  function syncState() {
15151
15261
  set(spaces, controller.spaces, true);
15152
15262
  set(currentSpaceId, controller.currentSpaceId, true);
@@ -15162,7 +15272,7 @@ function HostShell($$anchor, $$props) {
15162
15272
  set(publishMessage, controller.publishMessage, true);
15163
15273
  set(publishUrl, controller.publishUrl, true);
15164
15274
  }
15165
- let uninstalledExtensions = /* @__PURE__ */ user_derived(() => get(publishedExtensions).filter((ext) => ext.extensionId !== $$props.channelId && !get(installedExtensionIds).includes(ext.extensionId)));
15275
+ let uninstalledExtensions = /* @__PURE__ */ user_derived(() => get(publishedExtensions).filter((ext) => ext.extensionId !== channelId && !get(installedExtensionIds).includes(ext.extensionId)));
15166
15276
  syncState();
15167
15277
  onMount(() => {
15168
15278
  controller.boot();
@@ -15179,10 +15289,10 @@ function HostShell($$anchor, $$props) {
15179
15289
  return controller;
15180
15290
  },
15181
15291
  get manifest() {
15182
- return $$props.manifest;
15292
+ return manifest;
15183
15293
  },
15184
15294
  get manifestError() {
15185
- return $$props.manifestError;
15295
+ return manifestError;
15186
15296
  },
15187
15297
  get spaces() {
15188
15298
  return get(spaces);
@@ -15268,14 +15378,18 @@ var style = document.createElement("style");
15268
15378
  style.textContent = app_default + "\n" + gridstack_default;
15269
15379
  document.head.appendChild(style);
15270
15380
  var root = document.getElementById("rool-host");
15381
+ var channelId = root.dataset.channelId ?? "extension-dev";
15382
+ var extensionUrl = root.dataset.extensionUrl ?? "/";
15383
+ var manifest = root.dataset.manifest ? JSON.parse(root.dataset.manifest) : null;
15384
+ var manifestError = root.dataset.manifestError ?? null;
15271
15385
  mount(HostShell, {
15272
15386
  target: root,
15273
- props: {
15274
- channelId: root.dataset.channelId ?? "extension-dev",
15275
- extensionUrl: root.dataset.extensionUrl ?? "/",
15276
- manifest: root.dataset.manifest ? JSON.parse(root.dataset.manifest) : null,
15277
- manifestError: root.dataset.manifestError ?? null
15278
- }
15387
+ context: new Map([["hostConfig", {
15388
+ channelId,
15389
+ extensionUrl,
15390
+ manifest,
15391
+ manifestError
15392
+ }]])
15279
15393
  });
15280
15394
  //#endregion
15281
15395