aws-runtime-bridge 1.7.49 → 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 +104 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -0
- package/dist/adapter/AcodeSdkAdapter.js +644 -0
- package/dist/adapter/AcodeSdkAdapter.test.d.ts +2 -0
- package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +1 -0
- package/dist/adapter/AcodeSdkAdapter.test.js +1280 -0
- package/dist/adapter/AdapterRegistry.d.ts +1 -1
- package/dist/adapter/AdapterRegistry.d.ts.map +1 -1
- package/dist/adapter/AdapterRegistry.js +2 -0
- package/dist/adapter/AdapterRegistry.test.js +10 -1
- 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/SdkProviderSpi.js +10 -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/index.d.ts +1 -0
- package/dist/adapter/index.d.ts.map +1 -1
- package/dist/adapter/index.js +1 -0
- 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/adapters/cc-switch/index.d.ts +2 -0
- package/dist/adapters/cc-switch/index.d.ts.map +1 -1
- package/dist/adapters/cc-switch/index.js +6 -0
- package/dist/adapters/cc-switch/mcp-acode.d.ts +17 -0
- package/dist/adapters/cc-switch/mcp-acode.d.ts.map +1 -0
- package/dist/adapters/cc-switch/mcp-acode.js +71 -0
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts +2 -0
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +1 -0
- package/dist/adapters/cc-switch/mcp-acode.test.js +80 -0
- package/dist/adapters/cc-switch/mcp-claude.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-claude.js +1 -0
- package/dist/adapters/cc-switch/mcp-claudecode.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-claudecode.js +1 -0
- package/dist/adapters/cc-switch/mcp-claudecode.test.js +1 -0
- package/dist/adapters/cc-switch/mcp-codex.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-codex.js +1 -0
- package/dist/adapters/cc-switch/mcp-codex.test.js +1 -0
- package/dist/adapters/cc-switch/mcp-opencode.d.ts.map +1 -1
- package/dist/adapters/cc-switch/mcp-opencode.js +1 -0
- package/dist/adapters/cc-switch/mcp-opencode.test.js +1 -0
- package/dist/adapters/cc-switch/skill-acode.d.ts +11 -0
- package/dist/adapters/cc-switch/skill-acode.d.ts.map +1 -0
- package/dist/adapters/cc-switch/skill-acode.js +12 -0
- package/dist/adapters/cc-switch/skill-directory.test.js +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/index.js +2 -0
- package/dist/routes/ai-sources.js +3 -2
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +7 -8
- package/dist/routes/instance.test.js +2 -1
- package/dist/routes/mcp.js +2 -2
- package/dist/routes/mcp.test.js +2 -1
- package/dist/routes/runtime-binding.d.ts +3 -2
- package/dist/routes/runtime-binding.d.ts.map +1 -1
- package/dist/routes/runtime-binding.js +11 -58
- package/dist/routes/runtime-binding.test.js +4 -80
- package/dist/routes/sessions.d.ts +5 -0
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +36 -16
- package/dist/routes/sessions.test.js +13 -0
- package/dist/routes/skills.js +2 -2
- package/dist/routes/skills.test.js +2 -1
- package/dist/routes/terminal.d.ts +27 -7
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +439 -171
- package/dist/routes/terminal.test.js +385 -197
- package/dist/services/acode-package.d.ts +8 -0
- package/dist/services/acode-package.d.ts.map +1 -0
- package/dist/services/acode-package.js +82 -0
- package/dist/services/ai-source-apply.d.ts +4 -0
- package/dist/services/ai-source-apply.d.ts.map +1 -1
- package/dist/services/ai-source-apply.js +8 -0
- package/dist/services/ai-source-apply.test.js +8 -1
- package/dist/services/cc-switch-sdk.d.ts.map +1 -1
- package/dist/services/cc-switch-sdk.js +9 -1
- package/dist/services/instance-init-service.d.ts.map +1 -1
- package/dist/services/instance-init-service.js +3 -2
- package/dist/services/instance-init-service.test.js +6 -5
- package/dist/services/instance-service.d.ts.map +1 -1
- package/dist/services/instance-service.js +1 -0
- package/dist/services/instance-state.js +1 -1
- package/dist/services/instance-state.test.js +4 -4
- 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/dist/services/tool-installer.d.ts.map +1 -1
- package/dist/services/tool-installer.js +10 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +1 -0
- package/dist/utils/mcp-utils.d.ts.map +1 -1
- package/dist/utils/mcp-utils.js +4 -1
- package/dist/utils/validation.js +1 -1
- package/node_modules/@cc-switch/sdk/dist/constants.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/constants.js +7 -0
- package/node_modules/@cc-switch/sdk/dist/schemas.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/schemas.js +1 -0
- package/node_modules/@cc-switch/sdk/dist/sdk.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/sdk.js +7 -3
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts +6 -0
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts.map +1 -1
- package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.js +27 -1
- package/node_modules/@cc-switch/sdk/dist/types.d.ts +4 -1
- package/node_modules/@cc-switch/sdk/dist/types.d.ts.map +1 -1
- package/package/acode/README.md +88 -0
- package/package/acode/dist/built-in-file-tools.d.ts +16 -0
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -0
- package/package/acode/dist/built-in-file-tools.js +569 -0
- package/package/acode/dist/cli.d.ts +3 -0
- package/package/acode/dist/cli.d.ts.map +1 -0
- package/package/acode/dist/cli.js +37 -0
- package/package/acode/dist/commands/commandLoader.d.ts +7 -0
- package/package/acode/dist/commands/commandLoader.d.ts.map +1 -0
- package/package/acode/dist/commands/commandLoader.js +33 -0
- package/package/acode/dist/commands/commandParser.d.ts +7 -0
- package/package/acode/dist/commands/commandParser.d.ts.map +1 -0
- package/package/acode/dist/commands/commandParser.js +77 -0
- package/package/acode/dist/commands/commandRegistry.d.ts +13 -0
- package/package/acode/dist/commands/commandRegistry.d.ts.map +1 -0
- package/package/acode/dist/commands/commandRegistry.js +30 -0
- package/package/acode/dist/config.d.ts +4 -0
- package/package/acode/dist/config.d.ts.map +1 -0
- package/package/acode/dist/config.js +80 -0
- package/package/acode/dist/hooks/hookBus.d.ts +19 -0
- package/package/acode/dist/hooks/hookBus.d.ts.map +1 -0
- package/package/acode/dist/hooks/hookBus.js +40 -0
- package/package/acode/dist/index.d.ts +8 -0
- package/package/acode/dist/index.d.ts.map +1 -0
- package/package/acode/dist/index.js +7 -0
- package/package/acode/dist/runtime.d.ts +42 -0
- package/package/acode/dist/runtime.d.ts.map +1 -0
- package/package/acode/dist/runtime.js +1283 -0
- package/package/acode/dist/types.d.ts +112 -0
- package/package/acode/dist/types.d.ts.map +1 -0
- package/package/acode/dist/types.js +1 -0
- package/package/acode/package.json +24 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +4 -4
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +9 -6
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +21 -0
- package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +1 -17
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +7 -113
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.test.js +22 -2
- package/package/aws-client-agent-mcp/dist/http-client.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -5
- 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 -24
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +90 -5
- 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 +4 -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 +0 -13
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +5 -100
- 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/cc-switch-sdk/dist/constants.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/constants.js +7 -0
- package/package/cc-switch-sdk/dist/schemas.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/schemas.js +1 -0
- package/package/cc-switch-sdk/dist/sdk.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/sdk.js +7 -3
- package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts +6 -0
- package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts.map +1 -1
- package/package/cc-switch-sdk/dist/services/ai-config-service.js +27 -1
- package/package/cc-switch-sdk/dist/types.d.ts +4 -1
- package/package/cc-switch-sdk/dist/types.d.ts.map +1 -1
- package/package.json +8 -4
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,34 +166,50 @@ 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");
|
|
170
|
+
const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus);
|
|
121
171
|
return {
|
|
122
172
|
sessionId: entry.sessionId,
|
|
123
|
-
status
|
|
124
|
-
runtimeStatus:
|
|
173
|
+
status,
|
|
174
|
+
runtimeStatus: status,
|
|
125
175
|
runtimeActionType: entry.currentRuntimeActionType,
|
|
126
176
|
runtimeActionLabel: entry.currentRuntimeActionLabel,
|
|
127
177
|
runtimeActionDetail: entry.currentRuntimeActionDetail,
|
|
128
178
|
agentId: entry.agentId,
|
|
129
179
|
workspacePath: entry.workspacePath,
|
|
130
180
|
command: entry.config.command,
|
|
131
|
-
mode:
|
|
181
|
+
mode: "sdk",
|
|
132
182
|
providerSessionId: adapter.getProviderSessionId(entry.sessionId),
|
|
133
183
|
reused: true,
|
|
134
184
|
};
|
|
135
185
|
}
|
|
136
186
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
187
|
+
* 解析 SDK 会话对外展示状态。
|
|
188
|
+
* 主流程:优先使用 adapter 当前细粒度状态,其次使用路由内存记录的最近状态,最后才兼容旧会话回退 running。
|
|
189
|
+
*/
|
|
190
|
+
export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus) {
|
|
191
|
+
const normalizedAdapterStatus = String(adapterStatus || "").trim();
|
|
192
|
+
if (normalizedAdapterStatus) {
|
|
193
|
+
return normalizedAdapterStatus;
|
|
194
|
+
}
|
|
195
|
+
const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
|
|
196
|
+
return normalizedRuntimeStatus || "running";
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Decide whether a persisted SDK session can be reused for a start request.
|
|
200
|
+
* Main flow: require a live PID, then reject in-process providers whose adapter control channel cannot be restored.
|
|
139
201
|
*/
|
|
140
202
|
export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessRunning) {
|
|
141
203
|
if (!session.pid) {
|
|
142
|
-
return { reusable: false, reason:
|
|
204
|
+
return { reusable: false, reason: "missing-pid" };
|
|
143
205
|
}
|
|
144
206
|
if (!isPidRunning(session.pid)) {
|
|
145
|
-
return { reusable: false, reason:
|
|
207
|
+
return { reusable: false, reason: "dead-pid" };
|
|
208
|
+
}
|
|
209
|
+
if (resolveSdkProviderId(session.command) === "acode") {
|
|
210
|
+
return { reusable: false, reason: "in-process-provider" };
|
|
146
211
|
}
|
|
147
|
-
return { reusable: true, reason:
|
|
212
|
+
return { reusable: true, reason: "active-pid" };
|
|
148
213
|
}
|
|
149
214
|
function stopTerminalCommandProcess(sessionId) {
|
|
150
215
|
const state = terminalCommandStates.get(sessionId);
|
|
@@ -177,7 +242,7 @@ export function formatTerminalPrompt(currentDirectory) {
|
|
|
177
242
|
return `\x1b[36m${currentDirectory}>\x1b[0m `;
|
|
178
243
|
}
|
|
179
244
|
function resolveDirectoryTarget(currentDirectory, target) {
|
|
180
|
-
if (target ===
|
|
245
|
+
if (target === "~") {
|
|
181
246
|
return os.homedir();
|
|
182
247
|
}
|
|
183
248
|
return path.resolve(currentDirectory, target);
|
|
@@ -217,7 +282,7 @@ async function handleDirectoryChangeCommand(entry, state, command) {
|
|
|
217
282
|
async function executeTerminalCommand(sessionId, commandValue) {
|
|
218
283
|
const entry = sdkSessions.get(sessionId);
|
|
219
284
|
if (!entry) {
|
|
220
|
-
throw new Error(
|
|
285
|
+
throw new Error("SDK session not found");
|
|
221
286
|
}
|
|
222
287
|
const command = normalizeTerminalCommandInput(commandValue);
|
|
223
288
|
const state = getTerminalCommandState(entry);
|
|
@@ -225,12 +290,12 @@ async function executeTerminalCommand(sessionId, commandValue) {
|
|
|
225
290
|
return { started: false, cwd: state.currentDirectory };
|
|
226
291
|
}
|
|
227
292
|
if (state.runningProcess) {
|
|
228
|
-
throw new Error(
|
|
293
|
+
throw new Error("A terminal command is already running for this session");
|
|
229
294
|
}
|
|
230
295
|
const displayCommand = redactTransientClipboardPastePaths(command);
|
|
231
296
|
appendCommandOutput(entry, `${formatTerminalPrompt(state.currentDirectory)}${displayCommand}\r\n`);
|
|
232
297
|
if (isTransientClipboardPasteFilePath(command)) {
|
|
233
|
-
appendCommandOutput(entry,
|
|
298
|
+
appendCommandOutput(entry, "[Terminal Warning] 检测到剪贴板临时 paste 文件路径,已跳过执行。请粘贴文件内容或使用显式读取命令。\r\n");
|
|
234
299
|
appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
|
|
235
300
|
return { started: false, cwd: state.currentDirectory };
|
|
236
301
|
}
|
|
@@ -238,9 +303,9 @@ async function executeTerminalCommand(sessionId, commandValue) {
|
|
|
238
303
|
if (handledDirectoryChange) {
|
|
239
304
|
return { started: false, cwd: state.currentDirectory };
|
|
240
305
|
}
|
|
241
|
-
await sendStatus(entry.agentId, sessionId,
|
|
242
|
-
actionType:
|
|
243
|
-
actionLabel:
|
|
306
|
+
await sendStatus(entry.agentId, sessionId, "tool_using", {
|
|
307
|
+
actionType: "bash",
|
|
308
|
+
actionLabel: "终端命令",
|
|
244
309
|
actionDetail: displayCommand,
|
|
245
310
|
});
|
|
246
311
|
const child = spawn(command, {
|
|
@@ -251,19 +316,81 @@ async function executeTerminalCommand(sessionId, commandValue) {
|
|
|
251
316
|
state.runningProcess = child;
|
|
252
317
|
const stdoutDecoder = createTerminalOutputDecoder();
|
|
253
318
|
const stderrDecoder = createTerminalOutputDecoder();
|
|
254
|
-
|
|
255
|
-
|
|
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));
|
|
256
376
|
});
|
|
257
|
-
child.stderr.on(
|
|
258
|
-
|
|
377
|
+
child.stderr.on("data", (chunk) => {
|
|
378
|
+
appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, chunk));
|
|
259
379
|
});
|
|
260
|
-
child.on(
|
|
380
|
+
child.on("error", (error) => {
|
|
261
381
|
appendCommandOutput(entry, `\r\n[Terminal Error] ${error.message}\r\n`);
|
|
262
382
|
});
|
|
263
|
-
child.on(
|
|
383
|
+
child.on("close", (code, signal) => {
|
|
384
|
+
clearInactivityTimer();
|
|
264
385
|
state.runningProcess = null;
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
}
|
|
267
394
|
if (code && code !== 0) {
|
|
268
395
|
appendCommandOutput(entry, `\r\n[exit ${code}]\r\n`);
|
|
269
396
|
}
|
|
@@ -271,7 +398,7 @@ async function executeTerminalCommand(sessionId, commandValue) {
|
|
|
271
398
|
appendCommandOutput(entry, `\r\n[signal ${signal}]\r\n`);
|
|
272
399
|
}
|
|
273
400
|
appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
|
|
274
|
-
void sendStatus(entry.agentId, sessionId,
|
|
401
|
+
void sendStatus(entry.agentId, sessionId, "waiting_input");
|
|
275
402
|
});
|
|
276
403
|
return { started: true, cwd: state.currentDirectory };
|
|
277
404
|
}
|
|
@@ -299,7 +426,7 @@ export function buildRuntimeEnv(agentId, workspacePath, baseEnv, envOverrides) {
|
|
|
299
426
|
}
|
|
300
427
|
}
|
|
301
428
|
env.AWS_AGENT_ID = String(agentId);
|
|
302
|
-
env.AWS_MCP_CLAIM_LAUNCH_BINDING =
|
|
429
|
+
env.AWS_MCP_CLAIM_LAUNCH_BINDING = "true";
|
|
303
430
|
return env;
|
|
304
431
|
}
|
|
305
432
|
function stripWrappingQuotes(value) {
|
|
@@ -324,7 +451,7 @@ export async function resolveSdkInputMessage(input) {
|
|
|
324
451
|
if (!path.isAbsolute(filePath)) {
|
|
325
452
|
return message;
|
|
326
453
|
}
|
|
327
|
-
return fs.readFile(filePath,
|
|
454
|
+
return fs.readFile(filePath, "utf-8");
|
|
328
455
|
}
|
|
329
456
|
/**
|
|
330
457
|
* 主流程:将已持久化的 ClaudeCode AI 配置合并到启动配置,再让请求级覆盖保持最高优先级。
|
|
@@ -347,9 +474,9 @@ export function buildClaudeCodeLaunchConfig(savedClaudeConfig, requestEnvOverrid
|
|
|
347
474
|
envOverrides[normalizedKey] = normalizedValue;
|
|
348
475
|
}
|
|
349
476
|
}
|
|
350
|
-
const model = envOverrides.ANTHROPIC_MODEL?.trim()
|
|
351
|
-
|
|
352
|
-
|
|
477
|
+
const model = envOverrides.ANTHROPIC_MODEL?.trim() ||
|
|
478
|
+
envOverrides.CODEX_MODEL?.trim() ||
|
|
479
|
+
undefined;
|
|
353
480
|
return { envOverrides, model };
|
|
354
481
|
}
|
|
355
482
|
/**
|
|
@@ -369,7 +496,7 @@ function formatToolResultForTimeline(result) {
|
|
|
369
496
|
if (result === undefined || result === null) {
|
|
370
497
|
return undefined;
|
|
371
498
|
}
|
|
372
|
-
const text = typeof result ===
|
|
499
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
373
500
|
const normalized = text.trim();
|
|
374
501
|
if (!normalized) {
|
|
375
502
|
return undefined;
|
|
@@ -381,18 +508,20 @@ function formatToolResultForTimeline(result) {
|
|
|
381
508
|
}
|
|
382
509
|
function isMcpProviderEvent(event) {
|
|
383
510
|
const actionType = event.data.actionType;
|
|
384
|
-
if (actionType ===
|
|
511
|
+
if (actionType === "mcp") {
|
|
385
512
|
return true;
|
|
386
513
|
}
|
|
387
|
-
const toolName = String(event.data.toolName ||
|
|
388
|
-
return toolName.startsWith(
|
|
514
|
+
const toolName = String(event.data.toolName || "").toLowerCase();
|
|
515
|
+
return (toolName.startsWith("mcp__") ||
|
|
516
|
+
toolName.includes("-mcp__") ||
|
|
517
|
+
toolName.includes("mcp_"));
|
|
389
518
|
}
|
|
390
519
|
/**
|
|
391
520
|
* 将 SDK 工具结束事件转换为时间线返回数据状态。
|
|
392
521
|
* 主流程:格式化工具结果 -> 保留 MCP 专用展示 -> 其它工具按动作类型追加到对应时间线事件。
|
|
393
522
|
*/
|
|
394
523
|
export function buildToolResultTimelineActionInfo(event) {
|
|
395
|
-
if (event.type !==
|
|
524
|
+
if (event.type !== "tool_use_end") {
|
|
396
525
|
return undefined;
|
|
397
526
|
}
|
|
398
527
|
const actionResult = formatToolResultForTimeline(event.data.toolResult ?? event.data.actionResult);
|
|
@@ -401,13 +530,13 @@ export function buildToolResultTimelineActionInfo(event) {
|
|
|
401
530
|
}
|
|
402
531
|
const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
|
|
403
532
|
const actionType = event.data.actionType || inferredAction.actionType;
|
|
404
|
-
if (actionType ===
|
|
533
|
+
if (actionType === "idle") {
|
|
405
534
|
return undefined;
|
|
406
535
|
}
|
|
407
|
-
if (actionType ===
|
|
536
|
+
if (actionType === "mcp" && isMcpProviderEvent(event)) {
|
|
408
537
|
return {
|
|
409
|
-
actionType:
|
|
410
|
-
actionLabel:
|
|
538
|
+
actionType: "mcp",
|
|
539
|
+
actionLabel: "MCP返回",
|
|
411
540
|
actionDetail: event.data.actionDetail || event.data.toolName,
|
|
412
541
|
actionId: event.data.actionId || event.data.toolUseId,
|
|
413
542
|
actionResult,
|
|
@@ -416,17 +545,122 @@ export function buildToolResultTimelineActionInfo(event) {
|
|
|
416
545
|
return {
|
|
417
546
|
actionType,
|
|
418
547
|
actionLabel: event.data.actionLabel || inferredAction.actionLabel,
|
|
419
|
-
actionDetail: event.data.actionDetail ||
|
|
548
|
+
actionDetail: event.data.actionDetail ||
|
|
549
|
+
inferredAction.actionDetail ||
|
|
550
|
+
event.data.toolName,
|
|
420
551
|
actionId: event.data.actionId || event.data.toolUseId,
|
|
421
552
|
actionResult,
|
|
422
553
|
};
|
|
423
554
|
}
|
|
424
555
|
export function formatSdkOutputEvent(event) {
|
|
425
|
-
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") {
|
|
426
564
|
return event.data.text || undefined;
|
|
427
565
|
}
|
|
428
|
-
if (event.type ===
|
|
429
|
-
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" };
|
|
430
664
|
}
|
|
431
665
|
return undefined;
|
|
432
666
|
}
|
|
@@ -436,7 +670,7 @@ function forwardSdkOutputEvent(event, entry) {
|
|
|
436
670
|
return;
|
|
437
671
|
}
|
|
438
672
|
entry.seq += 1;
|
|
439
|
-
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 });
|
|
440
674
|
}
|
|
441
675
|
export function resolveSdkProviderId(command) {
|
|
442
676
|
return resolveSdkProviderIdByCommand(command);
|
|
@@ -450,45 +684,52 @@ function ensureAdapterInitialized() {
|
|
|
450
684
|
adapterInitialized = true;
|
|
451
685
|
}
|
|
452
686
|
function wireSdkAdapterEvents(adapter, definition) {
|
|
453
|
-
adapter.on(
|
|
687
|
+
adapter.on("event", (event) => {
|
|
454
688
|
const entry = sdkSessions.get(event.sessionId);
|
|
455
689
|
if (entry) {
|
|
456
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
|
+
}
|
|
457
695
|
forwardSdkOutputEvent(event, entry);
|
|
458
696
|
const toolResultActionInfo = buildToolResultTimelineActionInfo(event);
|
|
459
697
|
if (toolResultActionInfo) {
|
|
460
|
-
void sendStatus(entry.agentId, event.sessionId,
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
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
|
+
});
|
|
464
701
|
}
|
|
465
702
|
}
|
|
466
703
|
});
|
|
467
|
-
adapter.on(
|
|
704
|
+
adapter.on("status-change", (sessionId, status, actionInfo) => {
|
|
468
705
|
const entry = sdkSessions.get(sessionId);
|
|
469
706
|
if (entry) {
|
|
470
707
|
entry.currentRuntimeStatus = status;
|
|
471
708
|
entry.currentRuntimeActionType = actionInfo?.actionType;
|
|
472
709
|
entry.currentRuntimeActionLabel = actionInfo?.actionLabel;
|
|
473
710
|
entry.currentRuntimeActionDetail = actionInfo?.actionDetail;
|
|
474
|
-
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 || "");
|
|
475
712
|
void updatePersistedSessionRuntimeState(sessionId, {
|
|
476
713
|
runtimeStatus: status,
|
|
477
714
|
runtimeActionType: actionInfo?.actionType,
|
|
478
715
|
runtimeActionLabel: actionInfo?.actionLabel,
|
|
479
716
|
runtimeActionDetail: actionInfo?.actionDetail,
|
|
480
717
|
});
|
|
481
|
-
void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo)
|
|
718
|
+
void sendStatus(entry.agentId, sessionId, status, actionInfo, resolveStatusChangeUsage(actionInfo), {
|
|
719
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
720
|
+
});
|
|
482
721
|
}
|
|
483
722
|
});
|
|
484
|
-
adapter.on(
|
|
723
|
+
adapter.on("ask-user-question", (data) => {
|
|
485
724
|
const entry = sdkSessions.get(data.sessionId);
|
|
486
725
|
if (entry) {
|
|
487
726
|
console.log(`[${definition.displayName} Adapter] AskUserQuestion for agent ${entry.agentId}`);
|
|
488
|
-
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
|
+
});
|
|
489
730
|
}
|
|
490
731
|
});
|
|
491
|
-
adapter.on(
|
|
732
|
+
adapter.on("permission-request", (data) => {
|
|
492
733
|
const entry = sdkSessions.get(data.sessionId);
|
|
493
734
|
if (entry) {
|
|
494
735
|
console.log(`[${definition.displayName} Adapter] Permission request: ${data.toolName} for agent ${entry.agentId}`);
|
|
@@ -499,10 +740,10 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
499
740
|
* 启动 SDK Agent 会话
|
|
500
741
|
* POST /runtime/start
|
|
501
742
|
*/
|
|
502
|
-
terminalRouter.post(
|
|
503
|
-
const { agentId, workspacePath, mode =
|
|
743
|
+
terminalRouter.post("/start", validateToken, async (req, res) => {
|
|
744
|
+
const { agentId, workspacePath, mode = "sdk" } = req.body || {};
|
|
504
745
|
if (!agentId || !workspacePath) {
|
|
505
|
-
res.status(400).json({ error:
|
|
746
|
+
res.status(400).json({ error: "agentId and workspacePath are required" });
|
|
506
747
|
return;
|
|
507
748
|
}
|
|
508
749
|
enqueueMcpLaunchBinding({
|
|
@@ -513,7 +754,7 @@ terminalRouter.post('/start', validateToken, async (req, res) => {
|
|
|
513
754
|
userId: req.body?.userId,
|
|
514
755
|
schedulerBaseUrl: req.body?.schedulerBaseUrl,
|
|
515
756
|
});
|
|
516
|
-
if (mode && mode !==
|
|
757
|
+
if (mode && mode !== "sdk") {
|
|
517
758
|
console.warn(`[Runtime] Unsupported runtime mode "${mode}" requested for agent ${agentId}; starting SDK session instead.`);
|
|
518
759
|
}
|
|
519
760
|
await startSdkSession(req, res);
|
|
@@ -529,11 +770,12 @@ async function startSdkSession(req, res) {
|
|
|
529
770
|
// 空闲命令配置
|
|
530
771
|
idleInputAutoCommand, nonInputAutoCommand, } = req.body || {};
|
|
531
772
|
let sessionId = null;
|
|
532
|
-
const normalizedAgentId = String(agentId ||
|
|
773
|
+
const normalizedAgentId = String(agentId || "").trim();
|
|
774
|
+
const runtimeAccessToken = String(req.body?.runtimeAccessToken || "").trim() || undefined;
|
|
533
775
|
try {
|
|
534
776
|
ensureAdapterInitialized();
|
|
535
777
|
if (!normalizedAgentId) {
|
|
536
|
-
res.status(400).json({ error:
|
|
778
|
+
res.status(400).json({ error: "agentId is required" });
|
|
537
779
|
return;
|
|
538
780
|
}
|
|
539
781
|
const activeSession = findActiveSdkSessionByAgentId(normalizedAgentId);
|
|
@@ -545,8 +787,8 @@ async function startSdkSession(req, res) {
|
|
|
545
787
|
if (persistedSession) {
|
|
546
788
|
const reuseDecision = evaluatePersistedSessionReuse(persistedSession);
|
|
547
789
|
if (!reuseDecision.reusable) {
|
|
548
|
-
console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, `
|
|
549
|
-
|
|
790
|
+
console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, ` +
|
|
791
|
+
`sessionId=${persistedSession.sessionId}, reason=${reuseDecision.reason}`);
|
|
550
792
|
await removePersistedSession(persistedSession.sessionId);
|
|
551
793
|
}
|
|
552
794
|
else {
|
|
@@ -556,7 +798,7 @@ async function startSdkSession(req, res) {
|
|
|
556
798
|
agentId: persistedSession.agentId,
|
|
557
799
|
workspacePath: persistedSession.workspacePath,
|
|
558
800
|
command: persistedSession.command,
|
|
559
|
-
mode: persistedSession.mode ||
|
|
801
|
+
mode: persistedSession.mode || "sdk",
|
|
560
802
|
pid: persistedSession.pid,
|
|
561
803
|
reused: true,
|
|
562
804
|
persistedOnly: true,
|
|
@@ -565,26 +807,28 @@ async function startSdkSession(req, res) {
|
|
|
565
807
|
}
|
|
566
808
|
}
|
|
567
809
|
if (startingAgents.has(normalizedAgentId)) {
|
|
568
|
-
res
|
|
810
|
+
res
|
|
811
|
+
.status(409)
|
|
812
|
+
.json({ error: "SDK session is already starting for this agent" });
|
|
569
813
|
return;
|
|
570
814
|
}
|
|
571
815
|
startingAgents.add(normalizedAgentId);
|
|
572
816
|
// 根据命令选择对应的 adapter
|
|
573
|
-
const normalizedCommand = (command ||
|
|
817
|
+
const normalizedCommand = (command || "acode").toLowerCase();
|
|
574
818
|
const providerId = resolveSdkProviderId(command);
|
|
575
819
|
// ★ 特征日志:明确输出选择的 provider
|
|
576
820
|
console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
|
|
577
821
|
const adapter = adapterRegistry.get(providerId);
|
|
578
822
|
sessionId = uuidv4();
|
|
579
|
-
const savedClaudeConfig = providerId ===
|
|
823
|
+
const savedClaudeConfig = providerId === "claude-code"
|
|
580
824
|
? await readSavedClaudeCodeLaunchConfig()
|
|
581
825
|
: null;
|
|
582
|
-
const claudeLaunchConfig = providerId ===
|
|
826
|
+
const claudeLaunchConfig = providerId === "claude-code"
|
|
583
827
|
? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
|
|
584
828
|
: buildClaudeCodeLaunchConfig(null, envOverrides);
|
|
585
829
|
// 构建SDK配置
|
|
586
830
|
const config = {
|
|
587
|
-
command: command ||
|
|
831
|
+
command: command || "acode",
|
|
588
832
|
workingDirectory: workspacePath,
|
|
589
833
|
autoAccept: Boolean(autoAccept),
|
|
590
834
|
initialPrompt,
|
|
@@ -602,22 +846,30 @@ async function startSdkSession(req, res) {
|
|
|
602
846
|
config,
|
|
603
847
|
providerId, // 记录使用的 provider
|
|
604
848
|
seq: 0,
|
|
849
|
+
runtimeAccessToken,
|
|
605
850
|
});
|
|
606
851
|
// 启动 SDK 会话
|
|
607
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
|
+
}
|
|
608
860
|
// ★ 尝试获取 SDK 进程的 PID(用于孤儿进程检测)
|
|
609
861
|
// SDK 不直接暴露 PID,通过进程检测服务查找匹配的进程
|
|
610
862
|
let sdkPid;
|
|
611
863
|
try {
|
|
612
864
|
// 等待一小段时间让进程启动
|
|
613
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
865
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
614
866
|
// 尝试通过 adapter 获取 PID(如果支持)
|
|
615
867
|
sdkPid = adapter.getSessionPid?.(sessionId);
|
|
616
868
|
// 如果 adapter 不支持,尝试通过进程检测服务查找
|
|
617
869
|
if (!sdkPid) {
|
|
618
870
|
const processes = findClaudeCodeProcesses();
|
|
619
871
|
// 查找匹配 agentId 或工作目录的进程
|
|
620
|
-
const matched = processes.find(p => p.agentId === agentId ||
|
|
872
|
+
const matched = processes.find((p) => p.agentId === agentId ||
|
|
621
873
|
p.cwd === workspacePath ||
|
|
622
874
|
p.command.includes(workspacePath));
|
|
623
875
|
if (matched) {
|
|
@@ -630,15 +882,16 @@ async function startSdkSession(req, res) {
|
|
|
630
882
|
console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
|
|
631
883
|
}
|
|
632
884
|
// ★ 持久化会话信息(包含 PID)
|
|
885
|
+
const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus);
|
|
633
886
|
await upsertPersistedSession({
|
|
634
887
|
sessionId,
|
|
635
888
|
agentId,
|
|
636
889
|
workspacePath,
|
|
637
890
|
command: config.command,
|
|
638
891
|
startedAt: new Date().toISOString(),
|
|
639
|
-
status:
|
|
640
|
-
runtimeStatus:
|
|
641
|
-
mode:
|
|
892
|
+
status: "running",
|
|
893
|
+
runtimeStatus: startedStatus,
|
|
894
|
+
mode: "sdk",
|
|
642
895
|
pid: sdkPid,
|
|
643
896
|
providerSessionId: adapter.getProviderSessionId(sessionId),
|
|
644
897
|
});
|
|
@@ -649,25 +902,19 @@ async function startSdkSession(req, res) {
|
|
|
649
902
|
agentId: normalizedAgentId,
|
|
650
903
|
sessionId,
|
|
651
904
|
pid: sdkPid,
|
|
652
|
-
mode:
|
|
905
|
+
mode: "sdk",
|
|
653
906
|
workspacePath,
|
|
654
907
|
command: config.command,
|
|
655
908
|
});
|
|
656
909
|
}
|
|
657
|
-
// 存储空闲命令配置(用于SDK模式的空闲检测)
|
|
658
|
-
if (idleInputAutoCommand || nonInputAutoCommand) {
|
|
659
|
-
adapter.setIdleCommands?.(sessionId, {
|
|
660
|
-
idleInputCommand: idleInputAutoCommand || '',
|
|
661
|
-
nonInputCommand: nonInputAutoCommand || '',
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
910
|
res.json({
|
|
665
911
|
sessionId,
|
|
666
|
-
status:
|
|
912
|
+
status: startedStatus,
|
|
913
|
+
runtimeStatus: startedStatus,
|
|
667
914
|
agentId,
|
|
668
915
|
workspacePath,
|
|
669
916
|
command: config.command,
|
|
670
|
-
mode:
|
|
917
|
+
mode: "sdk",
|
|
671
918
|
pid: sdkPid, // ★ 返回 PID 信息
|
|
672
919
|
});
|
|
673
920
|
}
|
|
@@ -677,7 +924,9 @@ async function startSdkSession(req, res) {
|
|
|
677
924
|
await removePersistedSession(sessionId);
|
|
678
925
|
}
|
|
679
926
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
680
|
-
res
|
|
927
|
+
res
|
|
928
|
+
.status(500)
|
|
929
|
+
.json({ error: `Failed to start SDK session: ${errorMessage}` });
|
|
681
930
|
}
|
|
682
931
|
finally {
|
|
683
932
|
if (normalizedAgentId) {
|
|
@@ -690,20 +939,20 @@ async function startSdkSession(req, res) {
|
|
|
690
939
|
* 发送消息到 SDK 会话
|
|
691
940
|
* POST /runtime/sdk/message
|
|
692
941
|
*/
|
|
693
|
-
terminalRouter.post(
|
|
942
|
+
terminalRouter.post("/sdk/message", validateToken, async (req, res) => {
|
|
694
943
|
const { sessionId, message } = req.body || {};
|
|
695
944
|
if (!sessionId || !message) {
|
|
696
|
-
res.status(400).json({ error:
|
|
945
|
+
res.status(400).json({ error: "sessionId and message are required" });
|
|
697
946
|
return;
|
|
698
947
|
}
|
|
699
948
|
const entry = sdkSessions.get(sessionId);
|
|
700
949
|
if (!entry) {
|
|
701
|
-
res.status(404).json({ error:
|
|
950
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
702
951
|
return;
|
|
703
952
|
}
|
|
704
953
|
try {
|
|
705
954
|
// 根据 providerId 获取对应的 adapter
|
|
706
|
-
const providerId = entry.providerId ||
|
|
955
|
+
const providerId = entry.providerId || "claude-code";
|
|
707
956
|
const adapter = adapterRegistry.get(providerId);
|
|
708
957
|
await adapter.sendMessage(sessionId, message);
|
|
709
958
|
res.json({ ok: true });
|
|
@@ -717,20 +966,20 @@ terminalRouter.post('/sdk/message', validateToken, async (req, res) => {
|
|
|
717
966
|
* 回答 AskUserQuestion
|
|
718
967
|
* POST /runtime/sdk/answer
|
|
719
968
|
*/
|
|
720
|
-
terminalRouter.post(
|
|
969
|
+
terminalRouter.post("/sdk/answer", validateToken, async (req, res) => {
|
|
721
970
|
const { sessionId, answers } = req.body || {};
|
|
722
971
|
if (!sessionId || !answers) {
|
|
723
|
-
res.status(400).json({ error:
|
|
972
|
+
res.status(400).json({ error: "sessionId and answers are required" });
|
|
724
973
|
return;
|
|
725
974
|
}
|
|
726
975
|
const entry = sdkSessions.get(sessionId);
|
|
727
976
|
if (!entry) {
|
|
728
|
-
res.status(404).json({ error:
|
|
977
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
729
978
|
return;
|
|
730
979
|
}
|
|
731
980
|
try {
|
|
732
981
|
// 根据 providerId 获取对应的 adapter
|
|
733
|
-
const providerId = entry.providerId ||
|
|
982
|
+
const providerId = entry.providerId || "claude-code";
|
|
734
983
|
const adapter = adapterRegistry.get(providerId);
|
|
735
984
|
if (adapter.sendQuestionAnswer) {
|
|
736
985
|
await adapter.sendQuestionAnswer(sessionId, answers);
|
|
@@ -746,20 +995,20 @@ terminalRouter.post('/sdk/answer', validateToken, async (req, res) => {
|
|
|
746
995
|
* 确认权限请求
|
|
747
996
|
* POST /runtime/sdk/confirm
|
|
748
997
|
*/
|
|
749
|
-
terminalRouter.post(
|
|
998
|
+
terminalRouter.post("/sdk/confirm", validateToken, async (req, res) => {
|
|
750
999
|
const { sessionId, accept } = req.body || {};
|
|
751
1000
|
if (!sessionId) {
|
|
752
|
-
res.status(400).json({ error:
|
|
1001
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
753
1002
|
return;
|
|
754
1003
|
}
|
|
755
1004
|
const entry = sdkSessions.get(sessionId);
|
|
756
1005
|
if (!entry) {
|
|
757
|
-
res.status(404).json({ error:
|
|
1006
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
758
1007
|
return;
|
|
759
1008
|
}
|
|
760
1009
|
try {
|
|
761
1010
|
// 根据 providerId 获取对应的 adapter
|
|
762
|
-
const providerId = entry.providerId ||
|
|
1011
|
+
const providerId = entry.providerId || "claude-code";
|
|
763
1012
|
const adapter = adapterRegistry.get(providerId);
|
|
764
1013
|
await adapter.sendConfirmation(sessionId, Boolean(accept));
|
|
765
1014
|
res.json({ ok: true });
|
|
@@ -773,20 +1022,20 @@ terminalRouter.post('/sdk/confirm', validateToken, async (req, res) => {
|
|
|
773
1022
|
* 中止当前轮次
|
|
774
1023
|
* POST /runtime/sdk/abort
|
|
775
1024
|
*/
|
|
776
|
-
terminalRouter.post(
|
|
1025
|
+
terminalRouter.post("/sdk/abort", validateToken, async (req, res) => {
|
|
777
1026
|
const { sessionId } = req.body || {};
|
|
778
1027
|
if (!sessionId) {
|
|
779
|
-
res.status(400).json({ error:
|
|
1028
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
780
1029
|
return;
|
|
781
1030
|
}
|
|
782
1031
|
const entry = sdkSessions.get(sessionId);
|
|
783
1032
|
if (!entry) {
|
|
784
|
-
res.status(404).json({ error:
|
|
1033
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
785
1034
|
return;
|
|
786
1035
|
}
|
|
787
1036
|
try {
|
|
788
1037
|
// 根据 providerId 获取对应的 adapter
|
|
789
|
-
const providerId = entry.providerId ||
|
|
1038
|
+
const providerId = entry.providerId || "claude-code";
|
|
790
1039
|
const adapter = adapterRegistry.get(providerId);
|
|
791
1040
|
await adapter.abortCurrentTurn(sessionId);
|
|
792
1041
|
res.json({ ok: true });
|
|
@@ -800,20 +1049,20 @@ terminalRouter.post('/sdk/abort', validateToken, async (req, res) => {
|
|
|
800
1049
|
* 停止 SDK 会话
|
|
801
1050
|
* POST /runtime/sdk/stop
|
|
802
1051
|
*/
|
|
803
|
-
terminalRouter.post(
|
|
1052
|
+
terminalRouter.post("/sdk/stop", validateToken, async (req, res) => {
|
|
804
1053
|
const { sessionId } = req.body || {};
|
|
805
1054
|
if (!sessionId) {
|
|
806
|
-
res.status(400).json({ error:
|
|
1055
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
807
1056
|
return;
|
|
808
1057
|
}
|
|
809
1058
|
const entry = sdkSessions.get(sessionId);
|
|
810
1059
|
if (!entry) {
|
|
811
|
-
res.json({ ok: true, status:
|
|
1060
|
+
res.json({ ok: true, status: "stopped" });
|
|
812
1061
|
return;
|
|
813
1062
|
}
|
|
814
1063
|
try {
|
|
815
1064
|
// 根据 providerId 获取对应的 adapter
|
|
816
|
-
const providerId = entry.providerId ||
|
|
1065
|
+
const providerId = entry.providerId || "claude-code";
|
|
817
1066
|
const adapter = adapterRegistry.get(providerId);
|
|
818
1067
|
await adapter.terminateSession(sessionId);
|
|
819
1068
|
sdkSessions.delete(sessionId);
|
|
@@ -822,8 +1071,10 @@ terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
|
|
|
822
1071
|
// ★ 从进程管理器中移除
|
|
823
1072
|
const processManager = getAgentProcessManager();
|
|
824
1073
|
await processManager.removeProcess(entry.agentId);
|
|
825
|
-
await sendStatus(entry.agentId, null,
|
|
826
|
-
|
|
1074
|
+
await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
|
|
1075
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1076
|
+
});
|
|
1077
|
+
res.json({ ok: true, status: "stopped" });
|
|
827
1078
|
}
|
|
828
1079
|
catch (error) {
|
|
829
1080
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -834,20 +1085,20 @@ terminalRouter.post('/sdk/stop', validateToken, async (req, res) => {
|
|
|
834
1085
|
* 获取 SDK 会话状态
|
|
835
1086
|
* GET /runtime/sdk/status/:sessionId
|
|
836
1087
|
*/
|
|
837
|
-
terminalRouter.get(
|
|
1088
|
+
terminalRouter.get("/sdk/status/:sessionId", validateToken, (req, res) => {
|
|
838
1089
|
const sessionId = req.params.sessionId;
|
|
839
1090
|
if (!sessionId) {
|
|
840
|
-
res.status(400).json({ error:
|
|
1091
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
841
1092
|
return;
|
|
842
1093
|
}
|
|
843
1094
|
const entry = sdkSessions.get(sessionId);
|
|
844
1095
|
if (!entry) {
|
|
845
|
-
res.status(404).json({ error:
|
|
1096
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
846
1097
|
return;
|
|
847
1098
|
}
|
|
848
1099
|
try {
|
|
849
1100
|
// 根据 providerId 获取对应的 adapter
|
|
850
|
-
const providerId = entry.providerId ||
|
|
1101
|
+
const providerId = entry.providerId || "claude-code";
|
|
851
1102
|
const adapter = adapterRegistry.get(providerId);
|
|
852
1103
|
const status = adapter.getSessionStatus(sessionId);
|
|
853
1104
|
const conversation = adapter.getConversation(sessionId);
|
|
@@ -870,15 +1121,15 @@ terminalRouter.get('/sdk/status/:sessionId', validateToken, (req, res) => {
|
|
|
870
1121
|
* 主流程:校验 session/command -> 读取会话 workspacePath -> 在该 cwd 下启动 shell 命令 -> 通过终端输出回调流式返回。
|
|
871
1122
|
* POST /runtime/command
|
|
872
1123
|
*/
|
|
873
|
-
terminalRouter.post(
|
|
1124
|
+
terminalRouter.post("/command", validateToken, async (req, res) => {
|
|
874
1125
|
const { sessionId, command } = req.body || {};
|
|
875
1126
|
if (!sessionId || command === undefined || command === null) {
|
|
876
|
-
res.status(400).json({ error:
|
|
1127
|
+
res.status(400).json({ error: "sessionId and command are required" });
|
|
877
1128
|
return;
|
|
878
1129
|
}
|
|
879
1130
|
try {
|
|
880
1131
|
const result = await executeTerminalCommand(String(sessionId), command);
|
|
881
|
-
res.json({ ok: true, mode:
|
|
1132
|
+
res.json({ ok: true, mode: "terminal", ...result });
|
|
882
1133
|
}
|
|
883
1134
|
catch (error) {
|
|
884
1135
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -892,31 +1143,33 @@ terminalRouter.post('/command', validateToken, async (req, res) => {
|
|
|
892
1143
|
* 主流程:校验 session/input -> 定位 SDK 会话 -> 通过 provider adapter 发送用户消息。
|
|
893
1144
|
* POST /runtime/input
|
|
894
1145
|
*/
|
|
895
|
-
terminalRouter.post(
|
|
1146
|
+
terminalRouter.post("/input", validateToken, async (req, res) => {
|
|
896
1147
|
const { sessionId, input, mode } = req.body || {};
|
|
897
1148
|
if (!sessionId || input === undefined || input === null) {
|
|
898
|
-
res.status(400).json({ error:
|
|
1149
|
+
res.status(400).json({ error: "sessionId and input are required" });
|
|
899
1150
|
return;
|
|
900
1151
|
}
|
|
901
1152
|
const entry = sdkSessions.get(sessionId);
|
|
902
1153
|
if (!entry) {
|
|
903
|
-
res.status(404).json({ error:
|
|
1154
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
904
1155
|
return;
|
|
905
1156
|
}
|
|
906
1157
|
try {
|
|
907
|
-
const providerId = entry.providerId ||
|
|
1158
|
+
const providerId = entry.providerId || "claude-code";
|
|
908
1159
|
const adapter = adapterRegistry.get(providerId);
|
|
909
|
-
if (mode ===
|
|
1160
|
+
if (mode === "shortcut") {
|
|
910
1161
|
if (!adapter.sendShortcutInput) {
|
|
911
|
-
res.status(400).json({
|
|
1162
|
+
res.status(400).json({
|
|
1163
|
+
error: "shortcut input is not supported by this SDK provider",
|
|
1164
|
+
});
|
|
912
1165
|
return;
|
|
913
1166
|
}
|
|
914
1167
|
await adapter.sendShortcutInput(sessionId, String(input));
|
|
915
|
-
res.json({ ok: true, mode:
|
|
1168
|
+
res.json({ ok: true, mode: "shortcut" });
|
|
916
1169
|
return;
|
|
917
1170
|
}
|
|
918
1171
|
await adapter.sendMessage(sessionId, await resolveSdkInputMessage(input));
|
|
919
|
-
res.json({ ok: true, mode:
|
|
1172
|
+
res.json({ ok: true, mode: "sdk" });
|
|
920
1173
|
}
|
|
921
1174
|
catch (error) {
|
|
922
1175
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -929,22 +1182,22 @@ terminalRouter.post('/input', validateToken, async (req, res) => {
|
|
|
929
1182
|
* 主流程:按 agentId 定位 SDK 会话 -> 更新 adapter 内存配置 -> 同步持久化会话字段。
|
|
930
1183
|
* POST /runtime/update-auto-commands
|
|
931
1184
|
*/
|
|
932
|
-
terminalRouter.post(
|
|
933
|
-
const { agentId, idleInputAutoCommand =
|
|
934
|
-
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();
|
|
935
1188
|
if (!normalizedAgentId) {
|
|
936
|
-
res.status(400).json({ error:
|
|
1189
|
+
res.status(400).json({ error: "agentId is required" });
|
|
937
1190
|
return;
|
|
938
1191
|
}
|
|
939
1192
|
const entry = [...sdkSessions.values()].find((session) => session.agentId === normalizedAgentId);
|
|
940
1193
|
const commands = {
|
|
941
|
-
idleInputCommand: String(idleInputAutoCommand ||
|
|
942
|
-
nonInputCommand: String(nonInputAutoCommand ||
|
|
1194
|
+
idleInputCommand: String(idleInputAutoCommand || ""),
|
|
1195
|
+
nonInputCommand: String(nonInputAutoCommand || ""),
|
|
943
1196
|
};
|
|
944
1197
|
try {
|
|
945
1198
|
let sessionUpdated = false;
|
|
946
1199
|
if (entry) {
|
|
947
|
-
const adapter = adapterRegistry.get(entry.providerId ||
|
|
1200
|
+
const adapter = adapterRegistry.get(entry.providerId || "claude-code");
|
|
948
1201
|
adapter.setIdleCommands?.(entry.sessionId, commands);
|
|
949
1202
|
sessionUpdated = true;
|
|
950
1203
|
}
|
|
@@ -952,11 +1205,11 @@ terminalRouter.post('/update-auto-commands', validateToken, async (req, res) =>
|
|
|
952
1205
|
idleInputAutoCommand: commands.idleInputCommand,
|
|
953
1206
|
nonInputAutoCommand: commands.nonInputCommand,
|
|
954
1207
|
});
|
|
955
|
-
res.json({ ok: true, mode:
|
|
1208
|
+
res.json({ ok: true, mode: "sdk", sessionUpdated, persistedUpdated });
|
|
956
1209
|
}
|
|
957
1210
|
catch (error) {
|
|
958
1211
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
959
|
-
res.status(500).json({ ok: false, mode:
|
|
1212
|
+
res.status(500).json({ ok: false, mode: "sdk", error: errorMessage });
|
|
960
1213
|
}
|
|
961
1214
|
});
|
|
962
1215
|
/**
|
|
@@ -965,37 +1218,43 @@ terminalRouter.post('/update-auto-commands', validateToken, async (req, res) =>
|
|
|
965
1218
|
* SDK 会话没有终端尺寸概念;保留该端点用于前端 xterm fit 回调,返回 no-op。
|
|
966
1219
|
* POST /runtime/resize
|
|
967
1220
|
*/
|
|
968
|
-
terminalRouter.post(
|
|
1221
|
+
terminalRouter.post("/resize", validateToken, (req, res) => {
|
|
969
1222
|
const { sessionId, cols, rows } = req.body || {};
|
|
970
1223
|
if (!sessionId || !cols || !rows) {
|
|
971
|
-
res.status(400).json({ error:
|
|
1224
|
+
res.status(400).json({ error: "sessionId, cols, rows are required" });
|
|
972
1225
|
return;
|
|
973
1226
|
}
|
|
974
1227
|
if (!sdkSessions.has(sessionId)) {
|
|
975
|
-
res.status(404).json({ error:
|
|
1228
|
+
res.status(404).json({ error: "SDK session not found" });
|
|
976
1229
|
return;
|
|
977
1230
|
}
|
|
978
|
-
res.json({
|
|
1231
|
+
res.json({
|
|
1232
|
+
ok: true,
|
|
1233
|
+
mode: "sdk",
|
|
1234
|
+
noop: true,
|
|
1235
|
+
cols: Number(cols),
|
|
1236
|
+
rows: Number(rows),
|
|
1237
|
+
});
|
|
979
1238
|
});
|
|
980
1239
|
/**
|
|
981
1240
|
* 停止 SDK 会话。
|
|
982
1241
|
* POST /runtime/stop
|
|
983
1242
|
*/
|
|
984
|
-
terminalRouter.post(
|
|
1243
|
+
terminalRouter.post("/stop", validateToken, async (req, res) => {
|
|
985
1244
|
const { sessionId } = req.body || {};
|
|
986
1245
|
if (!sessionId) {
|
|
987
|
-
res.status(400).json({ error:
|
|
1246
|
+
res.status(400).json({ error: "sessionId is required" });
|
|
988
1247
|
return;
|
|
989
1248
|
}
|
|
990
1249
|
const sdkEntry = sdkSessions.get(sessionId);
|
|
991
1250
|
if (!sdkEntry) {
|
|
992
1251
|
await removePersistedSession(sessionId);
|
|
993
|
-
res.json({ ok: true, status:
|
|
1252
|
+
res.json({ ok: true, status: "stopped", mode: "none" });
|
|
994
1253
|
return;
|
|
995
1254
|
}
|
|
996
1255
|
const agentId = sdkEntry.agentId;
|
|
997
1256
|
try {
|
|
998
|
-
const providerId = sdkEntry.providerId ||
|
|
1257
|
+
const providerId = sdkEntry.providerId || "claude-code";
|
|
999
1258
|
const adapter = adapterRegistry.get(providerId);
|
|
1000
1259
|
await adapter.terminateSession(sessionId);
|
|
1001
1260
|
sdkSessions.delete(sessionId);
|
|
@@ -1003,13 +1262,20 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
|
|
|
1003
1262
|
await removePersistedSession(sessionId);
|
|
1004
1263
|
const processManager = getAgentProcessManager();
|
|
1005
1264
|
await processManager.removeProcess(agentId);
|
|
1006
|
-
await sendStatus(agentId, null,
|
|
1007
|
-
|
|
1265
|
+
await sendStatus(agentId, null, "stopped", undefined, undefined, {
|
|
1266
|
+
runtimeAccessToken: sdkEntry.runtimeAccessToken,
|
|
1267
|
+
});
|
|
1268
|
+
res.json({ ok: true, status: "stopped", mode: "sdk" });
|
|
1008
1269
|
}
|
|
1009
1270
|
catch (error) {
|
|
1010
1271
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1011
1272
|
console.error(`[Runtime] Failed to terminate SDK session ${sessionId}:`, errorMessage);
|
|
1012
|
-
res.status(500).json({
|
|
1273
|
+
res.status(500).json({
|
|
1274
|
+
ok: false,
|
|
1275
|
+
status: "stop_failed",
|
|
1276
|
+
mode: "sdk",
|
|
1277
|
+
error: errorMessage,
|
|
1278
|
+
});
|
|
1013
1279
|
}
|
|
1014
1280
|
});
|
|
1015
1281
|
/**
|
|
@@ -1017,20 +1283,22 @@ terminalRouter.post('/stop', validateToken, async (req, res) => {
|
|
|
1017
1283
|
* POST /runtime/stop-all
|
|
1018
1284
|
* 用于 aws-mcp-server 重启时清理所有 SDK Agent 会话
|
|
1019
1285
|
*/
|
|
1020
|
-
terminalRouter.post(
|
|
1286
|
+
terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
|
|
1021
1287
|
const stoppedSdk = [];
|
|
1022
1288
|
const errors = [];
|
|
1023
1289
|
// 1. 停止所有 SDK 会话
|
|
1024
1290
|
for (const [sessionId, entry] of sdkSessions.entries()) {
|
|
1025
1291
|
try {
|
|
1026
1292
|
// 根据 providerId 获取对应的 adapter
|
|
1027
|
-
const providerId = entry.providerId ||
|
|
1293
|
+
const providerId = entry.providerId || "claude-code";
|
|
1028
1294
|
const adapter = adapterRegistry.get(providerId);
|
|
1029
1295
|
await adapter.terminateSession(sessionId);
|
|
1030
1296
|
sdkSessions.delete(sessionId);
|
|
1031
1297
|
stopTerminalCommandProcess(sessionId);
|
|
1032
1298
|
stoppedSdk.push(sessionId);
|
|
1033
|
-
await sendStatus(entry.agentId, null,
|
|
1299
|
+
await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
|
|
1300
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1301
|
+
});
|
|
1034
1302
|
}
|
|
1035
1303
|
catch (error) {
|
|
1036
1304
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1041,7 +1309,7 @@ terminalRouter.post('/stop-all', validateToken, async (_req, res) => {
|
|
|
1041
1309
|
// 直接清空整个持久化文件,确保不会有残留
|
|
1042
1310
|
try {
|
|
1043
1311
|
await savePersistedSessions([]);
|
|
1044
|
-
console.log(
|
|
1312
|
+
console.log("[Runtime] 已清空所有持久化会话");
|
|
1045
1313
|
}
|
|
1046
1314
|
catch (error) {
|
|
1047
1315
|
const errorMessage = error instanceof Error ? error.message : String(error);
|