@agenthippo-ai/agenthippo 0.1.1 → 1.110.9

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.
Files changed (158) hide show
  1. package/out/agentpack/index.js +2 -1
  2. package/out/agentpack/paths.js +23 -0
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +293 -82
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +23 -25
  8. package/out/analytics/sessionEvents.js +109 -22
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +49 -15
  12. package/out/auth/onboardingPrompt.js +112 -0
  13. package/out/auth/provider.js +9 -22
  14. package/out/auth/sessionStore.js +3 -40
  15. package/out/auth/signInFlow.js +2 -1
  16. package/out/auth-broker/config.js +54 -4
  17. package/out/auth-broker/server.js +152 -20
  18. package/out/auth-broker/store.js +25 -24
  19. package/out/authority/warrant.js +206 -0
  20. package/out/branding.js +27 -1
  21. package/out/cli/commands/ask.js +3 -0
  22. package/out/cli/commands/chat.js +3 -0
  23. package/out/cli/commands/conformance.js +249 -0
  24. package/out/cli/commands/data-plane.js +67 -0
  25. package/out/cli/commands/deploy.js +50 -10
  26. package/out/cli/commands/gateway.js +58 -0
  27. package/out/cli/commands/idp.js +85 -0
  28. package/out/cli/commands/pack.js +182 -3
  29. package/out/cli/commands/sandbox.js +332 -0
  30. package/out/cli/commands/serve.js +1165 -82
  31. package/out/cli/commands/store.js +12 -4
  32. package/out/cli/credential-context.js +42 -80
  33. package/out/cli/data-plane/broker.js +78 -0
  34. package/out/cli/data-plane/connector-host.js +121 -0
  35. package/out/cli/data-plane/egress.js +189 -0
  36. package/out/cli/data-plane/gateway.js +218 -0
  37. package/out/cli/data-plane/index.js +152 -0
  38. package/out/cli/data-plane/registry.js +89 -0
  39. package/out/cli/data-plane/remote.js +106 -0
  40. package/out/cli/data-plane/sidecar.js +161 -0
  41. package/out/cli/deploy/blueprints/registry.js +84 -0
  42. package/out/cli/deploy/layers/identity.js +172 -0
  43. package/out/cli/deploy-init.js +1182 -347
  44. package/out/cli/deps.js +106 -2
  45. package/out/cli/idp-init.js +338 -0
  46. package/out/cli/index.js +192 -23
  47. package/out/cli/pack-install-ref.js +15 -0
  48. package/out/cli/pack-loader.js +45 -3
  49. package/out/cli/pack-runtime.js +61 -0
  50. package/out/cli/runtime.js +44 -3
  51. package/out/cli/serve-hardening.js +139 -3
  52. package/out/cli/state-sync.js +16 -15
  53. package/out/cli/utils.js +12 -1
  54. package/out/engines/base.js +32 -3
  55. package/out/engines/claude.js +47 -8
  56. package/out/engines/codex.js +125 -22
  57. package/out/engines/custom.js +54 -12
  58. package/out/evidence/sessionShip.js +610 -0
  59. package/out/extension.js +13 -2
  60. package/out/gateway/bridge.js +40 -2
  61. package/out/gateway/client.js +34 -1
  62. package/out/gateway/installSource.js +68 -0
  63. package/out/gateway/lifecycle.js +185 -51
  64. package/out/gateway/outboundPaths.js +4 -1
  65. package/out/gateway/outboundSend.js +6 -6
  66. package/out/gateway/secretsPreflight.js +90 -0
  67. package/out/gateway/service.js +7 -2
  68. package/out/headless/index.js +40 -9
  69. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  70. package/out/live-probe/connector.mjs +8 -0
  71. package/out/live-probe/env-report.mjs +8 -0
  72. package/out/mcp.js +115 -67
  73. package/out/pack/caps.js +271 -0
  74. package/out/pack/governor.js +309 -0
  75. package/out/pack/lease-schema.js +270 -0
  76. package/out/pack/lease.js +640 -0
  77. package/out/pack/reconcile.js +217 -0
  78. package/out/pack/signature.js +839 -0
  79. package/out/remote/handleRemoteChat.js +44 -3
  80. package/out/remote/remoteTrust.js +277 -0
  81. package/out/remote/serveClient.js +52 -5
  82. package/out/runner/host.js +131 -0
  83. package/out/runner/protocol.js +108 -0
  84. package/out/runner/remoteEngine.js +130 -0
  85. package/out/runtime/interfaces/edit-tracker.js +2 -0
  86. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  87. package/out/sandbox/containerDriver.js +570 -0
  88. package/out/sandbox/processDriver.js +315 -0
  89. package/out/sandbox/sandboxRunner.js +191 -0
  90. package/out/sandbox/support.js +301 -0
  91. package/out/sandbox/types.js +55 -0
  92. package/out/scripts/agentide-litellm.sh +67 -5
  93. package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
  94. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
  95. package/out/scripts/analytics/setup-analytics.ps1 +37 -13
  96. package/out/scripts/analytics/setup-analytics.sh +93 -13
  97. package/out/scripts/analytics/start-analytics.sh +86 -14
  98. package/out/scripts/gateway/setup-gateway.sh +72 -32
  99. package/out/shared/models.js +12 -111
  100. package/out/shared/providers.js +2 -2
  101. package/out/spotlight/install.js +7 -1
  102. package/out/store/index.js +41 -10
  103. package/out/sync.js +3 -1
  104. package/out/test/analytics.test.js +198 -34
  105. package/out/test/auth-baseurl.test.js +132 -0
  106. package/out/test/auth-bootstrapApply.test.js +27 -6
  107. package/out/test/auth-broker-hardening.test.js +227 -0
  108. package/out/test/auth-broker.test.js +112 -9
  109. package/out/test/blueprint-registry.test.js +105 -0
  110. package/out/test/branding.test.js +13 -0
  111. package/out/test/caps-governor.test.js +455 -0
  112. package/out/test/codex-new-file-survival.test.js +119 -0
  113. package/out/test/configuration-scopes.test.js +131 -0
  114. package/out/test/credential-context.test.js +23 -28
  115. package/out/test/custom-env-layering.test.js +151 -0
  116. package/out/test/data-plane-engine-wiring.test.js +250 -0
  117. package/out/test/data-plane.test.js +311 -0
  118. package/out/test/deploy-init.test.js +295 -4
  119. package/out/test/egress-broker.test.js +172 -0
  120. package/out/test/fleetDisplayUtils.test.js +4 -0
  121. package/out/test/gateway-client.test.js +28 -0
  122. package/out/test/gateway-install-source.test.js +111 -0
  123. package/out/test/gateway-lifecycle.test.js +1 -1
  124. package/out/test/gateway-outbound.test.js +7 -0
  125. package/out/test/gateway-secrets-preflight.test.js +127 -0
  126. package/out/test/gateway-session-routing.test.js +18 -0
  127. package/out/test/governor-hardening.test.js +217 -0
  128. package/out/test/headless-sync.test.js +22 -0
  129. package/out/test/idp-init.test.js +151 -0
  130. package/out/test/lease-signature.test.js +357 -0
  131. package/out/test/lease-trust-root.test.js +273 -0
  132. package/out/test/lease-validation.test.js +169 -0
  133. package/out/test/lease.test.js +114 -0
  134. package/out/test/mcp-obo.test.js +52 -27
  135. package/out/test/mcp.test.js +69 -3
  136. package/out/test/model-routing.test.js +133 -0
  137. package/out/test/pack-admission.test.js +216 -0
  138. package/out/test/pack-digest-parity.test.js +140 -0
  139. package/out/test/pack-reconcile.test.js +183 -0
  140. package/out/test/pack-signature-artifact.test.js +103 -0
  141. package/out/test/pack-signature.test.js +176 -0
  142. package/out/test/pack-trust-root.test.js +202 -0
  143. package/out/test/remote-trust.test.js +242 -0
  144. package/out/test/runner-protocol.test.js +249 -0
  145. package/out/test/sandbox-driver.test.js +553 -0
  146. package/out/test/sandbox-hardening.test.js +395 -0
  147. package/out/test/sandbox-runner.test.js +343 -0
  148. package/out/test/serve-gateway-turn.test.js +267 -0
  149. package/out/test/serve-hardening.test.js +125 -6
  150. package/out/test/serveClient.test.js +38 -5
  151. package/out/test/sessionShip.test.js +560 -0
  152. package/out/test/spotlightPublish.test.js +28 -0
  153. package/out/test/state-sync-peer.test.js +18 -5
  154. package/out/test/store-install-safe.test.js +69 -0
  155. package/out/test/tls-bypass.test.js +90 -0
  156. package/out/test/warrant.test.js +138 -0
  157. package/out/utils.js +73 -20
  158. package/package.json +1 -1
