@streamoid/agent 0.6.14 → 0.6.16

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/dist/index.d.ts CHANGED
@@ -33,6 +33,34 @@ export interface AgentChatShellWorkspace {
33
33
  [key: string]: unknown;
34
34
  }
35
35
 
36
+ /** What the host reports about the page the panel is sitting on. Unknown keys
37
+ * are dropped server-side, so hosts may add their own freely. */
38
+ export interface AgentHostContext {
39
+ /** Which application, e.g. `artifax`. */
40
+ app?: string;
41
+ /** Human name of the current page, e.g. `Boards`. */
42
+ page?: string;
43
+ /** Route pattern rather than the filled path, e.g. `/dashboard/:workspaceId/home`. */
44
+ route?: string;
45
+ /** Absolute URL of the host page. */
46
+ url?: string;
47
+ /** Identifiers a tool call might need: workspaceId, boardId, sessionId… */
48
+ ids?: Record<string, string | number>;
49
+ /** A short list of what is on screen — board names, selected items. */
50
+ visible?: string[];
51
+ [key: string]: unknown;
52
+ }
53
+
54
+ /** Whether the panel currently needs the host's whole screen, and why. */
55
+ export interface AgentSurfaceState {
56
+ /** `fullscreen` while the conversation on screen has an artefact panel open. */
57
+ mode: 'inline' | 'fullscreen';
58
+ /** How many panels that conversation has open. */
59
+ panelCount: number;
60
+ /** The panel in front, when there is one. */
61
+ activePanel?: { id: string; kind: string; title: string } | null;
62
+ }
63
+
36
64
  export interface AgentChatShellAdapters {
37
65
  auth: AgentChatShellAuth;
38
66
  workspace: { current: AgentChatShellWorkspace };
@@ -40,6 +68,13 @@ export interface AgentChatShellAdapters {
40
68
  config: {
41
69
  apiBase: string;
42
70
  hide?: { sidepane?: boolean; newBrief?: boolean; notifications?: boolean };
71
+ /**
72
+ * `inline_ask` declares this mount an inline assistant sitting on top of a
73
+ * host application page. It unlocks the tools that only make sense there —
74
+ * `capture_inline_screenshot`, `navigate_parent` — and adds the inline-mode
75
+ * instructions to the prompt. Defaults to `normal`.
76
+ */
77
+ widgetMode?: 'normal' | 'inline_ask';
43
78
  };
44
79
  navigation: {
45
80
  chatId: string | null;
@@ -48,6 +83,25 @@ export interface AgentChatShellAdapters {
48
83
  /** Backend recovered after an outage — host decides what to do (must NOT
49
84
  * reload the host window). */
50
85
  onBackendRecovered?: () => void;
86
+ /**
87
+ * The page the panel is sitting on. Sent with every message, so update it as
88
+ * the user navigates: the agent then knows where they are without spending a
89
+ * turn on `capture_inline_screenshot`.
90
+ */
91
+ hostContext?: AgentHostContext | null;
92
+ /**
93
+ * Called when the panel starts or stops showing a non-text artefact — a
94
+ * table, an image set, a widget, a techpack, a board.
95
+ *
96
+ * The panel cannot give an artefact more room than the host gave the panel,
97
+ * and a rail wide enough for a conversation is not wide enough for a
98
+ * spreadsheet. So the decision is reported rather than taken: widen your
99
+ * container on `fullscreen` and restore it on `inline`. Do nothing and
100
+ * behaviour is unchanged.
101
+ *
102
+ * Fires only on change, and only for the conversation on screen.
103
+ */
104
+ onSurfaceChange?: (state: AgentSurfaceState) => void;
51
105
  /**
52
106
  * Supply the canvas as a component and the Dreamer panel drops its iframe:
53
107
  * no embedded page, so no cross-origin auth, no postMessage handshake, no
package/dist/index.js CHANGED
@@ -15205,6 +15205,7 @@ const trimTrailingSlashes = (e) => "".replace(/\/+$/, ""), DEFAULT_API_BASE = tr
15205
15205
  mode: "unknown",
15206
15206
  widgetMode: "normal",
15207
15207
  inlineCollapsed: !1,
15208
+ hostContext: null,
15208
15209
  setAuth: (t) => e(() => (storeWorkspaceId(t.workspaceId), {
15209
15210
  encToken: t.encToken || null,
15210
15211
  workspaceId: t.workspaceId,
@@ -15230,11 +15231,25 @@ const trimTrailingSlashes = (e) => "".replace(/\/+$/, ""), DEFAULT_API_BASE = tr
15230
15231
  setApiBase: (t) => e({ apiBase: t }),
15231
15232
  setCentralDashboardUrl: (t) => e({ centralDashboardUrl: t }),
15232
15233
  setMode: (t) => e({ mode: t }),
15233
- setWidgetMode: (t) => {
15234
- const n = normalizeWidgetMode(t);
15235
- e({ widgetMode: n, inlineCollapsed: n === "inline_ask" });
15236
- },
15234
+ /* A WRITE THAT CHANGES NOTHING STILL COSTS A RENDER.
15235
+ *
15236
+ * `setWidgetMode` and `setHostContext` are both called from effects the
15237
+ * native shell runs on mount, and hosts that pass neither (every host before
15238
+ * this feature) were therefore paying two store notifications for two no-ops.
15239
+ * Nothing in the store is cheap to notify: `ChatScreen` subscribes to the
15240
+ * WHOLE auth store, so each notification re-renders the transcript.
15241
+ *
15242
+ * Returning the current state object short-circuits zustand's setState
15243
+ * (`Object.is(nextState, state)`), so an unchanged value notifies nobody. */
15244
+ setWidgetMode: (t) => e((n) => {
15245
+ const o = normalizeWidgetMode(t), s = o === "inline_ask";
15246
+ return n.widgetMode === o && n.inlineCollapsed === s ? n : { widgetMode: o, inlineCollapsed: s };
15247
+ }),
15237
15248
  setInlineCollapsed: (t) => e({ inlineCollapsed: t }),
15249
+ setHostContext: (t) => e((n) => {
15250
+ const o = t ?? null;
15251
+ return n.hostContext === o || n.hostContext && o && JSON.stringify(n.hostContext) === JSON.stringify(o) ? n : { hostContext: o };
15252
+ }),
15238
15253
  refreshToken: (t) => e({ encToken: t }),
15239
15254
  clearAuth: () => {
15240
15255
  const t = window;
@@ -15251,6 +15266,7 @@ const trimTrailingSlashes = (e) => "".replace(/\/+$/, ""), DEFAULT_API_BASE = tr
15251
15266
  hideNewBrief: !1,
15252
15267
  hideNotifications: !1,
15253
15268
  widgetMode: "normal",
15269
+ hostContext: null,
15254
15270
  inlineCollapsed: !1,
15255
15271
  ready: !1
15256
15272
  });
@@ -16300,8 +16316,8 @@ async function sendMessageToAgent(e, t, n, o, s, l = !1, c = {}) {
16300
16316
  enc_token: getEncTokenValue(),
16301
16317
  color_mode: useThemeStore.getState().colorMode,
16302
16318
  widget_mode: c.widgetMode ?? "normal"
16303
- };
16304
- return c.clientMessageId && (f.client_message_id = c.clientMessageId), c.resumeValues !== void 0 ? (f.resume_values = c.resumeValues, f.resume_message = s) : c.resume_values !== void 0 && (f.resume_values = c.resume_values, f.resume_message = s), c.autonomy !== void 0 && (f.autonomy = c.autonomy), c.llmModel && (f.llm_model = c.llmModel), c.agentId && (f.agent_id = c.agentId), await apiFetch(`/workspaces/${e}/conversations/send`, {
16319
+ }, h = useAuthStore.getState().hostContext;
16320
+ return h && Object.keys(h).length > 0 && (f.host_context = h), c.clientMessageId && (f.client_message_id = c.clientMessageId), c.resumeValues !== void 0 ? (f.resume_values = c.resumeValues, f.resume_message = s) : c.resume_values !== void 0 && (f.resume_values = c.resume_values, f.resume_message = s), c.autonomy !== void 0 && (f.autonomy = c.autonomy), c.llmModel && (f.llm_model = c.llmModel), c.agentId && (f.agent_id = c.agentId), await apiFetch(`/workspaces/${e}/conversations/send`, {
16305
16321
  method: "POST",
16306
16322
  body: JSON.stringify(f)
16307
16323
  });
@@ -128668,67 +128684,76 @@ function buildCanvasScreenshot({
128668
128684
  width: e,
128669
128685
  height: t,
128670
128686
  title: n,
128671
- lines: o
128687
+ lines: o,
128688
+ source: s
128672
128689
  }) {
128673
- const s = document.createElement("canvas");
128674
- s.width = e, s.height = t;
128675
- const l = s.getContext("2d");
128676
- if (!l)
128690
+ const l = document.createElement("canvas");
128691
+ l.width = e, l.height = t;
128692
+ const c = l.getContext("2d");
128693
+ if (!c)
128677
128694
  throw new Error("Canvas rendering is not available");
128678
- const c = document.documentElement.classList.contains("dark");
128679
- return l.fillStyle = c ? "#0b0b0f" : "#f6f7fb", l.fillRect(0, 0, e, t), l.fillStyle = c ? "#f5f5f5" : "#111827", l.font = "600 22px sans-serif", l.fillText(n, 24, 42), l.font = "14px sans-serif", l.fillStyle = c ? "#d1d5db" : "#4b5563", o.slice(0, 18).forEach((f, h) => {
128680
- l.fillText(f.slice(0, 120), 24, 76 + h * 24);
128695
+ const f = document.documentElement.classList.contains("dark");
128696
+ return c.fillStyle = f ? "#0b0b0f" : "#f6f7fb", c.fillRect(0, 0, e, t), c.fillStyle = f ? "#f5f5f5" : "#111827", c.font = "600 22px sans-serif", c.fillText(n, 24, 42), c.font = "14px sans-serif", c.fillStyle = f ? "#d1d5db" : "#4b5563", o.slice(0, 18).forEach((h, g) => {
128697
+ c.fillText(h.slice(0, 120), 24, 76 + g * 24);
128681
128698
  }), {
128682
- dataUrl: s.toDataURL("image/png"),
128699
+ dataUrl: l.toDataURL("image/png"),
128683
128700
  width: e,
128684
128701
  height: t,
128685
- source: "widget_iframe"
128702
+ source: s
128686
128703
  };
128687
128704
  }
128705
+ function ownDocumentIsHostPage() {
128706
+ return !(window.parent && window.parent !== window);
128707
+ }
128688
128708
  async function captureIframeScreenshot() {
128689
- console.info("[UIComponents] Capturing widget iframe screenshot");
128690
- const e = Math.max(1, Math.ceil(window.innerWidth || 1024)), t = Math.max(1, Math.ceil(window.innerHeight || 768)), n = document.documentElement.cloneNode(!0);
128691
- n.querySelectorAll("script").forEach((f) => f.remove()), n.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
128692
- const o = new XMLSerializer().serializeToString(n), s = `
128693
- <svg xmlns="http://www.w3.org/2000/svg" width="${e}" height="${t}">
128694
- <foreignObject width="100%" height="100%">${o}</foreignObject>
128709
+ const e = ownDocumentIsHostPage() ? "host_page" : "widget_iframe";
128710
+ console.info("[UIComponents] Capturing own document screenshot", { source: e });
128711
+ const t = Math.max(1, Math.ceil(window.innerWidth || 1024)), n = Math.max(1, Math.ceil(window.innerHeight || 768)), o = document.documentElement.cloneNode(!0);
128712
+ o.querySelectorAll("script").forEach((h) => h.remove()), o.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
128713
+ const s = new XMLSerializer().serializeToString(o), l = `
128714
+ <svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${n}">
128715
+ <foreignObject width="100%" height="100%">${s}</foreignObject>
128695
128716
  </svg>
128696
- `, l = new Blob([s], { type: "image/svg+xml;charset=utf-8" }), c = URL.createObjectURL(l);
128717
+ `, c = new Blob([l], { type: "image/svg+xml;charset=utf-8" }), f = URL.createObjectURL(c);
128697
128718
  try {
128698
- const f = new Image(), h = new Promise((E, R) => {
128699
- const T = window.setTimeout(
128700
- () => R(new Error("Widget iframe screenshot timed out")),
128719
+ const h = new Image(), g = new Promise((R, T) => {
128720
+ const M = window.setTimeout(
128721
+ () => T(new Error("Widget iframe screenshot timed out")),
128701
128722
  2e3
128702
128723
  );
128703
- f.onload = () => {
128704
- window.clearTimeout(T), E();
128705
- }, f.onerror = () => {
128706
- window.clearTimeout(T), R(new Error("Widget iframe screenshot failed to render"));
128724
+ h.onload = () => {
128725
+ window.clearTimeout(M), R();
128726
+ }, h.onerror = () => {
128727
+ window.clearTimeout(M), T(new Error("Widget iframe screenshot failed to render"));
128707
128728
  };
128708
128729
  });
128709
- f.src = c, await h;
128710
- const g = document.createElement("canvas");
128711
- g.width = e, g.height = t;
128712
- const b = g.getContext("2d");
128713
- if (!b) throw new Error("Canvas rendering is not available");
128714
- return b.drawImage(f, 0, 0, e, t), {
128715
- dataUrl: g.toDataURL("image/png"),
128716
- width: e,
128717
- height: t,
128718
- source: "widget_iframe"
128730
+ h.src = f, await g;
128731
+ const b = document.createElement("canvas");
128732
+ b.width = t, b.height = n;
128733
+ const E = b.getContext("2d");
128734
+ if (!E) throw new Error("Canvas rendering is not available");
128735
+ return E.drawImage(h, 0, 0, t, n), {
128736
+ dataUrl: b.toDataURL("image/png"),
128737
+ width: t,
128738
+ height: n,
128739
+ source: e
128719
128740
  };
128720
- } catch (f) {
128721
- return console.info("[UIComponents] DOM screenshot fallback failed; using summary canvas", f), buildCanvasScreenshot({
128722
- width: e,
128723
- height: t,
128741
+ } catch (h) {
128742
+ return console.info("[UIComponents] DOM screenshot fallback failed; using summary canvas", h), buildCanvasScreenshot({
128743
+ width: t,
128744
+ height: n,
128724
128745
  title: document.title || "Copilot Widget",
128725
128746
  lines: [
128726
128747
  `URL: ${window.location.href}`,
128727
128748
  `Visible text: ${(document.body.innerText || "").replace(/\s+/g, " ").trim()}`
128728
- ]
128749
+ ],
128750
+ /* Rasterisation failed and this is a canvas with the page's text drawn
128751
+ * onto it. It is not a screenshot, so it does not claim to be one: the
128752
+ * backend declines to describe it as the host page. */
128753
+ source: "summary_canvas"
128729
128754
  });
128730
128755
  } finally {
128731
- URL.revokeObjectURL(c);
128756
+ URL.revokeObjectURL(f);
128732
128757
  }
128733
128758
  }
128734
128759
  async function captureScreenshotWithFallback(e) {
@@ -128741,7 +128766,7 @@ async function captureScreenshotWithFallback(e) {
128741
128766
  dataUrl: n.dataUrl,
128742
128767
  width: n.width,
128743
128768
  height: n.height,
128744
- source: n.source || "host_page"
128769
+ source: n.source === "summary_canvas" || n.source === "widget_iframe" ? n.source : "host_page"
128745
128770
  };
128746
128771
  } catch (n) {
128747
128772
  console.info("[UIComponents] Host screenshot unavailable; falling back to widget iframe capture", n);
@@ -130329,7 +130354,24 @@ function SidePaneHost({
130329
130354
  className: [
130330
130355
  "shrink-0 rounded-[8px] px-2.5 py-1 text-[13px] leading-[18px] transition-colors",
130331
130356
  "outline-none focus-visible:ring-2 focus-visible:ring-[var(--alias-border-focus)]",
130332
- E ? "bg-[var(--alias-surface-subtle)] text-[var(--alias-text---icons-primary)]" : "text-[var(--alias-text---icons-secondary)] hover:text-[var(--alias-text---icons-primary)]"
130357
+ /* SELECTED IS A FILL, NOT A SURFACE.
130358
+ *
130359
+ * This was `surface-subtle`, which is one step lighter than
130360
+ * `surface-base` in the dark theme and therefore read as a
130361
+ * selected tab -- and is `#ffffff` in the LIGHT theme, the
130362
+ * same as the card it sits on, so the highlighted tab was
130363
+ * invisible. Every `--alias-surface-*` token collapses to
130364
+ * white there on purpose: light mode expresses elevation
130365
+ * with shadow and border, which a 24px tab has neither of.
130366
+ *
130367
+ * `fill-neutral-neutralselected` is the design system's
130368
+ * answer for exactly this state and has contrast in both
130369
+ * themes (#242424 on #101010 dark, #ededed on #ffffff
130370
+ * light). Hover on an unselected tab stays a text change:
130371
+ * the light-mode hover fill is DARKER than the selected
130372
+ * fill, so filling on hover would make hover look more
130373
+ * selected than selected. */
130374
+ E ? "bg-[var(--alias-fill-neutral-neutralselected)] text-[var(--alias-text---icons-primary)]" : "text-[var(--alias-text---icons-secondary)] hover:text-[var(--alias-text---icons-primary)]"
130333
130375
  ].join(" "),
130334
130376
  children: b.title
130335
130377
  },
@@ -130346,7 +130388,7 @@ function SidePaneHost({
130346
130388
  "aria-label": `Close ${h.title}`,
130347
130389
  title: `Close ${h.title}`,
130348
130390
  "data-testid": "side-pane-close",
130349
- className: "inline-flex size-8 shrink-0 items-center justify-center rounded-[8px] text-[var(--alias-text---icons-secondary)] transition-colors hover:bg-[var(--alias-surface-subtle)] hover:text-[var(--alias-text---icons-primary)]",
130391
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-[8px] text-[var(--alias-text---icons-secondary)] transition-colors hover:bg-[var(--alias-fill-neutral-neutralhover)] hover:text-[var(--alias-text---icons-primary)]",
130350
130392
  children: /* @__PURE__ */ jsx(PanelRightClose, { className: "size-4" })
130351
130393
  }
130352
130394
  )
@@ -136582,6 +136624,17 @@ function AttentionToast() {
136582
136624
  n.id
136583
136625
  ) }) }) : null;
136584
136626
  }
136627
+ function deriveSurfaceState(e, t, n) {
136628
+ const o = e.filter((l) => l.chatId === t), s = o.find((l) => l.id === n) ?? o[o.length - 1];
136629
+ return {
136630
+ mode: o.length > 0 ? "fullscreen" : "inline",
136631
+ panelCount: o.length,
136632
+ activePanel: s ? { id: s.id, kind: s.kind, title: s.title } : null
136633
+ };
136634
+ }
136635
+ function surfaceFingerprint(e) {
136636
+ return `${e.mode}:${e.panelCount}:${e.activePanel?.id ?? ""}`;
136637
+ }
136585
136638
  function getCentralUrl() {
136586
136639
  return useAuthStore.getState().centralDashboardUrl;
136587
136640
  }
@@ -136803,7 +136856,7 @@ function useFetchConversations(e) {
136803
136856
  function AgentChatShell({ adapters: e }) {
136804
136857
  const t = useRef(e);
136805
136858
  t.current = e;
136806
- const { auth: n, workspace: o, theme: s, config: l, navigation: c } = e;
136859
+ const { auth: n, workspace: o, theme: s, config: l, navigation: c } = e, { hostContext: f, onSurfaceChange: h } = e;
136807
136860
  useState(() => {
136808
136861
  configureStreamAgentClient({
136809
136862
  getApiBase: () => t.current.config.apiBase,
@@ -136812,8 +136865,8 @@ function AgentChatShell({ adapters: e }) {
136812
136865
  onSessionExpired: () => t.current.auth.onSessionExpired?.(),
136813
136866
  onBackendRecovered: () => t.current.onBackendRecovered?.()
136814
136867
  });
136815
- const h = useAuthStore.getState();
136816
- return h.setMode("iframe"), h.setAuth({
136868
+ const I = useAuthStore.getState();
136869
+ return I.setMode("iframe"), I.setAuth({
136817
136870
  workspaceId: o.current.id,
136818
136871
  userId: n.user.id,
136819
136872
  email: n.user.email,
@@ -136825,19 +136878,33 @@ function AgentChatShell({ adapters: e }) {
136825
136878
  totalCredits: o.current.totalCredits ?? null,
136826
136879
  hideSidepane: l.hide?.sidepane ?? !0,
136827
136880
  hideNewBrief: l.hide?.newBrief ?? !0,
136828
- hideNotifications: l.hide?.notifications ?? !0
136829
- }), useThemeStore.getState().setTheme(s.mode), !0;
136881
+ hideNotifications: l.hide?.notifications ?? !0,
136882
+ widgetMode: l.widgetMode ?? "normal"
136883
+ }), useAuthStore.getState().setHostContext(f ?? null), useThemeStore.getState().setTheme(s.mode), !0;
136830
136884
  }), useEffect(() => {
136831
136885
  useAuthStore.getState().switchWorkspace(o.current.id);
136832
136886
  }, [o.current.id]), useEffect(() => {
136833
136887
  useThemeStore.getState().setTheme(s.mode);
136834
136888
  }, [s.mode]), useEffect(() => {
136889
+ useAuthStore.getState().setWidgetMode(l.widgetMode ?? "normal");
136890
+ }, [l.widgetMode]), useEffect(() => {
136891
+ useAuthStore.getState().setHostContext(f ?? null);
136892
+ }, [f]), useEffect(() => {
136835
136893
  useAuthStore.getState().setCredits(
136836
136894
  o.current.availableCredits ?? null,
136837
136895
  o.current.totalCredits ?? null
136838
136896
  );
136839
- }, [o.current.availableCredits, o.current.totalCredits]), useSessionKeepalive(), useCompletionNotifier(), useFetchConversations(o.current.id);
136840
- const f = useMemo(
136897
+ }, [o.current.availableCredits, o.current.totalCredits]);
136898
+ const g = useChatStore((I) => I.panels), b = useChatStore((I) => I.activePanelId), E = useChatStore((I) => I.activeChatId), R = useMemo(
136899
+ () => deriveSurfaceState(g, E, b),
136900
+ [g, b, E]
136901
+ ), T = useRef(null);
136902
+ useEffect(() => {
136903
+ if (!h) return;
136904
+ const I = surfaceFingerprint(R);
136905
+ T.current !== I && (T.current = I, h(R));
136906
+ }, [R, h]), useSessionKeepalive(), useCompletionNotifier(), useFetchConversations(o.current.id);
136907
+ const M = useMemo(
136841
136908
  () => ({
136842
136909
  chatId: c.chatId,
136843
136910
  navigateToChat: c.onChatIdChange
@@ -136849,7 +136916,7 @@ function AgentChatShell({ adapters: e }) {
136849
136916
  {
136850
136917
  className: "sa-surface relative flex h-full w-full flex-col overflow-hidden bg-transparent text-[var(--alias-text---icons-primary)]",
136851
136918
  "data-theme": "Mode 1",
136852
- children: /* @__PURE__ */ jsx(ChatNavigationProvider, { value: f, children: /* @__PURE__ */ jsxs(HostCanvasProvider, { value: e.canvas ?? null, children: [
136919
+ children: /* @__PURE__ */ jsx(ChatNavigationProvider, { value: M, children: /* @__PURE__ */ jsxs(HostCanvasProvider, { value: e.canvas ?? null, children: [
136853
136920
  /* @__PURE__ */ jsx(AttentionToast, {}),
136854
136921
  /* @__PURE__ */ jsx(ChatScreen, {})
136855
136922
  ] }) })