create-spatius-app 0.1.0 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-spatius-app",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Create a Spatius app with a web frontend, Cloudflare Worker, and LiveKit agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "engines": {
35
35
  "node": ">=22"
36
36
  },
37
- "packageManager": "pnpm@11.1.2",
37
+ "packageManager": "pnpm@11.25.0",
38
38
  "publishConfig": {
39
39
  "access": "public",
40
40
  "provenance": true
@@ -34,4 +34,6 @@ a matching voice. This configures local development only, not deployment.
34
34
  __SPATIUS_DEV_COMMAND__
35
35
  ```
36
36
 
37
- This starts the frontend, Cloudflare Worker, and Python agent together.
37
+ This starts the Python agent first, then starts the frontend and Cloudflare
38
+ Worker once the agent has registered with LiveKit. If registration takes more
39
+ than 60 seconds, startup stops with an error.
@@ -5,7 +5,7 @@ description = "A LiveKit voice agent with a Spatius avatar"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11,<3.15"
7
7
  dependencies = [
8
- "livekit-agents[spatius]~=1.7.1",
8
+ "livekit-agents[spatius]~=1.8.0",
9
9
  "livekit-plugins-noise-cancellation~=0.2.6",
10
10
  "python-dotenv>=1.2.1,<2",
11
11
  ]
@@ -1,5 +1,7 @@
1
1
  import logging
2
+ import os
2
3
  import textwrap
4
+ from pathlib import Path
3
5
 
4
6
  from dotenv import load_dotenv
5
7
  from livekit import rtc
@@ -43,6 +45,14 @@ class Assistant(Agent):
43
45
  server = AgentServer()
44
46
 
45
47
 
48
+ @server.on("worker_registered")
49
+ def report_dev_ready(_worker_id: str, _server_info: object) -> None:
50
+ # Only the root dev supervisor supplies this per-run path. Registration,
51
+ # not process launch or the local health endpoint, releases web startup.
52
+ if ready_file := os.environ.get("SPATIUS_DEV_READY_FILE"):
53
+ Path(ready_file).touch()
54
+
55
+
46
56
  @server.rtc_session(agent_name="spatius-agent")
47
57
  async def spatius_agent(ctx: JobContext) -> None:
48
58
  ctx.log_context_fields = {"room": ctx.room.name}
@@ -207,5 +207,26 @@ class AgentStartupTests(unittest.IsolatedAsyncioTestCase):
207
207
  )
208
208
 
209
209
 
210
+ class DevReadinessTests(unittest.TestCase):
211
+ def test_registration_writes_only_the_supervisors_ready_file(self) -> None:
212
+ with (
213
+ patch.dict(
214
+ "os.environ", {"SPATIUS_DEV_READY_FILE": "/tmp/test-agent-ready"}
215
+ ),
216
+ patch.object(agent_module, "Path") as path,
217
+ ):
218
+ agent_module.server.emit("worker_registered", "worker-test", object())
219
+ path.assert_called_once_with("/tmp/test-agent-ready")
220
+ path.return_value.touch.assert_called_once_with()
221
+
222
+ def test_standalone_agent_does_not_write_a_ready_file(self) -> None:
223
+ with (
224
+ patch.dict("os.environ", {}, clear=True),
225
+ patch.object(agent_module, "Path") as path,
226
+ ):
227
+ agent_module.server.emit("worker_registered", "worker-test", object())
228
+ path.assert_not_called()
229
+
230
+
210
231
  if __name__ == "__main__":
211
232
  unittest.main()
@@ -777,7 +777,7 @@ wheels = [
777
777
 
778
778
  [[package]]
779
779
  name = "livekit"
780
- version = "1.1.15"
780
+ version = "1.1.17"
781
781
  source = { registry = "https://pypi.org/simple" }
782
782
  dependencies = [
783
783
  { name = "aiofiles" },
@@ -786,18 +786,18 @@ dependencies = [
786
786
  { name = "protobuf" },
787
787
  { name = "types-protobuf" },
788
788
  ]
789
- sdist = { url = "https://files.pythonhosted.org/packages/ae/0d/383cb1ff9dd0f5de8e8046f0a83aa37e7d137f47552973395aa17871163e/livekit-1.1.15.tar.gz", hash = "sha256:8fe6d06127cecbe8d56c83b0c09c4604ea33cbab826508785bfd8dea5d5e1a4a", size = 378304, upload-time = "2026-08-24T17:19:00.999Z" }
789
+ sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/e20f97969cf21959061cdc6e1c89e3ad88e23693030398df283c236430fc/livekit-1.1.17.tar.gz", hash = "sha256:0553bfc9041dc8430029597a2278b43e4bb75594a72cb9dcc42bf3ee8e1689a0", size = 378467, upload-time = "2026-09-01T05:03:19.976Z" }
790
790
  wheels = [
791
- { url = "https://files.pythonhosted.org/packages/a6/8c/5cd2de868eea3065821a8ecb615b2676f7076db74cea437dd9ee83d98c22/livekit-1.1.15-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:f7e8485bb91d6f74c893ffc8545301e049d5c056f863e1ce72b857ed040db218", size = 10199958, upload-time = "2026-08-24T17:18:50.276Z" },
792
- { url = "https://files.pythonhosted.org/packages/48/e0/2027d03398c0b1d57345a040356109b3e49491e679605ae4f42f3ebc1bb9/livekit-1.1.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:285f3ef28e8ce78d8a9ae909571a67511e55ccaa81e7769822e024036d307f56", size = 9023382, upload-time = "2026-08-24T17:18:52.828Z" },
793
- { url = "https://files.pythonhosted.org/packages/a9/a5/72b4bedfc09df365d5e016979815b4791af1ca08781dd99e087626f3d550/livekit-1.1.15-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:4cb3894030cce1940ef5a6407d6e9d3049eb7150e204bf327fbc37eb903af4d5", size = 10046135, upload-time = "2026-08-24T17:18:54.602Z" },
794
- { url = "https://files.pythonhosted.org/packages/72/a4/563813a9282c827363e9add8f24785e65514f04c9d8e04d7ac3ad7a3b5b0/livekit-1.1.15-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:d0c72f36906f9d46b4c69d28cb39ecd847022e990df77037cbdd4ebf4d8db50c", size = 11449550, upload-time = "2026-08-24T17:18:56.744Z" },
795
- { url = "https://files.pythonhosted.org/packages/7c/14/88d9d90144b426e75d9ba6111d623063fae4ac714d36bd61349e8684bd3f/livekit-1.1.15-py3-none-win_amd64.whl", hash = "sha256:4b6027b986ec0abdc57fed0796b6ebb4d03011ada446a77efe158d9c2ed0bb5f", size = 10770308, upload-time = "2026-08-24T17:18:58.875Z" },
791
+ { url = "https://files.pythonhosted.org/packages/fd/74/d93c5dd19ff2139c070eacb634e075cd2aff239a9cf692002afaa529a630/livekit-1.1.17-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:39188ac6c805643c8ef952cccb96b2e81e430600e626dea9c6aa1faca7013fdb", size = 10468167, upload-time = "2026-09-01T05:03:06.001Z" },
792
+ { url = "https://files.pythonhosted.org/packages/d6/3a/9f4f5e3f0c325d0d5d19c5dc1ae345f4bf1353369db3b42e265a1ee4dbc5/livekit-1.1.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:078d249974aed5e845b60f840802e36dc9d2c9c1dd606341fb95ee018749169e", size = 9289118, upload-time = "2026-09-01T05:03:09.011Z" },
793
+ { url = "https://files.pythonhosted.org/packages/1a/d5/89293b55b7a719c7f7b7929d995c009bee7ce9f4d29876c77ee878fd9bf9/livekit-1.1.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:984c7c0efa3e1f917e973ce412a141ff8244baf98ff24587aa82c206b621054f", size = 10580945, upload-time = "2026-09-01T05:03:11.743Z" },
794
+ { url = "https://files.pythonhosted.org/packages/c9/4e/aad025688f8aceeecb4ec97aaae3541d64302e03454c84dc57e02bbaf5b9/livekit-1.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:100f2ddd8e433ea906ecf45f1d37063e43e72c7c2cff0b65ddddc11181bc8dd7", size = 11805780, upload-time = "2026-09-01T05:03:14.614Z" },
795
+ { url = "https://files.pythonhosted.org/packages/97/7a/5447238155494f2a2fd55ff0f7705b9769fe67e787b52150a5d269eeaa61/livekit-1.1.17-py3-none-win_amd64.whl", hash = "sha256:7a7b1bdb646775d8042ec97baec6c6b615c5828d6f9b6dd2adaa167841d6f6c2", size = 10968316, upload-time = "2026-09-01T05:03:17.671Z" },
796
796
  ]
797
797
 
798
798
  [[package]]
799
799
  name = "livekit-agents"
800
- version = "1.7.1"
800
+ version = "1.8.0"
801
801
  source = { registry = "https://pypi.org/simple" }
802
802
  dependencies = [
803
803
  { name = "aiofiles" },
@@ -833,9 +833,9 @@ dependencies = [
833
833
  { name = "typing-extensions" },
834
834
  { name = "watchfiles" },
835
835
  ]
836
- sdist = { url = "https://files.pythonhosted.org/packages/6d/ed/1d6909828103892de4ad94261a682d11f739a91ce0dd656432fab626d20e/livekit_agents-1.7.1.tar.gz", hash = "sha256:89bb7dc3d2b8b28671c846a80cec9265b699ddee805d790ef2d00a5e3129b8dd", size = 2662477, upload-time = "2026-08-27T03:33:39.986Z" }
836
+ sdist = { url = "https://files.pythonhosted.org/packages/99/88/ace06ebd95b8cc45e25430a2a10ec7ccfb6b386b67907192ad421493858f/livekit_agents-1.8.0.tar.gz", hash = "sha256:132029af152a585770b58623a1a2ac7c8b1b9241b045e8cd220b30415fbcf2ad", size = 2688509, upload-time = "2026-09-05T00:10:03.294Z" }
837
837
  wheels = [
838
- { url = "https://files.pythonhosted.org/packages/20/33/ec05c835192728f55aa571675912d81c01b423f8f24a98273f148c904e63/livekit_agents-1.7.1-py3-none-any.whl", hash = "sha256:f64ce088f22f115faa3b92448d335569b101d9d96fe6481781fd82f06c413b2e", size = 2777164, upload-time = "2026-08-27T03:33:37.048Z" },
838
+ { url = "https://files.pythonhosted.org/packages/f4/79/a1dce13ad49ab5b3248727b808ef265b2a535928c53161440d049a0b747d/livekit_agents-1.8.0-py3-none-any.whl", hash = "sha256:0da6c79c71eb7620af14d5c18adba1da796d6743338630813c341e9ce72f45c8", size = 2806075, upload-time = "2026-09-05T00:10:01.23Z" },
839
839
  ]
840
840
 
841
841
  [package.optional-dependencies]
@@ -935,15 +935,15 @@ wheels = [
935
935
 
936
936
  [[package]]
937
937
  name = "livekit-plugins-spatius"
938
- version = "1.7.1"
938
+ version = "1.8.0"
939
939
  source = { registry = "https://pypi.org/simple" }
940
940
  dependencies = [
941
941
  { name = "livekit-agents" },
942
942
  { name = "spatius", extra = ["opus"] },
943
943
  ]
944
- sdist = { url = "https://files.pythonhosted.org/packages/35/9c/99d24076ffc28065a582ae87953d220967c7a816da69257152b93c2fb05b/livekit_plugins_spatius-1.7.1.tar.gz", hash = "sha256:c6239cf47ff87729ae22e1e3b2db8a1d32b16d26b711d530dc09cd0fc525bc25", size = 8601, upload-time = "2026-08-27T03:35:40.562Z" }
944
+ sdist = { url = "https://files.pythonhosted.org/packages/c0/40/ce00a90cdfd3fd0840ef452e8706e88ae5da9ab2c2cc900a615bfae7abcf/livekit_plugins_spatius-1.8.0.tar.gz", hash = "sha256:76d80c79db4673da4c1ce84f51fbec986a0bd7b888226d8fced68992c7d798e8", size = 8601, upload-time = "2026-09-05T00:12:14.914Z" }
945
945
  wheels = [
946
- { url = "https://files.pythonhosted.org/packages/17/94/5951e01cbd34d73c22e75c8c52471c01be996957ba434288afc851b1421e/livekit_plugins_spatius-1.7.1-py3-none-any.whl", hash = "sha256:59a0e4ca9f80fb81683e3d8fa4e05a212276ad79d478a0a0cb73c57f6655235e", size = 8712, upload-time = "2026-08-27T03:35:39.411Z" },
946
+ { url = "https://files.pythonhosted.org/packages/4d/34/bd44feacb043d16e18396bb7bb9ccb8f100348efce5090d853f8a6bfff1d/livekit_plugins_spatius-1.8.0-py3-none-any.whl", hash = "sha256:c852c7c1ec26b094cc207fa127de1f65e305c6def43d626ee65a250339a1bb69", size = 8712, upload-time = "2026-09-05T00:12:13.838Z" },
947
947
  ]
948
948
 
949
949
  [[package]]
@@ -1891,7 +1891,7 @@ dev = [
1891
1891
 
1892
1892
  [package.metadata]
1893
1893
  requires-dist = [
1894
- { name = "livekit-agents", extras = ["spatius"], specifier = "~=1.7.1" },
1894
+ { name = "livekit-agents", extras = ["spatius"], specifier = "~=1.8.0" },
1895
1895
  { name = "livekit-plugins-noise-cancellation", specifier = "~=0.2.6" },
1896
1896
  { name = "python-dotenv", specifier = ">=1.2.1,<2" },
1897
1897
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<1" },
@@ -1,4 +1 @@
1
- auto-install-peers=false
2
- prefer-frozen-lockfile=true
3
1
  save-exact=true
4
- strict-peer-dependencies=true
@@ -10,7 +10,7 @@
10
10
  "cf-typegen": "wrangler types --env-interface CloudflareBindings",
11
11
  "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm test:dev && pnpm build",
12
12
  "deploy": "pnpm build && wrangler deploy",
13
- "dev": "concurrently --kill-others --names web,agent \"pnpm run dev:web\" \"pnpm run agent:dev\"",
13
+ "dev": "node scripts/dev.mjs \"pnpm run dev:web\" \"pnpm run agent:dev\"",
14
14
  "dev:web": "node scripts/dev-web.mjs",
15
15
  "format": "prettier --write .",
16
16
  "format:check": "prettier --check .",
@@ -25,7 +25,7 @@
25
25
  "engines": {
26
26
  "node": ">=22.12"
27
27
  },
28
- "packageManager": "pnpm@11.1.2",
28
+ "packageManager": "pnpm@11.25.0",
29
29
  "dependencies": {
30
30
  "@livekit/components-react": "2.9.24",
31
31
  "@spatius/avatarkit": "1.3.8",
@@ -1,7 +1,19 @@
1
1
  import { join } from 'node:path';
2
2
  import { projectDirectory } from './agent-runtime.mjs';
3
+ import { waitForAgent } from './wait-for-agent.mjs';
3
4
  import { runProcess } from './managed-process.mjs';
4
5
 
6
+ try {
7
+ if (process.env.SPATIUS_DEV_READY_FILE) {
8
+ console.log('Waiting for the LiveKit agent to register…');
9
+ await waitForAgent(process.env.SPATIUS_DEV_READY_FILE);
10
+ console.log('LiveKit agent registered. Starting frontend and Worker…');
11
+ }
12
+ } catch (error) {
13
+ console.error(error.message);
14
+ process.exit(1);
15
+ }
16
+
5
17
  const result = await runProcess(
6
18
  process.execPath,
7
19
  [
@@ -0,0 +1,29 @@
1
+ import { mkdtemp, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { projectDirectory } from './agent-runtime.mjs';
5
+ import { runProcess } from './managed-process.mjs';
6
+
7
+ // A fresh directory prevents a previous run or another project from releasing
8
+ // this run's startup gate. Keep it outside the agent's auto-reload watch tree.
9
+ const directory = await mkdtemp(join(tmpdir(), 'spatius-dev-'));
10
+ try {
11
+ const result = await runProcess(
12
+ 'concurrently',
13
+ ['--kill-others', '--names', 'web,agent', ...process.argv.slice(2)],
14
+ {
15
+ cwd: projectDirectory,
16
+ env: {
17
+ ...process.env,
18
+ SPATIUS_DEV_READY_FILE: join(directory, 'agent-ready'),
19
+ },
20
+ },
21
+ );
22
+ if (result.failed)
23
+ console.error(
24
+ 'Could not start development services. Install JavaScript dependencies and try again.',
25
+ );
26
+ process.exitCode = result.code;
27
+ } finally {
28
+ await rm(directory, { recursive: true, force: true });
29
+ }
@@ -0,0 +1,20 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { performance } from 'node:perf_hooks';
3
+ import { setTimeout as delay } from 'node:timers/promises';
4
+
5
+ export async function waitForAgent(path, timeout = 60_000) {
6
+ const deadline = performance.now() + timeout;
7
+ while (true) {
8
+ try {
9
+ await access(path);
10
+ return;
11
+ } catch (error) {
12
+ if (error.code !== 'ENOENT') throw error;
13
+ }
14
+ if (performance.now() >= deadline)
15
+ throw new Error(
16
+ 'LiveKit agent did not register within 60 seconds. Check the agent logs, credentials, and network connection, then run dev again.',
17
+ );
18
+ await delay(Math.min(100, Math.max(0, deadline - performance.now())));
19
+ }
20
+ }
@@ -25,7 +25,7 @@ export const credentials = {
25
25
  };
26
26
 
27
27
  const fakeRuntime = `
28
- import { appendFileSync, existsSync } from 'node:fs';
28
+ import { appendFileSync, existsSync, writeFileSync } from 'node:fs';
29
29
  import { spawn } from 'node:child_process';
30
30
  const role = process.argv[2];
31
31
  const args = process.argv.slice(3);
@@ -57,6 +57,17 @@ if (role === 'lk' && args[0] === '--version') {
57
57
  } else if (role === 'grandchild') {
58
58
  hold(); ready();
59
59
  } else {
60
+ if (role !== 'web' && process.env.SPATIUS_DEV_READY_FILE && process.env.DEV_TEST_RUNTIME_HOLD === '1') {
61
+ const register = () => {
62
+ event('registered');
63
+ writeFileSync(process.env.SPATIUS_DEV_READY_FILE, '');
64
+ };
65
+ if (process.env.DEV_TEST_REGISTER) {
66
+ const poll = setInterval(() => {
67
+ if (existsSync(process.env.DEV_TEST_REGISTER)) { clearInterval(poll); register(); }
68
+ }, 20);
69
+ } else register();
70
+ }
60
71
  if (process.env.DEV_TEST_GRANDCHILD === '1') {
61
72
  spawnGrandchild();
62
73
  }
@@ -37,7 +37,7 @@ async function supervise(f, extraEnv) {
37
37
  const sourcePackage = JSON.parse(
38
38
  await readFile(join(templateDirectory, 'package.json'), 'utf8'),
39
39
  );
40
- // Exercise the production script composition with npm, available alongside
40
+ // Exercise the production startup gate and supervisor with npm, available alongside
41
41
  // Node in both generated projects and root CI. Only PM selection changes.
42
42
  const scripts = Object.fromEntries(
43
43
  ['dev', 'dev:web', 'agent:dev'].map((name) => [
@@ -102,7 +102,7 @@ for (const [name, side, code] of [
102
102
  ['agent fails', 'agent', '29'],
103
103
  ]) {
104
104
  test(
105
- `real concurrently: ${name} stops its sibling and all grandchildren`,
105
+ `root dev supervisor: ${name} stops its sibling and all grandchildren`,
106
106
  { timeout: 15000 },
107
107
  async (t) => {
108
108
  const f = await fixture(t);
@@ -128,12 +128,14 @@ for (const [name, side, code] of [
128
128
  assert.match(result.output, /SIGTERM/);
129
129
  assertNoCredentials(result.output);
130
130
  await assertStopped(events.map(({ pid }) => pid));
131
+ // A bounded CLI probe may select Python fallback on a busy machine.
132
+ // Supervision must launch exactly one runtime through either supported path.
131
133
  assert.equal(
132
134
  (await readEvents(f.events)).filter(
133
135
  ({ role, args, type }) =>
134
- role === 'lk' &&
136
+ ['lk', 'uv', 'python'].includes(role) &&
135
137
  type === 'invoked' &&
136
- args.at(-1) === 'src/agent.py',
138
+ args.includes('src/agent.py'),
137
139
  ).length,
138
140
  1,
139
141
  );
@@ -142,7 +144,7 @@ for (const [name, side, code] of [
142
144
  }
143
145
 
144
146
  test(
145
- 'real concurrently: Ctrl+C stops both services and their grandchildren',
147
+ 'root dev supervisor: Ctrl+C stops both services and their grandchildren',
146
148
  {
147
149
  timeout: 15000,
148
150
  skip:
@@ -201,3 +203,104 @@ test(
201
203
  assertNoCredentials(result.output);
202
204
  },
203
205
  );
206
+
207
+ for (const fallback of [false, true]) {
208
+ test(
209
+ `web waits for registration (${fallback ? 'Python fallback' : 'lk'})`,
210
+ { timeout: 15000 },
211
+ async (t) => {
212
+ const f = await fixture(t, { uv: true });
213
+ const registration = join(f.root, 'register');
214
+ const run = await supervise(f, {
215
+ DEV_TEST_RUNTIME_HOLD: '1',
216
+ DEV_TEST_REGISTER: registration,
217
+ DEV_TEST_WEB_EXIT: '0',
218
+ ...(fallback ? { DEV_TEST_VERSION: 'lk version 2.17.0' } : {}),
219
+ });
220
+ const before = await waitForEvents(f.events, (events) =>
221
+ events.some(
222
+ ({ role, type }) =>
223
+ role === (fallback ? 'uv' : 'lk') && type === 'ready',
224
+ ),
225
+ );
226
+ await delay(200);
227
+ assert.ok(
228
+ !(await readEvents(f.events)).some(({ role }) => role === 'web'),
229
+ );
230
+ const readyPath = before.find(({ env }) => env.SPATIUS_DEV_READY_FILE)
231
+ ?.env.SPATIUS_DEV_READY_FILE;
232
+ assert.ok(readyPath);
233
+ await writeFile(registration, 'register');
234
+ const after = await waitForEvents(f.events, (events) =>
235
+ events.some(({ role, type }) => role === 'web' && type === 'ready'),
236
+ );
237
+ assert.ok(
238
+ after.findIndex(({ type }) => type === 'registered') <
239
+ after.findIndex(({ role }) => role === 'web'),
240
+ );
241
+ await writeFile(f.env.DEV_TEST_RELEASE, 'exit');
242
+ const result = await run.done;
243
+ assert.match(result.output, /LiveKit agent registered/);
244
+ assertNoCredentials(result.output);
245
+ await assert.rejects(readFile(readyPath), { code: 'ENOENT' });
246
+ await assertStopped(after.map(({ pid }) => pid));
247
+ },
248
+ );
249
+ }
250
+
251
+ test(
252
+ 'agent failure before registration never starts web',
253
+ { timeout: 15000 },
254
+ async (t) => {
255
+ const f = await fixture(t);
256
+ const run = await supervise(f, { DEV_TEST_RUNTIME_EXIT: '29' });
257
+ const result = await run.done;
258
+ assert.notEqual(result.code, 0);
259
+ assert.ok(!(await readEvents(f.events)).some(({ role }) => role === 'web'));
260
+ assertNoCredentials(result.output);
261
+ },
262
+ );
263
+
264
+ test(
265
+ 'registration timeout stops the agent without starting web',
266
+ { timeout: 15000 },
267
+ async (t) => {
268
+ const f = await fixture(t);
269
+ // Accelerate only the copied fixture's deadline; exercise the real web gate.
270
+ const path = join(f.root, 'scripts', 'wait-for-agent.mjs');
271
+ await writeFile(
272
+ path,
273
+ (await readFile(path, 'utf8')).replace('60_000', '1500'),
274
+ );
275
+ const run = await supervise(f, {
276
+ DEV_TEST_RUNTIME_HOLD: '1',
277
+ DEV_TEST_REGISTER: join(f.root, 'never-register'),
278
+ });
279
+ const result = await run.done;
280
+ assert.notEqual(result.code, 0);
281
+ assert.match(result.output, /LiveKit agent did not register/);
282
+ const events = await readEvents(f.events);
283
+ assert.ok(!events.some(({ role }) => role === 'web'));
284
+ await assertStopped(events.map(({ pid }) => pid));
285
+ assertNoCredentials(result.output);
286
+ },
287
+ );
288
+
289
+ test(
290
+ 'Ctrl+C while waiting for registration stops the agent without starting web',
291
+ { timeout: 15000, skip: windows },
292
+ async (t) => {
293
+ const f = await fixture(t);
294
+ const run = await supervise(f, {
295
+ DEV_TEST_RUNTIME_HOLD: '1',
296
+ DEV_TEST_REGISTER: join(f.root, 'never-register'),
297
+ });
298
+ const events = await waitForEvents(f.events, (events) =>
299
+ events.some(({ role, type }) => role === 'lk' && type === 'ready'),
300
+ );
301
+ process.kill(-run.child.pid, 'SIGINT');
302
+ await run.done;
303
+ assert.ok(!(await readEvents(f.events)).some(({ role }) => role === 'web'));
304
+ await assertStopped(events.map(({ pid }) => pid));
305
+ },
306
+ );