@@ -63,6 +63,49 @@ suite('serve-hardening: resolveStateDir', () => {
63
63
  assert.ok(r.dir.startsWith(os.tmpdir()));
64
64
  });
65
65
  });
66
+ suite('serve-hardening: resolveAnalyticsSessionsDir', () => {
67
+ // getAgentideHome() reads process.env directly (not injectable), so tests that need it to
68
+ // resolve deterministically save/restore the real env vars around themselves.
69
+ const saved = {};
70
+ setup(() => {
71
+ saved.AGENT_HOME_DIR = process.env.AGENT_HOME_DIR;
72
+ saved.AGENTHIPPO_HOME = process.env.AGENTHIPPO_HOME;
73
+ delete process.env.AGENT_HOME_DIR;
74
+ delete process.env.AGENTHIPPO_HOME;
75
+ });
76
+ teardown(() => {
77
+ if (saved.AGENT_HOME_DIR === undefined) {
78
+ delete process.env.AGENT_HOME_DIR;
79
+ }
80
+ else {
81
+ process.env.AGENT_HOME_DIR = saved.AGENT_HOME_DIR;
82
+ }
83
+ if (saved.AGENTHIPPO_HOME === undefined) {
84
+ delete process.env.AGENTHIPPO_HOME;
85
+ }
86
+ else {
87
+ process.env.AGENTHIPPO_HOME = saved.AGENTHIPPO_HOME;
88
+ }
89
+ });
90
+ test('AGENTHIPPO_HOME set → uses getAgentideHome(), not stateDir (stays discoverable to Spotlight/DuckDB)', () => {
91
+ process.env.AGENTHIPPO_HOME = '/data/.agent-hippo';
92
+ const dir = (0, serve_hardening_1.resolveAnalyticsSessionsDir)('/data/.agent-hippo/serve-state', false, { AGENTHIPPO_HOME: '/data/.agent-hippo' });
93
+ assert.strictEqual(dir, path.join('/data/.agent-hippo', 'analytics', 'sessions'));
94
+ });
95
+ test('AGENT_HOME_DIR set → same precedence as AGENTHIPPO_HOME', () => {
96
+ process.env.AGENT_HOME_DIR = '/data/.agent-hippo';
97
+ const dir = (0, serve_hardening_1.resolveAnalyticsSessionsDir)('/data/.agent-hippo/serve-state', false, { AGENT_HOME_DIR: '/data/.agent-hippo' });
98
+ assert.strictEqual(dir, path.join('/data/.agent-hippo', 'analytics', 'sessions'));
99
+ });
100
+ test('no home-dir env var, explicit non-ephemeral --state-dir → uses stateDir (never regress durability for discoverability)', () => {
101
+ const dir = (0, serve_hardening_1.resolveAnalyticsSessionsDir)('/mnt/durable-vol', false, {});
102
+ assert.strictEqual(dir, path.join('/mnt/durable-vol', 'analytics', 'sessions'));
103
+ });
104
+ test('no home-dir env var, ephemeral state (nothing durable configured) → falls back to getAgentideHome() default', () => {
105
+ const dir = (0, serve_hardening_1.resolveAnalyticsSessionsDir)(path.join(os.tmpdir(), 'agenthippo-server'), true, {});
106
+ assert.strictEqual(dir, path.join(os.homedir(), '.agent-hippo', 'analytics', 'sessions'));
107
+ });
108
+ });
66
109
  suite('serve-hardening: resolveBindHost', () => {
67
110
  test('defaults to loopback', () => {
68
111
  assert.strictEqual((0, serve_hardening_1.resolveBindHost)(undefined, {}), serve_hardening_1.DEFAULT_BIND_HOST);
@@ -124,9 +167,40 @@ suite('serve-hardening: RateLimiter', () => {
124
167
  });
125
168
  });
126
169
  suite('serve-hardening: resolveUserId', () => {
127
- test('X-Agent-User header wins over body', () => {
128
- const id = (0, serve_hardening_1.resolveUserId)({ 'x-agent-user': 'alice' }, { user_id: 'bob' });
129
- assert.strictEqual(id, 'alice');
170
+ /**
171
+ * BREAKING, and deliberately so. `x-agent-user` used to be the FIRST trusted alias, so on any
172
+ * deployment that had not pinned AGENTHIPPO_IDENTITY_HEADER, any HTTP client could send
173
+ * `x-agent-user: victim@example.com` and be namespaced into that user's session state, attach
174
+ * to their conversation, and be issued a data-plane turn nonce scoped to them. No platform
175
+ * strips it — Cloud Run IAP strips `X-Goog-*` and forwards this one untouched.
176
+ */
177
+ test('x-agent-user is NOT trusted by default — it is not stripped by any platform', () => {
178
+ assert.strictEqual((0, serve_hardening_1.resolveUserId)({ 'x-agent-user': 'victim@example.com' }, undefined, {}), undefined);
179
+ });
180
+ test('x-agent-user IS trusted when the deployment pins it (the Caddy forward_auth flow)', () => {
181
+ const env = { AGENTHIPPO_IDENTITY_HEADER: 'x-agent-user' };
182
+ assert.strictEqual((0, serve_hardening_1.resolveUserId)({ 'x-agent-user': 'alice@corp.com' }, { user_id: 'bob' }, env), 'alice@corp.com');
183
+ });
184
+ test('an unpinned deployment is told which env var to set, and why', () => {
185
+ const advice = (0, serve_hardening_1.identityHeaderAdvice)({});
186
+ assert.ok(advice.length > 0);
187
+ assert.ok(advice.every((l) => /AGENTHIPPO_IDENTITY_HEADER/.test(l)));
188
+ assert.ok(advice.some((l) => /x-agent-user/.test(l)), 'the removal must be explained, not silent');
189
+ assert.deepStrictEqual((0, serve_hardening_1.identityHeaderAdvice)({ AGENTHIPPO_IDENTITY_HEADER: 'x-agent-user' }), [], 'a pinned deployment is quiet');
190
+ });
191
+ test('advice severity follows reachability, so the loud case stays loud', () => {
192
+ const loopback = (0, serve_hardening_1.identityHeaderAdvice)({}, '127.0.0.1');
193
+ assert.strictEqual(loopback.length, 1, 'a local run gets one short note, not a lecture');
194
+ assert.ok(loopback.every((l) => /AGENTHIPPO_IDENTITY_HEADER/.test(l)));
195
+ assert.deepStrictEqual((0, serve_hardening_1.identityHeaderAdvice)({}, 'localhost').length, 1);
196
+ assert.deepStrictEqual((0, serve_hardening_1.identityHeaderAdvice)({}, '::1').length, 1);
197
+ const exposed = (0, serve_hardening_1.identityHeaderAdvice)({}, '0.0.0.0');
198
+ assert.ok(exposed.length > loopback.length, 'an exposed bind must say strictly more');
199
+ assert.ok(exposed.some((l) => /reachable beyond this machine/.test(l)));
200
+ assert.ok(exposed.every((l) => /AGENTHIPPO_IDENTITY_HEADER/.test(l)));
201
+ // Pinning silences both, exposed or not.
202
+ const pinned = { AGENTHIPPO_IDENTITY_HEADER: 'x-goog-authenticated-user-email' };
203
+ assert.deepStrictEqual((0, serve_hardening_1.identityHeaderAdvice)(pinned, '0.0.0.0'), []);
130
204
  });
131
205
  test('Databricks X-Forwarded-Email', () => {
132
206
  assert.strictEqual((0, serve_hardening_1.resolveUserId)({ 'x-forwarded-email': 'priya@meridian.com' }, undefined), 'priya@meridian.com');
@@ -138,12 +212,20 @@ suite('serve-hardening: resolveUserId', () => {
138
212
  test('Cloudflare Access header', () => {
139
213
  assert.strictEqual((0, serve_hardening_1.resolveUserId)({ 'cf-access-authenticated-user-email': 'susan@meridian.com' }, undefined), 'susan@meridian.com');
140
214
  });
141
- test('falls back to body.user_id when no header', () => {
142
- assert.strictEqual((0, serve_hardening_1.resolveUserId)({}, { user_id: 'bob' }), 'bob');
215
+ test('body.user_id is ignored unless explicitly opted in (§10 hole 3)', () => {
216
+ // Safe-by-convention was the hole: any deployment that forgot to pin a header let
217
+ // every caller name an arbitrary victim via the client-controlled body.
218
+ assert.strictEqual((0, serve_hardening_1.resolveUserId)({}, { user_id: 'bob' }, {}), undefined);
219
+ });
220
+ test('AGENTHIPPO_ALLOW_BODY_IDENTITY=1 restores the dev-convenience body fallback', () => {
221
+ const env = { AGENTHIPPO_ALLOW_BODY_IDENTITY: '1' };
222
+ assert.strictEqual((0, serve_hardening_1.resolveUserId)({}, { user_id: 'bob' }, env), 'bob');
223
+ // A trusted platform header still wins over the body even when opted in.
224
+ assert.strictEqual((0, serve_hardening_1.resolveUserId)({ 'x-forwarded-email': 'alice@corp.com' }, { user_id: 'bob' }, env), 'alice@corp.com');
143
225
  });
144
226
  test('undefined when nothing present', () => {
145
227
  assert.strictEqual((0, serve_hardening_1.resolveUserId)({}, undefined), undefined);
146
- assert.strictEqual((0, serve_hardening_1.resolveUserId)({}, { user_id: ' ' }), undefined);
228
+ assert.strictEqual((0, serve_hardening_1.resolveUserId)({}, { user_id: ' ' }, { AGENTHIPPO_ALLOW_BODY_IDENTITY: '1' }), undefined);
147
229
  });
148
230
  test('AGENTHIPPO_IDENTITY_HEADER pins trust to one header — spoofed aliases are ignored', () => {
149
231
  const env = { AGENTHIPPO_IDENTITY_HEADER: 'x-goog-authenticated-user-email' };
@@ -297,3 +379,40 @@ suite('serve-hardening: readBodyWithCap', () => {
297
379
  assert.strictEqual(serve_hardening_1.DEFAULT_MAX_BODY_BYTES, 1024 * 1024);
298
380
  });
299
381
  });
382
+ suite('serve-hardening: resolveServeSessionKey (secure-obo-design §3.4 Phase 0)', () => {
383
+ const PINNED = { AGENTHIPPO_IDENTITY_HEADER: 'x-forwarded-email' };
384
+ test('verified user + session_id → user-namespaced key', () => {
385
+ const r = (0, serve_hardening_1.resolveServeSessionKey)('chat-1', 'alice@corp.com', PINNED);
386
+ assert.strictEqual(r.sessionKey, `u${(0, serve_hardening_1.userNamespaceHash)('alice@corp.com')}--chat-1`);
387
+ assert.strictEqual(r.rejected, undefined);
388
+ });
389
+ test('two users presenting the SAME session_id can never share a key (the defect this closes)', () => {
390
+ const a = (0, serve_hardening_1.resolveServeSessionKey)('chat-1', 'alice@corp.com', PINNED);
391
+ const b = (0, serve_hardening_1.resolveServeSessionKey)('chat-1', 'bob@corp.com', PINNED);
392
+ assert.notStrictEqual(a.sessionKey, b.sessionKey);
393
+ });
394
+ test('verified user without session_id → stable per-user default key', () => {
395
+ const r1 = (0, serve_hardening_1.resolveServeSessionKey)(undefined, 'alice@corp.com', PINNED);
396
+ const r2 = (0, serve_hardening_1.resolveServeSessionKey)('', 'alice@corp.com', PINNED);
397
+ assert.strictEqual(r1.sessionKey, r2.sessionKey);
398
+ assert.strictEqual(r1.sessionKey, `u${(0, serve_hardening_1.userNamespaceHash)('alice@corp.com')}--default`);
399
+ });
400
+ test('pinned identity + session_id but NO verified user → fail closed', () => {
401
+ const r = (0, serve_hardening_1.resolveServeSessionKey)('chat-1', undefined, PINNED);
402
+ assert.strictEqual(r.rejected, serve_hardening_1.SESSION_WITHOUT_USER_REASON);
403
+ });
404
+ test('unpinned (local dev) keeps raw session_id behavior', () => {
405
+ const r = (0, serve_hardening_1.resolveServeSessionKey)('chat-1', undefined, {});
406
+ assert.strictEqual(r.sessionKey, 'chat-1');
407
+ assert.strictEqual(r.rejected, undefined);
408
+ });
409
+ test('unpinned, no user, no session_id → legacy anon bucket', () => {
410
+ const r = (0, serve_hardening_1.resolveServeSessionKey)(undefined, undefined, {});
411
+ assert.strictEqual(r.sessionKey, 'anon-unknown');
412
+ });
413
+ test('the key never embeds the raw user id (emails are not path segments)', () => {
414
+ const r = (0, serve_hardening_1.resolveServeSessionKey)('s', 'alice@corp.com', PINNED);
415
+ assert.ok(!r.sessionKey.includes('alice@corp.com'));
416
+ assert.ok(!r.sessionKey.includes('@'));
417
+ });
418
+ });
@@ -98,11 +98,12 @@ suite('ServeClient', () => {
98
98
  test('normalizeServeBaseUrl strips trailing slashes', () => {
99
99
  assert.strictEqual((0, serveClient_js_1.normalizeServeBaseUrl)('http://127.0.0.1:8787/'), 'http://127.0.0.1:8787');
100
100
  });
101
- test('buildServeAuthHeaders includes bearer and identity', () => {
102
- assert.deepStrictEqual((0, serveClient_js_1.buildServeAuthHeaders)({ accessToken: 'tok', userEmail: 'you@acme.com' }), {
103
- Authorization: 'Bearer tok',
104
- 'X-Agent-User': 'you@acme.com',
105
- });
101
+ test('buildServeAuthHeaders sends the bearer token and NO self-asserted identity', () => {
102
+ // `X-Agent-User` used to ride along here, and the serve side trusts `x-agent-user` as its
103
+ // first identity alias. On a deployment that does not pin AGENTHIPPO_IDENTITY_HEADER, a
104
+ // client could name someone else and be namespaced into that user's session/state and
105
+ // handed a data-plane nonce scoped to them. The bearer already carries a verified identity.
106
+ assert.deepStrictEqual((0, serveClient_js_1.buildServeAuthHeaders)({ accessToken: 'tok', userEmail: 'you@acme.com' }), { Authorization: 'Bearer tok' });
106
107
  });
107
108
  test('streamRemoteServeChat sends Authorization when auth provided', async () => {
108
109
  const mock = await startMockServeServer([
@@ -173,4 +174,36 @@ suite('ServeClient', () => {
173
174
  });
174
175
  });
175
176
  });
177
+ test('streamRemoteServeChat surfaces fail-closed credential rejection as HTTP error (not silent success)', async () => {
178
+ // Mirrors serve.ts after the fix: warrant denial happens BEFORE SSE writeHead(200),
179
+ // so the client sees a real non-2xx JSON error instead of 200 + raw body fallthrough.
180
+ const server = http.createServer((_req, res) => {
181
+ res.writeHead(500, { 'Content-Type': 'application/json' });
182
+ res.end(JSON.stringify({
183
+ error: 'OBO data token present without a verified user in request scope (fail-closed: token_without_verified_user)',
184
+ }));
185
+ });
186
+ await new Promise((resolve, reject) => {
187
+ server.listen(0, '127.0.0.1', async () => {
188
+ try {
189
+ const address = server.address();
190
+ if (!address || typeof address === 'string') {
191
+ throw new Error('bad address');
192
+ }
193
+ const emitter = createCaptureEmitter();
194
+ const result = await (0, serveClient_js_1.streamRemoteServeChat)({ baseUrl: `http://127.0.0.1:${address.port}`, message: 'ping' }, emitter);
195
+ assert.strictEqual(result.ok, false);
196
+ assert.match(result.error ?? '', /token_without_verified_user/);
197
+ assert.deepStrictEqual(emitter.events.map((e) => e.type), ['error', 'done']);
198
+ resolve();
199
+ }
200
+ catch (err) {
201
+ reject(err);
202
+ }
203
+ finally {
204
+ server.close();
205
+ }
206
+ });
207
+ });
208
+ });
176
209
  });