aws-runtime-bridge 1.7.50 → 1.8.0

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 (63) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +30 -3
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +427 -105
  4. package/dist/adapter/AcodeSdkAdapter.test.js +1093 -86
  5. package/dist/adapter/ClaudeSdkAdapter.test.js +31 -8
  6. package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
  7. package/dist/adapter/CodexSdkAdapter.js +2 -0
  8. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  9. package/dist/adapter/OpencodeSdkAdapter.js +2 -0
  10. package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
  11. package/dist/adapter/adapter.test.js +2 -2
  12. package/dist/adapter/idle-poll-loop.d.ts +4 -4
  13. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  14. package/dist/adapter/idle-poll-loop.js +3 -3
  15. package/dist/adapter/idle-poll-loop.test.js +15 -1
  16. package/dist/adapter/types.d.ts +25 -6
  17. package/dist/adapter/types.d.ts.map +1 -1
  18. package/dist/adapter/types.js +221 -110
  19. package/dist/adapter/types.test.js +10 -3
  20. package/dist/routes/instance.d.ts.map +1 -1
  21. package/dist/routes/instance.js +6 -7
  22. package/dist/routes/runtime-binding.js +2 -2
  23. package/dist/routes/terminal.d.ts +20 -5
  24. package/dist/routes/terminal.d.ts.map +1 -1
  25. package/dist/routes/terminal.js +420 -170
  26. package/dist/routes/terminal.test.js +380 -208
  27. package/dist/services/mcp-launch-binding-queue.d.ts +1 -0
  28. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  29. package/dist/services/mcp-launch-binding-queue.js +8 -2
  30. package/dist/services/mcp-launch-binding-queue.test.js +47 -4
  31. package/dist/services/session-output.d.ts +10 -6
  32. package/dist/services/session-output.d.ts.map +1 -1
  33. package/dist/services/session-output.js +29 -14
  34. package/dist/services/session-output.test.js +93 -39
  35. package/package/acode/dist/built-in-file-tools.d.ts +1 -1
  36. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  37. package/package/acode/dist/built-in-file-tools.js +388 -50
  38. package/package/acode/dist/runtime.d.ts +11 -3
  39. package/package/acode/dist/runtime.d.ts.map +1 -1
  40. package/package/acode/dist/runtime.js +902 -190
  41. package/package/acode/dist/types.d.ts +18 -5
  42. package/package/acode/dist/types.d.ts.map +1 -1
  43. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  44. package/package/aws-client-agent-mcp/dist/mcp-server.js +3 -2
  45. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  46. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +11 -9
  47. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  48. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
  49. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +1 -0
  50. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
  51. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +2 -0
  52. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  53. package/package/aws-client-agent-mcp/dist/status-reporter.js +31 -3
  54. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  55. package/package/aws-client-agent-mcp/dist/types.d.ts +1 -1
  56. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  57. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  58. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  59. package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -0
  60. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  61. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -0
  62. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
  63. package/package.json +1 -1
@@ -4,44 +4,91 @@
4
4
  * 提供 SDK Agent 会话的启动、输入、调整大小和停止功能
5
5
  * 终端 UI 仅作为 SDK Agent 的消息输入/状态输出载体
6
6
  */
7
- import { spawn } from 'node:child_process';
8
- import { promises as fs } from 'node:fs';
9
- import os from 'node:os';
10
- import path from 'node:path';
11
- import { TextDecoder } from 'node:util';
12
- import { Router } from 'express';
13
- import { v4 as uuidv4 } from 'uuid';
14
- import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath, redactTransientClipboardPastePaths, registerSdkProviders, resolveSdkProviderIdByCommand, } from '../adapter/index.js';
15
- import { validateToken } from '../middleware/auth.js';
16
- import { getAgentProcessManager } from '../services/agent-process-manager.js';
17
- import { getAiConfigService } from '../services/cc-switch-sdk.js';
18
- import { enqueueMcpLaunchBinding } from '../services/mcp-launch-binding-queue.js';
19
- import { findClaudeCodeProcesses, isProcessRunning } from '../services/process-detector.js';
20
- import { sendOutput, sendQuestionRequest, sendStatus } from '../services/session-output.js';
21
- import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, updatePersistedSessionRuntimeState, upsertPersistedSession, } from '../services/terminal-persistence.js';
7
+ import { spawn } from "node:child_process";
8
+ import { promises as fs } from "node:fs";
9
+ import os from "node:os";
10
+ import path from "node:path";
11
+ import { TextDecoder } from "node:util";
12
+ import { Router } from "express";
13
+ import { v4 as uuidv4 } from "uuid";
14
+ import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath, redactTransientClipboardPastePaths, registerSdkProviders, resolveSdkProviderIdByCommand, } from "../adapter/index.js";
15
+ import { validateToken } from "../middleware/auth.js";
16
+ import { getAgentProcessManager } from "../services/agent-process-manager.js";
17
+ import { getAiConfigService } from "../services/cc-switch-sdk.js";
18
+ import { enqueueMcpLaunchBinding } from "../services/mcp-launch-binding-queue.js";
19
+ import { findClaudeCodeProcesses, isProcessRunning, } from "../services/process-detector.js";
20
+ import { sendOutput, sendQuestionRequest, sendStatus, } from "../services/session-output.js";
21
+ import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, updatePersistedSessionRuntimeState, upsertPersistedSession, } from "../services/terminal-persistence.js";
22
22
  export const terminalRouter = Router();
23
23
  // 导出 SDK 会话存储,供其他模块使用(如 sessions.ts)
24
24
  export const sdkSessions = new Map();
25
25
  const TOOL_RESULT_PREVIEW_MAX_LENGTH = 4000;
26
+ const AI_REQUEST_BODY_MARKER = "\n\n请求体:\n";
26
27
  const startingAgents = new Set();
27
28
  export function resolveStatusChangeUsage(actionInfo) {
28
29
  const usage = actionInfo?.usage;
29
30
  if (!usage)
30
31
  return undefined;
31
- const inputTokens = Number.isFinite(usage.inputTokens) ? usage.inputTokens : 0;
32
- const outputTokens = Number.isFinite(usage.outputTokens) ? usage.outputTokens : 0;
32
+ const inputTokens = Number.isFinite(usage.inputTokens)
33
+ ? usage.inputTokens
34
+ : 0;
35
+ const outputTokens = Number.isFinite(usage.outputTokens)
36
+ ? usage.outputTokens
37
+ : 0;
33
38
  if (inputTokens <= 0 && outputTokens <= 0)
34
39
  return undefined;
35
40
  return { inputTokens, outputTokens };
36
41
  }
37
42
  const terminalCommandStates = new Map();
38
- const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = 'gb18030';
39
- const DEFAULT_TERMINAL_OUTPUT_ENCODING = 'utf-8';
43
+ const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = "gb18030";
44
+ const DEFAULT_TERMINAL_OUTPUT_ENCODING = "utf-8";
45
+ const TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT = 100;
46
+ const TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS = 60_000;
47
+ function padDatePart(value) {
48
+ return String(value).padStart(2, "0");
49
+ }
50
+ export function buildAcodeCommandOutputPath(workspacePath, now = new Date()) {
51
+ const year = now.getFullYear();
52
+ const month = padDatePart(now.getMonth() + 1);
53
+ const day = padDatePart(now.getDate());
54
+ const hour = padDatePart(now.getHours());
55
+ const minute = padDatePart(now.getMinutes());
56
+ const timestamp = `${year}${month}${day}-${hour}${minute}${padDatePart(now.getSeconds())}-${String(now.getMilliseconds()).padStart(3, "0")}`;
57
+ return path.join(workspacePath, ".agentswork", "acode", "cmd-dist", `${year}-${month}-${day}`, hour, minute, `${timestamp}-cmd.out`);
58
+ }
59
+ export function countTerminalOutputLines(output) {
60
+ if (!output) {
61
+ return 0;
62
+ }
63
+ const normalized = output.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
64
+ const withoutTrailingLineBreak = normalized.endsWith("\n")
65
+ ? normalized.slice(0, -1)
66
+ : normalized;
67
+ return withoutTrailingLineBreak
68
+ ? withoutTrailingLineBreak.split("\n").length
69
+ : 1;
70
+ }
71
+ export function formatCommandOutputSpilloverNotice(outputPath, lineLimit = TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
72
+ return `\r\n[ACode] 命令输出超过 ${lineLimit} 行,完整输出已写入: ${outputPath}\r\n[ACode] Agent 可自行读取该文件,或在该文件中搜索需要的内容。\r\n`;
73
+ }
74
+ export function formatCommandInactivityTimeoutNotice(timeoutMs = TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS) {
75
+ return `\r\n[ACode] 命令超过 ${Math.round(timeoutMs / 1000)} 秒没有新的输出,已停止执行并保留此前输出。\r\n`;
76
+ }
77
+ async function persistTerminalCommandOutput(state) {
78
+ const outputPath = state.outputPath ||
79
+ buildAcodeCommandOutputPath(state.workspacePath, state.startedAt);
80
+ state.outputPath = outputPath;
81
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
82
+ await fs.writeFile(outputPath, state.chunks.join(""), "utf-8");
83
+ return outputPath;
84
+ }
40
85
  /**
41
86
  * 标准化终端输入:将前端回车符还原为 shell 可执行命令文本。
42
87
  */
