aws-runtime-bridge 1.8.5 → 1.8.6

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 (42) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  2. package/dist/adapter/AcodeSdkAdapter.js +6 -98
  3. package/dist/adapter/AcodeSdkAdapter.test.js +2 -313
  4. package/dist/adapter/OpencodeSdkAdapter.js +1 -1
  5. package/dist/adapter/types.d.ts.map +1 -1
  6. package/dist/adapter/types.js +3 -7
  7. package/dist/config.d.ts +3 -2
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +13 -36
  10. package/dist/config.test.js +20 -24
  11. package/dist/routes/ai-sources.js +1 -5
  12. package/dist/routes/ai-sources.test.js +0 -21
  13. package/dist/routes/instance.d.ts.map +1 -1
  14. package/dist/routes/instance.js +14 -31
  15. package/dist/routes/instance.test.js +224 -215
  16. package/dist/routes/runtime-binding.d.ts.map +1 -1
  17. package/dist/routes/runtime-binding.js +1 -36
  18. package/dist/routes/terminal.d.ts +0 -2
  19. package/dist/routes/terminal.d.ts.map +1 -1
  20. package/dist/routes/terminal.js +7 -109
  21. package/dist/routes/terminal.test.js +1 -118
  22. package/dist/services/mcp-launch-binding-queue.d.ts +0 -7
  23. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  24. package/dist/services/mcp-launch-binding-queue.js +0 -25
  25. package/dist/services/session-output.d.ts +3 -7
  26. package/dist/services/session-output.d.ts.map +1 -1
  27. package/package/acode/dist/runtime.d.ts +0 -1
  28. package/package/acode/dist/runtime.d.ts.map +1 -1
  29. package/package/acode/dist/runtime.js +31 -265
  30. package/package/acode/dist/types.d.ts +1 -1
  31. package/package/acode/dist/types.d.ts.map +1 -1
  32. package/package/aws-client-agent-mcp/dist/config.d.ts +0 -4
  33. package/package/aws-client-agent-mcp/dist/config.d.ts.map +1 -1
  34. package/package/aws-client-agent-mcp/dist/config.js +0 -15
  35. package/package/aws-client-agent-mcp/dist/config.js.map +1 -1
  36. package/package/aws-client-agent-mcp/dist/http-client.d.ts +0 -10
  37. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
  38. package/package/aws-client-agent-mcp/dist/http-client.js +1 -79
  39. package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
  40. package/package/aws-client-agent-mcp/dist/http-client.test.js +265 -129
  41. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
  42. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC,MAAM,mCAAmC,GAAG,OAAO,CAAC;AACpD,MAAM,8BAA8B,GAAG,KAAK,CAAC;AAE7C,SAAS,uBAAuB,CAAC,GAAW;IAC1C,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,IAAI,8BAA8B,EAAE,CAAC;QACrD,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,8BAA8B,CAAC,gBAAgB,CAAC;AAC7E,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,OAAO,CAChB,iEAAiE,EACjE,IAAI,CACL,CAAC;AACJ,CAAC;AAED,SAAS,6BAA6B,CAAC,QAAgB;IACrD,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QACjF,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAc,EAAE,UAA+C,EAAE;IAC5F,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC3B,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC;QACzB,CAAC;aAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YACnC,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC5C,CAAC;QACD,GAAG,CAAC,QAAQ,GAAG,6BAA6B,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,QAAQ,GAAG,OAAO,CAAC,qBAAqB;YAC5C,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;YAC7C,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;QACjB,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC;YAC3C,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC;QAC9C,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,UAAU,GAAG,CAAC,OAAO,CAAC,qBAAqB;YAC/C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC1C,CAAC,CAAC,QAAQ,CACX,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtB,OAAO,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,WAAW,CAAC;IAClF,CAAC;AACH,CAAC;AAED,MAAM,OAAO,UAAU;IACb,YAAY,CAAe;IAC3B,UAAU,CAAS;IAE3B,YAAY,YAA0B;QACpC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CACZ,QAAgB,EAChB,IAA6B;QAE7B,MAAM,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAEtC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE/D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CACT,yBAAyB,QAAQ,iEAAiE,CACnG,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,YAAY,GAChB,OAAO,QAAQ,CAAC,IAAI,KAAK,UAAU;gBACjC,CAAC,CAAC,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;gBAChE,CAAC,CAAC,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CACb,YAAY,QAAQ,iBAAiB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,MAAM,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACzH,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAoB,CAAC;QAC1D,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,KAAK,CAAC,YAAY,QAAQ,YAAY,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,MAAW,CAAC;IACrB,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,kBAAkB,CAC9B,QAAgB,EAChB,IAA6B;QAE7B,OAAO,MAAM,IAAI,CAAC,gBAAgB,CAChC,IAAI,CAAC,UAAU,EACf;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE;YACnC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,IAAI,EAAE,QAAQ;gBACd,SAAS,EAAE,IAAI;aAChB,CAAC;SACH,EACD,YAAY,QAAQ,EAAE,CACvB,CAAC;IACJ,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,gBAAgB,CAC5B,GAAW,EACX,IAAiB,EACjB,aAAqB;QAErB,MAAM,SAAS,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QAEhE,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE;gBACtB,GAAG,IAAI;gBACP,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,aAAa,oBAAoB,SAAS,IAAI,CAAC,CAAC;YACrE,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAEO,uBAAuB;QAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,uBAAuB,CAAC;QAC7D,IACE,OAAO,UAAU,KAAK,QAAQ;YAC9B,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC3B,UAAU,GAAG,CAAC,EACd,CAAC;YACD,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,mCAAmC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACK,mBAAmB;QACzB,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;SACnC,CAAC;QAEF,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC;YACzC,OAAO,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC;YAClE,OAAO,CAAC,aAAa;gBACnB,UAAU,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAC;QACrD,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,wBAAwB;QACpC,IACE,IAAI,CAAC,YAAY,CAAC,kBAAkB;YACpC,CAAC,IAAI,CAAC,YAAY,CAAC,oBAAoB,EACvC,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;QAChD,IAAI,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;YACnD,IAAI,UAAU,EAAE,CAAC;gBACf,OAAO,UAAU,CAAC;YACpB,CAAC;QACH,CAAC;QAED,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE;YACrE,qBAAqB,EAAE,IAAI;SAC5B,CAAC,CAAC;QACH,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF"}
