aws-runtime-bridge 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"AcodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/AcodeSdkAdapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AcodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/AcodeSdkAdapter.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ import { v4 as uuidv4 } from "uuid";
11
11
  import { getAcodeConfigFile, getAcodeSkillsDir, getRuntimeHomeDir, } from "../config.js";
12
12
  import { getReleasedAcodeEntryPath } from "../services/acode-package.js";
13
13
  import { IdlePollLoop } from "./idle-poll-loop.js";
14
- import { getCanonicalMcpDetail, getToolActionInfo, } from "./types.js";
14
+ import { getCanonicalMcpDetail, getToolActionInfo } from "./types.js";
15
15
  const ACODE_AUTO_POLL_PROMPT = [
16
16
  "Continue the AgentsWork message loop now.",
17
17
  "You MUST call the exact MCP tool named aws-mcp__poll_message as your next action and wait for the next assigned message or task.",
@@ -46,6 +46,9 @@ function toUsage(value) {
46
46
  const usage = value;
47
47
  const inputTokens = toFiniteTokenCount(usage.inputTokens, usage.input_tokens, usage.promptTokens, usage.prompt_tokens);
48
48
  const outputTokens = toFiniteTokenCount(usage.outputTokens, usage.output_tokens, usage.completionTokens, usage.completion_tokens);
49
+ if (inputTokens === 0 && outputTokens === 0) {
50
+ return undefined;
51
+ }
49
52
  return {
50
53
  inputTokens,
51
54
  outputTokens,
@@ -60,6 +63,9 @@ function toFiniteTokenCount(...values) {
60
63
  }
61
64
  return 0;
62
65
  }
66
+ function toEstimatedContextTokens(data) {
67
+ return toFiniteTokenCount(data.estimatedContextTokens, data.contextTokens, data.context_tokens, data.contextUsedTokens, data.context_used_tokens);
68
+ }
63
69
  /**
64
70
  * Check whether an ACode turn_complete event represents a failed turn.
65
71
  * Main flow: only explicit failed=true prevents error status from being reset to waiting_input.
@@ -83,6 +89,30 @@ function isAcodeSemanticToolAction(actionType) {
83
89
  actionType === "write_file" ||
84
90
  actionType === "bash");
85
91
  }
92
+ function hasPollingMessages(result) {
93
+ const parsed = parseToolResultObject(result);
94
+ if (!parsed) {
95
+ return typeof result === "string" && result.trim().length > 0;
96
+ }
97
+ return [parsed.directMessages, parsed.groupMessages, parsed.messages].some((value) => Array.isArray(value) && value.length > 0);
98
+ }
99
+ function parseToolResultObject(result) {
100
+ if (result && typeof result === "object" && !Array.isArray(result)) {
101
+ return result;
102
+ }
103
+ if (typeof result !== "string" || !result.trim()) {
104
+ return undefined;
105
+ }
106
+ try {
107
+ const parsed = JSON.parse(result);
108
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
109
+ ? parsed
110
+ : undefined;
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ }
86
116
  export class AcodeSdkAdapter extends EventEmitter {
87
117
  constructor() {
88
118
  super(...arguments);
@@ -113,7 +143,9 @@ export class AcodeSdkAdapter extends EventEmitter {
113
143
  try {
114
144
  await entry.runtime.start();
115
145
  session.status = "waiting_input";
116
- this.emit("status-change", sessionId, "waiting_input");
146
+ this.emit("status-change", sessionId, "waiting_input", {
147
+ usage: entry.adapterSession.totalUsage,
148
+ });
117
149
  if (config.initialPrompt?.trim()) {
118
150
  this.startInitialPromptTurn(sessionId, config.initialPrompt);
119
151
  }
@@ -305,6 +337,14 @@ export class AcodeSdkAdapter extends EventEmitter {
305
337
  return;
306
338
  }
307
339
  if (event.type === "ai_request") {
340
+ const estimatedTokens = toEstimatedContextTokens(event.data);
341
+ if (estimatedTokens > 0) {
342
+ entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
343
+ entry.adapterSession.totalUsage.outputTokens = 0;
344
+ this.emit("status-change", event.sessionId, entry.adapterSession.status, {
345
+ usage: entry.adapterSession.totalUsage,
346
+ });
347
+ }
308
348
  this.emitProviderEvent(event, "ai_request", {
309
349
  model: typeof event.data.model === "string" ? event.data.model : undefined,
310
350
  endpoint: typeof event.data.endpoint === "string"
@@ -431,7 +471,9 @@ export class AcodeSdkAdapter extends EventEmitter {
431
471
  const server = typeof event.data.server === "string" ? event.data.server : "mcp";
432
472
  const tool = typeof event.data.tool === "string" ? event.data.tool : "";
433
473
  const toolName = server && tool ? `${server}__${tool}` : tool || server;
434
- const toolInput = event.data.arguments && typeof event.data.arguments === "object" && !Array.isArray(event.data.arguments)
474
+ const toolInput = event.data.arguments &&
475
+ typeof event.data.arguments === "object" &&
476
+ !Array.isArray(event.data.arguments)
435
477
  ? event.data.arguments
436
478
  : undefined;
437
479
  const actionInfo = getToolActionInfo(toolName, toolInput);
@@ -439,7 +481,8 @@ export class AcodeSdkAdapter extends EventEmitter {
439
481
  entry.autoPollObservedPollingTool = true;
440
482
  }
441
483
  const actionDetail = isAcodeSemanticToolAction(actionInfo.actionType)
442
- ? actionInfo.actionDetail || toAcodeMcpActionDetail(undefined, server, tool)
484
+ ? actionInfo.actionDetail ||
485
+ toAcodeMcpActionDetail(undefined, server, tool)
443
486
  : toAcodeMcpActionDetail(actionInfo.actionDetail, server, tool);
444
487
  const toolUseId = typeof event.data.toolUseId === "string"
445
488
  ? event.data.toolUseId
@@ -459,8 +502,12 @@ export class AcodeSdkAdapter extends EventEmitter {
459
502
  ? event.data.result
460
503
  : JSON.stringify(event.data.result ?? ""),
461
504
  });
462
- entry.adapterSession.status = "thinking";
463
- this.emit("status-change", event.sessionId, "thinking");
505
+ const status = actionInfo.actionType === "idle" &&
506
+ !hasPollingMessages(event.data.result)
507
+ ? "waiting_input"
508
+ : "thinking";
509
+ entry.adapterSession.status = status;
510
+ this.emit("status-change", event.sessionId, status);
464
511
  return;
465
512
  }
466
513
  const status = actionInfo.actionType === "idle" ? "waiting_input" : "tool_using";
@@ -469,6 +516,17 @@ export class AcodeSdkAdapter extends EventEmitter {
469
516
  this.emit("status-change", event.sessionId, status, eventData);
470
517
  return;
471
518
  }
519
+ if (event.type === "session_started") {
520
+ const estimatedTokens = toEstimatedContextTokens(event.data);
521
+ if (estimatedTokens > 0) {
522
+ entry.adapterSession.totalUsage.inputTokens = estimatedTokens;
523
+ entry.adapterSession.totalUsage.outputTokens = 0;
524
+ this.emit("status-change", event.sessionId, entry.adapterSession.status, {
525
+ usage: entry.adapterSession.totalUsage,
526
+ });
527
+ }
528
+ return;
529
+ }
472
530
  this.emitProviderEvent(event, "thinking", {
473
531
  text: JSON.stringify(event.data),
474
532
  });
@@ -121,6 +121,65 @@ class PollMessageStatusRuntime extends EventEmitter {
121
121
  async abort() { }
122
122
  async stop() { }
123
123
  }
124
+ class EmptyPollResultRuntime extends EventEmitter {
125
+ constructor(config) {
126
+ super();
127
+ this.config = config;
128
+ }
129
+ async start() { }
130
+ async submit(_message) {
131
+ this.emit("event", {
132
+ type: "mcp_status",
133
+ sessionId: this.config.sessionId,
134
+ timestamp: new Date(0).toISOString(),
135
+ data: { action: "call_tool", server: "aws-mcp", tool: "poll_message" },
136
+ });
137
+ this.emit("event", {
138
+ type: "mcp_status",
139
+ sessionId: this.config.sessionId,
140
+ timestamp: new Date(0).toISOString(),
141
+ data: {
142
+ action: "tool_result",
143
+ server: "aws-mcp",
144
+ tool: "poll_message",
145
+ result: JSON.stringify({ directMessages: [], groupMessages: [] }),
146
+ },
147
+ });
148
+ }
149
+ async abort() { }
150
+ async stop() { }
151
+ }
152
+ class PollResultWithMessageRuntime extends EventEmitter {
153
+ constructor(config) {
154
+ super();
155
+ this.config = config;
156
+ }
157
+ async start() { }
158
+ async submit(_message) {
159
+ this.emit("event", {
160
+ type: "mcp_status",
161
+ sessionId: this.config.sessionId,
162
+ timestamp: new Date(0).toISOString(),
163
+ data: { action: "call_tool", server: "aws-mcp", tool: "poll_message" },
164
+ });
165
+ this.emit("event", {
166
+ type: "mcp_status",
167
+ sessionId: this.config.sessionId,
168
+ timestamp: new Date(0).toISOString(),
169
+ data: {
170
+ action: "tool_result",
171
+ server: "aws-mcp",
172
+ tool: "poll_message",
173
+ result: JSON.stringify({
174
+ directMessages: [{ senderName: "User", content: "ping" }],
175
+ groupMessages: [],
176
+ }),
177
+ },
178
+ });
179
+ }
180
+ async abort() { }
181
+ async stop() { }
182
+ }
124
183
  class CommandStatusRuntime extends EventEmitter {
125
184
  constructor(config) {
126
185
  super();
@@ -156,6 +215,7 @@ class AiRequestRuntime extends EventEmitter {
156
215
  messageCount: 3,
157
216
  toolCount: 2,
158
217
  lastUserMessagePreview: "inspect status",
218
+ estimatedContextTokens: 6800,
159
219
  requestBody: '{"model":"test-model"}',
160
220
  },
161
221
  });
@@ -620,7 +680,11 @@ describe("AcodeSdkAdapter turn completion state", () => {
620
680
  const adapter = new AcodeSdkAdapter();
621
681
  adapter.setRuntimeConstructorForTest(ResponseBodyTurnCompleteRuntime);
622
682
  const events = [];
683
+ const statuses = [];
623
684
  adapter.on("event", (event) => events.push(event));
685
+ adapter.on("status-change", (_sessionId, status, actionInfo) => {
686
+ statuses.push({ status, usage: actionInfo?.usage });
687
+ });
624
688
  await adapter.startSession("session-response-body", {
625
689
  command: "acode",
626
690
  workingDirectory: process.cwd(),
@@ -822,6 +886,63 @@ describe("AcodeSdkAdapter turn completion state", () => {
822
886
  });
823
887
  expect(adapter.getSessionStatus("session-poll-message")).toBe("waiting_input");
824
888
  });
889
+ it("keeps empty poll_message results in waiting_input instead of showing stuck thinking", async () => {
890
+ const adapter = new AcodeSdkAdapter();
891
+ adapter.setRuntimeConstructorForTest(EmptyPollResultRuntime);
892
+ const events = [];
893
+ const statuses = [];
894
+ adapter.on("event", (event) => events.push(event));
895
+ adapter.on("status-change", (_sessionId, status) => statuses.push(status));
896
+ await adapter.startSession("session-empty-poll-result", {
897
+ command: "acode",
898
+ workingDirectory: process.cwd(),
899
+ autoAccept: true,
900
+ extraMcpServers: {},
901
+ envOverrides: {},
902
+ });
903
+ await adapter.sendMessage("session-empty-poll-result", "listen");
904
+ expect(events.map((event) => event.type)).toEqual([
905
+ "tool_use_start",
906
+ "tool_use_end",
907
+ ]);
908
+ expect(statuses).toEqual([
909
+ "starting",
910
+ "waiting_input",
911
+ "thinking",
912
+ "waiting_input",
913
+ "waiting_input",
914
+ ]);
915
+ expect(adapter.getSessionStatus("session-empty-poll-result")).toBe("waiting_input");
916
+ });
917
+ it("shows thinking after poll_message returns actual messages to process", async () => {
918
+ const adapter = new AcodeSdkAdapter();
919
+ adapter.setRuntimeConstructorForTest(PollResultWithMessageRuntime);
920
+ const events = [];
921
+ const statuses = [];
922
+ adapter.on("event", (event) => events.push(event));
923
+ adapter.on("status-change", (_sessionId, status) => statuses.push(status));
924
+ await adapter.startSession("session-poll-result-with-message", {
925
+ command: "acode",
926
+ workingDirectory: process.cwd(),
927
+ autoAccept: true,
928
+ extraMcpServers: {},
929
+ envOverrides: {},
930
+ });
931
+ await adapter.sendMessage("session-poll-result-with-message", "listen");
932
+ expect(events.map((event) => event.type)).toEqual([
933
+ "tool_use_start",
934
+ "tool_use_end",
935
+ ]);
936
+ expect(statuses).toEqual([
937
+ "starting",
938
+ "waiting_input",
939
+ "thinking",
940
+ "waiting_input",
941
+ "thinking",
942
+ "waiting_input",
943
+ ]);
944
+ expect(adapter.getSessionStatus("session-poll-result-with-message")).toBe("waiting_input");
945
+ });
825
946
  it("forwards ACode command status as thinking without changing session status", async () => {
826
947
  const adapter = new AcodeSdkAdapter();
827
948
  adapter.setRuntimeConstructorForTest(CommandStatusRuntime);
@@ -852,7 +973,11 @@ describe("AcodeSdkAdapter turn completion state", () => {
852
973
  const adapter = new AcodeSdkAdapter();
853
974
  adapter.setRuntimeConstructorForTest(AiRequestRuntime);
854
975
  const events = [];
976
+ const statuses = [];
855
977
  adapter.on("event", (event) => events.push(event));
978
+ adapter.on("status-change", (_sessionId, status, actionInfo) => {
979
+ statuses.push({ status, usage: actionInfo?.usage });
980
+ });
856
981
  await adapter.startSession("session-ai-request", {
857
982
  command: "acode",
858
983
  workingDirectory: process.cwd(),
@@ -874,6 +999,10 @@ describe("AcodeSdkAdapter turn completion state", () => {
874
999
  requestBody: '{"model":"test-model"}',
875
1000
  },
876
1001
  });
1002
+ expect(statuses).toContainEqual({
1003
+ status: "thinking",
1004
+ usage: { inputTokens: 6800, outputTokens: 0 },
1005
+ });
877
1006
  });
878
1007
  it("forwards ACode thinking deltas as realtime thinking provider events", async () => {
879
1008
  const adapter = new AcodeSdkAdapter();
@@ -517,7 +517,7 @@ export class OpencodeSdkAdapter extends EventEmitter {
517
517
  data: { usage: session.adapterSession.totalUsage },
518
518
  });
519
519
  session.adapterSession.status = 'waiting_input';
520
- this.emit('status-change', sessionId, 'waiting_input');
520
+ this.emit('status-change', sessionId, 'waiting_input', { usage: session.adapterSession.totalUsage });
521
521
  this.startIdleDetection(sessionId);
522
522
  break;
523
523
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;AAE7C,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,wBAAwB,GAC9B,yBAAyB,CAM3B;AAqMD,wBAAgB,6BAA6B,IAAI,IAAI,CAapD"}
1
+ {"version":3,"file":"runtime-binding.d.ts","sourceRoot":"","sources":["../../src/routes/runtime-binding.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,oBAAoB,4CAAW,CAAC;AAE7C,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,wBAAwB,GAC9B,yBAAyB,CAM3B;AAgPD,wBAAgB,6BAA6B,IAAI,IAAI,CAapD"}
@@ -1,6 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import { validateToken } from "../middleware/auth.js";
3
- import { claimMcpLaunchBinding, getMcpLaunchQueueSize } from "../services/mcp-launch-binding-queue.js";
3
+ import { claimMcpLaunchBinding, getLatestAgentToken, getMcpLaunchQueueSize } from "../services/mcp-launch-binding-queue.js";
4
4
  import { clearRuntimeBinding, getRuntimeAccessToken, getRuntimeBindingPublicState, getRuntimePairingCode, hasRuntimeBinding, saveRuntimeBinding, validateRuntimeBindingToken, validateRuntimePairingCode, } from "../services/runtime-binding.js";
5
5
  import { createLogger } from "../utils/logger.js";
6
6
  const log = createLogger("runtime-binding-route");
@@ -133,6 +133,41 @@ runtimeBindingRouter.post("/binding/claim-agent", (req, res) => {
133
133
  remainingQueueSize: getMcpLaunchQueueSize(workspacePath),
134
134
  });
135
135
  });
136
+ /**
137
+ * MCP node 在 401 后向 bridge 查询最新 session token 的端点。
138
+ * 仅接受本地回环请求,仅返回缓存中最新的 token。
139
+ */
140
+ runtimeBindingRouter.post("/binding/refresh-token", (req, res) => {
141
+ if (!isLoopbackRequest(req)) {
142
+ res.status(403).json({ ok: false, error: "loopback request required" });
143
+ return;
144
+ }
145
+ const { agentId, currentToken } = req.body || {};
146
+ const normalizedAgentId = String(agentId || "").trim();
147
+ if (!normalizedAgentId) {
148
+ res.status(400).json({ ok: false, error: "agentId is required" });
149
+ return;
150
+ }
151
+ const latest = getLatestAgentToken(normalizedAgentId);
152
+ if (!latest) {
153
+ res.status(404).json({ ok: false, error: "no cached token for agent" });
154
+ return;
155
+ }
156
+ // 如果最新 token 与当前 token 相同,说明服务端已撤销,bridge 侧没有更新的
157
+ const normalizedCurrent = String(currentToken || "").trim();
158
+ if (normalizedCurrent && latest.runtimeAccessToken === normalizedCurrent) {
159
+ res.status(409).json({
160
+ ok: false,
161
+ error: "token_unchanged",
162
+ message: "Bridge cached token is the same as current; token may be revoked server-side",
163
+ });
164
+ return;
165
+ }
166
+ res.json({
167
+ ok: true,
168
+ runtimeAccessToken: latest.runtimeAccessToken,
169
+ });
170
+ });
136
171
  runtimeBindingRouter.post("/binding/issue", validateToken, (req, res) => {
137
172
  const { accessToken, instanceId, userId, schedulerBaseUrl } = req.body || {};
138
173
  const nextToken = String(accessToken || "").trim();
@@ -32,4 +32,11 @@ export declare function enqueueMcpLaunchBinding(input: McpLaunchBindingInput): M
32
32
  export declare function claimMcpLaunchBinding(request: McpLaunchBindingClaimRequest): ClaimedMcpLaunchBinding | null;
33
33
  export declare function getMcpLaunchQueueSize(workspacePath: unknown): number;
34
34
  export declare function clearMcpLaunchBindingQueues(): void;
35
+ /**
36
+ * 获取指定 agent 的最新 MCP session token。
37
+ * 供 MCP node 在 401 后向 bridge 查询最新 token 使用。
38
+ */
39
+ export declare function getLatestAgentToken(agentId: string): {
40
+ runtimeAccessToken: string;
41
+ } | null;
35
42
  //# sourceMappingURL=mcp-launch-binding-queue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA2DD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CA+B7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,uBAAuB,GAAG,IAAI,CAkD3G;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAOpE;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
1
+ {"version":3,"file":"mcp-launch-binding-queue.d.ts","sourceRoot":"","sources":["../../src/services/mcp-launch-binding-queue.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,gBAAgB;IAC/D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA2DD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CAyC7F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,uBAAuB,GAAG,IAAI,CAkD3G;AAED,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,CAOpE;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM1F"}
@@ -2,6 +2,12 @@ import path from "node:path";
2
2
  import { schedulerBaseUrl as defaultSchedulerBaseUrl } from "../config.js";
3
3
  import { getRuntimeAccessToken, loadRuntimeBinding, normalizeSchedulerBaseUrl } from "./runtime-binding.js";
4
4
  const LAUNCH_BINDING_TTL_MS = 5 * 60 * 1000;
5
+ /**
6
+ * 每个 agent 最新的 MCP session token 缓存。
7
+ * key = agentId,value = { runtimeAccessToken, updatedAt }
8
+ * 当 /runtime/start 入队新 binding 时更新,供 MCP node 401 后刷新使用。
9
+ */
10
+ const latestAgentTokens = new Map();
5
11
  const launchBindings = [];
6
12
  function normalizeWorkspacePath(workspacePath) {
7
13
  const raw = String(workspacePath || "").trim();
@@ -76,6 +82,14 @@ export function enqueueMcpLaunchBinding(input) {
76
82
  enqueuedAt: new Date().toISOString(),
77
83
  };
78
84
  launchBindings.push(binding);
85
+ // 缓存最新 token,供 MCP node 401 后刷新
86
+ const tokenValue = binding.runtimeAccessToken;
87
+ if (tokenValue) {
88
+ latestAgentTokens.set(agentId, {
89
+ runtimeAccessToken: tokenValue,
90
+ updatedAt: Date.now(),
91
+ });
92
+ }
79
93
  return binding;
80
94
  }
81
95
  export function claimMcpLaunchBinding(request) {
@@ -131,3 +145,14 @@ export function getMcpLaunchQueueSize(workspacePath) {
131
145
  export function clearMcpLaunchBindingQueues() {
132
146
  launchBindings.splice(0, launchBindings.length);
133
147
  }
148
+ /**
149
+ * 获取指定 agent 的最新 MCP session token。
150
+ * 供 MCP node 在 401 后向 bridge 查询最新 token 使用。
151
+ */
152
+ export function getLatestAgentToken(agentId) {
153
+ const cached = latestAgentTokens.get(agentId);
154
+ if (!cached || !cached.runtimeAccessToken) {
155
+ return null;
156
+ }
157
+ return { runtimeAccessToken: cached.runtimeAccessToken };
158
+ }
@@ -33,6 +33,7 @@ export declare class ACodeRuntime extends EventEmitter {
33
33
  private requestChatCompletion;
34
34
  private readChatCompletionStream;
35
35
  private handleToolCalls;
36
+ private callMcpToolWithTimeout;
36
37
  private appendSyntheticMultimodalUserMessage;
37
38
  private appendToolFailureResult;
38
39
  private connectMcpServers;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB3C,OAAO,KAAK,EAOV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA44BpB,qBAAa,YAAa,SAAQ,YAAY;IAmChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAlCnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,KAAK,CAoBX;IAEF,OAAO,CAAC,eAAe,CAAgC;IAEvD,OAAO,CAAC,WAAW,CAAoC;IAEvD,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,sBAAsB,CAAS;gBAEV,MAAM,EAAE,kBAAkB;IAevD,OAAO,CAAC,cAAc;IAatB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwF5B;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAM9B,aAAa;IA2E3B,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,uCAAuC;IACjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B,OAAO,CAAC,kBAAkB;YAaZ,YAAY;YA6DZ,qBAAqB;YAiHrB,wBAAwB;YA6ExB,eAAe;YAwJf,oCAAoC;IAkBlD,OAAO,CAAC,uBAAuB;YAuBjB,iBAAiB;IA2C/B,OAAO,CAAC,kBAAkB;YA2BZ,oBAAoB;CAiBnC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB3C,OAAO,KAAK,EAOV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AAuoCpB,qBAAa,YAAa,SAAQ,YAAY;IAmChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAlCnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,KAAK,CAoBX;IAEF,OAAO,CAAC,eAAe,CAAgC;IAEvD,OAAO,CAAC,WAAW,CAAoC;IAEvD,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,sBAAsB,CAAS;gBAEV,MAAM,EAAE,kBAAkB;IAevD,OAAO,CAAC,cAAc;IAatB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyF5B;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAM9B,aAAa;IA2E3B,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,uCAAuC;IACjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B,OAAO,CAAC,kBAAkB;YAaZ,YAAY;YAoEZ,qBAAqB;YA4JrB,wBAAwB;YA6ExB,eAAe;YAoJf,sBAAsB;YAqCtB,oCAAoC;IAkBlD,OAAO,CAAC,uBAAuB;YAuBjB,iBAAiB;IA2C/B,OAAO,CAAC,kBAAkB;YA2BZ,oBAAoB;CAiBnC"}