@m6d/cortex-angular 1.3.0 → 1.4.1

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
@@ -27,16 +27,17 @@ No root-level providers needed — each widget instance is fully self-contained.
27
27
 
28
28
  ### Configuration
29
29
 
30
- | Property | Type | Description |
31
- | ---------------------- | ------------------------------- | ------------------------------------------------------- |
32
- | `locale` | `Signal<string>` | Active locale (`"en"`, `"ar"`) |
33
- | `transport.baseUrl` | `string \| Signal<string>` | Agent API base URL |
34
- | `transport.getHeaders` | `() => Record<string, string>` | Request headers provider |
35
- | `wsUrl` | `string` | WebSocket URL for real-time events (optional) |
36
- | `theme` | `CortexClientTheme` | `'light' \| 'dark' \| 'system'` (default: `'light'`) |
37
- | `showDebugButton` | `boolean` | Show/hide the debug toggle button (default: `false`) |
38
- | `toolComponents` | `Record<string, Type<unknown>>` | Custom Angular components for tool rendering (optional) |
39
- | `onToolCall` | `(toolCall) => unknown` | Client-side tool call handler (optional) |
30
+ | Property | Type | Description |
31
+ | ----------------------- | ------------------------------- | ------------------------------------------------------------------------------- |
32
+ | `locale` | `Signal<string>` | Active locale (`"en"`, `"ar"`) |
33
+ | `transport.baseUrl` | `string \| Signal<string>` | Agent API base URL |
34
+ | `transport.getHeaders` | `() => Record<string, string>` | Request headers provider |
35
+ | `wsUrl` | `string` | WebSocket URL for real-time events (optional) |
36
+ | `theme` | `CortexClientTheme` | `'light' \| 'dark' \| 'system'` (default: `'light'`) |
37
+ | `showDebugButton` | `boolean` | Show/hide the debug toggle button (default: `false`) |
38
+ | `toolComponents` | `Record<string, Type<unknown>>` | Custom Angular components for tool rendering (optional) |
39
+ | `onToolCall` | `(toolCall) => unknown` | Client-side tool call handler (optional) |
40
+ | `trustSameOriginEmbeds` | `boolean` | Allow embedded tool pages served from this page's own origin (default: `false`) |
40
41
 
41
42
  `transport.getHeaders` can return any request headers needed by the host app. WebSocket auth continues to derive the `token` query param from a bearer `Authorization` header when present.
42
43
 
@@ -48,7 +49,13 @@ initiate, shows the returned page in a sandboxed iframe (inline card or
48
49
  modal, per tool config), and submits the page's `cortex:client-tool`
49
50
  postMessage outcome back to the run as-is — anything that must actually be
50
51
  true lives in the integrating backend's own records. No widget configuration