43
88
  export function normalizeTerminalCommandInput(value) {
44
- return String(value ?? '').replace(/\r/g, '\n').trim();
89
+ return String(value ?? "")
90
+ .replace(/\r/g, "\n")
91
+ .trim();
45
92
  }
46
93
  /**
47
94
  * 解析终端命令输出解码编码。
@@ -52,7 +99,9 @@ export function resolveTerminalOutputEncoding(platform = process.platform, env =
52
99
  if (configuredEncoding) {
53
100
  return configuredEncoding;
54
101
  }
55
- return platform === 'win32' ? DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING : DEFAULT_TERMINAL_OUTPUT_ENCODING;
102
+ return platform === "win32"
103
+ ? DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING
104
+ : DEFAULT_TERMINAL_OUTPUT_ENCODING;
56
105
  }
57
106
  /**
58
107
  * 创建终端输出流式解码器,避免多字节中文被 chunk 边界截断。
@@ -73,7 +122,7 @@ export function decodeTerminalOutputChunk(decoder, chunk, end = false) {
73
122
  return decoder.decode();
74
123
  }
75
124
  if (!chunk || chunk.length === 0) {
76
- return '';
125
+ return "";
77
126
  }
78
127
  return decoder.decode(chunk, { stream: true });
79
128
  }
@@ -89,9 +138,9 @@ export function parseTerminalDirectoryChangeTarget(command) {
89
138
  if (!rawTarget) {
90
139
  return null;
91
140
  }
92
- const withoutDriveSwitch = rawTarget.replace(/^\/d\s+/i, '').trim();
93
- if ((withoutDriveSwitch.startsWith('"') && withoutDriveSwitch.endsWith('"'))
94
- || (withoutDriveSwitch.startsWith("'") && withoutDriveSwitch.endsWith("'"))) {
141
+ const withoutDriveSwitch = rawTarget.replace(/^\/d\s+/i, "").trim();
142
+ if ((withoutDriveSwitch.startsWith('"') && withoutDriveSwitch.endsWith('"')) ||
143
+ (withoutDriveSwitch.startsWith("'") && withoutDriveSwitch.endsWith("'"))) {
95
144
  return withoutDriveSwitch.slice(1, -1);
96
145
  }
97
146
  return withoutDriveSwitch;
@@ -117,7 +166,7 @@ function findActiveSdkSessionByAgentId(agentId) {
117
166
  return undefined;
118
167
  }
119
168
  async function buildExistingSessionResponse(entry) {
120
- const adapter = adapterRegistry.get(entry.providerId || 'claude-code');
169
+ const adapter = adapterRegistry.get(entry.providerId || "claude-code");
121
170
  const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus);
122
171
  return {
123
172
  sessionId: entry.sessionId,
@@ -129,7 +178,7 @@ async function buildExistingSessionResponse(entry) {
129
178
  agentId: entry.agentId,
130
179
  workspacePath: entry.workspacePath,
131
180
  command: entry.config.command,
132
- mode: 'sdk',
181
+ mode: "sdk",
133
182
  providerSessionId: adapter.getProviderSessionId(entry.sessionId),
134
183
  reused: true,
135
184
  };
@@ -139,12 +188,12 @@ async function buildExistingSessionResponse(entry) {
139
188
  * 主流程:优先使用 adapter 当前细粒度状态,其次使用路由内存记录的最近状态,最后才兼容旧会话回退 running。
140
189
  */
141
190
  export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus) {
142
- const normalizedAdapterStatus = String(adapterStatus || '').trim();
191
+ const normalizedAdapterStatus = String(adapterStatus || "").trim();
143
192
  if (normalizedAdapterStatus) {
144
193
  return normalizedAdapterStatus;
145
194
  }
146
- const normalizedRuntimeStatus = String(currentRuntimeStatus || '').trim();
147
- return normalizedRuntimeStatus || 'running';
195
+ const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
196
+ return normalizedRuntimeStatus || "running";
148
197
  }
149
198
  /**
150
199
  * Decide whether a persisted SDK session can be reused for a start request.
@@ -152,15 +201,15 @@ export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStat
152
201
  */
