@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.
- package/dist/components/UiFrame.svelte +2 -1
- package/dist/components/UiFrame.svelte.d.ts.map +1 -1
- package/dist/ui-frame-protocol.d.ts +13 -1
- package/dist/ui-frame-protocol.d.ts.map +1 -1
- package/dist/ui-frame-protocol.js +16 -2
- package/dist/ui-frame-protocol.test.js +15 -1
- package/package.json +3 -3
|
@@ -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
|
-
|
|
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;
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
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.
|
|
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.
|
|
21
|
-
"@classic-homes/agent-contracts": "0.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": {
|