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.
- package/dist/adapter/AcodeSdkAdapter.d.ts +30 -3
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +427 -105
- package/dist/adapter/AcodeSdkAdapter.test.js +1093 -86
- package/dist/adapter/ClaudeSdkAdapter.test.js +31 -8
- package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/CodexSdkAdapter.js +2 -0
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +2 -0
- package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
- package/dist/adapter/adapter.test.js +2 -2
- package/dist/adapter/idle-poll-loop.d.ts +4 -4
- package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
- package/dist/adapter/idle-poll-loop.js +3 -3
- package/dist/adapter/idle-poll-loop.test.js +15 -1
- package/dist/adapter/types.d.ts +25 -6
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +221 -110
- package/dist/adapter/types.test.js +10 -3
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +6 -7
- package/dist/routes/runtime-binding.js +2 -2
- package/dist/routes/terminal.d.ts +20 -5
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +420 -170
- package/dist/routes/terminal.test.js +380 -208
- package/dist/services/mcp-launch-binding-queue.d.ts +1 -0
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
- package/dist/services/mcp-launch-binding-queue.js +8 -2
- package/dist/services/mcp-launch-binding-queue.test.js +47 -4
- package/dist/services/session-output.d.ts +10 -6
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +29 -14
- package/dist/services/session-output.test.js +93 -39
- package/package/acode/dist/built-in-file-tools.d.ts +1 -1
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/built-in-file-tools.js +388 -50
- package/package/acode/dist/runtime.d.ts +11 -3
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +902 -190
- package/package/acode/dist/types.d.ts +18 -5
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +3 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +11 -9
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +1 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +31 -3
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
- package/package.json +1 -1
package/dist/routes/terminal.js
CHANGED
|
@@ -4,44 +4,91 @@
|
|
|
4
4
|
* 提供 SDK Agent 会话的启动、输入、调整大小和停止功能
|
|
5
5
|
* 终端 UI 仅作为 SDK Agent 的消息输入/状态输出载体
|
|
6
6
|
*/
|
|
7
|
-
import { spawn } from
|
|
8
|
-
import { promises as fs } from
|
|
9
|
-
import os from
|
|
10
|
-
import path from
|
|
11
|
-
import { TextDecoder } from
|
|
12
|
-
import { Router } from
|
|
13
|
-
import { v4 as uuidv4 } from
|
|
14
|
-
import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath, redactTransientClipboardPastePaths, registerSdkProviders, resolveSdkProviderIdByCommand, } from
|
|
15
|
-
import { validateToken } from
|
|
16
|
-
import { getAgentProcessManager } from
|
|
17
|
-
import { getAiConfigService } from
|
|
18
|
-
import { enqueueMcpLaunchBinding } from
|
|
19
|
-
import { findClaudeCodeProcesses, isProcessRunning } from
|
|
20
|
-
import { sendOutput, sendQuestionRequest, sendStatus } from
|
|
21
|
-
import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, updatePersistedSessionRuntimeState, upsertPersistedSession, } from
|
|
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)
|
|
32
|
-
|
|
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 =
|
|
39
|
-
const DEFAULT_TERMINAL_OUTPUT_ENCODING =
|
|
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 ??
|
|
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 ===
|
|
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,
|
|
93
|
-
if ((withoutDriveSwitch.startsWith('"') && withoutDriveSwitch.endsWith('"'))
|
|
94
|
-
|
|
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 ||
|
|
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:
|
|
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 ||
|
|
191
|
+
const normalizedAdapterStatus = String(adapterStatus || "").trim();
|
|
143
192
|
if (normalizedAdapterStatus) {
|
|
144
193
|
return normalizedAdapterStatus;
|
|
145
194
|
}
|
|
146
|
-
const normalizedRuntimeStatus = String(currentRuntimeStatus ||
|
|
147
|
-
return normalizedRuntimeStatus ||
|
|
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:
|
|
204
|
+
return { reusable: false, reason: "missing-pid" };
|
|
156
205
|
}
|
|
157
206
|
if (!isPidRunning(session.pid)) {
|
|
158
|
-
return { reusable: false, reason:
|
|
207
|
+
return { reusable: false, reason: "dead-pid" };
|
|
159
208
|
}
|
|
160
|
-
if (resolveSdkProviderId(session.command) ===
|
|
161
|
-
return { reusable: false, reason:
|
|
209
|
+
if (resolveSdkProviderId(session.command) === "acode") {
|
|
210
|
+
return { reusable: false, reason: "in-process-provider" };
|
|
162
211
|
}
|
|
163
|
-
return { reusable: true, reason:
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
258
|
-
actionType:
|
|
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
|
-
|
|
271
|
-
|
|
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(
|
|
274
|
-
|
|
377
|
+
child.stderr.on("data", (chunk) => {
|
|
378
|
+
appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, chunk));
|
|
275
379
|
});
|
|
276
|
-
child.on(
|
|
380
|
+
child.on("error", (error) => {
|
|
277
381
|
appendCommandOutput(entry, `\r\n[Terminal Error] ${error.message}\r\n`);
|
|
278
382
|
});
|
|
279
|
-
child.on(
|
|
383
|
+
child.on("close", (code, signal) => {
|
|
384
|
+
clearInactivityTimer();
|
|
280
385
|
state.runningProcess = null;
|
|
281
|
-
|
|
282
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
368
|
-
|
|
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 ===
|
|
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 ===
|
|
511
|
+
if (actionType === "mcp") {
|
|
401
512
|
return true;
|
|
402
513
|
}
|
|
403
|
-
const toolName = String(event.data.toolName ||
|
|
404
|
-
return toolName.startsWith(
|
|
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 !==
|
|
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 ===
|
|
533
|
+
if (actionType === "idle") {
|
|
421
534
|
return undefined;
|
|
422
535
|
}
|
|
423
|
-
if (actionType ===
|
|
536
|
+
if (actionType === "mcp" && isMcpProviderEvent(event)) {
|
|
424
537
|
return {
|
|
425
|
-
actionType:
|
|
426
|
-
actionLabel:
|
|
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 ||
|
|
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 ===
|
|
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 ===
|
|
445
|
-
return event.data.text
|
|
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(
|
|
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,
|
|
477
|
-
|
|
478
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
519
|
-
const { agentId, workspacePath, mode =
|
|
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:
|
|
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 !==
|
|
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 ||
|
|
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:
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
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 ||
|
|
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 ===
|
|
823
|
+
const savedClaudeConfig = providerId === "claude-code"
|
|
596
824
|
? await readSavedClaudeCodeLaunchConfig()
|
|
597
825
|
: null;
|
|
598
|
-
const claudeLaunchConfig = providerId ===
|
|
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 ||
|
|
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:
|
|
892
|
+
status: "running",
|
|
657
893
|
runtimeStatus: startedStatus,
|
|
658
|
-
mode:
|
|
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:
|
|
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:
|
|
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
|
|
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(
|
|
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:
|
|
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:
|
|
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 ||
|
|
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(
|
|
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:
|
|
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:
|
|
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 ||
|
|
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(
|
|
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:
|
|
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:
|
|
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 ||
|
|
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(
|
|
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:
|
|
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:
|
|
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 ||
|
|
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(
|
|
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:
|
|
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:
|
|
1060
|
+
res.json({ ok: true, status: "stopped" });
|
|
830
1061
|
return;
|
|
831
1062
|
}
|
|
832
1063
|
try {
|
|
833
1064
|
// 根据 providerId 获取对应的 adapter
|
|
834
|
-
const providerId = entry.providerId ||
|
|
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,
|
|
844
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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 ||
|
|
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(
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
1154
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
922
1155
|
return;
|
|
923
1156
|
}
|
|
924
1157
|
try {
|
|
925
|
-
const providerId = entry.providerId ||
|
|
1158
|
+
const providerId = entry.providerId || "claude-code";
|
|
926
1159
|
const adapter = adapterRegistry.get(providerId);
|
|
927
|
-
if (mode ===
|
|
1160
|
+
if (mode === "shortcut") {
|
|
928
1161
|
if (!adapter.sendShortcutInput) {
|
|
929
|
-
res.status(400).json({
|
|
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:
|
|
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:
|
|
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(
|
|
951
|
-
const { agentId, idleInputAutoCommand =
|
|
952
|
-
const normalizedAgentId = String(agentId ||
|
|
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:
|
|
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 ||
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
1228
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
994
1229
|
return;
|
|
995
1230
|
}
|
|
996
|
-
res.json({
|
|
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(
|
|
1243
|
+
terminalRouter.post("/stop", validateToken, async (req, res) => {
|
|
1003
1244
|
const { sessionId } = req.body || {};
|
|
1004
1245
|
if (!sessionId) {
|
|
1005
|
-
res.status(400).json({ error:
|
|
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:
|
|
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 ||
|
|
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,
|
|
1025
|
-
|
|
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({
|
|
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(
|
|
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 ||
|
|
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,
|
|
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(
|
|
1312
|
+
console.log("[Runtime] 已清空所有持久化会话");
|
|
1063
1313
|
}
|
|
1064
1314
|
catch (error) {
|
|
1065
1315
|
const errorMessage = error instanceof Error ? error.message : String(error);
|