@classic-homes/agent-chat 0.1.1 → 0.1.2

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.
@@ -2,6 +2,7 @@
2
2
  import { onMount } from 'svelte';
3
3
  import type { UiResourceState } from '../reducer.js';
4
4
  import {
5
+ FRAME_TARGET_ORIGIN,
5
6
  MIN_FRAME_HEIGHT,
6
7
  UI_FRAME_RENDER,
7
8
  composerTextFor,
@@ -27,7 +28,7 @@
27
28
  function send(): void {
28
29
  frame?.contentWindow?.postMessage(
29
30
  { type: UI_FRAME_RENDER, html: resource.html, uri: resource.uri },
30
- sandboxOrigin,
31
+ FRAME_TARGET_ORIGIN,
31
32
  );
32
33
  }
33
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"UiFrame.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/UiFrame.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAWnD,UAAU,KAAK;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,gGAAgG;IAChG,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA8DH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
1
+ {"version":3,"file":"UiFrame.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/UiFrame.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAYnD,UAAU,KAAK;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,gGAAgG;IAChG,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA8DH,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -33,8 +33,20 @@ export declare const MIN_FRAME_HEIGHT = 120;
33
33
  export declare const MAX_FRAME_HEIGHT = 1200;
34
34
  /** Parse a message from the sandbox into a FrameAction, or null when it is not one. */
35
35
  export declare function parseFrameMessage(data: unknown): FrameAction | null;
36
- /** True when a message event came from our sandbox frame and nowhere else. */
36
+ /**
37
+ * True when a message event came from our sandbox frame and nowhere else. The frame runs with
38
+ * `sandbox` and no `allow-same-origin`, so its origin is opaque and the browser reports it as
39
+ * the string "null"; the window identity is the check that matters. The page's own origin is
40
+ * accepted too for a frame that is not sandboxed (tests, future same-origin hosting).
41
+ */
37
42
  export declare function isFromFrame(event: MessageEvent, frame: HTMLIFrameElement | null, sandboxOrigin: string): boolean;
43
+ /**
44
+ * Target origin for messages to the sandbox frame. An opaque origin matches nothing but "*",
45
+ * so a message addressed to the sandbox's https origin would be dropped silently. The window
46
+ * reference itself (`frame.contentWindow`) is what scopes the message; the HTML it carries is
47
+ * model output that is untrusted anyway.
48
+ */
49
+ export declare const FRAME_TARGET_ORIGIN = "*";
38
50
  /** Text an intent or prompt action becomes in the composer. */
39
51
  export declare function composerTextFor(action: Extract<FrameAction, {
40
52
  type: 'prompt' | 'intent';
@@ -1 +1 @@
1
- {"version":3,"file":"ui-frame-protocol.d.ts","sourceRoot":"","sources":["../src/lib/ui-frame-protocol.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,cAAc,8BAA8B,CAAC;AAC1D,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CA0CnE;AAQD,8EAA8E;AAC9E,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAC/B,aAAa,EAAE,MAAM,GACpB,OAAO,CAET;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAAE,CAAC,GAC1D,MAAM,CAIR"}
1
+ {"version":3,"file":"ui-frame-protocol.d.ts","sourceRoot":"","sources":["../src/lib/ui-frame-protocol.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,cAAc,8BAA8B,CAAC;AAC1D,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CA0CnE;AAQD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAC/B,aAAa,EAAE,MAAM,GACpB,OAAO,CAGT;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAAE,CAAC,GAC1D,MAAM,CAIR"}
@@ -60,10 +60,24 @@ function asRecord(value) {
60
60
  ? value
61
61
  : undefined;
62
62
  }
63
- /** True when a message event came from our sandbox frame and nowhere else. */
63
+ /**
64
+ * True when a message event came from our sandbox frame and nowhere else. The frame runs with
65
+ * `sandbox` and no `allow-same-origin`, so its origin is opaque and the browser reports it as
66
+ * the string "null"; the window identity is the check that matters. The page's own origin is
67
+ * accepted too for a frame that is not sandboxed (tests, future same-origin hosting).
68
+ */
64
69
  export function isFromFrame(event, frame, sandboxOrigin) {
65
- return !!frame && event.source === frame.contentWindow && event.origin === sandboxOrigin;
70
+ if (!frame || !frame.contentWindow || event.source !== frame.contentWindow)
71
+ return false;
72
+ return event.origin === 'null' || event.origin === sandboxOrigin;
66
73
  }
74
+ /**
75
+ * Target origin for messages to the sandbox frame. An opaque origin matches nothing but "*",
76
+ * so a message addressed to the sandbox's https origin would be dropped silently. The window
77
+ * reference itself (`frame.contentWindow`) is what scopes the message; the HTML it carries is
78
+ * model output that is untrusted anyway.
79
+ */
80
+ export const FRAME_TARGET_ORIGIN = '*';
67
81
  /** Text an intent or prompt action becomes in the composer. */
68
82
  export function composerTextFor(action) {
69
83
  if (action.type === 'prompt')
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { composerTextFor, parseFrameMessage } from './ui-frame-protocol.js';
2
+ import { composerTextFor, isFromFrame, parseFrameMessage } from './ui-frame-protocol.js';
3
3
  describe('parseFrameMessage', () => {
4
4
  it('accepts the MCP-UI action set and clamps sizes', () => {
5
5
  expect(parseFrameMessage({ type: 'ui-lifecycle-iframe-ready' })).toEqual({ type: 'ready' });
@@ -32,3 +32,17 @@ describe('parseFrameMessage', () => {
32
32
  expect(parseFrameMessage({ type: 'eval', payload: {} })).toBeNull();
33
33
  });
34
34
  });
35
+ describe('isFromFrame', () => {
36
+ const win = {};
37
+ const frame = { contentWindow: win };
38
+ const event = (source, origin) => ({ source, origin });
39
+ it('accepts the sandbox window with an opaque ("null") or the sandbox origin', () => {
40
+ expect(isFromFrame(event(win, 'null'), frame, 'https://sandbox.example')).toBe(true);
41
+ expect(isFromFrame(event(win, 'https://sandbox.example'), frame, 'https://sandbox.example')).toBe(true);
42
+ });
43
+ it('rejects other windows, other origins and a missing frame', () => {
44
+ expect(isFromFrame(event({}, 'null'), frame, 'https://sandbox.example')).toBe(false);
45
+ expect(isFromFrame(event(win, 'https://evil.example'), frame, 'https://sandbox.example')).toBe(false);
46
+ expect(isFromFrame(event(win, 'null'), null, 'https://sandbox.example')).toBe(false);
47
+ });
48
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/agent-chat",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Svelte 5 chat UI for Classic Agent: session state, message list, composer, tool timeline, artifacts and MCP-UI sandbox frames. Consumed by the Classic App.",
@@ -17,8 +17,8 @@
17
17
  "dist"
18
18
  ],
19
19
  "dependencies": {
20
- "@classic-homes/agent-client": "0.1.1",
21
- "@classic-homes/agent-contracts": "0.1.1",
20
+ "@classic-homes/agent-client": "0.1.2",
21
+ "@classic-homes/agent-contracts": "0.1.2",
22
22
  "dompurify": "3.4.17"
23
23
  },
24
24
  "peerDependencies": {