153
202
  export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessRunning) {
154
203
  if (!session.pid) {
155
- return { reusable: false, reason: 'missing-pid' };
204
+ return { reusable: false, reason: "missing-pid" };
156
205
  }
157
206
  if (!isPidRunning(session.pid)) {
158
- return { reusable: false, reason: 'dead-pid' };
207
+ return { reusable: false, reason: "dead-pid" };
159
208
  }
160
- if (resolveSdkProviderId(session.command) === 'acode') {
161
- return { reusable: false, reason: 'in-process-provider' };
209
+ if (resolveSdkProviderId(session.command) === "acode") {
210
+ return { reusable: false, reason: "in-process-provider" };
162
211
  }
163
- return { reusable: true, reason: 'active-pid' };
212
+ return { reusable: true, reason: "active-pid" };
164
213
  }
165
214
  function stopTerminalCommandProcess(sessionId) {
166
215
  const state = terminalCommandStates.get(sessionId);
@@ -193,7 +242,7 @@ export function formatTerminalPrompt(currentDirectory) {
193
242
  return `\x1b[36m${currentDirectory}>\x1b[0m `;
194
243
  }
195
244
  function resolveDirectoryTarget(currentDirectory, target) {
196
- if (target === '~') {
245
+ if (target === "~") {
197
246
  return os.homedir();
198
247
  }
199
248
  return path.resolve(currentDirectory, target);
@@ -233,7 +282,7 @@ async function handleDirectoryChangeCommand(entry, state, command) {
233
282
  async function executeTerminalCommand(sessionId, commandValue) {
234
283
  const entry = sdkSessions.get(sessionId);
235
284
  if (!entry) {
236
- throw new Error('SDK session not found');
285
+ throw new Error("SDK session not found");
237
286
  }
238
287
  const command = normalizeTerminalCommandInput(commandValue);
239
288
  const state = getTerminalCommandState(entry);
@@ -241,12 +290,12 @@ async function executeTerminalCommand(sessionId, commandValue) {
241
290
  return { started: false, cwd: state.currentDirectory };
242
291
  }
243
292
  if (state.runningProcess) {
244
- throw new Error('A terminal command is already running for this session');
293
+ throw new Error("A terminal command is already running for this session");
245
294
  }
246
295
  const displayCommand = redactTransientClipboardPastePaths(command);
247
296
  appendCommandOutput(entry, `${formatTerminalPrompt(state.currentDirectory)}${displayCommand}\r\n`);
248
297
  if (isTransientClipboardPasteFilePath(command)) {
249
- appendCommandOutput(entry, '[Terminal Warning] 检测到剪贴板临时 paste 文件路径,已跳过执行。请粘贴文件内容或使用显式读取命令。\r\n');
298
+ appendCommandOutput(entry, "[Terminal Warning] 检测到剪贴板临时 paste 文件路径,已跳过执行。请粘贴文件内容或使用显式读取命令。\r\n");
250
299
  appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
251
300
  return { started: false, cwd: state.currentDirectory };
252
301
  }
@@ -254,9 +303,9 @@ async function executeTerminalCommand(sessionId, commandValue) {
254
303
  if (handledDirectoryChange) {
255
304
  return { started: false, cwd: state.currentDirectory };
256
305
  }
257
- await sendStatus(entry.agentId, sessionId, 'tool_using', {
258
- actionType: 'bash',
259
- actionLabel: '终端命令',
306
+ await sendStatus(entry.agentId, sessionId, "tool_using", {
307
+ actionType: "bash",
308
+ actionLabel: "终端命令",
260
309
  actionDetail: displayCommand,
261
310
  });
262
311
  const child = spawn(command, {
@@ -267,19 +316,81 @@ async function executeTerminalCommand(sessionId, commandValue) {
267
316
  state.runningProcess = child;
268
317
  const stdoutDecoder = createTerminalOutputDecoder();
269
318
  const stderrDecoder = createTerminalOutputDecoder();
270
- child.stdout.on('data', (chunk) => {
271
- appendCommandOutput(entry, decodeTerminalOutputChunk(stdoutDecoder, chunk));
319
+ const usesAcodeCommandOutputPolicy = entry.providerId === "acode";
320
+ const outputSpilloverState = {
321
+ workspacePath: entry.workspacePath,
322
+ startedAt: new Date(),
323
+ chunks: [],
324
+ spilled: false,
325
+ };
326
+ let inactivityTimer;
327
+ const clearInactivityTimer = () => {
328
+ if (inactivityTimer) {
329
+ clearTimeout(inactivityTimer);
330
+ inactivityTimer = undefined;
331
+ }
332
+ };
333
+ const resetInactivityTimer = () => {
334
+ clearInactivityTimer();
335
+ inactivityTimer = setTimeout(() => {
336
+ if (state.runningProcess === child) {
337
+ appendCommandOutput(entry, formatCommandInactivityTimeoutNotice());
338
+ child.kill();
339
+ }
340
+ }, TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS);
341
+ };
342
+ const appendProcessOutput = (output, trackActivity = true) => {
343
+ if (!output) {
344
+ return;
345
+ }
346
+ if (usesAcodeCommandOutputPolicy) {
347
+ outputSpilloverState.chunks.push(output);
348
+ }
349
+ if (usesAcodeCommandOutputPolicy && trackActivity) {
350
+ resetInactivityTimer();
351
+ }
352
+ if (usesAcodeCommandOutputPolicy && outputSpilloverState.spilled) {
353
+ return;
354
+ }
355
+ if (usesAcodeCommandOutputPolicy &&
356
+ countTerminalOutputLines(outputSpilloverState.chunks.join("")) >
357
+ TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
358
+ outputSpilloverState.spilled = true;
359
+ void persistTerminalCommandOutput(outputSpilloverState)
360
+ .then((outputPath) => {
361
+ appendCommandOutput(entry, formatCommandOutputSpilloverNotice(outputPath));
362
+ })
363
+ .catch((error) => {
364
+ const message = error instanceof Error ? error.message : String(error);
365
+ appendCommandOutput(entry, `\r\n[ACode] 命令输出落盘失败: ${message}\r\n`);
366
+ });
367
+ return;
368
+ }
369
+ appendCommandOutput(entry, output);
370
+ };
371
+ if (usesAcodeCommandOutputPolicy) {
372
+ resetInactivityTimer();
373
+ }
374
+ child.stdout.on("data", (chunk) => {
375
+ appendProcessOutput(decodeTerminalOutputChunk(stdoutDecoder, chunk));
272
376
  });
273
- child.stderr.on('data', (chunk) => {
274
- appendCommandOutput(entry, decodeTerminalOutputChunk(stderrDecoder, chunk));
377
+ child.stderr.on("data", (chunk) => {
378
+ appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, chunk));
275
379
  });
276
- child.on('error', (error) => {
380
+ child.on("error", (error) => {
277
381
  appendCommandOutput(entry, `\r\n[Terminal Error] ${error.message}\r\n`);
278
382
  });
279
- child.on('close', (code, signal) => {
383
+ child.on("close", (code, signal) => {
384
+ clearInactivityTimer();
280
385
  state.runningProcess = null;
281
- appendCommandOutput(entry, decodeTerminalOutputChunk(stdoutDecoder, undefined, true));
282
- appendCommandOutput(entry, decodeTerminalOutputChunk(stderrDecoder, undefined, true));
386
+ appendProcessOutput(decodeTerminalOutputChunk(stdoutDecoder, undefined, true), false);
387
+ appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, undefined, true), false);
388
+ if (outputSpilloverState.spilled) {
389
+ void persistTerminalCommandOutput(outputSpilloverState).catch((error) => {
390
+ const message = error instanceof Error ? error.message : String(error);
391
+ appendCommandOutput(entry, `\r\n[ACode] 命令输出最终落盘失败: ${message}\r\n`);
392
+ });
393
+ }
283
394
  if (code && code !== 0) {
284
395
  appendCommandOutput(entry, `\r\n[exit ${code}]\r\n`);
285
396
  }
@@ -287,7 +398,7 @@ async function executeTerminalCommand(sessionId, commandValue) {
287
398
  appendCommandOutput(entry, `\r\n[signal ${signal}]\r\n`);
288
399
  }
289
400
  appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
290
- void sendStatus(entry.agentId, sessionId, 'waiting_input');
401
+ void sendStatus(entry.agentId, sessionId, "waiting_input");
291
402
  });
292
403
  return { started: true, cwd: state.currentDirectory };
293
404
  }
@@ -315,7 +426,7 @@ export function buildRuntimeEnv(agentId, workspacePath, baseEnv, envOverrides) {
315
426
  }
316
427
  }
317
428
  env.AWS_AGENT_ID = String(agentId);
318
- env.AWS_MCP_CLAIM_LAUNCH_BINDING = 'true';
429
+ env.AWS_MCP_CLAIM_LAUNCH_BINDING = "true";
319
430
  return env;
320
431
  }
321
432
  function stripWrappingQuotes(value) {
@@ -340,7 +451,7 @@ export async function resolveSdkInputMessage(input) {
340
451
  if (!path.isAbsolute(filePath)) {
341
452
  return message;
342
453
  }
343
- return fs.readFile(filePath, 'utf-8');
454
+ return fs.readFile(filePath, "utf-8");
344
455
  }
345
456
  /**
346
457
  * 主流程:将已持久化的 ClaudeCode AI 配置合并到启动配置,再让请求级覆盖保持最高优先级。
@@ -363,9 +474,9 @@ export function buildClaudeCodeLaunchConfig(savedClaudeConfig, requestEnvOverrid
363
474
  envOverrides[normalizedKey] = normalizedValue;
364
475
  }
365
476
  }
366
- const model = envOverrides.ANTHROPIC_MODEL?.trim()
367
- || envOverrides.CODEX_MODEL?.trim()
368
- || undefined;
477
+ const model = envOverrides.ANTHROPIC_MODEL?.trim() ||
478
+ envOverrides.CODEX_MODEL?.trim() ||
479
+ undefined;
369
480
  return { envOverrides, model };
370
481
  }
371
482
  /**
@@ -385,7 +496,7 @@ function formatToolResultForTimeline(result) {
385
496
  if (result === undefined || result === null) {
386
497
  return undefined;
387
498
  }
388
- const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
499
+ const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
389
500
  const normalized = text.trim();
390
501
  if (!normalized) {
391
502
  return undefined;
@@ -397,18 +508,20 @@ function formatToolResultForTimeline(result) {
397
508
  }
398
509
  function isMcpProviderEvent(event) {
399
510
  const actionType = event.data.actionType;
400
- if (actionType === 'mcp') {
511
+ if (actionType === "mcp") {
401
512
  return true;
402
513
  }
403
- const toolName = String(event.data.toolName || '').toLowerCase();
404
- return toolName.startsWith('mcp__') || toolName.includes('-mcp__') || toolName.includes('mcp_');
514
+ const toolName = String(event.data.toolName || "").toLowerCase();
515
+ return (toolName.startsWith("mcp__") ||
516
+ toolName.includes("-mcp__") ||
517
+ toolName.includes("mcp_"));
405
518
  }
406
519
  /**
407
520
  * 将 SDK 工具结束事件转换为时间线返回数据状态。
408
521
  * 主流程:格式化工具结果 -> 保留 MCP 专用展示 -> 其它工具按动作类型追加到对应时间线事件。
409
522
  */
410
523
  export function buildToolResultTimelineActionInfo(event) {
411
- if (event.type !== 'tool_use_end') {
524
+ if (event.type !== "tool_use_end") {
412
525
  return undefined;
413
526
  }
414
527
  const actionResult = formatToolResultForTimeline(event.data.toolResult ?? event.data.actionResult);
@@ -417,13 +530,13 @@ export function buildToolResultTimelineActionInfo(event) {
417
530
  }
418
531
  const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
419
532
  const actionType = event.data.actionType || inferredAction.actionType;
420
- if (actionType === 'idle') {
533
+ if (actionType === "idle") {
421
534
  return undefined;
422
535
  }
423
- if (actionType === 'mcp' && isMcpProviderEvent(event)) {
536
+ if (actionType === "mcp" && isMcpProviderEvent(event)) {
424
537
  return {
425
- actionType: 'mcp',
426
- actionLabel: 'MCP返回',
538
+ actionType: "mcp",
539
+ actionLabel: "MCP返回",
427
540
  actionDetail: event.data.actionDetail || event.data.toolName,
428
541
  actionId: event.data.actionId || event.data.toolUseId,
429
542
  actionResult,
@@ -432,17 +545,122 @@ export function buildToolResultTimelineActionInfo(event) {
432
545
  return {
433
546
  actionType,
434
547
  actionLabel: event.data.actionLabel || inferredAction.actionLabel,
435
- actionDetail: event.data.actionDetail || inferredAction.actionDetail || event.data.toolName,
548
+ actionDetail: event.data.actionDetail ||
549
+ inferredAction.actionDetail ||
550
+ event.data.toolName,
436
551
  actionId: event.data.actionId || event.data.toolUseId,
437
552
  actionResult,
438
553
  };
439
554
  }
440
555
  export function formatSdkOutputEvent(event) {
441
- if (event.type === 'text_delta' || event.type === 'thinking') {
556
+ if (event.type === "ai_request") {
557
+ const model = event.data.model ? ` ${event.data.model}` : "";
558
+ const preview = event.data.lastUserMessagePreview
559
+ ? `:${event.data.lastUserMessagePreview}`
560
+ : "";
561
+ return `\r\n[ACode] 正在请求模型${model}${preview}\r\n`;
562
+ }
563
+ if (event.type === "text_delta") {
442
564
  return event.data.text || undefined;
443
565
  }
444
- if (event.type === 'error') {
445
- return event.data.text ? `\r\n[SDK Error] ${event.data.text}\r\n` : undefined;
566
+ if (event.type === "thinking") {
567
+ return event.data.text || undefined;
568
+ }
569
+ if (event.type === "error") {
570
+ return event.data.text
571
+ ? `\r\n[SDK Error] ${event.data.text}\r\n`
572
+ : undefined;
573
+ }
574
+ return undefined;
575
+ }
576
+ function formatAiRequestDetail(data) {
577
+ const lines = [
578
+ data.model ? `模型: ${data.model}` : undefined,
579
+ data.endpoint ? `接口: ${data.endpoint}` : undefined,
580
+ typeof data.stream === "boolean"
581
+ ? `流式: ${data.stream ? "是" : "否"}`
582
+ : undefined,
583
+ typeof data.messageCount === "number"
584
+ ? `上下文消息: ${data.messageCount}`
585
+ : undefined,
586
+ typeof data.toolCount === "number"
587
+ ? `可用工具: ${data.toolCount}`
588
+ : undefined,
589
+ data.lastUserMessagePreview
590
+ ? `最后请求: ${data.lastUserMessagePreview}`
591
+ : undefined,
592
+ ].filter((line) => Boolean(line));
593
+ const base = lines.join("\n") || "ACode 正在发送 AI 请求";
594
+ return data.requestBody
595
+ ? `${base}${AI_REQUEST_BODY_MARKER}${data.requestBody}`
596
+ : base;
597
+ }
598
+ /**
599
+ * 主干流程:把 provider 事件转换为面板运行状态。
600
+ *
601
+ * text_delta 表示助手正在生成;thinking 表示推理/内部过程。
602
+ * tool_use_start 表示正在执行工具;turn_complete 才表示真正空闲。
603
+ */
604
+ export function resolveRuntimeStatusFromProviderEvent(event) {
605
+ if (event.type === "ai_request") {
606
+ return {
607
+ status: "thinking",
608
+ actionInfo: {
609
+ actionType: "ai_request",
610
+ actionLabel: "AI请求",
611
+ actionDetail: formatAiRequestDetail(event.data),
612
+ },
613
+ };
614
+ }
615
+ if (event.type === "thinking" || event.type === "text_delta") {
616
+ return { status: "thinking" };
617
+ }
618
+ if (event.type === "tool_use_start") {
619
+ const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
620
+ if (event.data.actionType === "idle" ||
621
+ inferredAction.actionType === "idle") {
622
+ return {
623
+ status: "waiting_input",
624
+ actionInfo: {
625
+ actionType: event.data.actionType || inferredAction.actionType,
626
+ actionLabel: event.data.actionLabel || inferredAction.actionLabel,
627
+ actionDetail: event.data.actionDetail ||
628
+ inferredAction.actionDetail ||
629
+ event.data.toolName,
630
+ actionId: event.data.actionId || event.data.toolUseId,
631
+ },
632
+ };
633
+ }
634
+ return {
635
+ status: "tool_using",
636
+ actionInfo: {
637
+ actionType: event.data.actionType || inferredAction.actionType,
638
+ actionLabel: event.data.actionLabel || inferredAction.actionLabel,
639
+ actionDetail: event.data.actionDetail ||
640
+ inferredAction.actionDetail ||
641
+ event.data.toolName,
642
+ actionId: event.data.actionId || event.data.toolUseId,
643
+ },
644
+ };
645
+ }
646
+ if (event.type === "turn_complete") {
647
+ const responseActionInfo = event.data
648
+ .responseBody
649
+ ? {
650
+ actionType: "ai_request",
651
+ actionLabel: "AI响应",
652
+ actionResult: event.data.responseBody,
653
+ usage: event.data.usage,
654
+ }
655
+ : undefined;
656
+ return {
657
+ status: "waiting_input",
658
+ actionInfo: responseActionInfo ||
659
+ (event.data.usage ? { usage: event.data.usage } : undefined),
660
+ };
661
+ }
662
+ if (event.type === "error") {
663
+ return { status: "error" };
446
664
  }
447
665
  return undefined;
448
666
  }
@@ -452,7 +670,7 @@ function forwardSdkOutputEvent(event, entry) {
452
670
  return;
453
671
  }
454
672
  entry.seq += 1;
455
- void sendOutput(entry.agentId, output, event.sessionId, entry.seq);
673
+ void sendOutput(entry.agentId, output, event.sessionId, entry.seq, { sourceEventType: event.type }, { runtimeAccessToken: entry.runtimeAccessToken });
456
674
  }
457
675
  export function resolveSdkProviderId(command) {
458
676
  return resolveSdkProviderIdByCommand(command);
@@ -466,45 +684,52 @@ function ensureAdapterInitialized() {
466
684
  adapterInitialized = true;
467
685
  }
468
686
  function wireSdkAdapterEvents(adapter, definition) {
469
- adapter.on('event', (event) => {
687
+ adapter.on("event", (event) => {
470
688
  const entry = sdkSessions.get(event.sessionId);
471
689
  if (entry) {
472
690
  console.log(`[${definition.displayName} Adapter] Event: ${event.type} for session ${event.sessionId}`);
691
+ const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event);
692
+ if (runtimeStatus) {
693
+ void sendStatus(entry.agentId, event.sessionId, runtimeStatus.status, runtimeStatus.actionInfo, resolveStatusChangeUsage(runtimeStatus.actionInfo), { runtimeAccessToken: entry.runtimeAccessToken });
694
+ }
473
695
  forwardSdkOutputEvent(event, entry);
474
696
  const toolResultActionInfo = buildToolResultTimelineActionInfo(event);
475
697
  if (toolResultActionInfo) {
476
- void sendStatus(entry.agentId, event.sessionId, 'tool_result', toolResultActionInfo);
477
- }
478
- if (event.type === 'turn_complete' && event.data.usage) {
479
- sendStatus(entry.agentId, event.sessionId, 'waiting_input', undefined, resolveStatusChangeUsage({ usage: event.data.usage }));
698
+ void sendStatus(entry.agentId, event.sessionId, "tool_result", toolResultActionInfo, undefined, {
699
+ runtimeAccessToken: entry.runtimeAccessToken,
700
+ });
480
701
  }
481
702
  }
482
703
  });
483
- adapter.on('status-change', (sessionId, status, actionInfo) => {
704
+ adapter.on("status-change", (sessionId, status, actionInfo) => {
484
705
  const entry = sdkSessions.get(sessionId);
485
706
  if (entry) {
486
707
  entry.currentRuntimeStatus = status;
487
708
  entry.currentRuntimeActionType = actionInfo?.actionType;
488
709
  entry.currentRuntimeActionLabel = actionInfo?.actionLabel;
489
710
  entry.currentRuntimeActionDetail = actionInfo?.actionDetail;
490
- console.log(`[${definition.displayName} Adapter] Status changed: ${status} for agent ${entry.agentId}`, actionInfo || '');
711
+ console.log(`[${definition.displayName} Adapter] Status changed: ${status} for agent ${entry.agentId}`, actionInfo || "");
491
712
  void updatePersistedSessionRuntimeState(sessionId, {
492
713
  runtimeStatus: status,
493
714
  runtimeActionType: actionInfo?.actionType,
494
715
  runtimeActionLabel: actionInfo?.actionLabel,
495
716
  runtimeActionDetail: actionInfo?.actionDetail,
496
717
  });
497
- void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo));
718
+ void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo), {
719
+ runtimeAccessToken: entry.runtimeAccessToken,
720
+ });
498
721
  }
499
722
  });
500
- adapter.on('ask-user-question', (data) => {
723
+ adapter.on("ask-user-question", (data) => {
501
724
  const entry = sdkSessions.get(data.sessionId);
502
725
  if (entry) {
503
726
  console.log(`[${definition.displayName} Adapter] AskUserQuestion for agent ${entry.agentId}`);
504
- void sendQuestionRequest(entry.agentId, data.sessionId, Array.isArray(data.questions) ? data.questions : []);
727
+ void sendQuestionRequest(entry.agentId, data.sessionId, Array.isArray(data.questions) ? data.questions : [], {
728
+ runtimeAccessToken: entry.runtimeAccessToken,
729
+ });
505
730
  }
506
731
  });
507
- adapter.on('permission-request', (data) => {
732
+ adapter.on("permission-request", (data) => {
508
733
  const entry = sdkSessions.get(data.sessionId);
509
734
  if (entry) {
510
735
  console.log(`[${definition.displayName} Adapter] Permission request: ${data.toolName} for agent ${entry.agentId}`);
@@ -515,10 +740,10 @@ function wireSdkAdapterEvents(adapter, definition) {
515
740
  * 启动 SDK Agent 会话
516
741
  * POST /runtime/start
517
742
  */
518
- terminalRouter.post('/start', validateToken, async (req, res) => {
519
- const { agentId, workspacePath, mode = 'sdk', } = req.body || {};
743
+ terminalRouter.post("/start", validateToken, async (req, res) => {
744
+ const { agentId, workspacePath, mode = "sdk" } = req.body || {};
520
745
  if (!agentId || !workspacePath) {
521
- res.status(400).json({ error: 'agentId and workspacePath are required' });
746
+ res.status(400).json({ error: "agentId and workspacePath are required" });
522
747
  return;
523
748
  }
524
749
  enqueueMcpLaunchBinding({
@@ -529,7 +754,7 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
529
754
  userId: req.body?.userId,
530
755
  schedulerBaseUrl: req.body?.schedulerBaseUrl,
531
756
  });
532
- if (mode && mode !== 'sdk') {
757
+ if (mode && mode !== "sdk") {
533
758
  console.warn(`[Runtime] Unsupported runtime mode "${mode}" requested for agent ${agentId}; starting SDK session instead.`);
534
759
  }
535
760
  await startSdkSession(req, res);
@@ -545,11 +770,12 @@ async function startSdkSession(req, res) {
545
770
  // 空闲命令配置
546
771
  idleInputAutoCommand, nonInputAutoCommand, } = req.body || {};
547
772
  let sessionId = null;
548
- const normalizedAgentId = String(agentId || '').trim();
773
+ const normalizedAgentId = String(agentId || "").trim();
774
+ const runtimeAccessToken = String(req.body?.runtimeAccessToken || "").trim() || undefined;
549
775
  try {
550
776
  ensureAdapterInitialized();
551
777
  if (!normalizedAgentId) {
552
- res.status(400).json({ error: 'agentId is required' });
778
+ res.status(400).json({ error: "agentId is required" });
553
779
  return;
554
780
  }
555
781
  const activeSession = findActiveSdkSessionByAgentId(normalizedAgentId);
@@ -561,8 +787,8 @@ async function startSdkSession(req, res) {
561
787
  if (persistedSession) {
562
788
  const reuseDecision = evaluatePersistedSessionReuse(persistedSession);
563
789
  if (!reuseDecision.reusable) {
564
- console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, `
565
- + `sessionId=${persistedSession.sessionId}, reason=${reuseDecision.reason}`);
790
+ console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, ` +
791
+ `sessionId=${persistedSession.sessionId}, reason=${reuseDecision.reason}`);
566
792
  await removePersistedSession(persistedSession.sessionId);
567
793
  }
568
794
  else {
@@ -572,7 +798,7 @@ async function startSdkSession(req, res) {
572
798
  agentId: persistedSession.agentId,
573
799
  workspacePath: persistedSession.workspacePath,
574
800
  command: persistedSession.command,
575
- mode: persistedSession.mode || 'sdk',
801
+ mode: persistedSession.mode || "sdk",
576
802
  pid: persistedSession.pid,
577
803
  reused: true,
578
804
  persistedOnly: true,
@@ -581,26 +807,28 @@ async function startSdkSession(req, res) {
581
807
  }
582
808
  }
583
809
  if (startingAgents.has(normalizedAgentId)) {
584
- res.status(409).json({ error: 'SDK session is already starting for this agent' });
810
+ res
811
+ .status(409)
812
+ .json({ error: "SDK session is already starting for this agent" });
585
813
  return;
586
814
  }
587
815
  startingAgents.add(normalizedAgentId);
588
816
  // 根据命令选择对应的 adapter
589
- const normalizedCommand = (command || 'acode').toLowerCase();
817
+ const normalizedCommand = (command || "acode").toLowerCase();
590
818
  const providerId = resolveSdkProviderId(command);
591
819
  // ★ 特征日志:明确输出选择的 provider
592
820
  console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
593
821
  const adapter = adapterRegistry.get(providerId);
594
822
  sessionId = uuidv4();
595
- const savedClaudeConfig = providerId === 'claude-code'
823
+ const savedClaudeConfig = providerId === "claude-code"
596
824
  ? await readSavedClaudeCodeLaunchConfig()
597
825
  : null;
598
- const claudeLaunchConfig = providerId === 'claude-code'
826
+ const claudeLaunchConfig = providerId === "claude-code"
599
827
  ? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
600
828
  : buildClaudeCodeLaunchConfig(null, envOverrides);
601
829
  // 构建SDK配置
602
830
  const config = {
603
- command: command || 'acode',
831
+ command: command || "acode",
604
832
  workingDirectory: workspacePath,
605
833
  autoAccept: Boolean(autoAccept),
606
834
  initialPrompt,
@@ -618,22 +846,30 @@ async function startSdkSession(req, res) {
618
846
  config,
619
847
  providerId, // 记录使用的 provider
620
848
  seq: 0,
849
+ runtimeAccessToken,
621
850
  });
622
851
  // 启动 SDK 会话
623
852
  await adapter.startSession(sessionId, config);
853
+ // 尽早注入空闲命令,避免后续 PID 探测/进程注册延迟 poll_message 空闲轮询。
854
+ if (idleInputAutoCommand || nonInputAutoCommand) {
855
+ adapter.setIdleCommands?.(sessionId, {
856
+ idleInputCommand: idleInputAutoCommand || "",
857
+ nonInputCommand: nonInputAutoCommand || "",
858
+ });
859
+ }
624
860
  // ★ 尝试获取 SDK 进程的 PID(用于孤儿进程检测)
625
861
  // SDK 不直接暴露 PID,通过进程检测服务查找匹配的进程
626
862
  let sdkPid;
627
863
  try {
628
864
  // 等待一小段时间让进程启动
629
- await new Promise(resolve => setTimeout(resolve, 500));
865
+ await new Promise((resolve) => setTimeout(resolve, 500));
630
866
  // 尝试通过 adapter 获取 PID(如果支持)
631
867
  sdkPid = adapter.getSessionPid?.(sessionId);
632
868
  // 如果 adapter 不支持,尝试通过进程检测服务查找
633
869
  if (!sdkPid) {
634
870
  const processes = findClaudeCodeProcesses();
635
871
  // 查找匹配 agentId 或工作目录的进程
636
- const matched = processes.find(p => p.agentId === agentId ||
872
+ const matched = processes.find((p) => p.agentId === agentId ||
637
873
  p.cwd === workspacePath ||
638
874
  p.command.includes(workspacePath));
639
875
  if (matched) {
@@ -653,9 +889,9 @@ async function startSdkSession(req, res) {
653
889
  workspacePath,
654
890
  command: config.command,
655
891
  startedAt: new Date().toISOString(),
656
- status: 'running',
892
+ status: "running",
657
893
  runtimeStatus: startedStatus,
658
- mode: 'sdk',
894
+ mode: "sdk",
659
895
  pid: sdkPid,
660
896
  providerSessionId: adapter.getProviderSessionId(sessionId),
661
897
  });
@@ -666,18 +902,11 @@ async function startSdkSession(req, res) {
666
902
  agentId: normalizedAgentId,
667
903
  sessionId,
668
904
  pid: sdkPid,
669
- mode: 'sdk',
905
+ mode: "sdk",
670
906
  workspacePath,
671
907
  command: config.command,
672
908
  });
673
909
  }
674
- // 存储空闲命令配置(用于SDK模式的空闲检测)
675
- if (idleInputAutoCommand || nonInputAutoCommand) {
676
- adapter.setIdleCommands?.(sessionId, {
677
- idleInputCommand: idleInputAutoCommand || '',
678
- nonInputCommand: nonInputAutoCommand || '',
679
- });
680
- }
681
910
  res.json({
682
911
  sessionId,
683
912
  status: startedStatus,
@@ -685,7 +914,7 @@ async function startSdkSession(req, res) {
685
914
  agentId,
686
915
  workspacePath,
687
916
  command: config.command,
688
- mode: 'sdk',
917
+ mode: "sdk",
689
918
  pid: sdkPid, // ★ 返回 PID 信息
690
919
  });
691
920
  }
@@ -695,7 +924,9 @@ async function startSdkSession(req, res) {
695
924
  await removePersistedSession(sessionId);
696
925
  }
697
926
  const errorMessage = error instanceof Error ? error.message : String(error);
698
- res.status(500).json({ error: `Failed to start SDK session: ${errorMessage}` });
927
+ res
928
+ .status(500)
929
+ .json({ error: `Failed to start SDK session: ${errorMessage}` });
699
930
  }
700
931
  finally {
701
932
  if (normalizedAgentId) {
@@ -708,20 +939,20 @@ async function startSdkSession(req, res) {
708
939
  * 发送消息到 SDK 会话
709
940
  * POST /runtime/sdk/message
710
941
  */
711
- terminalRouter.post('/sdk/message', validateToken, async (req, res) => {
942
+ terminalRouter.post("/sdk/message", validateToken, async (req, res) => {
712
943
  const { sessionId, message } = req.body || {};
713
944
  if (!sessionId || !message) {
714
- res.status(400).json({ error: 'sessionId and message are required' });
945
+ res.status(400).json({ error: "sessionId and message are required" });
715
946
  return;
716
947
  }
717
948
  const entry = sdkSessions.get(sessionId);
718
949
  if (!entry) {
719
- res.status(404).json({ error: 'SDK session not found' });
950
+ res.status(404).json({ error: "SDK session not found" });
720
951
  return;
721
952
  }
722
953
  try {
723
954
  // 根据 providerId 获取对应的 adapter
724
- const providerId = entry.providerId || 'claude-code';
955
+ const providerId = entry.providerId || "claude-code";
725
956
  const adapter = adapterRegistry.get(providerId);
726
957
  await adapter.sendMessage(sessionId, message);
727
958
  res.json({ ok: true });
@@ -735,20 +966,20 @@ terminalRouter.post('/sdk/message', validateToken, async (req, res) => {
735
966
  * 回答 AskUserQuestion
736
967
  * POST /runtime/sdk/answer
737
968
  */
738
- terminalRouter.post('/sdk/answer', validateToken, async (req, res) => {
969
+ terminalRouter.post("/sdk/answer", validateToken, async (req, res) => {
739
970
  const { sessionId, answers } = req.body || {};
740
971
  if (!sessionId || !answers) {
741
- res.status(400).json({ error: 'sessionId and answers are required' });
972
+ res.status(400).json({ error: "sessionId and answers are required" });
742
973
  return;
743
974
  }
744
975
  const entry = sdkSessions.get(sessionId);
745
976
  if (!entry) {
746
- res.status(404).json({ error: 'SDK session not found' });
977
+ res.status(404).json({ error: "SDK session not found" });
747
978
  return;
748
979
  }
749
980
  try {
750
981
  // 根据 providerId 获取对应的 adapter
751
- const providerId = entry.providerId || 'claude-code';
982
+ const providerId = entry.providerId || "claude-code";
752
983
  const adapter = adapterRegistry.get(providerId);
753
984
  if (adapter.sendQuestionAnswer) {
754
985
  await adapter.sendQuestionAnswer(sessionId, answers);
@@ -764,20 +995,20 @@ terminalRouter.post('/sdk/answer', validateToken, async (req, res) => {
764
995
  * 确认权限请求
765
996
  * POST /runtime/sdk/confirm
766
997
  */
767
- terminalRouter.post('/sdk/confirm', validateToken, async (req, res) => {
998
+ terminalRouter.post("/sdk/confirm", validateToken, async (req, res) => {
768
999
  const { sessionId, accept } = req.body || {};
769
1000
  if (!sessionId) {
770
- res.status(400).json({ error: 'sessionId is required' });
1001
+ res.status(400).json({ error: "sessionId is required" });
771
1002
  return;
772
1003
  }
773
1004
  const entry = sdkSessions.get(sessionId);
774
1005
  if (!entry) {
775
- res.status(404).json({ error: 'SDK session not found' });
1006
+ res.status(404).json({ error: "SDK session not found" });
776
1007
  return;
777
1008
  }
778
1009
  try {
779
1010
  // 根据 providerId 获取对应的 adapter
780
- const providerId = entry.providerId || 'claude-code';
1011
+ const providerId = entry.providerId || "claude-code";
781
1012
  const adapter = adapterRegistry.get(providerId);
782
1013
  await adapter.sendConfirmation(sessionId, Boolean(accept));
783
1014
  res.json({ ok: true });
@@ -791,20 +1022,20 @@ terminalRouter.post('/sdk/confirm', validateToken, async (req, res) => {
791
1022
  * 中止当前轮次
792
1023
  * POST /runtime/sdk/abort
793
1024
  */
794
- terminalRouter.post('/sdk/abort', validateToken, async (req, res) => {
1025
+ terminalRouter.post("/sdk/abort", validateToken, async (req, res) => {
795
1026
  const { sessionId } = req.body || {};
796
1027
  if (!sessionId) {
797
- res.status(400).json({ error: 'sessionId is required' });
1028
+ res.status(400).json({ error: "sessionId is required" });
798
1029
  return;
799
1030
  }
800
1031
  const entry = sdkSessions.get(sessionId);
801
1032
  if (!entry) {
802
- res.status(404).json({ error: 'SDK session not found' });
1033
+ res.status(404).json({ error: "SDK session not found" });
803
1034
  return;
804
1035
  }
805
1036
  try {
806
1037
  // 根据 providerId 获取对应的 adapter
807
- const providerId = entry.providerId || 'claude-code';
1038
+ const providerId = entry.providerId || "claude-code";
808
1039
  const adapter = adapterRegistry.get(providerId);
809
1040
  await adapter.abortCurrentTurn(sessionId);
810
1041
  res.json({ ok: true });
@@ -818,20 +1049,20 @@ terminalRouter.post('/sdk/abort', validateToken, async (req, res) => {
818
1049
  * 停止 SDK 会话
819
1050
  * POST /runtime/sdk/stop
820
1051
  */
821
- terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
1052
+ terminalRouter.post("/sdk/stop", validateToken, async (req, res) => {
822
1053
  const { sessionId } = req.body || {};
823
1054
  if (!sessionId) {
824
- res.status(400).json({ error: 'sessionId is required' });
1055
+ res.status(400).json({ error: "sessionId is required" });
825
1056
  return;
826
1057
  }
827
1058
  const entry = sdkSessions.get(sessionId);
828
1059
  if (!entry) {
829
- res.json({ ok: true, status: 'stopped' });
1060
+ res.json({ ok: true, status: "stopped" });
830
1061
  return;
831
1062
  }
832
1063
  try {
833
1064
  // 根据 providerId 获取对应的 adapter
834
- const providerId = entry.providerId || 'claude-code';
1065
+ const providerId = entry.providerId || "claude-code";
835
1066
  const adapter = adapterRegistry.get(providerId);
836
1067
  await adapter.terminateSession(sessionId);
837
1068
  sdkSessions.delete(sessionId);
@@ -840,8 +1071,10 @@ terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
840
1071
  // ★ 从进程管理器中移除
841
1072
  const processManager = getAgentProcessManager();
842
1073
  await processManager.removeProcess(entry.agentId);
843
- await sendStatus(entry.agentId, null, 'stopped');
844
- res.json({ ok: true, status: 'stopped' });
1074
+ await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
1075
+ runtimeAccessToken: entry.runtimeAccessToken,
1076
+ });
1077
+ res.json({ ok: true, status: "stopped" });
845
1078
  }
846
1079
  catch (error) {
847
1080
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -852,20 +1085,20 @@ terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
852
1085
  * 获取 SDK 会话状态
853
1086
  * GET /runtime/sdk/status/:sessionId
854
1087
  */
855
- terminalRouter.get('/sdk/status/:sessionId', validateToken, (req, res) => {
1088
+ terminalRouter.get("/sdk/status/:sessionId", validateToken, (req, res) => {
856
1089
  const sessionId = req.params.sessionId;
857
1090
  if (!sessionId) {
858
- res.status(400).json({ error: 'sessionId is required' });
1091
+ res.status(400).json({ error: "sessionId is required" });
859
1092
  return;
860
1093
  }
861
1094
  const entry = sdkSessions.get(sessionId);
862
1095
  if (!entry) {
863
- res.status(404).json({ error: 'SDK session not found' });
1096
+ res.status(404).json({ error: "SDK session not found" });
864
1097
  return;
865
1098
  }
866
1099
  try {
867
1100
  // 根据 providerId 获取对应的 adapter
868
- const providerId = entry.providerId || 'claude-code';
1101
+ const providerId = entry.providerId || "claude-code";
869
1102
  const adapter = adapterRegistry.get(providerId);
870
1103
  const status = adapter.getSessionStatus(sessionId);
871
1104
  const conversation = adapter.getConversation(sessionId);
@@ -888,15 +1121,15 @@ terminalRouter.get('/sdk/status/:sessionId', validateToken, (req, res) => {
888
1121
  * 主流程:校验 session/command -> 读取会话 workspacePath -> 在该 cwd 下启动 shell 命令 -> 通过终端输出回调流式返回。
889
1122
  * POST /runtime/command
890
1123
  */
891
- terminalRouter.post('/command', validateToken, async (req, res) => {
1124
+ terminalRouter.post("/command", validateToken, async (req, res) => {
892
1125
  const { sessionId, command } = req.body || {};
893
1126
  if (!sessionId || command === undefined || command === null) {
894
- res.status(400).json({ error: 'sessionId and command are required' });
1127
+ res.status(400).json({ error: "sessionId and command are required" });
895
1128
  return;
896
1129
  }
897
1130
  try {
898
1131
  const result = await executeTerminalCommand(String(sessionId), command);
899
- res.json({ ok: true, mode: 'terminal', ...result });
1132
+ res.json({ ok: true, mode: "terminal", ...result });
900
1133
  }
901
1134
  catch (error) {
902
1135
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -910,31 +1143,33 @@ terminalRouter.post('/command', validateToken, async (req, res) => {
910
1143
  * 主流程:校验 session/input -> 定位 SDK 会话 -> 通过 provider adapter 发送用户消息。
911
1144
  * POST /runtime/input
912
1145
  */
913
- terminalRouter.post('/input', validateToken, async (req, res) => {
1146
+ terminalRouter.post("/input", validateToken, async (req, res) => {
914
1147
  const { sessionId, input, mode } = req.body || {};
915
1148
  if (!sessionId || input === undefined || input === null) {
916
- res.status(400).json({ error: 'sessionId and input are required' });
1149
+ res.status(400).json({ error: "sessionId and input are required" });
917
1150
  return;
918
1151
  }
919
1152
  const entry = sdkSessions.get(sessionId);
920
1153
  if (!entry) {
921
- res.status(404).json({ error: 'SDK session not found' });
1154
+ res.status(404).json({ error: "SDK session not found" });
922
1155
  return;
923
1156
  }
924
1157
  try {
925
- const providerId = entry.providerId || 'claude-code';
1158
+ const providerId = entry.providerId || "claude-code";
926
1159
  const adapter = adapterRegistry.get(providerId);
927
- if (mode === 'shortcut') {
1160
+ if (mode === "shortcut") {
928
1161
  if (!adapter.sendShortcutInput) {
929
- res.status(400).json({ error: 'shortcut input is not supported by this SDK provider' });
1162
+ res.status(400).json({
1163
+ error: "shortcut input is not supported by this SDK provider",
1164
+ });
930
1165
  return;
931
1166
  }
932
1167
  await adapter.sendShortcutInput(sessionId, String(input));
933
- res.json({ ok: true, mode: 'shortcut' });
1168
+ res.json({ ok: true, mode: "shortcut" });
934
1169
  return;
935
1170
  }
936
1171
  await adapter.sendMessage(sessionId, await resolveSdkInputMessage(input));
937
- res.json({ ok: true, mode: 'sdk' });
1172
+ res.json({ ok: true, mode: "sdk" });
938
1173
  }
939
1174
  catch (error) {
940
1175
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -947,22 +1182,22 @@ terminalRouter.post('/input', validateToken, async (req, res) => {
947
1182
  * 主流程:按 agentId 定位 SDK 会话 -> 更新 adapter 内存配置 -> 同步持久化会话字段。
948
1183
  * POST /runtime/update-auto-commands
949
1184
  */
950
- terminalRouter.post('/update-auto-commands', validateToken, async (req, res) => {
951
- const { agentId, idleInputAutoCommand = '', nonInputAutoCommand = '' } = req.body || {};
952
- const normalizedAgentId = String(agentId || '').trim();
1185
+ terminalRouter.post("/update-auto-commands", validateToken, async (req, res) => {
1186
+ const { agentId, idleInputAutoCommand = "", nonInputAutoCommand = "", } = req.body || {};
1187
+ const normalizedAgentId = String(agentId || "").trim();
953
1188
  if (!normalizedAgentId) {
954
- res.status(400).json({ error: 'agentId is required' });
1189
+ res.status(400).json({ error: "agentId is required" });
955
1190
  return;
956
1191
  }
957
1192
  const entry = [...sdkSessions.values()].find((session) => session.agentId === normalizedAgentId);
958
1193
  const commands = {
959
- idleInputCommand: String(idleInputAutoCommand || ''),
960
- nonInputCommand: String(nonInputAutoCommand || ''),
1194
+ idleInputCommand: String(idleInputAutoCommand || ""),
1195
+ nonInputCommand: String(nonInputAutoCommand || ""),
961
1196
  };
962
1197
  try {
963
1198
  let sessionUpdated = false;
964
1199
  if (entry) {
965
- const adapter = adapterRegistry.get(entry.providerId || 'claude-code');
1200
+ const adapter = adapterRegistry.get(entry.providerId || "claude-code");
966
1201
  adapter.setIdleCommands?.(entry.sessionId, commands);
967
1202
  sessionUpdated = true;
968
1203
  }
@@ -970,11 +1205,11 @@ terminalRouter.post('/update-auto-commands', validateToken, async (req, res) =>
970
1205
  idleInputAutoCommand: commands.idleInputCommand,
971
1206
  nonInputAutoCommand: commands.nonInputCommand,
972
1207
  });
973
- res.json({ ok: true, mode: 'sdk', sessionUpdated, persistedUpdated });
1208
+ res.json({ ok: true, mode: "sdk", sessionUpdated, persistedUpdated });
974
1209
  }
975
1210
  catch (error) {
976
1211
  const errorMessage = error instanceof Error ? error.message : String(error);
977
- res.status(500).json({ ok: false, mode: 'sdk', error: errorMessage });
1212
+ res.status(500).json({ ok: false, mode: "sdk", error: errorMessage });
978
1213
  }
979
1214
  });
980
1215
  /**
@@ -983,37 +1218,43 @@ terminalRouter.post('/update-auto-commands', validateToken, async (req, res) =>
983
1218
  * SDK 会话没有终端尺寸概念;保留该端点用于前端 xterm fit 回调,返回 no-op。
984
1219
  * POST /runtime/resize
985
1220
  */
986
- terminalRouter.post('/resize', validateToken, (req, res) => {
1221
+ terminalRouter.post("/resize", validateToken, (req, res) => {
987
1222
  const { sessionId, cols, rows } = req.body || {};
988
1223
  if (!sessionId || !cols || !rows) {
989
- res.status(400).json({ error: 'sessionId, cols, rows are required' });
1224
+ res.status(400).json({ error: "sessionId, cols, rows are required" });
990
1225
  return;
991
1226
  }
992
1227
  if (!sdkSessions.has(sessionId)) {
993
- res.status(404).json({ error: 'SDK session not found' });
1228
+ res.status(404).json({ error: "SDK session not found" });
994
1229
  return;
995
1230
  }
996
- res.json({ ok: true, mode: 'sdk', noop: true, cols: Number(cols), rows: Number(rows) });
1231
+ res.json({
1232
+ ok: true,
1233
+ mode: "sdk",
1234
+ noop: true,
1235
+ cols: Number(cols),
1236
+ rows: Number(rows),
1237
+ });
997
1238
  });
998
1239
  /**
999
1240
  * 停止 SDK 会话。
1000
1241
  * POST /runtime/stop
1001
1242
  */
1002
- terminalRouter.post('/stop', validateToken, async (req, res) => {
1243
+ terminalRouter.post("/stop", validateToken, async (req, res) => {
1003
1244
  const { sessionId } = req.body || {};
1004
1245
  if (!sessionId) {
1005
- res.status(400).json({ error: 'sessionId is required' });
1246
+ res.status(400).json({ error: "sessionId is required" });
1006
1247
  return;
1007
1248
  }
1008
1249
  const sdkEntry = sdkSessions.get(sessionId);
1009
1250
  if (!sdkEntry) {
1010
1251
  await removePersistedSession(sessionId);
1011
- res.json({ ok: true, status: 'stopped', mode: 'none' });
1252
+ res.json({ ok: true, status: "stopped", mode: "none" });
1012
1253
  return;
1013
1254
  }
1014
1255
  const agentId = sdkEntry.agentId;
1015
1256
  try {
1016
- const providerId = sdkEntry.providerId || 'claude-code';
1257
+ const providerId = sdkEntry.providerId || "claude-code";
1017
1258
  const adapter = adapterRegistry.get(providerId);
1018
1259
  await adapter.terminateSession(sessionId);
1019
1260
  sdkSessions.delete(sessionId);
@@ -1021,13 +1262,20 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
1021
1262
  await removePersistedSession(sessionId);
1022
1263
  const processManager = getAgentProcessManager();
1023
1264
  await processManager.removeProcess(agentId);
1024
- await sendStatus(agentId, null, 'stopped');
1025
- res.json({ ok: true, status: 'stopped', mode: 'sdk' });
1265
+ await sendStatus(agentId, null, "stopped", undefined, undefined, {
1266
+ runtimeAccessToken: sdkEntry.runtimeAccessToken,
1267
+ });
1268
+ res.json({ ok: true, status: "stopped", mode: "sdk" });
1026
1269
  }
1027
1270
  catch (error) {
1028
1271
  const errorMessage = error instanceof Error ? error.message : String(error);
1029
1272
  console.error(`[Runtime] Failed to terminate SDK session ${sessionId}:`, errorMessage);
1030
- res.status(500).json({ ok: false, status: 'stop_failed', mode: 'sdk', error: errorMessage });
1273
+ res.status(500).json({
1274
+ ok: false,
1275
+ status: "stop_failed",
1276
+ mode: "sdk",
1277
+ error: errorMessage,
1278
+ });
1031
1279
  }
1032
1280
  });
1033
1281
  /**
@@ -1035,20 +1283,22 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
1035
1283
  * POST /runtime/stop-all
1036
1284
  * 用于 aws-mcp-server 重启时清理所有 SDK Agent 会话
1037
1285
  */
1038
- terminalRouter.post('/stop-all', validateToken, async (_req, res) => {
1286
+ terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
1039
1287
  const stoppedSdk = [];
1040
1288
  const errors = [];
1041
1289
  // 1. 停止所有 SDK 会话
1042
1290
  for (const [sessionId, entry] of sdkSessions.entries()) {
1043
1291
  try {
1044
1292
  // 根据 providerId 获取对应的 adapter
1045
- const providerId = entry.providerId || 'claude-code';
1293
+ const providerId = entry.providerId || "claude-code";
1046
1294
  const adapter = adapterRegistry.get(providerId);
1047
1295
  await adapter.terminateSession(sessionId);
1048
1296
  sdkSessions.delete(sessionId);
1049
1297
  stopTerminalCommandProcess(sessionId);
1050
1298
  stoppedSdk.push(sessionId);
1051
- await sendStatus(entry.agentId, null, 'stopped');
1299
+ await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
1300
+ runtimeAccessToken: entry.runtimeAccessToken,
1301
+ });
1052
1302
  }
1053
1303
  catch (error) {
1054
1304
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1059,7 +1309,7 @@ terminalRouter.post('/stop-all', validateToken, async (_req, res) => {
1059
1309
  // 直接清空整个持久化文件,确保不会有残留
1060
1310
  try {
1061
1311
  await savePersistedSessions([]);
1062
- console.log('[Runtime] 已清空所有持久化会话');
1312
+ console.log("[Runtime] 已清空所有持久化会话");
1063
1313
  }
1064
1314
  catch (error) {
1065
1315
  const errorMessage = error instanceof Error ? error.message : String(error);