@@ -1,6 +1,19 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { EventEmitter } from 'node:events';
2
3
  import { afterEach, beforeEach, describe, it, mock } from 'node:test';
4
+ import WebSocket from 'ws';
3
5
  import { HttpClient } from './http-client.js';
6
+ import { WebSocketClient } from './websocket-client.js';
7
+ class FakeSocket extends EventEmitter {
8
+ readyState = WebSocket.CONNECTING;
9
+ send() { }
10
+ close() {
11
+ this.readyState = WebSocket.CLOSED;
12
+ }
13
+ terminate() {
14
+ this.readyState = WebSocket.CLOSED;
15
+ }
16
+ }
4
17
  describe('HttpClient', () => {
5
18
  const originalFetch = global.fetch;
6
19
  let fetchCalls = [];
@@ -283,12 +296,12 @@ describe('HttpClient', () => {
283
296
  statusText: 'Unauthorized',
284
297
  };
285
298
  }
286
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
299
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
287
300
  return {
288
301
  ok: true,
289
302
  status: 200,
290
303
  statusText: 'OK',
291
- json: async () => ({ runtimeAccessToken: 'fresh-token' }),
304
+ json: async () => ({ ok: true, runtimeAccessToken: 'fresh-token' }),
292
305
  };
293
306
  }
294
307
  return {
@@ -302,15 +315,17 @@ describe('HttpClient', () => {
302
315
  assert.deepEqual(result, { data: 'retried' });
303
316
  assert.equal(fetchCalls.length, 3);
304
317
  assert.equal(fetchCalls[0][1].headers['X-Runtime-Token'], 'stale-token');
305
- assert.equal(fetchCalls[1][0], 'http://127.0.0.1:18081/runtime/binding/refresh-token');
318
+ assert.equal(fetchCalls[1][0], 'http://127.0.0.1:18081/runtime/binding/request-token');
306
319
  assert.equal(fetchCalls[1][1].body, JSON.stringify({
307
- agentId: 'agent-1',
308
- currentToken: 'stale-token',
320
+ userId: 'user-1',
321
+ serverUrl: 'ws://localhost:8080/ws/agent',
322
+ forceRefresh: true,
323
+ rejectedRuntimeAccessToken: 'stale-token',
309
324
  }));
310
325
  assert.equal(fetchCalls[2][1].headers['X-Runtime-Token'], 'fresh-token');
311
326
  assert.equal(config.runtimeAccessToken, 'fresh-token');
312
327
  });
313
- it('shares one bridge token refresh across concurrent 401 responses', async () => {
328
+ it('shares one forced runtime token refresh across concurrent 401 responses', async () => {
314
329
  const config = {
315
330
  serverUrl: 'ws://localhost:8080/ws/agent',
316
331
  heartbeatInterval: 15000,
@@ -324,13 +339,13 @@ describe('HttpClient', () => {
324
339
  const staleTokenCalls = new Set();
325
340
  global.fetch = async (url, options) => {
326
341
  fetchCalls.push([url.toString(), options]);
327
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
342
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
328
343
  await new Promise((resolve) => setTimeout(resolve, 10));
329
344
  return {
330
345
  ok: true,
331
346
  status: 200,
332
347
  statusText: 'OK',
333
- json: async () => ({ runtimeAccessToken: 'fresh-token' }),
348
+ json: async () => ({ ok: true, runtimeAccessToken: 'fresh-token' }),
334
349
  };
335
350
  }
336
351
  const body = JSON.parse(String(options?.body || '{}'));
@@ -357,10 +372,10 @@ describe('HttpClient', () => {
357
372
  assert.deepEqual(first, { tool: 'get_memory_stats', token: 'fresh-token' });
358
373
  assert.deepEqual(second, { tool: 'discover_group_rooms', token: 'fresh-token' });
359
374
  assert.deepEqual(staleTokenCalls, new Set(['get_memory_stats', 'discover_group_rooms']));
360
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/refresh-token').length, 1);
375
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/request-token').length, 1);
361
376
  assert.equal(config.runtimeAccessToken, 'fresh-token');
362
377
  });
363
- it('throws when no runtimeAccessToken and no bridge for startup', async () => {
378
+ it('shares one initial runtime token request across concurrent calls without a token', async () => {
364
379
  const config = {
365
380
  serverUrl: 'ws://localhost:8080/ws/agent',
366
381
  heartbeatInterval: 15000,
@@ -370,139 +385,270 @@ describe('HttpClient', () => {
370
385
  runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
371
386
  };
372
387
  const client = new HttpClient(config);
373
- global.fetch = async () => ({
374
- ok: true,
375
- status: 200,
376
- statusText: 'OK',
377
- json: async () => ({ result: 'success' }),
378
- });
379
- await assert.rejects(() => client.callTool('get_memory_stats', { agentId: 'agent-1' }), /runtimeAccessToken is required; MCP nodes must claim a session token from launch binding/);
388
+ global.fetch = async (url, options) => {
389
+ fetchCalls.push([url.toString(), options]);
390
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
391
+ await new Promise((resolve) => setTimeout(resolve, 10));
392
+ return {
393
+ ok: true,
394
+ status: 200,
395
+ statusText: 'OK',
396
+ json: async () => ({ ok: true, runtimeAccessToken: 'startup-token' }),
397
+ };
398
+ }
399
+ const body = JSON.parse(String(options?.body || '{}'));
400
+ return {
401
+ ok: true,
402
+ status: 200,
403
+ statusText: 'OK',
404
+ json: async () => ({ tool: body.tool, token: options.headers['X-Runtime-Token'] }),
405
+ };
406
+ };
407
+ const [first, second] = await Promise.all([
408
+ client.callTool('get_memory_stats', { agentId: 'agent-1' }),
409
+ client.callTool('discover_group_rooms', { agentId: 'agent-1' }),
410
+ ]);
411
+ assert.deepEqual(first, { tool: 'get_memory_stats', token: 'startup-token' });
412
+ assert.deepEqual(second, { tool: 'discover_group_rooms', token: 'startup-token' });
413
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/request-token').length, 1);
414
+ assert.equal(fetchCalls[0][1].body, JSON.stringify({
415
+ userId: 'user-1',
416
+ serverUrl: 'ws://localhost:8080/ws/agent',
417
+ forceRefresh: false,
418
+ }));
419
+ assert.equal(config.runtimeAccessToken, 'startup-token');
380
420
  });
381
- it('throws original 401 when bridge refresh fails', async () => {
421
+ it('clears failed runtime token refresh so later calls can retry', async () => {
382
422
  const config = {
383
423
  serverUrl: 'ws://localhost:8080/ws/agent',
384
424
  heartbeatInterval: 15000,
385
425
  reconnectInterval: 200,
386
426
  maxReconnectAttempts: -1,
387
427
  userId: 'user-1',
388
- runtimeAccessToken: 'stale-token',
389
428
  runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
390
429
  };
391
430
  const client = new HttpClient(config);
431
+ let tokenRequestCount = 0;
392
432
  global.fetch = async (url, options) => {
393
433
  fetchCalls.push([url.toString(), options]);
394
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
395
- return {
396
- ok: false,
397
- status: 503,
398
- statusText: 'Service Unavailable',
399
- };
400
- }
401
- return {
402
- ok: false,
403
- status: 401,
404
- statusText: 'Unauthorized',
405
- };
406
- };
407
- await assert.rejects(() => client.callTool('get_memory_stats', { agentId: 'agent-1' }), /MCP tool get_memory_stats call failed: 401 Unauthorized/);
408
- // Can retry again after failure
409
- global.fetch = async (url, options) => {
410
- fetchCalls.push([url.toString(), options]);
411
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
434
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
435
+ tokenRequestCount += 1;
436
+ if (tokenRequestCount === 1) {
437
+ return {
438
+ ok: false,
439
+ status: 503,
440
+ statusText: 'Service Unavailable',
441
+ text: async () => 'bridge unavailable',
442
+ };
443
+ }
412
444
  return {
413
445
  ok: true,
414
446
  status: 200,
415
447
  statusText: 'OK',
416
- json: async () => ({ runtimeAccessToken: 'recovered-token' }),
417
- };
418
- }
419
- const token = options.headers['X-Runtime-Token'];
420
- if (token === 'recovered-token') {
421
- return {
422
- ok: true,
423
- status: 200,
424
- statusText: 'OK',
425
- json: async () => ({ token }),
448
+ json: async () => ({ ok: true, runtimeAccessToken: 'recovered-token' }),
426
449
  };
427
450
  }
428
451
  return {
429
- ok: false,
430
- status: 401,
431
- statusText: 'Unauthorized',
452
+ ok: true,
453
+ status: 200,
454
+ statusText: 'OK',
455
+ json: async () => ({ token: options.headers['X-Runtime-Token'] }),
432
456
  };
433
457
  };
458
+ await assert.rejects(() => client.callTool('get_memory_stats', { agentId: 'agent-1' }), /Failed to request runtime token from runtime-bridge: 503 Service Unavailable - bridge unavailable/);
434
459
  const result = await client.callTool('get_memory_stats', { agentId: 'agent-1' });
435
460
  assert.deepEqual(result, { token: 'recovered-token' });
461
+ assert.equal(tokenRequestCount, 2);
436
462
  assert.equal(config.runtimeAccessToken, 'recovered-token');
437
463
  });
438
- it('returns 401 when bridge refresh has no new token for running agent', async () => {
464
+ it('shares one runtime token refresh between HTTP tools and websocket recovery', async () => {
439
465
  const config = {
440
466
  serverUrl: 'ws://localhost:8080/ws/agent',
441
- heartbeatInterval: 15000,
442
- reconnectInterval: 200,
443
- maxReconnectAttempts: -1,
467
+ heartbeatInterval: 1000,
468
+ heartbeatTimeout: 3000,
469
+ reconnectInterval: 10,
470
+ maxReconnectAttempts: 2,
471
+ userId: 'user-1',
444
472
  runtimeAccessToken: 'stale-token',
445
473
  runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
446
474
  };
447
- const client = new HttpClient(config);
475
+ const httpClient = new HttpClient(config);
476
+ let tokenRequestCount = 0;
477
+ let websocketAttempt = 0;
478
+ const websocketUrls = [];
479
+ let releaseRefresh;
480
+ const refreshGate = new Promise((resolve) => {
481
+ releaseRefresh = resolve;
482
+ });
448
483
  global.fetch = async (url, options) => {
449
484
  fetchCalls.push([url.toString(), options]);
450
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
451
- // Bridge says no new token available (409 conflict)
485
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
486
+ tokenRequestCount += 1;
487
+ await refreshGate;
488
+ return {
489
+ ok: true,
490
+ status: 200,
491
+ statusText: 'OK',
492
+ json: async () => ({ ok: true, runtimeAccessToken: 'shared-fresh-token' }),
493
+ };
494
+ }
495
+ const body = JSON.parse(String(options?.body || '{}'));
496
+ const token = options.headers['X-Runtime-Token'];
497
+ if (token === 'stale-token') {
452
498
  return {
453
499
  ok: false,
454
- status: 409,
455
- statusText: 'Conflict',
456
- json: async () => ({ error: 'token_unchanged' }),
500
+ status: 401,
501
+ statusText: 'Unauthorized',
457
502
  };
458
503
  }
459
504
  return {
460
- ok: false,
461
- status: 401,
462
- statusText: 'Unauthorized',
505
+ ok: true,
506
+ status: 200,
507
+ statusText: 'OK',
508
+ json: async () => ({ tool: body.tool, token }),
463
509
  };
464
510
  };
465
- await assert.rejects(() => client.callTool('get_memory_stats', { agentId: 'agent-1' }), /MCP tool get_memory_stats call failed: 401 Unauthorized/);
466
- // 1 initial MCP call + 1 refresh attempt + 0 retries (refresh failed)
467
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:8080/mcp/call').length, 1);
468
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/refresh-token').length, 1);
469
- assert.equal(config.runtimeAccessToken, 'stale-token');
470
- });
471
- it('returns 404 from bridge refresh when agent has no cached token', async () => {
511
+ const wsClient = new WebSocketClient(config, (url) => {
512
+ websocketUrls.push(url);
513
+ websocketAttempt += 1;
514
+ const socket = new FakeSocket();
515
+ setImmediate(() => {
516
+ socket.readyState = WebSocket.OPEN;
517
+ socket.emit('open');
518
+ if (websocketAttempt === 1) {
519
+ socket.readyState = WebSocket.CLOSED;
520
+ socket.emit('close', 1008, Buffer.from('policy violation'));
521
+ }
522
+ });
523
+ return socket;
524
+ });
525
+ try {
526
+ wsClient.on('error', () => {
527
+ // suppress unhandled error event in test environment
528
+ });
529
+ const websocketConnected = wsClient.connect('agent-1');
530
+ const httpCall = httpClient.callTool('get_memory_stats', { agentId: 'agent-1' });
531
+ await new Promise((resolve, reject) => {
532
+ const timer = setTimeout(() => reject(new Error('expected both refresh waiters')), 500);
533
+ const interval = setInterval(() => {
534
+ if (tokenRequestCount === 1 && config.runtimeAccessTokenRefreshPromise) {
535
+ clearInterval(interval);
536
+ clearTimeout(timer);
537
+ resolve();
538
+ }
539
+ }, 5);
540
+ });
541
+ const websocketReconnected = new Promise((resolve, reject) => {
542
+ const timer = setTimeout(() => reject(new Error('expected websocket reconnect')), 500);
543
+ wsClient.on('connected', () => {
544
+ if (websocketAttempt >= 2) {
545
+ clearTimeout(timer);
546
+ resolve();
547
+ }
548
+ });
549
+ });
550
+ releaseRefresh?.();
551
+ const result = await httpCall;
552
+ await websocketConnected;
553
+ await websocketReconnected;
554
+ assert.deepEqual(result, { tool: 'get_memory_stats', token: 'shared-fresh-token' });
555
+ assert.equal(tokenRequestCount, 1);
556
+ assert.equal(config.runtimeAccessToken, 'shared-fresh-token');
557
+ assert.equal(new URL(websocketUrls[1] || '').searchParams.get('runtimeAccessToken'), 'shared-fresh-token');
558
+ }
559
+ finally {
560
+ releaseRefresh?.();
561
+ wsClient.disconnect();
562
+ }
563
+ });
564
+ it('shares one forced refresh among multiple waiters after an initial non-forced refresh', async () => {
472
565
  const config = {
473
566
  serverUrl: 'ws://localhost:8080/ws/agent',
474
567
  heartbeatInterval: 15000,
475
568
  reconnectInterval: 200,
476
569
  maxReconnectAttempts: -1,
477
570
  userId: 'user-1',
478
- runtimeAccessToken: 'stale-token',
479
571
  runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
480
572
  };
481
- const client = new HttpClient(config);
573
+ const initialClient = new HttpClient(config);
574
+ const forceClient = new HttpClient(config);
575
+ let tokenRequestCount = 0;
576
+ let forceRequestCount = 0;
577
+ let releaseInitialRefresh;
578
+ const initialRefreshGate = new Promise((resolve) => {
579
+ releaseInitialRefresh = resolve;
580
+ });
482
581
  global.fetch = async (url, options) => {
483
582
  fetchCalls.push([url.toString(), options]);
484
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
485
- // Bridge has no cached token for this agent
583
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
584
+ tokenRequestCount += 1;
585
+ const body = JSON.parse(String(options?.body || '{}'));
586
+ if (body.forceRefresh) {
587
+ forceRequestCount += 1;
588
+ await new Promise((resolve) => setTimeout(resolve, 10));
589
+ return {
590
+ ok: true,
591
+ status: 200,
592
+ statusText: 'OK',
593
+ json: async () => ({ ok: true, runtimeAccessToken: 'forced-token' }),
594
+ };
595
+ }
596
+ await initialRefreshGate;
597
+ return {
598
+ ok: true,
599
+ status: 200,
600
+ statusText: 'OK',
601
+ json: async () => ({ ok: true, runtimeAccessToken: 'initial-token' }),
602
+ };
603
+ }
604
+ const body = JSON.parse(String(options?.body || '{}'));
605
+ const token = options.headers['X-Runtime-Token'];
606
+ if (token === 'initial-token') {
486
607
  return {
487
608
  ok: false,
488
- status: 404,
489
- statusText: 'Not Found',
490
- json: async () => ({ error: 'no cached token for agent' }),
609
+ status: 401,
610
+ statusText: 'Unauthorized',
491
611
  };
492
612
  }
493
613
  return {
494
- ok: false,
495
- status: 401,
496
- statusText: 'Unauthorized',
614
+ ok: true,
615
+ status: 200,
616
+ statusText: 'OK',
617
+ json: async () => ({ tool: body.tool, token }),
497
618
  };
498
619
  };
499
- await assert.rejects(() => client.callTool('get_profile', { agentId: 'agent-unknown' }), /MCP tool get_profile call failed: 401 Unauthorized/);
500
- // 1 initial MCP call + 1 refresh attempt (404) + 0 retries
501
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:8080/mcp/call').length, 1);
502
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/refresh-token').length, 1);
503
- assert.equal(config.runtimeAccessToken, 'stale-token');
504
- });
505
- it('does not retry 401 after bridge refresh returns same token', async () => {
620
+ try {
621
+ const initialCall = initialClient.callTool('get_profile', { agentId: 'agent-1' });
622
+ await new Promise((resolve, reject) => {
623
+ const timer = setTimeout(() => reject(new Error('expected initial refresh to start')), 500);
624
+ const interval = setInterval(() => {
625
+ if (tokenRequestCount === 1 && config.runtimeAccessTokenRefreshPromise) {
626
+ clearInterval(interval);
627
+ clearTimeout(timer);
628
+ resolve();
629
+ }
630
+ }, 5);
631
+ });
632
+ const firstForceCall = forceClient.callTool('get_memory_stats', { agentId: 'agent-1' });
633
+ const secondForceCall = forceClient.callTool('discover_group_rooms', { agentId: 'agent-1' });
634
+ releaseInitialRefresh?.();
635
+ const [initialResult, firstForceResult, secondForceResult] = await Promise.all([
636
+ initialCall,
637
+ firstForceCall,
638
+ secondForceCall,
639
+ ]);
640
+ assert.deepEqual(initialResult, { tool: 'get_profile', token: 'forced-token' });
641
+ assert.deepEqual(firstForceResult, { tool: 'get_memory_stats', token: 'forced-token' });
642
+ assert.deepEqual(secondForceResult, { tool: 'discover_group_rooms', token: 'forced-token' });
643
+ assert.equal(tokenRequestCount, 2);
644
+ assert.equal(forceRequestCount, 1);
645
+ assert.equal(config.runtimeAccessToken, 'forced-token');
646
+ }
647
+ finally {
648
+ releaseInitialRefresh?.();
649
+ }
650
+ });
651
+ it('does not retry 401 more than once after forced token refresh', async () => {
506
652
  const config = {
507
653
  serverUrl: 'ws://localhost:8080/ws/agent',
508
654
  heartbeatInterval: 15000,
@@ -514,13 +660,12 @@ describe('HttpClient', () => {
514
660
  const client = new HttpClient(config);
515
661
  global.fetch = async (url, options) => {
516
662
  fetchCalls.push([url.toString(), options]);
517
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
518
- // Bridge returns same token
663
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
519
664
  return {
520
665
  ok: true,
521
666
  status: 200,
522
667
  statusText: 'OK',
523
- json: async () => ({ runtimeAccessToken: 'stale-token' }),
668
+ json: async () => ({ ok: true, runtimeAccessToken: 'fresh-token' }),
524
669
  };
525
670
  }
526
671
  return {
@@ -530,11 +675,11 @@ describe('HttpClient', () => {
530
675
  };
531
676
  };
532
677
  await assert.rejects(() => client.callTool('my_task', { agentId: 'agent-1' }), /MCP tool my_task call failed: 401 Unauthorized/);
533
- // 1 initial MCP call + 1 refresh + 0 retries (same token, not updated)
534
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:8080/mcp/call').length, 1);
535
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/refresh-token').length, 1);
678
+ assert.equal(fetchCalls.length, 3);
679
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:8080/mcp/call').length, 2);
680
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/request-token').length, 1);
536
681
  });
537
- it('retries refresh on second call if first call 401 with different fresh token', async () => {
682
+ it('does not repeatedly force-refresh the same rejected runtime token across calls', async () => {
538
683
  const config = {
539
684
  serverUrl: 'ws://localhost:8080/ws/agent',
540
685
  heartbeatInterval: 15000,
@@ -545,24 +690,14 @@ describe('HttpClient', () => {
545
690
  runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
546
691
  };
547
692
  const client = new HttpClient(config);
548
- let refreshCount = 0;
549
693
  global.fetch = async (url, options) => {
550
694
  fetchCalls.push([url.toString(), options]);
551
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
552
- refreshCount += 1;
695
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
553
696
  return {
554
697
  ok: true,
555
698
  status: 200,
556
699
  statusText: 'OK',
557
- json: async () => ({ runtimeAccessToken: `fresh-token-${refreshCount}` }),
558
- };
559
- }
560
- const token = options.headers['X-Runtime-Token'];
561
- if (token && token.startsWith('fresh-token-')) {
562
- return {
563
- ok: false,
564
- status: 401,
565
- statusText: 'Unauthorized',
700
+ json: async () => ({ ok: true, runtimeAccessToken: 'fresh-but-rejected-token' }),
566
701
  };
567
702
  }
568
703
  return {
@@ -572,12 +707,13 @@ describe('HttpClient', () => {
572
707
  };
573
708
  };
574
709
  await assert.rejects(() => client.callTool('discover_group_rooms', { agentId: 'agent-1' }), /MCP tool discover_group_rooms call failed: 401 Unauthorized/);
575
- // Second call also tries refresh
576
710
  await assert.rejects(() => client.callTool('poll_message', { agentId: 'agent-1' }), /MCP tool poll_message call failed: 401 Unauthorized/);
577
- assert.equal(config.runtimeAccessToken, 'fresh-token-2');
578
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/refresh-token').length, 2);
711
+ assert.equal(config.runtimeAccessToken, 'fresh-but-rejected-token');
712
+ assert.equal(config.lastForceRefreshAttemptedForToken, 'fresh-but-rejected-token');
713
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/request-token').length, 1);
714
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:8080/mcp/call').length, 3);
579
715
  });
580
- it('shares one refresh promise among concurrent 401 calls', async () => {
716
+ it('does not force-refresh again while the fresh runtime token is being validated', async () => {
581
717
  const config = {
582
718
  serverUrl: 'ws://localhost:8080/ws/agent',
583
719
  heartbeatInterval: 15000,
@@ -588,27 +724,32 @@ describe('HttpClient', () => {
588
724
  runtimeBridgeBaseUrl: 'http://127.0.0.1:18081',
589
725
  };
590
726
  const client = new HttpClient(config);
591
- let resolveRefresh = () => { };
592
- const refreshGate = new Promise((resolve) => {
593
- resolveRefresh = resolve;
727
+ let resolveFreshTokenValidationStarted = () => { };
728
+ let releaseFreshTokenValidation = () => { };
729
+ const freshTokenValidationStarted = new Promise((resolve) => {
730
+ resolveFreshTokenValidationStarted = resolve;
731
+ });
732
+ const freshTokenValidationReleased = new Promise((resolve) => {
733
+ releaseFreshTokenValidation = resolve;
594
734
  });
595
- let refreshCallCount = 0;
596
735
  let freshTokenMcpCallCount = 0;
597
736
  global.fetch = async (url, options) => {
598
737
  fetchCalls.push([url.toString(), options]);
599
- if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/refresh-token') {
600
- refreshCallCount += 1;
601
- await refreshGate;
738
+ if (url.toString() === 'http://127.0.0.1:18081/runtime/binding/request-token') {
602
739
  return {
603
740
  ok: true,
604
741
  status: 200,
605
742
  statusText: 'OK',
606
- json: async () => ({ runtimeAccessToken: 'fresh-but-rejected-token' }),
743
+ json: async () => ({ ok: true, runtimeAccessToken: 'fresh-but-rejected-token' }),
607
744
  };
608
745
  }
609
746
  const requestHeaders = (options?.headers ?? {});
610
747
  if (requestHeaders.Authorization === 'Bearer fresh-but-rejected-token') {
611
748
  freshTokenMcpCallCount += 1;
749
+ if (freshTokenMcpCallCount === 1) {
750
+ resolveFreshTokenValidationStarted();
751
+ await freshTokenValidationReleased;
752
+ }
612
753
  }
613
754
  return {
614
755
  ok: false,
@@ -617,19 +758,14 @@ describe('HttpClient', () => {
617
758
  };
618
759
  };
619
760
  const firstCall = client.callTool('discover_group_rooms', { agentId: 'agent-1' });
620
- // Wait for the refresh to start
621
- await new Promise((resolve) => setTimeout(resolve, 20));
622
- // Second call should share the same refresh promise
623
- const secondCall = client.callTool('poll_message', { agentId: 'agent-1' });
624
- resolveRefresh();
761
+ await freshTokenValidationStarted;
762
+ await assert.rejects(() => client.callTool('poll_message', { agentId: 'agent-1' }), /MCP tool poll_message call failed: 401 Unauthorized/);
763
+ releaseFreshTokenValidation();
625
764
  await assert.rejects(() => firstCall, /MCP tool discover_group_rooms call failed: 401 Unauthorized/);
626
- await assert.rejects(() => secondCall, /MCP tool poll_message call failed: 401 Unauthorized/);
627
765
  assert.equal(config.runtimeAccessToken, 'fresh-but-rejected-token');
628
- // Only one refresh call to bridge
629
- assert.equal(refreshCallCount, 1);
630
- assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/refresh-token').length, 1);
631
- // Both tools retried once with fresh token
632
- assert.equal(freshTokenMcpCallCount, 2);
766
+ assert.equal(config.lastForceRefreshAttemptedForToken, 'fresh-but-rejected-token');
767
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:18081/runtime/binding/request-token').length, 1);
768
+ assert.equal(fetchCalls.filter(([url]) => url === 'http://127.0.0.1:8080/mcp/call').length, 3);
633
769
  });
634
770
  it('handles 404 not found response', async () => {
635
771
  const config = {