aws-runtime-bridge 1.7.50 → 1.8.1
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 +461 -105
- package/dist/adapter/AcodeSdkAdapter.test.js +1282 -93
- 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 +226 -111
- package/dist/adapter/types.test.js +10 -3
- package/dist/routes/ai-sources.js +5 -1
- package/dist/routes/ai-sources.test.js +21 -0
- 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 +22 -5
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +522 -170
- package/dist/routes/terminal.test.js +497 -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 +14 -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,224 @@ 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
|
+
export function buildSdkOutputMetadata(event) {
|
|
577
|
+
const logMessage = formatSdkLogMessage(event);
|
|
578
|
+
return {
|
|
579
|
+
sourceEventType: event.type,
|
|
580
|
+
logCategory: "acode",
|
|
581
|
+
...(logMessage
|
|
582
|
+
? {
|
|
583
|
+
logLevel: event.type === "error" ? "error" : "info",
|
|
584
|
+
logMessage,
|
|
585
|
+
}
|
|
586
|
+
: {}),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function formatSdkLogMessage(event) {
|
|
590
|
+
if (event.type === "ai_request") {
|
|
591
|
+
const model = event.data.model || "unknown-model";
|
|
592
|
+
const endpoint = event.data.endpoint ? ` @ ${event.data.endpoint}` : "";
|
|
593
|
+
const toolCount = typeof event.data.toolCount === "number"
|
|
594
|
+
? `, tools=${event.data.toolCount}`
|
|
595
|
+
: "";
|
|
596
|
+
return `AI request started: ${model}${endpoint}${toolCount}`;
|
|
597
|
+
}
|
|
598
|
+
if (event.type === "tool_use_start") {
|
|
599
|
+
return formatSdkToolLogMessage(event, "开始");
|
|
600
|
+
}
|
|
601
|
+
if (event.type === "tool_use_end") {
|
|
602
|
+
return formatSdkToolLogMessage(event, "完成");
|
|
603
|
+
}
|
|
604
|
+
if (event.type === "turn_complete") {
|
|
605
|
+
const finishReason = event.data.finishReason
|
|
606
|
+
? `, finish=${event.data.finishReason}`
|
|
607
|
+
: "";
|
|
608
|
+
return `Turn completed${finishReason}`;
|
|
609
|
+
}
|
|
610
|
+
if (event.type === "session_complete") {
|
|
611
|
+
return `Session completed: exitCode=${event.data.exitCode ?? 0}`;
|
|
612
|
+
}
|
|
613
|
+
if (event.type === "error") {
|
|
614
|
+
return event.data.text || "ACode runtime error";
|
|
615
|
+
}
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
function formatSdkToolLogMessage(event, phaseLabel) {
|
|
619
|
+
const actionInfo = getToolActionInfo(event.data.toolName, event.data.toolInput);
|
|
620
|
+
const actionLabel = event.data.actionLabel || actionInfo.actionLabel || "调用工具";
|
|
621
|
+
const detail = resolveSdkToolLogDetail(event, actionInfo);
|
|
622
|
+
return detail
|
|
623
|
+
? `${phaseLabel}${actionLabel}: ${detail}`
|
|
624
|
+
: `${phaseLabel}${actionLabel}: ${event.data.actionDetail || event.data.toolName || "tool"}`;
|
|
625
|
+
}
|
|
626
|
+
function resolveSdkToolLogDetail(event, actionInfo) {
|
|
627
|
+
const input = event.data.toolInput;
|
|
628
|
+
if (actionInfo.actionType === "bash") {
|
|
629
|
+
return getCommandDisplayDetail(input) || event.data.actionDetail || actionInfo.actionDetail;
|
|
630
|
+
}
|
|
631
|
+
if (actionInfo.actionType === "read_file" ||
|
|
632
|
+
actionInfo.actionType === "edit_file" ||
|
|
633
|
+
actionInfo.actionType === "write_file") {
|
|
634
|
+
return getFileDisplayDetail(input) || event.data.actionDetail || actionInfo.actionDetail;
|
|
635
|
+
}
|
|
636
|
+
return event.data.actionDetail || actionInfo.actionDetail;
|
|
637
|
+
}
|
|
638
|
+
function getCommandDisplayDetail(input) {
|
|
639
|
+
const command = firstDisplayString(input?.command, input?.cmd);
|
|
640
|
+
if (command)
|
|
641
|
+
return command;
|
|
642
|
+
if (Array.isArray(input?.argv)) {
|
|
643
|
+
const argv = input.argv
|
|
644
|
+
.map((item) => String(item ?? "").trim())
|
|
645
|
+
.filter(Boolean)
|
|
646
|
+
.join(" ");
|
|
647
|
+
return argv ? redactTransientClipboardPastePaths(argv) : undefined;
|
|
648
|
+
}
|
|
649
|
+
return firstDisplayString(input?.args);
|
|
650
|
+
}
|
|
651
|
+
function getFileDisplayDetail(input) {
|
|
652
|
+
const paths = displayStringList(input?.paths);
|
|
653
|
+
if (paths.length > 0) {
|
|
654
|
+
return paths.length === 1
|
|
655
|
+
? paths[0]
|
|
656
|
+
: `${paths.length}个文件: ${paths.join(", ")}`;
|
|
657
|
+
}
|
|
658
|
+
return firstDisplayString(input?.file_path, input?.filePath, input?.path, input?.target_file, input?.targetFile);
|
|
659
|
+
}
|
|
660
|
+
function firstDisplayString(...values) {
|
|
661
|
+
for (const value of values) {
|
|
662
|
+
if (typeof value === "string" && value.trim()) {
|
|
663
|
+
return redactTransientClipboardPastePaths(value.trim());
|
|
664
|
+
}
|
|
665
|
+
if (Array.isArray(value)) {
|
|
666
|
+
const first = displayStringList(value)[0];
|
|
667
|
+
if (first)
|
|
668
|
+
return first;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return undefined;
|
|
672
|
+
}
|
|
673
|
+
function displayStringList(value) {
|
|
674
|
+
if (!Array.isArray(value))
|
|
675
|
+
return [];
|
|
676
|
+
return value
|
|
677
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
678
|
+
.filter(Boolean)
|
|
679
|
+
.map((item) => redactTransientClipboardPastePaths(item));
|
|
680
|
+
}
|
|
681
|
+
function formatAiRequestDetail(data) {
|
|
682
|
+
const lines = [
|
|
683
|
+
data.model ? `模型: ${data.model}` : undefined,
|
|
684
|
+
data.endpoint ? `接口: ${data.endpoint}` : undefined,
|
|
685
|
+
typeof data.stream === "boolean"
|
|
686
|
+
? `流式: ${data.stream ? "是" : "否"}`
|
|
687
|
+
: undefined,
|
|
688
|
+
typeof data.messageCount === "number"
|
|
689
|
+
? `上下文消息: ${data.messageCount}`
|
|
690
|
+
: undefined,
|
|
691
|
+
typeof data.toolCount === "number"
|
|
692
|
+
? `可用工具: ${data.toolCount}`
|
|
693
|
+
: undefined,
|
|
694
|
+
data.lastUserMessagePreview
|
|
695
|
+
? `最后请求: ${data.lastUserMessagePreview}`
|
|
696
|
+
: undefined,
|
|
697
|
+
].filter((line) => Boolean(line));
|
|
698
|
+
const base = lines.join("\n") || "ACode 正在发送 AI 请求";
|
|
699
|
+
return data.requestBody
|
|
700
|
+
? `${base}${AI_REQUEST_BODY_MARKER}${data.requestBody}`
|
|
701
|
+
: base;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* 主干流程:把 provider 事件转换为面板运行状态。
|
|
705
|
+
*
|
|
706
|
+
* text_delta 表示助手正在生成;thinking 表示推理/内部过程。
|
|
707
|
+
* tool_use_start 表示正在执行工具;turn_complete 才表示真正空闲。
|
|
708
|
+
*/
|
|
709
|
+
export function resolveRuntimeStatusFromProviderEvent(event) {
|
|
710
|
+
if (event.type === "ai_request") {
|
|
711
|
+
return {
|
|
712
|
+
status: "thinking",
|
|
713
|
+
actionInfo: {
|
|
714
|
+
actionType: "ai_request",
|
|
715
|
+
actionLabel: "AI请求",
|
|
716
|
+
actionDetail: formatAiRequestDetail(event.data),
|
|
717
|
+
},
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if (event.type === "thinking" || event.type === "text_delta") {
|
|
721
|
+
return { status: "thinking" };
|
|
722
|
+
}
|
|
723
|
+
if (event.type === "tool_use_start") {
|
|
724
|
+
const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
|
|
725
|
+
const actionDetail = resolveSdkToolLogDetail(event, inferredAction);
|
|
726
|
+
if (event.data.actionType === "idle" ||
|
|
727
|
+
inferredAction.actionType === "idle") {
|
|
728
|
+
return {
|
|
729
|
+
status: "waiting_input",
|
|
730
|
+
actionInfo: {
|
|
731
|
+
actionType: event.data.actionType || inferredAction.actionType,
|
|
732
|
+
actionLabel: event.data.actionLabel || inferredAction.actionLabel,
|
|
733
|
+
actionDetail: actionDetail || event.data.actionDetail || inferredAction.actionDetail || event.data.toolName,
|
|
734
|
+
actionId: event.data.actionId || event.data.toolUseId,
|
|
735
|
+
},
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
return {
|
|
739
|
+
status: "tool_using",
|
|
740
|
+
actionInfo: {
|
|
741
|
+
actionType: event.data.actionType || inferredAction.actionType,
|
|
742
|
+
actionLabel: event.data.actionLabel || inferredAction.actionLabel,
|
|
743
|
+
actionDetail: actionDetail || event.data.actionDetail || inferredAction.actionDetail || event.data.toolName,
|
|
744
|
+
actionId: event.data.actionId || event.data.toolUseId,
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
if (event.type === "turn_complete") {
|
|
749
|
+
const responseActionInfo = event.data
|
|
750
|
+
.responseBody
|
|
751
|
+
? {
|
|
752
|
+
actionType: "ai_request",
|
|
753
|
+
actionLabel: "AI响应",
|
|
754
|
+
actionResult: event.data.responseBody,
|
|
755
|
+
usage: event.data.usage,
|
|
756
|
+
}
|
|
757
|
+
: undefined;
|
|
758
|
+
return {
|
|
759
|
+
status: "waiting_input",
|
|
760
|
+
actionInfo: responseActionInfo ||
|
|
761
|
+
(event.data.usage ? { usage: event.data.usage } : undefined),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
if (event.type === "error") {
|
|
765
|
+
return { status: "error" };
|
|
446
766
|
}
|
|
447
767
|
return undefined;
|
|
448
768
|
}
|
|
@@ -452,7 +772,7 @@ function forwardSdkOutputEvent(event, entry) {
|
|
|
452
772
|
return;
|
|
453
773
|
}
|
|
454
774
|
entry.seq += 1;
|
|
455
|
-
void sendOutput(entry.agentId, output, event.sessionId, entry.seq);
|
|
775
|
+
void sendOutput(entry.agentId, output, event.sessionId, entry.seq, buildSdkOutputMetadata(event), { runtimeAccessToken: entry.runtimeAccessToken });
|
|
456
776
|
}
|
|
457
777
|
export function resolveSdkProviderId(command) {
|
|
458
778
|
return resolveSdkProviderIdByCommand(command);
|
|
@@ -466,45 +786,52 @@ function ensureAdapterInitialized() {
|
|
|
466
786
|
adapterInitialized = true;
|
|
467
787
|
}
|
|
468
788
|
function wireSdkAdapterEvents(adapter, definition) {
|
|
469
|
-
adapter.on(
|
|
789
|
+
adapter.on("event", (event) => {
|
|
470
790
|
const entry = sdkSessions.get(event.sessionId);
|
|
471
791
|
if (entry) {
|
|
472
792
|
console.log(`[${definition.displayName} Adapter] Event: ${event.type} for session ${event.sessionId}`);
|
|
793
|
+
const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event);
|
|
794
|
+
if (runtimeStatus) {
|
|
795
|
+
void sendStatus(entry.agentId, event.sessionId, runtimeStatus.status, runtimeStatus.actionInfo, resolveStatusChangeUsage(runtimeStatus.actionInfo), { runtimeAccessToken: entry.runtimeAccessToken });
|
|
796
|
+
}
|
|
473
797
|
forwardSdkOutputEvent(event, entry);
|
|
474
798
|
const toolResultActionInfo = buildToolResultTimelineActionInfo(event);
|
|
475
799
|
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 }));
|
|
800
|
+
void sendStatus(entry.agentId, event.sessionId, "tool_result", toolResultActionInfo, undefined, {
|
|
801
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
802
|
+
});
|
|
480
803
|
}
|
|
481
804
|
}
|
|
482
805
|
});
|
|
483
|
-
adapter.on(
|
|
806
|
+
adapter.on("status-change", (sessionId, status, actionInfo) => {
|
|
484
807
|
const entry = sdkSessions.get(sessionId);
|
|
485
808
|
if (entry) {
|
|
486
809
|
entry.currentRuntimeStatus = status;
|
|
487
810
|
entry.currentRuntimeActionType = actionInfo?.actionType;
|
|
488
811
|
entry.currentRuntimeActionLabel = actionInfo?.actionLabel;
|
|
489
812
|
entry.currentRuntimeActionDetail = actionInfo?.actionDetail;
|
|
490
|
-
console.log(`[${definition.displayName} Adapter] Status changed: ${status} for agent ${entry.agentId}`, actionInfo ||
|
|
813
|
+
console.log(`[${definition.displayName} Adapter] Status changed: ${status} for agent ${entry.agentId}`, actionInfo || "");
|
|
491
814
|
void updatePersistedSessionRuntimeState(sessionId, {
|
|
492
815
|
runtimeStatus: status,
|
|
493
816
|
runtimeActionType: actionInfo?.actionType,
|
|
494
817
|
runtimeActionLabel: actionInfo?.actionLabel,
|
|
495
818
|
runtimeActionDetail: actionInfo?.actionDetail,
|
|
496
819
|
});
|
|
497
|
-
void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo)
|
|
820
|
+
void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo), {
|
|
821
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
822
|
+
});
|
|
498
823
|
}
|
|
499
824
|
});
|
|
500
|
-
adapter.on(
|
|
825
|
+
adapter.on("ask-user-question", (data) => {
|
|
501
826
|
const entry = sdkSessions.get(data.sessionId);
|
|
502
827
|
if (entry) {
|
|
503
828
|
console.log(`[${definition.displayName} Adapter] AskUserQuestion for agent ${entry.agentId}`);
|
|
504
|
-
void sendQuestionRequest(entry.agentId, data.sessionId, Array.isArray(data.questions) ? data.questions : []
|
|
829
|
+
void sendQuestionRequest(entry.agentId, data.sessionId, Array.isArray(data.questions) ? data.questions : [], {
|
|
830
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
831
|
+
});
|
|
505
832
|
}
|
|
506
833
|
});
|
|
507
|
-
adapter.on(
|
|
834
|
+
adapter.on("permission-request", (data) => {
|
|
508
835
|
const entry = sdkSessions.get(data.sessionId);
|
|
509
836
|
if (entry) {
|
|
510
837
|
console.log(`[${definition.displayName} Adapter] Permission request: ${data.toolName} for agent ${entry.agentId}`);
|
|
@@ -515,10 +842,10 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
515
842
|
* 启动 SDK Agent 会话
|
|
516
843
|
* POST /runtime/start
|
|
517
844
|
*/
|
|
518
|
-
terminalRouter.post(
|
|
519
|
-
const { agentId, workspacePath, mode =
|
|
845
|
+
terminalRouter.post("/start", validateToken, async (req, res) => {
|
|
846
|
+
const { agentId, workspacePath, mode = "sdk" } = req.body || {};
|
|
520
847
|
if (!agentId || !workspacePath) {
|
|
521
|
-
res.status(400).json({ error:
|
|
848
|
+
res.status(400).json({ error: "agentId and workspacePath are required" });
|
|
522
849
|
return;
|
|
523
850
|
}
|
|
524
851
|
enqueueMcpLaunchBinding({
|
|
@@ -529,7 +856,7 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
|
|
|
529
856
|
userId: req.body?.userId,
|
|
530
857
|
schedulerBaseUrl: req.body?.schedulerBaseUrl,
|
|
531
858
|
});
|
|
532
|
-
if (mode && mode !==
|
|
859
|
+
if (mode && mode !== "sdk") {
|
|
533
860
|
console.warn(`[Runtime] Unsupported runtime mode "${mode}" requested for agent ${agentId}; starting SDK session instead.`);
|
|
534
861
|
}
|
|
535
862
|
await startSdkSession(req, res);
|
|
@@ -545,11 +872,12 @@ async function startSdkSession(req, res) {
|
|
|
545
872
|
// 空闲命令配置
|
|
546
873
|
idleInputAutoCommand, nonInputAutoCommand, } = req.body || {};
|
|
547
874
|
let sessionId = null;
|
|
548
|
-
const normalizedAgentId = String(agentId ||
|
|
875
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
876
|
+
const runtimeAccessToken = String(req.body?.runtimeAccessToken || "").trim() || undefined;
|
|
549
877
|
try {
|
|
550
878
|
ensureAdapterInitialized();
|
|
551
879
|
if (!normalizedAgentId) {
|
|
552
|
-
res.status(400).json({ error:
|
|
880
|
+
res.status(400).json({ error: "agentId is required" });
|
|
553
881
|
return;
|
|
554
882
|
}
|
|
555
883
|
const activeSession = findActiveSdkSessionByAgentId(normalizedAgentId);
|
|
@@ -561,8 +889,8 @@ async function startSdkSession(req, res) {
|
|
|
561
889
|
if (persistedSession) {
|
|
562
890
|
const reuseDecision = evaluatePersistedSessionReuse(persistedSession);
|
|
563
891
|
if (!reuseDecision.reusable) {
|
|
564
|
-
console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, `
|
|
565
|
-
|
|
892
|
+
console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, ` +
|
|
893
|
+
`sessionId=${persistedSession.sessionId}, reason=${reuseDecision.reason}`);
|
|
566
894
|
await removePersistedSession(persistedSession.sessionId);
|
|
567
895
|
}
|
|
568
896
|
else {
|
|
@@ -572,7 +900,7 @@ async function startSdkSession(req, res) {
|
|
|
572
900
|
agentId: persistedSession.agentId,
|
|
573
901
|
workspacePath: persistedSession.workspacePath,
|
|
574
902
|
command: persistedSession.command,
|
|
575
|
-
mode: persistedSession.mode ||
|
|
903
|
+
mode: persistedSession.mode || "sdk",
|
|
576
904
|
pid: persistedSession.pid,
|
|
577
905
|
reused: true,
|
|
578
906
|
persistedOnly: true,
|
|
@@ -581,26 +909,28 @@ async function startSdkSession(req, res) {
|
|
|
581
909
|
}
|
|
582
910
|
}
|
|
583
911
|
if (startingAgents.has(normalizedAgentId)) {
|
|
584
|
-
res
|
|
912
|
+
res
|
|
913
|
+
.status(409)
|
|
914
|
+
.json({ error: "SDK session is already starting for this agent" });
|
|
585
915
|
return;
|
|
586
916
|
}
|
|
587
917
|
startingAgents.add(normalizedAgentId);
|
|
588
918
|
// 根据命令选择对应的 adapter
|
|
589
|
-
const normalizedCommand = (command ||
|
|
919
|
+
const normalizedCommand = (command || "acode").toLowerCase();
|
|
590
920
|
const providerId = resolveSdkProviderId(command);
|
|
591
921
|
// ★ 特征日志:明确输出选择的 provider
|
|
592
922
|
console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
|
|
593
923
|
const adapter = adapterRegistry.get(providerId);
|
|
594
924
|
sessionId = uuidv4();
|
|
595
|
-
const savedClaudeConfig = providerId ===
|
|
925
|
+
const savedClaudeConfig = providerId === "claude-code"
|
|
596
926
|
? await readSavedClaudeCodeLaunchConfig()
|
|
597
927
|
: null;
|
|
598
|
-
const claudeLaunchConfig = providerId ===
|
|
928
|
+
const claudeLaunchConfig = providerId === "claude-code"
|
|
599
929
|
? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
|
|
600
930
|
: buildClaudeCodeLaunchConfig(null, envOverrides);
|
|
601
931
|
// 构建SDK配置
|
|
602
932
|
const config = {
|
|
603
|
-
command: command ||
|
|
933
|
+
command: command || "acode",
|
|
604
934
|
workingDirectory: workspacePath,
|
|
605
935
|
autoAccept: Boolean(autoAccept),
|
|
606
936
|
initialPrompt,
|
|
@@ -618,22 +948,30 @@ async function startSdkSession(req, res) {
|
|
|
618
948
|
config,
|
|
619
949
|
providerId, // 记录使用的 provider
|
|
620
950
|
seq: 0,
|
|
951
|
+
runtimeAccessToken,
|
|
621
952
|
});
|
|
622
953
|
// 启动 SDK 会话
|
|
623
954
|
await adapter.startSession(sessionId, config);
|
|
955
|
+
// 尽早注入空闲命令,避免后续 PID 探测/进程注册延迟 poll_message 空闲轮询。
|
|
956
|
+
if (idleInputAutoCommand || nonInputAutoCommand) {
|
|
957
|
+
adapter.setIdleCommands?.(sessionId, {
|
|
958
|
+
idleInputCommand: idleInputAutoCommand || "",
|
|
959
|
+
nonInputCommand: nonInputAutoCommand || "",
|
|
960
|
+
});
|
|
961
|
+
}
|
|
624
962
|
// ★ 尝试获取 SDK 进程的 PID(用于孤儿进程检测)
|
|
625
963
|
// SDK 不直接暴露 PID,通过进程检测服务查找匹配的进程
|
|
626
964
|
let sdkPid;
|
|
627
965
|
try {
|
|
628
966
|
// 等待一小段时间让进程启动
|
|
629
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
967
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
630
968
|
// 尝试通过 adapter 获取 PID(如果支持)
|
|
631
969
|
sdkPid = adapter.getSessionPid?.(sessionId);
|
|
632
970
|
// 如果 adapter 不支持,尝试通过进程检测服务查找
|
|
633
971
|
if (!sdkPid) {
|
|
634
972
|
const processes = findClaudeCodeProcesses();
|
|
635
973
|
// 查找匹配 agentId 或工作目录的进程
|
|
636
|
-
const matched = processes.find(p => p.agentId === agentId ||
|
|
974
|
+
const matched = processes.find((p) => p.agentId === agentId ||
|
|
637
975
|
p.cwd === workspacePath ||
|
|
638
976
|
p.command.includes(workspacePath));
|
|
639
977
|
if (matched) {
|
|
@@ -653,9 +991,9 @@ async function startSdkSession(req, res) {
|
|
|
653
991
|
workspacePath,
|
|
654
992
|
command: config.command,
|
|
655
993
|
startedAt: new Date().toISOString(),
|
|
656
|
-
status:
|
|
994
|
+
status: "running",
|
|
657
995
|
runtimeStatus: startedStatus,
|
|
658
|
-
mode:
|
|
996
|
+
mode: "sdk",
|
|
659
997
|
pid: sdkPid,
|
|
660
998
|
providerSessionId: adapter.getProviderSessionId(sessionId),
|
|
661
999
|
});
|
|
@@ -666,18 +1004,11 @@ async function startSdkSession(req, res) {
|
|
|
666
1004
|
agentId: normalizedAgentId,
|
|
667
1005
|
sessionId,
|
|
668
1006
|
pid: sdkPid,
|
|
669
|
-
mode:
|
|
1007
|
+
mode: "sdk",
|
|
670
1008
|
workspacePath,
|
|
671
1009
|
command: config.command,
|
|
672
1010
|
});
|
|
673
1011
|
}
|
|
674
|
-
// 存储空闲命令配置(用于SDK模式的空闲检测)
|
|
675
|
-
if (idleInputAutoCommand || nonInputAutoCommand) {
|
|
676
|
-
adapter.setIdleCommands?.(sessionId, {
|
|
677
|
-
idleInputCommand: idleInputAutoCommand || '',
|
|
678
|
-
nonInputCommand: nonInputAutoCommand || '',
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
1012
|
res.json({
|
|
682
1013
|
sessionId,
|
|
683
1014
|
status: startedStatus,
|
|
@@ -685,7 +1016,7 @@ async function startSdkSession(req, res) {
|
|
|
685
1016
|
agentId,
|
|
686
1017
|
workspacePath,
|
|
687
1018
|
command: config.command,
|
|
688
|
-
mode:
|
|
1019
|
+
mode: "sdk",
|
|
689
1020
|
pid: sdkPid, // ★ 返回 PID 信息
|
|
690
1021
|
});
|
|
691
1022
|
}
|
|
@@ -695,7 +1026,9 @@ async function startSdkSession(req, res) {
|
|
|
695
1026
|
await removePersistedSession(sessionId);
|
|
696
1027
|
}
|
|
697
1028
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
698
|
-
res
|
|
1029
|
+
res
|
|
1030
|
+
.status(500)
|
|
1031
|
+
.json({ error: `Failed to start SDK session: ${errorMessage}` });
|
|
699
1032
|
}
|
|
700
1033
|
finally {
|
|
701
1034
|
if (normalizedAgentId) {
|
|
@@ -708,20 +1041,20 @@ async function startSdkSession(req, res) {
|
|
|
708
1041
|
* 发送消息到 SDK 会话
|
|
709
1042
|
* POST /runtime/sdk/message
|
|
710
1043
|
*/
|
|
711
|
-
terminalRouter.post(
|
|
1044
|
+
terminalRouter.post("/sdk/message", validateToken, async (req, res) => {
|
|
712
1045
|
const { sessionId, message } = req.body || {};
|
|
713
1046
|
if (!sessionId || !message) {
|
|
714
|
-
res.status(400).json({ error:
|
|
1047
|
+
res.status(400).json({ error: "sessionId and message are required" });
|
|
715
1048
|
return;
|
|
716
1049
|
}
|
|
717
1050
|
const entry = sdkSessions.get(sessionId);
|
|
718
1051
|
if (!entry) {
|
|
719
|
-
res.status(404).json({ error:
|
|
1052
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
720
1053
|
return;
|
|
721
1054
|
}
|
|
722
1055
|
try {
|
|
723
1056
|
// 根据 providerId 获取对应的 adapter
|
|
724
|
-
const providerId = entry.providerId ||
|
|
1057
|
+
const providerId = entry.providerId || "claude-code";
|
|
725
1058
|
const adapter = adapterRegistry.get(providerId);
|
|
726
1059
|
await adapter.sendMessage(sessionId, message);
|
|
727
1060
|
res.json({ ok: true });
|
|
@@ -735,20 +1068,20 @@ terminalRouter.post('/sdk/message', validateToken, async (req, res) => {
|
|
|
735
1068
|
* 回答 AskUserQuestion
|
|
736
1069
|
* POST /runtime/sdk/answer
|
|
737
1070
|
*/
|
|
738
|
-
terminalRouter.post(
|
|
1071
|
+
terminalRouter.post("/sdk/answer", validateToken, async (req, res) => {
|
|
739
1072
|
const { sessionId, answers } = req.body || {};
|
|
740
1073
|
if (!sessionId || !answers) {
|
|
741
|
-
res.status(400).json({ error:
|
|
1074
|
+
res.status(400).json({ error: "sessionId and answers are required" });
|
|
742
1075
|
return;
|
|
743
1076
|
}
|
|
744
1077
|
const entry = sdkSessions.get(sessionId);
|
|
745
1078
|
if (!entry) {
|
|
746
|
-
res.status(404).json({ error:
|
|
1079
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
747
1080
|
return;
|
|
748
1081
|
}
|
|
749
1082
|
try {
|
|
750
1083
|
// 根据 providerId 获取对应的 adapter
|
|
751
|
-
const providerId = entry.providerId ||
|
|
1084
|
+
const providerId = entry.providerId || "claude-code";
|
|
752
1085
|
const adapter = adapterRegistry.get(providerId);
|
|
753
1086
|
if (adapter.sendQuestionAnswer) {
|
|
754
1087
|
await adapter.sendQuestionAnswer(sessionId, answers);
|
|
@@ -764,20 +1097,20 @@ terminalRouter.post('/sdk/answer', validateToken, async (req, res) => {
|
|
|
764
1097
|
* 确认权限请求
|
|
765
1098
|
* POST /runtime/sdk/confirm
|
|
766
1099
|
*/
|
|
767
|
-
terminalRouter.post(
|
|
1100
|
+
terminalRouter.post("/sdk/confirm", validateToken, async (req, res) => {
|
|
768
1101
|
const { sessionId, accept } = req.body || {};
|
|
769
1102
|
if (!sessionId) {
|
|
770
|
-
res.status(400).json({ error:
|
|
1103
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
771
1104
|
return;
|
|
772
1105
|
}
|
|
773
1106
|
const entry = sdkSessions.get(sessionId);
|
|
774
1107
|
if (!entry) {
|
|
775
|
-
res.status(404).json({ error:
|
|
1108
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
776
1109
|
return;
|
|
777
1110
|
}
|
|
778
1111
|
try {
|
|
779
1112
|
// 根据 providerId 获取对应的 adapter
|
|
780
|
-
const providerId = entry.providerId ||
|
|
1113
|
+
const providerId = entry.providerId || "claude-code";
|
|
781
1114
|
const adapter = adapterRegistry.get(providerId);
|
|
782
1115
|
await adapter.sendConfirmation(sessionId, Boolean(accept));
|
|
783
1116
|
res.json({ ok: true });
|
|
@@ -791,20 +1124,20 @@ terminalRouter.post('/sdk/confirm', validateToken, async (req, res) => {
|
|
|
791
1124
|
* 中止当前轮次
|
|
792
1125
|
* POST /runtime/sdk/abort
|
|
793
1126
|
*/
|
|
794
|
-
terminalRouter.post(
|
|
1127
|
+
terminalRouter.post("/sdk/abort", validateToken, async (req, res) => {
|
|
795
1128
|
const { sessionId } = req.body || {};
|
|
796
1129
|
if (!sessionId) {
|
|
797
|
-
res.status(400).json({ error:
|
|
1130
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
798
1131
|
return;
|
|
799
1132
|
}
|
|
800
1133
|
const entry = sdkSessions.get(sessionId);
|
|
801
1134
|
if (!entry) {
|
|
802
|
-
res.status(404).json({ error:
|
|
1135
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
803
1136
|
return;
|
|
804
1137
|
}
|
|
805
1138
|
try {
|
|
806
1139
|
// 根据 providerId 获取对应的 adapter
|
|
807
|
-
const providerId = entry.providerId ||
|
|
1140
|
+
const providerId = entry.providerId || "claude-code";
|
|
808
1141
|
const adapter = adapterRegistry.get(providerId);
|
|
809
1142
|
await adapter.abortCurrentTurn(sessionId);
|
|
810
1143
|
res.json({ ok: true });
|
|
@@ -818,20 +1151,20 @@ terminalRouter.post('/sdk/abort', validateToken, async (req, res) => {
|
|
|
818
1151
|
* 停止 SDK 会话
|
|
819
1152
|
* POST /runtime/sdk/stop
|
|
820
1153
|
*/
|
|
821
|
-
terminalRouter.post(
|
|
1154
|
+
terminalRouter.post("/sdk/stop", validateToken, async (req, res) => {
|
|
822
1155
|
const { sessionId } = req.body || {};
|
|
823
1156
|
if (!sessionId) {
|
|
824
|
-
res.status(400).json({ error:
|
|
1157
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
825
1158
|
return;
|
|
826
1159
|
}
|
|
827
1160
|
const entry = sdkSessions.get(sessionId);
|
|
828
1161
|
if (!entry) {
|
|
829
|
-
res.json({ ok: true, status:
|
|
1162
|
+
res.json({ ok: true, status: "stopped" });
|
|
830
1163
|
return;
|
|
831
1164
|
}
|
|
832
1165
|
try {
|
|
833
1166
|
// 根据 providerId 获取对应的 adapter
|
|
834
|
-
const providerId = entry.providerId ||
|
|
1167
|
+
const providerId = entry.providerId || "claude-code";
|
|
835
1168
|
const adapter = adapterRegistry.get(providerId);
|
|
836
1169
|
await adapter.terminateSession(sessionId);
|
|
837
1170
|
sdkSessions.delete(sessionId);
|
|
@@ -840,8 +1173,10 @@ terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
|
|
|
840
1173
|
// ★ 从进程管理器中移除
|
|
841
1174
|
const processManager = getAgentProcessManager();
|
|
842
1175
|
await processManager.removeProcess(entry.agentId);
|
|
843
|
-
await sendStatus(entry.agentId, null,
|
|
844
|
-
|
|
1176
|
+
await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
|
|
1177
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1178
|
+
});
|
|
1179
|
+
res.json({ ok: true, status: "stopped" });
|
|
845
1180
|
}
|
|
846
1181
|
catch (error) {
|
|
847
1182
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -852,20 +1187,20 @@ terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
|
|
|
852
1187
|
* 获取 SDK 会话状态
|
|
853
1188
|
* GET /runtime/sdk/status/:sessionId
|
|
854
1189
|
*/
|
|
855
|
-
terminalRouter.get(
|
|
1190
|
+
terminalRouter.get("/sdk/status/:sessionId", validateToken, (req, res) => {
|
|
856
1191
|
const sessionId = req.params.sessionId;
|
|
857
1192
|
if (!sessionId) {
|
|
858
|
-
res.status(400).json({ error:
|
|
1193
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
859
1194
|
return;
|
|
860
1195
|
}
|
|
861
1196
|
const entry = sdkSessions.get(sessionId);
|
|
862
1197
|
if (!entry) {
|
|
863
|
-
res.status(404).json({ error:
|
|
1198
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
864
1199
|
return;
|
|
865
1200
|
}
|
|
866
1201
|
try {
|
|
867
1202
|
// 根据 providerId 获取对应的 adapter
|
|
868
|
-
const providerId = entry.providerId ||
|
|
1203
|
+
const providerId = entry.providerId || "claude-code";
|
|
869
1204
|
const adapter = adapterRegistry.get(providerId);
|
|
870
1205
|
const status = adapter.getSessionStatus(sessionId);
|
|
871
1206
|
const conversation = adapter.getConversation(sessionId);
|
|
@@ -888,15 +1223,15 @@ terminalRouter.get('/sdk/status/:sessionId', validateToken, (req, res) => {
|
|
|
888
1223
|
* 主流程:校验 session/command -> 读取会话 workspacePath -> 在该 cwd 下启动 shell 命令 -> 通过终端输出回调流式返回。
|
|
889
1224
|
* POST /runtime/command
|
|
890
1225
|
*/
|
|
891
|
-
terminalRouter.post(
|
|
1226
|
+
terminalRouter.post("/command", validateToken, async (req, res) => {
|
|
892
1227
|
const { sessionId, command } = req.body || {};
|
|
893
1228
|
if (!sessionId || command === undefined || command === null) {
|
|
894
|
-
res.status(400).json({ error:
|
|
1229
|
+
res.status(400).json({ error: "sessionId and command are required" });
|
|
895
1230
|
return;
|
|
896
1231
|
}
|
|
897
1232
|
try {
|
|
898
1233
|
const result = await executeTerminalCommand(String(sessionId), command);
|
|
899
|
-
res.json({ ok: true, mode:
|
|
1234
|
+
res.json({ ok: true, mode: "terminal", ...result });
|
|
900
1235
|
}
|
|
901
1236
|
catch (error) {
|
|
902
1237
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -910,31 +1245,33 @@ terminalRouter.post('/command', validateToken, async (req, res) => {
|
|
|
910
1245
|
* 主流程:校验 session/input -> 定位 SDK 会话 -> 通过 provider adapter 发送用户消息。
|
|
911
1246
|
* POST /runtime/input
|
|
912
1247
|
*/
|
|
913
|
-
terminalRouter.post(
|
|
1248
|
+
terminalRouter.post("/input", validateToken, async (req, res) => {
|
|
914
1249
|
const { sessionId, input, mode } = req.body || {};
|
|
915
1250
|
if (!sessionId || input === undefined || input === null) {
|
|
916
|
-
res.status(400).json({ error:
|
|
1251
|
+
res.status(400).json({ error: "sessionId and input are required" });
|
|
917
1252
|
return;
|
|
918
1253
|
}
|
|
919
1254
|
const entry = sdkSessions.get(sessionId);
|
|
920
1255
|
if (!entry) {
|
|
921
|
-
res.status(404).json({ error:
|
|
1256
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
922
1257
|
return;
|
|
923
1258
|
}
|
|
924
1259
|
try {
|
|
925
|
-
const providerId = entry.providerId ||
|
|
1260
|
+
const providerId = entry.providerId || "claude-code";
|
|
926
1261
|
const adapter = adapterRegistry.get(providerId);
|
|
927
|
-
if (mode ===
|
|
1262
|
+
if (mode === "shortcut") {
|
|
928
1263
|
if (!adapter.sendShortcutInput) {
|
|
929
|
-
res.status(400).json({
|
|
1264
|
+
res.status(400).json({
|
|
1265
|
+
error: "shortcut input is not supported by this SDK provider",
|
|
1266
|
+
});
|
|
930
1267
|
return;
|
|
931
1268
|
}
|
|
932
1269
|
await adapter.sendShortcutInput(sessionId, String(input));
|
|
933
|
-
res.json({ ok: true, mode:
|
|
1270
|
+
res.json({ ok: true, mode: "shortcut" });
|
|
934
1271
|
return;
|
|
935
1272
|
}
|
|
936
1273
|
await adapter.sendMessage(sessionId, await resolveSdkInputMessage(input));
|
|
937
|
-
res.json({ ok: true, mode:
|
|
1274
|
+
res.json({ ok: true, mode: "sdk" });
|
|
938
1275
|
}
|
|
939
1276
|
catch (error) {
|
|
940
1277
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -947,22 +1284,22 @@ terminalRouter.post('/input', validateToken, async (req, res) => {
|
|
|
947
1284
|
* 主流程:按 agentId 定位 SDK 会话 -> 更新 adapter 内存配置 -> 同步持久化会话字段。
|
|
948
1285
|
* POST /runtime/update-auto-commands
|
|
949
1286
|
*/
|
|
950
|
-
terminalRouter.post(
|
|
951
|
-
const { agentId, idleInputAutoCommand =
|
|
952
|
-
const normalizedAgentId = String(agentId ||
|
|
1287
|
+
terminalRouter.post("/update-auto-commands", validateToken, async (req, res) => {
|
|
1288
|
+
const { agentId, idleInputAutoCommand = "", nonInputAutoCommand = "", } = req.body || {};
|
|
1289
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
953
1290
|
if (!normalizedAgentId) {
|
|
954
|
-
res.status(400).json({ error:
|
|
1291
|
+
res.status(400).json({ error: "agentId is required" });
|
|
955
1292
|
return;
|
|
956
1293
|
}
|
|
957
1294
|
const entry = [...sdkSessions.values()].find((session) => session.agentId === normalizedAgentId);
|
|
958
1295
|
const commands = {
|
|
959
|
-
idleInputCommand: String(idleInputAutoCommand ||
|
|
960
|
-
nonInputCommand: String(nonInputAutoCommand ||
|
|
1296
|
+
idleInputCommand: String(idleInputAutoCommand || ""),
|
|
1297
|
+
nonInputCommand: String(nonInputAutoCommand || ""),
|
|
961
1298
|
};
|
|
962
1299
|
try {
|
|
963
1300
|
let sessionUpdated = false;
|
|
964
1301
|
if (entry) {
|
|
965
|
-
const adapter = adapterRegistry.get(entry.providerId ||
|
|
1302
|
+
const adapter = adapterRegistry.get(entry.providerId || "claude-code");
|
|
966
1303
|
adapter.setIdleCommands?.(entry.sessionId, commands);
|
|
967
1304
|
sessionUpdated = true;
|
|
968
1305
|
}
|
|
@@ -970,11 +1307,11 @@ terminalRouter.post('/update-auto-commands', validateToken, async (req, res) =>
|
|
|
970
1307
|
idleInputAutoCommand: commands.idleInputCommand,
|
|
971
1308
|
nonInputAutoCommand: commands.nonInputCommand,
|
|
972
1309
|
});
|
|
973
|
-
res.json({ ok: true, mode:
|
|
1310
|
+
res.json({ ok: true, mode: "sdk", sessionUpdated, persistedUpdated });
|
|
974
1311
|
}
|
|
975
1312
|
catch (error) {
|
|
976
1313
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
977
|
-
res.status(500).json({ ok: false, mode:
|
|
1314
|
+
res.status(500).json({ ok: false, mode: "sdk", error: errorMessage });
|
|
978
1315
|
}
|
|
979
1316
|
});
|
|
980
1317
|
/**
|
|
@@ -983,37 +1320,43 @@ terminalRouter.post('/update-auto-commands', validateToken, async (req, res) =>
|
|
|
983
1320
|
* SDK 会话没有终端尺寸概念;保留该端点用于前端 xterm fit 回调,返回 no-op。
|
|
984
1321
|
* POST /runtime/resize
|
|
985
1322
|
*/
|
|
986
|
-
terminalRouter.post(
|
|
1323
|
+
terminalRouter.post("/resize", validateToken, (req, res) => {
|
|
987
1324
|
const { sessionId, cols, rows } = req.body || {};
|
|
988
1325
|
if (!sessionId || !cols || !rows) {
|
|
989
|
-
res.status(400).json({ error:
|
|
1326
|
+
res.status(400).json({ error: "sessionId, cols, rows are required" });
|
|
990
1327
|
return;
|
|
991
1328
|
}
|
|
992
1329
|
if (!sdkSessions.has(sessionId)) {
|
|
993
|
-
res.status(404).json({ error:
|
|
1330
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
994
1331
|
return;
|
|
995
1332
|
}
|
|
996
|
-
res.json({
|
|
1333
|
+
res.json({
|
|
1334
|
+
ok: true,
|
|
1335
|
+
mode: "sdk",
|
|
1336
|
+
noop: true,
|
|
1337
|
+
cols: Number(cols),
|
|
1338
|
+
rows: Number(rows),
|
|
1339
|
+
});
|
|
997
1340
|
});
|
|
998
1341
|
/**
|
|
999
1342
|
* 停止 SDK 会话。
|
|
1000
1343
|
* POST /runtime/stop
|
|
1001
1344
|
*/
|
|
1002
|
-
terminalRouter.post(
|
|
1345
|
+
terminalRouter.post("/stop", validateToken, async (req, res) => {
|
|
1003
1346
|
const { sessionId } = req.body || {};
|
|
1004
1347
|
if (!sessionId) {
|
|
1005
|
-
res.status(400).json({ error:
|
|
1348
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
1006
1349
|
return;
|
|
1007
1350
|
}
|
|
1008
1351
|
const sdkEntry = sdkSessions.get(sessionId);
|
|
1009
1352
|
if (!sdkEntry) {
|
|
1010
1353
|
await removePersistedSession(sessionId);
|
|
1011
|
-
res.json({ ok: true, status:
|
|
1354
|
+
res.json({ ok: true, status: "stopped", mode: "none" });
|
|
1012
1355
|
return;
|
|
1013
1356
|
}
|
|
1014
1357
|
const agentId = sdkEntry.agentId;
|
|
1015
1358
|
try {
|
|
1016
|
-
const providerId = sdkEntry.providerId ||
|
|
1359
|
+
const providerId = sdkEntry.providerId || "claude-code";
|
|
1017
1360
|
const adapter = adapterRegistry.get(providerId);
|
|
1018
1361
|
await adapter.terminateSession(sessionId);
|
|
1019
1362
|
sdkSessions.delete(sessionId);
|
|
@@ -1021,13 +1364,20 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
|
|
|
1021
1364
|
await removePersistedSession(sessionId);
|
|
1022
1365
|
const processManager = getAgentProcessManager();
|
|
1023
1366
|
await processManager.removeProcess(agentId);
|
|
1024
|
-
await sendStatus(agentId, null,
|
|
1025
|
-
|
|
1367
|
+
await sendStatus(agentId, null, "stopped", undefined, undefined, {
|
|
1368
|
+
runtimeAccessToken: sdkEntry.runtimeAccessToken,
|
|
1369
|
+
});
|
|
1370
|
+
res.json({ ok: true, status: "stopped", mode: "sdk" });
|
|
1026
1371
|
}
|
|
1027
1372
|
catch (error) {
|
|
1028
1373
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1029
1374
|
console.error(`[Runtime] Failed to terminate SDK session ${sessionId}:`, errorMessage);
|
|
1030
|
-
res.status(500).json({
|
|
1375
|
+
res.status(500).json({
|
|
1376
|
+
ok: false,
|
|
1377
|
+
status: "stop_failed",
|
|
1378
|
+
mode: "sdk",
|
|
1379
|
+
error: errorMessage,
|
|
1380
|
+
});
|
|
1031
1381
|
}
|
|
1032
1382
|
});
|
|
1033
1383
|
/**
|
|
@@ -1035,20 +1385,22 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
|
|
|
1035
1385
|
* POST /runtime/stop-all
|
|
1036
1386
|
* 用于 aws-mcp-server 重启时清理所有 SDK Agent 会话
|
|
1037
1387
|
*/
|
|
1038
|
-
terminalRouter.post(
|
|
1388
|
+
terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
|
|
1039
1389
|
const stoppedSdk = [];
|
|
1040
1390
|
const errors = [];
|
|
1041
1391
|
// 1. 停止所有 SDK 会话
|
|
1042
1392
|
for (const [sessionId, entry] of sdkSessions.entries()) {
|
|
1043
1393
|
try {
|
|
1044
1394
|
// 根据 providerId 获取对应的 adapter
|
|
1045
|
-
const providerId = entry.providerId ||
|
|
1395
|
+
const providerId = entry.providerId || "claude-code";
|
|
1046
1396
|
const adapter = adapterRegistry.get(providerId);
|
|
1047
1397
|
await adapter.terminateSession(sessionId);
|
|
1048
1398
|
sdkSessions.delete(sessionId);
|
|
1049
1399
|
stopTerminalCommandProcess(sessionId);
|
|
1050
1400
|
stoppedSdk.push(sessionId);
|
|
1051
|
-
await sendStatus(entry.agentId, null,
|
|
1401
|
+
await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
|
|
1402
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1403
|
+
});
|
|
1052
1404
|
}
|
|
1053
1405
|
catch (error) {
|
|
1054
1406
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1059,7 +1411,7 @@ terminalRouter.post('/stop-all', validateToken, async (_req, res) => {
|
|
|
1059
1411
|
// 直接清空整个持久化文件,确保不会有残留
|
|
1060
1412
|
try {
|
|
1061
1413
|
await savePersistedSessions([]);
|
|
1062
|
-
console.log(
|
|
1414
|
+
console.log("[Runtime] 已清空所有持久化会话");
|
|
1063
1415
|
}
|
|
1064
1416
|
catch (error) {
|
|
1065
1417
|
const errorMessage = error instanceof Error ? error.message : String(error);
|