@elizaos/plugin-xr 2.0.3-beta.5 → 2.0.3-beta.7
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/actions/xr-query-vision.d.ts +3 -0
- package/dist/actions/xr-query-vision.d.ts.map +1 -0
- package/dist/actions/xr-query-vision.js +39 -0
- package/dist/actions/xr-query-vision.js.map +1 -0
- package/dist/actions/xr-view-actions.d.ts +18 -0
- package/dist/actions/xr-view-actions.d.ts.map +1 -0
- package/dist/actions/xr-view-actions.js +304 -0
- package/dist/actions/xr-view-actions.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +124 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -0
- package/dist/providers/xr-context.d.ts +3 -0
- package/dist/providers/xr-context.d.ts.map +1 -0
- package/dist/providers/xr-context.js +34 -0
- package/dist/providers/xr-context.js.map +1 -0
- package/dist/routes/xr-connect.d.ts +3 -0
- package/dist/routes/xr-connect.d.ts.map +1 -0
- package/{src/routes/xr-connect.ts → dist/routes/xr-connect.js} +12 -15
- package/dist/routes/xr-connect.js.map +1 -0
- package/dist/routes/xr-simulator-route.d.ts +8 -0
- package/dist/routes/xr-simulator-route.d.ts.map +1 -0
- package/{src/routes/xr-simulator-route.ts → dist/routes/xr-simulator-route.js} +10 -16
- package/dist/routes/xr-simulator-route.js.map +1 -0
- package/dist/routes/xr-status.d.ts +3 -0
- package/dist/routes/xr-status.d.ts.map +1 -0
- package/{src/routes/xr-status.ts → dist/routes/xr-status.js} +13 -15
- package/dist/routes/xr-status.js.map +1 -0
- package/dist/routes/xr-view-host.d.ts +24 -0
- package/dist/routes/xr-view-host.d.ts.map +1 -0
- package/{src/routes/xr-view-host.ts → dist/routes/xr-view-host.js} +22 -59
- package/dist/routes/xr-view-host.js.map +1 -0
- package/dist/routes/xr-views.d.ts +8 -0
- package/dist/routes/xr-views.d.ts.map +1 -0
- package/dist/routes/xr-views.js +31 -0
- package/dist/routes/xr-views.js.map +1 -0
- package/dist/services/audio-pipeline.d.ts +20 -0
- package/dist/services/audio-pipeline.d.ts.map +1 -0
- package/{src/services/audio-pipeline.ts → dist/services/audio-pipeline.js} +25 -58
- package/dist/services/audio-pipeline.js.map +1 -0
- package/dist/services/vision-pipeline.d.ts +16 -0
- package/dist/services/vision-pipeline.d.ts.map +1 -0
- package/dist/services/vision-pipeline.js +39 -0
- package/dist/services/vision-pipeline.js.map +1 -0
- package/dist/services/xr-session-service.d.ts +50 -0
- package/dist/services/xr-session-service.d.ts.map +1 -0
- package/{src/services/xr-session-service.ts → dist/services/xr-session-service.js} +85 -194
- package/dist/services/xr-session-service.js.map +1 -0
- package/package.json +9 -4
- package/AGENTS.md +0 -151
- package/CLAUDE.md +0 -151
- package/simulator/bun.lock +0 -159
- package/simulator/package.json +0 -28
- package/simulator/src/emulator.ts +0 -174
- package/simulator/src/mock-agent.ts +0 -233
- package/simulator/src/node.ts +0 -9
- package/simulator/src/playwright-fixture.ts +0 -169
- package/simulator/src/types.ts +0 -51
- package/simulator/tsconfig.json +0 -13
- package/simulator/vite.config.ts +0 -25
- package/src/__tests__/audio-pipeline.test.ts +0 -129
- package/src/__tests__/protocol.test.ts +0 -53
- package/src/__tests__/routes-e2e.test.ts +0 -276
- package/src/__tests__/vision-pipeline.test.ts +0 -73
- package/src/__tests__/xr-bundle-coverage.test.ts +0 -303
- package/src/__tests__/xr-feature-parity.test.ts +0 -524
- package/src/__tests__/xr-functional-parity.test.ts +0 -522
- package/src/__tests__/xr-view-host-http.test.ts +0 -239
- package/src/__tests__/xr-view-host.test.ts +0 -174
- package/src/actions/xr-query-vision.ts +0 -64
- package/src/actions/xr-view-actions.ts +0 -386
- package/src/index.ts +0 -55
- package/src/protocol.ts +0 -126
- package/src/providers/xr-context.ts +0 -49
- package/src/routes/xr-views.ts +0 -43
- package/src/services/vision-pipeline.ts +0 -57
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -30
- package/vitest.config.ts +0 -21
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Full HTTP integration tests for the xrViewHostRoute.
|
|
3
|
-
*
|
|
4
|
-
* Unlike the unit tests in xr-view-host.test.ts (which call the route handler
|
|
5
|
-
* directly), these tests spin up a real Node.js HTTP server, make actual
|
|
6
|
-
* fetch() requests over TCP, and assert on the live HTTP response.
|
|
7
|
-
*
|
|
8
|
-
* This proves end-to-end integration:
|
|
9
|
-
* - URL parameter extraction works correctly
|
|
10
|
-
* - Route handler serializes the HTML response over a real socket
|
|
11
|
-
* - Content-Type and Content-Security-Policy headers are present in HTTP
|
|
12
|
-
* - Representative XR view IDs produce valid HTTP 200 responses
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { createServer } from "node:http";
|
|
16
|
-
import type { AddressInfo } from "node:net";
|
|
17
|
-
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
18
|
-
import { xrViewHostRoute } from "../routes/xr-view-host.ts";
|
|
19
|
-
|
|
20
|
-
const VIEW_HOST_SMOKE_IDS = [
|
|
21
|
-
"xr-route-smoke",
|
|
22
|
-
"hyphenated-view",
|
|
23
|
-
"space-panel",
|
|
24
|
-
] as const;
|
|
25
|
-
|
|
26
|
-
// ── HTTP server setup ─────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
let baseUrl = "";
|
|
29
|
-
let closeServer: () => Promise<void>;
|
|
30
|
-
|
|
31
|
-
beforeAll(() => {
|
|
32
|
-
return new Promise<void>((resolve) => {
|
|
33
|
-
const server = createServer(async (req, res) => {
|
|
34
|
-
if (req.method !== "GET") {
|
|
35
|
-
res.writeHead(405);
|
|
36
|
-
res.end("Method not allowed");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!req.url) {
|
|
41
|
-
res.writeHead(400);
|
|
42
|
-
res.end("Missing request URL");
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const url = new URL(req.url, "http://localhost");
|
|
47
|
-
|
|
48
|
-
// Route: GET /api/xr/view-host/:id
|
|
49
|
-
const match = url.pathname.match(/^\/api\/xr\/view-host\/(.+)$/);
|
|
50
|
-
if (match) {
|
|
51
|
-
const viewId = decodeURIComponent(match[1]);
|
|
52
|
-
const result = await xrViewHostRoute.routeHandler({
|
|
53
|
-
params: { id: viewId },
|
|
54
|
-
runtime: { port: 31337 },
|
|
55
|
-
} as never);
|
|
56
|
-
|
|
57
|
-
res.writeHead(result.status, result.headers as Record<string, string>);
|
|
58
|
-
res.end(result.body as string);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Route: GET /api/xr/view-host (no id — should 400)
|
|
63
|
-
if (url.pathname === "/api/xr/view-host") {
|
|
64
|
-
const result = await xrViewHostRoute.routeHandler({
|
|
65
|
-
params: {},
|
|
66
|
-
runtime: { port: 31337 },
|
|
67
|
-
} as never);
|
|
68
|
-
res.writeHead(result.status);
|
|
69
|
-
res.end(JSON.stringify(result.body));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
res.writeHead(404);
|
|
74
|
-
res.end("Not found");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
server.listen(0, "127.0.0.1", () => {
|
|
78
|
-
const { port } = server.address() as AddressInfo;
|
|
79
|
-
baseUrl = `http://127.0.0.1:${port}`;
|
|
80
|
-
closeServer = () => new Promise((r) => server.close(() => r()));
|
|
81
|
-
resolve();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
afterAll(() => closeServer());
|
|
87
|
-
|
|
88
|
-
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
describe("xrViewHostRoute — real HTTP server integration", () => {
|
|
91
|
-
it("GET /api/xr/view-host (no id) returns HTTP 400", async () => {
|
|
92
|
-
const res = await fetch(`${baseUrl}/api/xr/view-host`);
|
|
93
|
-
expect(res.status).toBe(400);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("GET /api/xr/view-host/:id returns HTTP 200 with text/html for representative view ids", async () => {
|
|
97
|
-
const failures: string[] = [];
|
|
98
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
99
|
-
const res = await fetch(
|
|
100
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
101
|
-
);
|
|
102
|
-
if (res.status !== 200) {
|
|
103
|
-
failures.push(`${id}: HTTP ${res.status}`);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
107
|
-
if (!ct.includes("text/html")) {
|
|
108
|
-
failures.push(`${id}: Content-Type "${ct}" is not text/html`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
expect(failures).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("every view-host response body is valid HTML with the view id in data-view-id", async () => {
|
|
115
|
-
const failures: string[] = [];
|
|
116
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
117
|
-
const res = await fetch(
|
|
118
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
119
|
-
);
|
|
120
|
-
const html = await res.text();
|
|
121
|
-
if (
|
|
122
|
-
!html.startsWith("<!DOCTYPE html") &&
|
|
123
|
-
!html.startsWith("<!doctype html")
|
|
124
|
-
) {
|
|
125
|
-
failures.push(`${id}: response does not start with DOCTYPE`);
|
|
126
|
-
}
|
|
127
|
-
if (!html.includes(`data-view-id="${id}"`)) {
|
|
128
|
-
failures.push(`${id}: data-view-id="${id}" not found in response`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
expect(failures).toEqual([]);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("every view-host response has Content-Security-Policy header", async () => {
|
|
135
|
-
const failures: string[] = [];
|
|
136
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
137
|
-
const res = await fetch(
|
|
138
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
139
|
-
);
|
|
140
|
-
const csp = res.headers.get("content-security-policy") ?? "";
|
|
141
|
-
if (!csp) {
|
|
142
|
-
failures.push(`${id}: missing Content-Security-Policy header`);
|
|
143
|
-
} else if (!csp.includes("esm.sh")) {
|
|
144
|
-
failures.push(`${id}: CSP missing esm.sh`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
expect(failures).toEqual([]);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("view-host pages with special characters in id serve correctly", async () => {
|
|
151
|
-
const specialIds = ["hyphenated-view"];
|
|
152
|
-
for (const id of specialIds) {
|
|
153
|
-
const res = await fetch(
|
|
154
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
155
|
-
);
|
|
156
|
-
expect(res.status, `${id}: expected 200`).toBe(200);
|
|
157
|
-
const html = await res.text();
|
|
158
|
-
expect(html, `${id}: data-view-id must match`).toContain(
|
|
159
|
-
`data-view-id="${id}"`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it("view-host pages embed the correct bundle URL for the elizaOS views API", async () => {
|
|
165
|
-
const failures: string[] = [];
|
|
166
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
167
|
-
const res = await fetch(
|
|
168
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
169
|
-
);
|
|
170
|
-
const html = await res.text();
|
|
171
|
-
// Bundle URL must reference /api/views/:id/bundle.js — the elizaOS views serving endpoint
|
|
172
|
-
const expectedBundlePath = `/api/views/${id}/bundle.js`;
|
|
173
|
-
if (!html.includes(expectedBundlePath)) {
|
|
174
|
-
failures.push(
|
|
175
|
-
`${id}: bundle URL "${expectedBundlePath}" not found in page`,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
expect(failures).toEqual([]);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it("view-host pages load React from CDN importmap (not bundled)", async () => {
|
|
183
|
-
const failures: string[] = [];
|
|
184
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
185
|
-
const res = await fetch(
|
|
186
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
187
|
-
);
|
|
188
|
-
const html = await res.text();
|
|
189
|
-
// React must come from importmap (esm.sh), not be bundled — proven by the importmap block
|
|
190
|
-
if (!html.includes("esm.sh/react")) {
|
|
191
|
-
failures.push(`${id}: React importmap missing`);
|
|
192
|
-
}
|
|
193
|
-
// importmap type must be present
|
|
194
|
-
if (!html.includes('type="importmap"')) {
|
|
195
|
-
failures.push(`${id}: importmap script tag missing`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
expect(failures).toEqual([]);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("each view-host page has voice-input infrastructure wired (fillFocusedInput, xr:transcript)", async () => {
|
|
202
|
-
const failures: string[] = [];
|
|
203
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
204
|
-
const res = await fetch(
|
|
205
|
-
`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`,
|
|
206
|
-
);
|
|
207
|
-
const html = await res.text();
|
|
208
|
-
if (!html.includes("fillFocusedInput")) {
|
|
209
|
-
failures.push(`${id}: fillFocusedInput missing`);
|
|
210
|
-
}
|
|
211
|
-
if (!html.includes("xr:transcript")) {
|
|
212
|
-
failures.push(`${id}: xr:transcript handler missing`);
|
|
213
|
-
}
|
|
214
|
-
if (!html.includes("xr:view-ready")) {
|
|
215
|
-
failures.push(`${id}: xr:view-ready postMessage missing`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
expect(failures).toEqual([]);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("concurrent requests for representative view ids resolve correctly in parallel", async () => {
|
|
222
|
-
// Proves the server handles concurrent requests without state corruption
|
|
223
|
-
const responses = await Promise.all(
|
|
224
|
-
VIEW_HOST_SMOKE_IDS.map((id) =>
|
|
225
|
-
fetch(`${baseUrl}/api/xr/view-host/${encodeURIComponent(id)}`).then(
|
|
226
|
-
async (r) => ({ id, status: r.status, html: await r.text() }),
|
|
227
|
-
),
|
|
228
|
-
),
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
const failures: string[] = [];
|
|
232
|
-
for (const { id, status, html } of responses) {
|
|
233
|
-
if (status !== 200) failures.push(`${id}: HTTP ${status}`);
|
|
234
|
-
else if (!html.includes(`data-view-id="${id}"`))
|
|
235
|
-
failures.push(`${id}: data-view-id mismatch in concurrent response`);
|
|
236
|
-
}
|
|
237
|
-
expect(failures).toEqual([]);
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the xrViewHostRoute handler.
|
|
3
|
-
*
|
|
4
|
-
* These tests call the real route handler directly — no mock server,
|
|
5
|
-
* no Playwright — proving that the elizaOS plugin infrastructure
|
|
6
|
-
* produces correct, complete HTML for arbitrary XR view IDs. This validates
|
|
7
|
-
* the shared host template without carrying a copied inventory of plugin views.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, expect, it } from "vitest";
|
|
11
|
-
import { xrViewHostRoute } from "../routes/xr-view-host.ts";
|
|
12
|
-
|
|
13
|
-
const VIEW_HOST_SMOKE_IDS = [
|
|
14
|
-
"xr-route-smoke",
|
|
15
|
-
"hyphenated-view",
|
|
16
|
-
"space-panel",
|
|
17
|
-
] as const;
|
|
18
|
-
|
|
19
|
-
function makeCtx(viewId: string) {
|
|
20
|
-
return {
|
|
21
|
-
params: { id: viewId },
|
|
22
|
-
runtime: { port: 31337 } as unknown as never,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function fetchHtml(viewId: string): Promise<string> {
|
|
27
|
-
const result = await xrViewHostRoute.routeHandler(makeCtx(viewId) as never);
|
|
28
|
-
expect(result.status).toBe(200);
|
|
29
|
-
expect(result.headers?.["Content-Type"]).toMatch(/text\/html/);
|
|
30
|
-
return result.body as string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe("xrViewHostRoute — real route handler", () => {
|
|
34
|
-
it("returns 400 for missing view id", async () => {
|
|
35
|
-
const result = await xrViewHostRoute.routeHandler({
|
|
36
|
-
params: {},
|
|
37
|
-
runtime: {},
|
|
38
|
-
} as never);
|
|
39
|
-
expect(result.status).toBe(400);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("returns 200 with Content-Type text/html for representative view ids", async () => {
|
|
43
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
44
|
-
const result = await xrViewHostRoute.routeHandler(makeCtx(id) as never);
|
|
45
|
-
expect(result.status, `${id}: expected status 200`).toBe(200);
|
|
46
|
-
expect(
|
|
47
|
-
result.headers?.["Content-Type"],
|
|
48
|
-
`${id}: expected text/html Content-Type`,
|
|
49
|
-
).toMatch(/text\/html/);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("each view-host page has a DOCTYPE and html[data-view-id] set to the view id", async () => {
|
|
54
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
55
|
-
const html = await fetchHtml(id);
|
|
56
|
-
expect(html, `${id}: should start with <!DOCTYPE html>`).toMatch(
|
|
57
|
-
/^<!DOCTYPE html>/i,
|
|
58
|
-
);
|
|
59
|
-
expect(html, `${id}: html tag should carry data-view-id`).toContain(
|
|
60
|
-
`data-view-id="${id}"`,
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("each view-host page contains the XR shell structure", async () => {
|
|
66
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
67
|
-
const html = await fetchHtml(id);
|
|
68
|
-
expect(html, `${id}: missing #xr-shell`).toContain('id="xr-shell"');
|
|
69
|
-
expect(html, `${id}: missing #xr-bar`).toContain('id="xr-bar"');
|
|
70
|
-
expect(html, `${id}: missing #view-mount`).toContain('id="view-mount"');
|
|
71
|
-
expect(html, `${id}: missing #btn-close`).toContain('id="btn-close"');
|
|
72
|
-
expect(html, `${id}: missing #voice-indicator`).toContain(
|
|
73
|
-
'id="voice-indicator"',
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("each view-host page includes the voice transcript routing script", async () => {
|
|
79
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
80
|
-
const html = await fetchHtml(id);
|
|
81
|
-
// The page must listen for xr:transcript messages and route to focused input
|
|
82
|
-
expect(html, `${id}: missing xr:transcript handler`).toContain(
|
|
83
|
-
"xr:transcript",
|
|
84
|
-
);
|
|
85
|
-
expect(html, `${id}: missing fillFocusedInput`).toContain(
|
|
86
|
-
"fillFocusedInput",
|
|
87
|
-
);
|
|
88
|
-
expect(html, `${id}: missing xr:focus-next handler`).toContain(
|
|
89
|
-
"xr:focus-next",
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("each view-host page sends xr:view-ready to parent on mount", async () => {
|
|
95
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
96
|
-
const html = await fetchHtml(id);
|
|
97
|
-
expect(html, `${id}: missing xr:view-ready postMessage`).toContain(
|
|
98
|
-
"xr:view-ready",
|
|
99
|
-
);
|
|
100
|
-
// And the view id must be encoded correctly in the page script
|
|
101
|
-
expect(html, `${id}: VIEW_ID constant not set`).toContain(
|
|
102
|
-
`const VIEW_ID = "${id}"`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("each view-host page has a React importmap pointing to esm.sh", async () => {
|
|
108
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
109
|
-
const html = await fetchHtml(id);
|
|
110
|
-
expect(html, `${id}: missing importmap`).toContain('type="importmap"');
|
|
111
|
-
expect(
|
|
112
|
-
html,
|
|
113
|
-
`${id}: importmap should reference react from esm.sh`,
|
|
114
|
-
).toContain("esm.sh/react");
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("each view-host page constructs the bundle URL from the agent origin", async () => {
|
|
119
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
120
|
-
const html = await fetchHtml(id);
|
|
121
|
-
// Bundle URL must reference the view id and the agent origin
|
|
122
|
-
expect(html, `${id}: bundle URL must include view id`).toContain(
|
|
123
|
-
`/api/views/${id}/bundle.js`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("each view-host page has XR-friendly form styling (min-height 44px)", async () => {
|
|
129
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
130
|
-
const html = await fetchHtml(id);
|
|
131
|
-
expect(html, `${id}: missing 44px touch target rule`).toContain(
|
|
132
|
-
"min-height: 44px",
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("each view-host page includes a transcript toast element", async () => {
|
|
138
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
139
|
-
const html = await fetchHtml(id);
|
|
140
|
-
expect(html, `${id}: missing #transcript-toast`).toContain(
|
|
141
|
-
'id="transcript-toast"',
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("Content-Security-Policy header allows the agent origin and esm.sh", async () => {
|
|
147
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
148
|
-
const result = await xrViewHostRoute.routeHandler(makeCtx(id) as never);
|
|
149
|
-
const csp = result.headers?.["Content-Security-Policy"] ?? "";
|
|
150
|
-
expect(csp, `${id}: CSP must include esm.sh`).toContain("esm.sh");
|
|
151
|
-
expect(csp, `${id}: CSP must include localhost agent origin`).toContain(
|
|
152
|
-
"localhost:31337",
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("view-host pages are distinct (each embeds its own VIEW_ID)", async () => {
|
|
158
|
-
const htmlMap = new Map<string, string>();
|
|
159
|
-
for (const id of VIEW_HOST_SMOKE_IDS) {
|
|
160
|
-
htmlMap.set(id, await fetchHtml(id));
|
|
161
|
-
}
|
|
162
|
-
// Every page should differ because VIEW_ID is embedded
|
|
163
|
-
for (const [id, html] of htmlMap) {
|
|
164
|
-
for (const [otherId, otherHtml] of htmlMap) {
|
|
165
|
-
if (id === otherId) continue;
|
|
166
|
-
// The two pages cannot be identical
|
|
167
|
-
expect(
|
|
168
|
-
html,
|
|
169
|
-
`${id} and ${otherId} pages are unexpectedly identical`,
|
|
170
|
-
).not.toBe(otherHtml);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Action,
|
|
3
|
-
ActionResult,
|
|
4
|
-
HandlerCallback,
|
|
5
|
-
IAgentRuntime,
|
|
6
|
-
Memory,
|
|
7
|
-
State,
|
|
8
|
-
} from "@elizaos/core";
|
|
9
|
-
import {
|
|
10
|
-
XR_SERVICE_TYPE,
|
|
11
|
-
type XRSessionService,
|
|
12
|
-
} from "../services/xr-session-service.ts";
|
|
13
|
-
|
|
14
|
-
export const xrQueryVisionAction: Action = {
|
|
15
|
-
name: "XR_QUERY_VISION",
|
|
16
|
-
description:
|
|
17
|
-
"Describe what the user is currently looking at through their XR headset camera. Use this when the user asks 'what do you see', 'look at this', or any question about their surroundings.",
|
|
18
|
-
|
|
19
|
-
validate: async (runtime: IAgentRuntime): Promise<boolean> => {
|
|
20
|
-
const svc = runtime.getService<XRSessionService>(XR_SERVICE_TYPE);
|
|
21
|
-
if (!svc?.hasActiveConnections()) return false;
|
|
22
|
-
// At least one connection must have a recent frame
|
|
23
|
-
return svc
|
|
24
|
-
.getConnections()
|
|
25
|
-
.some((c) => svc.getVisionPipeline().hasRecentFrame(c.id));
|
|
26
|
-
},
|
|
27
|
-
|
|
28
|
-
handler: async (
|
|
29
|
-
runtime: IAgentRuntime,
|
|
30
|
-
message: Memory,
|
|
31
|
-
_state: State | undefined,
|
|
32
|
-
_options: unknown,
|
|
33
|
-
callback?: HandlerCallback,
|
|
34
|
-
): Promise<ActionResult | undefined> => {
|
|
35
|
-
const svc = runtime.getService<XRSessionService>(XR_SERVICE_TYPE);
|
|
36
|
-
if (!svc) {
|
|
37
|
-
await callback?.({ text: "XR service is not running." });
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const conn = svc.getConnections().find((c) => c.roomId === message.roomId);
|
|
42
|
-
|
|
43
|
-
if (!conn) {
|
|
44
|
-
await callback?.({
|
|
45
|
-
text: "No XR device connection found for this session.",
|
|
46
|
-
});
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const description = await svc
|
|
51
|
-
.getVisionPipeline()
|
|
52
|
-
.describeFrame(runtime, conn.id);
|
|
53
|
-
|
|
54
|
-
if (!description) {
|
|
55
|
-
await callback?.({
|
|
56
|
-
text: "No recent camera frame available from the XR device.",
|
|
57
|
-
});
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
await callback?.({ text: description });
|
|
62
|
-
return undefined;
|
|
63
|
-
},
|
|
64
|
-
};
|