51
- is needed; other pending tool calls keep the existing `toolComponents` /
52
+ is needed. One caveat: an embed served from the host page's **own origin** is
53
+ refused by default, because a same-origin iframe cannot be sandboxed — it
54
+ could reach into the host page, and embed URLs come from Control Center tool
55
+ config, whose administrator is not necessarily the site owner. When both are
56
+ the same party and the embeds are the site's own pages, set
57
+ `trustSameOriginEmbeds: true` to render them anyway (with full access, as any
58
+ of the site's own pages has). Other pending tool calls keep the existing `toolComponents` /
52
59
  `hooks.onToolCall` behavior. That hook is also how static client tools work:
53
60
  declare an executor-less tool in the server config and answer it here by
54
61
  returning its output from `hooks.onToolCall` for that tool name. Consumer `hooks.onToolCall` implementations must
@@ -483,14 +483,22 @@ function parseClientToolHandshake(data, origin, embedOrigin) {
483
483
  }
484
484
 
485
485
  // src/internal/client/src/client-tools.ts
486
- function embedSandbox(embedUrl, pageOrigin) {
486
+ function embedSandbox(embedUrl, pageOrigin, trustSameOrigin) {
487
487
  const base = "allow-scripts allow-forms allow-popups";
488
488
  try {
489
- return new URL(embedUrl).origin === pageOrigin ? base : `${base} allow-same-origin`;
489
+ const sameOrigin = new URL(embedUrl).origin === pageOrigin;
490
+ return sameOrigin && !trustSameOrigin ? base : `${base} allow-same-origin`;
490
491
  } catch {
491
492
  return base;
492
493
  }
493
494
  }
495
+ function embedTrustedOnHostOrigin(embedUrl, pageOrigin, trustSameOrigin) {
496
+ try {
497
+ return trustSameOrigin === true && new URL(embedUrl).origin === pageOrigin;
498
+ } catch {
499
+ return false;
500
+ }
501
+ }
494
502
  function frameBreachedHostOrigin(frame, pageOrigin) {
495
503
  try {
496
504
  return frame.contentWindow?.location.origin === pageOrigin;
@@ -1468,9 +1476,11 @@ class CortexChatService {
1468
1476
  return this.threadService.delete(threadId);
1469
1477
  }
1470
1478
  mountChat(thread, mode) {
1479
+ const initialMessages = mode === "reload" ? this.chat()?.messages() : undefined;
1471
1480
  this.chatInjector?.destroy();
1472
1481
  this.dispatchedToolCalls.clear();
1473
- this.messageMetadata.set(new Map);
1482
+ if (mode !== "reload")
1483
+ this.messageMetadata.set(new Map);
1474
1484
  this.ownsRun = false;
1475
1485
  this.serverAnswered = false;
1476
1486
  this.wasAborted = false;
@@ -1481,6 +1491,7 @@ class CortexChatService {
1481
1491
  const chat = injectChat({
1482
1492
  threadId: thread.id,
1483
1493
  persistence: true,
1494
+ initialMessages,
1484
1495
  connection: this.createConnection(thread, mode, injector),
1485
1496
  onChunk: () => {
1486
1497
  this.serverAnswered = true;
@@ -3464,8 +3475,10 @@ class ClientToolEmbedComponent {
3464
3475
  }, ...ngDevMode ? [{ debugName: "isModal" }] : []);
3465
3476
  sandbox = computed(() => {
3466
3477
  const state = this.state();
3467
- return state.phase === "embedded" ? embedSandbox(state.embedUrl, window.location.origin) : "";
3478
+ return state.phase === "embedded" ? embedSandbox(state.embedUrl, window.location.origin, this.trustSameOriginEmbeds) : "";
3468
3479
  }, ...ngDevMode ? [{ debugName: "sandbox" }] : []);
3480
+ config = inject(CORTEX_CLIENT_CONFIG);
3481
+ trustSameOriginEmbeds;
3469
3482
  api = inject(CortexApiClient);
3470
3483
  chatService = inject(CortexChatService);
3471
3484
  sanitizer = inject(DomSanitizer);
@@ -3486,6 +3499,7 @@ class ClientToolEmbedComponent {
3486
3499
  });
3487
3500
  }
3488
3501
  ngOnInit() {
3502
+ this.trustSameOriginEmbeds = this.config().trustSameOriginEmbeds;
3489
3503
  const threadId = this.chatService.selectedThread()?.id;
3490
3504
  const part = this.toolCallPart();
3491
3505
  if (!threadId) {
@@ -3516,7 +3530,11 @@ class ClientToolEmbedComponent {
3516
3530
  const frame = event.target;
3517
3531
  if (!(frame instanceof HTMLIFrameElement))
3518
3532
  return;
3519
- if (frameBreachedHostOrigin(frame, window.location.origin)) {
3533
+ const state = this.state();
3534
+ if (state.phase !== "embedded")
3535
+ return;
3536
+ const trusted = embedTrustedOnHostOrigin(state.embedUrl, window.location.origin, this.trustSameOriginEmbeds);
3537
+ if (!trusted && frameBreachedHostOrigin(frame, window.location.origin)) {
3520
3538
  this.embed?.terminate();
3521
3539
  }
3522
3540
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m6d/cortex-angular",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Reusable AI agent chat UI library for Angular",
5
5
  "license": "MIT",
6
6
  "sideEffects": false,
@@ -106,6 +106,16 @@ export type CortexConfig = {
106
106
  /** Color scheme; `'system'` follows the OS preference. Defaults to `'light'`. */
107
107
  theme?: CortexTheme;
108
108
  showDebugButton?: boolean;
109
+ /**
110
+ * Render embedded tool pages served from this page's own origin with their
111
+ * full identity — which, same-origin, means no effective sandbox at all: such
112
+ * an embed can reach into the host page. By default they are refused
113
+ * (rendered inert), because embed URLs come from Control Center tool config,
114
+ * and whoever administers that is not necessarily whoever owns this site.
115
+ * Enable only when both are the same party and the embeds are this site's
116
+ * own pages — then a second origin just for embeds is no longer needed.
117
+ */
118
+ trustSameOriginEmbeds?: boolean;
109
119
  hooks?: CortexHooks;
110
120
  };
111
121
  export type QueuedAttachment = {