aws-runtime-bridge 1.9.10 → 1.9.21
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 +40 -0
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +376 -10
- package/dist/adapter/AcodeSdkAdapter.test.js +155 -8
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +17 -1
- package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/CodexSdkAdapter.js +15 -1
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +56 -4
- package/dist/adapter/instance-ai-config-resolver.d.ts +49 -0
- package/dist/adapter/instance-ai-config-resolver.d.ts.map +1 -0
- package/dist/adapter/instance-ai-config-resolver.js +46 -0
- package/dist/adapter/types.d.ts +29 -2
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/routes/dashboard.d.ts.map +1 -1
- package/dist/routes/dashboard.js +749 -15
- package/dist/routes/events.d.ts +7 -0
- package/dist/routes/events.d.ts.map +1 -0
- package/dist/routes/events.js +22 -0
- package/dist/routes/file-browser.d.ts +1 -1
- package/dist/routes/file-browser.d.ts.map +1 -1
- package/dist/routes/file-browser.js +25 -9
- package/dist/routes/file-snapshot.d.ts +10 -0
- package/dist/routes/file-snapshot.d.ts.map +1 -0
- package/dist/routes/file-snapshot.js +156 -0
- package/dist/routes/git.d.ts +10 -0
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +405 -2
- package/dist/routes/git.test.js +39 -2
- package/dist/routes/instance.js +3 -1
- package/dist/routes/memory.d.ts +3 -0
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +3 -0
- package/dist/routes/terminal.d.ts +17 -3
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +464 -47
- package/dist/routes/terminal.test.js +37 -9
- package/dist/services/agent-event-store.d.ts +60 -0
- package/dist/services/agent-event-store.d.ts.map +1 -0
- package/dist/services/agent-event-store.js +68 -0
- package/dist/services/event-delta-pusher.d.ts +40 -0
- package/dist/services/event-delta-pusher.d.ts.map +1 -0
- package/dist/services/event-delta-pusher.js +95 -0
- package/dist/services/fileSnapshotStore.d.ts +96 -0
- package/dist/services/fileSnapshotStore.d.ts.map +1 -0
- package/dist/services/fileSnapshotStore.js +500 -0
- package/dist/services/instance-ai-config-service.d.ts +49 -0
- package/dist/services/instance-ai-config-service.d.ts.map +1 -0
- package/dist/services/instance-ai-config-service.js +174 -0
- package/dist/services/panel-auth.d.ts +8 -0
- package/dist/services/panel-auth.d.ts.map +1 -1
- package/dist/services/panel-auth.js +8 -0
- package/dist/services/ring-buffer.d.ts +67 -0
- package/dist/services/ring-buffer.d.ts.map +1 -0
- package/dist/services/ring-buffer.js +103 -0
- package/dist/services/session-output.d.ts +49 -3
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +115 -2
- package/dist/utils/instance-ai-config-debug-logger.d.ts +37 -0
- package/dist/utils/instance-ai-config-debug-logger.d.ts.map +1 -0
- package/dist/utils/instance-ai-config-debug-logger.js +63 -0
- package/dist/utils/logger.d.ts +18 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +48 -4
- 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 +5 -3
- package/package/acode/dist/builtins/commandArtifacts.d.ts +8 -1
- package/package/acode/dist/builtins/commandArtifacts.d.ts.map +1 -1
- package/package/acode/dist/builtins/commandArtifacts.js +24 -4
- package/package/acode/dist/builtins/shellCommandResult.d.ts +1 -0
- package/package/acode/dist/builtins/shellCommandResult.d.ts.map +1 -1
- package/package/acode/dist/builtins/shellCommandResult.js +3 -3
- package/package/acode/dist/builtins/shellCommandRunner.d.ts +1 -0
- package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +1 -1
- package/package/acode/dist/builtins/shellCommandRunner.js +1 -0
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts +25 -2
- package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +1 -1
- package/package/acode/dist/builtins/shellOutputDecoder.js +69 -5
- package/package/acode/dist/cli.js +2 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +156 -5
- package/package/acode/dist/types.d.ts +1 -0
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +10 -3
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +33 -11
- package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts +136 -0
- package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/context-manager.js +473 -0
- package/package/aws-client-agent-mcp/dist/context-manager.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +6 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +1 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +307 -0
- package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -0
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +11 -0
- package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.js +67 -0
- package/package/aws-client-agent-mcp/dist/http-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +166 -10
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +933 -41
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +150 -130
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +15 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +62 -2
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +58 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +246 -15
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +8 -0
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js +106 -2
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +7 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +18 -0
- package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +103 -2
- 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 +27 -2
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +123 -5
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package.json +1 -1
package/dist/routes/terminal.js
CHANGED
|
@@ -16,16 +16,44 @@ import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath,
|
|
|
16
16
|
import { validateToken } from "../middleware/auth.js";
|
|
17
17
|
import { getAgentProcessManager } from "../services/agent-process-manager.js";
|
|
18
18
|
import { getAiConfigService } from "../services/cc-switch-sdk.js";
|
|
19
|
+
import { prepareAcodeInstanceConfig, redactForLog } from "../services/instance-ai-config-service.js";
|
|
19
20
|
import { enqueueMcpLaunchBinding } from "../services/mcp-launch-binding-queue.js";
|
|
20
21
|
import { findClaudeCodeProcesses, isProcessRunning, } from "../services/process-detector.js";
|
|
21
|
-
import { sendOutput, sendQuestionRequest, sendStatus, } from "../services/session-output.js";
|
|
22
|
+
import { bridgeEventBuffer, sendOutput, sendQuestionRequest, sendStatus, } from "../services/session-output.js";
|
|
23
|
+
import { getAllPushStats, startHeartbeatScheduler } from "../services/event-delta-pusher.js";
|
|
22
24
|
import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, updatePersistedSessionRuntimeState, upsertPersistedSession, } from "../services/terminal-persistence.js";
|
|
25
|
+
import { getInstanceAiConfigDebugLogger } from "../utils/instance-ai-config-debug-logger.js";
|
|
23
26
|
import { logger as log } from "../utils/logger.js";
|
|
24
27
|
export const terminalRouter = Router();
|
|
25
28
|
// 导出 SDK 会话存储,供其他模块使用(如 sessions.ts)
|
|
26
29
|
export const sdkSessions = new Map();
|
|
27
30
|
const TOOL_RESULT_PREVIEW_MAX_LENGTH = 4000;
|
|
28
31
|
const AI_REQUEST_BODY_MARKER = "\n\n请求体:\n";
|
|
32
|
+
/**
|
|
33
|
+
* 把请求体中的 instanceAiConfig 归一化为 AdapterInstanceAiConfig。
|
|
34
|
+
* 任意字段类型错误或非字符串都返回 undefined,调用方应继续走 env/file fallback。
|
|
35
|
+
*/
|
|
36
|
+
function sanitizeInstanceAiConfig(value) {
|
|
37
|
+
if (value == null)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (typeof value !== "object" || Array.isArray(value))
|
|
40
|
+
return undefined;
|
|
41
|
+
const raw = value;
|
|
42
|
+
const out = {};
|
|
43
|
+
if (typeof raw.model === "string" && raw.model.trim()) {
|
|
44
|
+
out.model = raw.model.trim();
|
|
45
|
+
}
|
|
46
|
+
if (typeof raw.baseURL === "string" && raw.baseURL.trim()) {
|
|
47
|
+
out.baseURL = raw.baseURL.trim();
|
|
48
|
+
}
|
|
49
|
+
if (typeof raw.apiKey === "string" && raw.apiKey.trim()) {
|
|
50
|
+
out.apiKey = raw.apiKey.trim();
|
|
51
|
+
}
|
|
52
|
+
if (typeof raw.effort === "string" && raw.effort.trim()) {
|
|
53
|
+
out.effort = raw.effort.trim();
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
29
57
|
const AI_REQUEST_HEADERS_MARKER = "\n\n请求头:\n";
|
|
30
58
|
const startingAgents = new Set();
|
|
31
59
|
export function resolveRuntimeContextResetSupport(providerId, hasResetContext) {
|
|
@@ -83,9 +111,13 @@ export function resolveStatusChangeUsage(actionInfo) {
|
|
|
83
111
|
const outputTokens = Number.isFinite(usage.outputTokens)
|
|
84
112
|
? usage.outputTokens
|
|
85
113
|
: 0;
|
|
86
|
-
if (inputTokens <= 0 && outputTokens <= 0)
|
|
114
|
+
if (inputTokens <= 0 && outputTokens <= 0 && !usage.contextLimit)
|
|
87
115
|
return undefined;
|
|
88
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
inputTokens,
|
|
118
|
+
outputTokens,
|
|
119
|
+
...(usage.contextLimit ? { contextLimit: usage.contextLimit } : {}),
|
|
120
|
+
};
|
|
89
121
|
}
|
|
90
122
|
export function resolveStatusChangeRuntimeState(status, actionInfo) {
|
|
91
123
|
const normalizedStatus = String(status || "")
|
|
@@ -102,21 +134,21 @@ export function resolveStatusChangeRuntimeState(status, actionInfo) {
|
|
|
102
134
|
return actionInfo ? { status, actionInfo } : { status };
|
|
103
135
|
}
|
|
104
136
|
const terminalCommandStates = new Map();
|
|
105
|
-
const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = "
|
|
137
|
+
const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = "utf-8";
|
|
106
138
|
const DEFAULT_TERMINAL_OUTPUT_ENCODING = "utf-8";
|
|
107
139
|
const TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT = 300;
|
|
108
140
|
const TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS = 60_000;
|
|
109
141
|
function padDatePart(value) {
|
|
110
142
|
return String(value).padStart(2, "0");
|
|
111
143
|
}
|
|
112
|
-
export function buildAcodeCommandOutputPath(
|
|
144
|
+
export function buildAcodeCommandOutputPath(agentId, now = new Date()) {
|
|
113
145
|
const year = now.getFullYear();
|
|
114
146
|
const month = padDatePart(now.getMonth() + 1);
|
|
115
147
|
const day = padDatePart(now.getDate());
|
|
116
148
|
const hour = padDatePart(now.getHours());
|
|
117
149
|
const minute = padDatePart(now.getMinutes());
|
|
118
150
|
const timestamp = `${year}${month}${day}-${hour}${minute}${padDatePart(now.getSeconds())}-${String(now.getMilliseconds()).padStart(3, "0")}`;
|
|
119
|
-
return path.join(
|
|
151
|
+
return path.join(os.homedir(), ".acode", "cmd-dist", agentId, `${year}-${month}-${day}`, hour, minute, `${timestamp}-cmd.out`);
|
|
120
152
|
}
|
|
121
153
|
export function countTerminalOutputLines(output) {
|
|
122
154
|
if (!output) {
|
|
@@ -138,7 +170,7 @@ export function formatCommandInactivityTimeoutNotice(timeoutMs = TERMINAL_COMMAN
|
|
|
138
170
|
}
|
|
139
171
|
async function persistTerminalCommandOutput(state) {
|
|
140
172
|
const outputPath = state.outputPath ||
|
|
141
|
-
buildAcodeCommandOutputPath(state.
|
|
173
|
+
buildAcodeCommandOutputPath(state.agentId, state.startedAt);
|
|
142
174
|
state.outputPath = outputPath;
|
|
143
175
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
144
176
|
await fs.writeFile(outputPath, state.chunks.join(""), "utf-8");
|
|
@@ -167,14 +199,65 @@ export function resolveTerminalOutputEncoding(platform = process.platform, env =
|
|
|
167
199
|
}
|
|
168
200
|
/**
|
|
169
201
|
* 创建终端输出流式解码器,避免多字节中文被 chunk 边界截断。
|
|
202
|
+
*
|
|
203
|
+
* 主流程:环境变量显式指定非 utf-8 时固定编码 → 否则自动检测
|
|
204
|
+
* (先 UTF-8,遇替换字符回退 GB18030,覆盖不同程序的不同编码)。
|
|
170
205
|
*/
|
|
171
206
|
export function createTerminalOutputDecoder(encoding = resolveTerminalOutputEncoding()) {
|
|
172
|
-
|
|
173
|
-
|
|
207
|
+
// 环境变量显式指定了非 utf-8 编码 → 固定编码,不做自动检测
|
|
208
|
+
if (encoding !== "utf-8") {
|
|
209
|
+
try {
|
|
210
|
+
return new TextDecoder(encoding, { fatal: false });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return new TextDecoder(DEFAULT_TERMINAL_OUTPUT_ENCODING, { fatal: false });
|
|
214
|
+
}
|
|
174
215
|
}
|
|
175
|
-
|
|
176
|
-
|
|
216
|
+
// 默认 utf-8 → 启用自动检测
|
|
217
|
+
return createAutoDetectTerminalDecoder();
|
|
218
|
+
}
|
|
219
|
+
function createAutoDetectTerminalDecoder() {
|
|
220
|
+
let utf8Decoder = null;
|
|
221
|
+
let gbDecoder = null;
|
|
222
|
+
let detectedEncoding = null;
|
|
223
|
+
const rawChunks = [];
|
|
224
|
+
function getDecoder(enc) {
|
|
225
|
+
try {
|
|
226
|
+
return new TextDecoder(enc, { fatal: false });
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return new TextDecoder("utf-8", { fatal: false });
|
|
230
|
+
}
|
|
177
231
|
}
|
|
232
|
+
return {
|
|
233
|
+
decode(chunk, options) {
|
|
234
|
+
if (chunk !== undefined && chunk !== null) {
|
|
235
|
+
rawChunks.push(chunk instanceof ArrayBuffer
|
|
236
|
+
? Buffer.from(chunk)
|
|
237
|
+
: Buffer.isBuffer(chunk)
|
|
238
|
+
? chunk
|
|
239
|
+
: Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
|
240
|
+
}
|
|
241
|
+
if (detectedEncoding === "gb18030") {
|
|
242
|
+
if (!gbDecoder)
|
|
243
|
+
gbDecoder = getDecoder("gb18030");
|
|
244
|
+
return gbDecoder.decode(chunk, options);
|
|
245
|
+
}
|
|
246
|
+
if (!utf8Decoder)
|
|
247
|
+
utf8Decoder = getDecoder("utf-8");
|
|
248
|
+
const result = utf8Decoder.decode(chunk, options);
|
|
249
|
+
if (detectedEncoding === null && result.includes("\uFFFD")) {
|
|
250
|
+
detectedEncoding = "gb18030";
|
|
251
|
+
gbDecoder = getDecoder("gb18030");
|
|
252
|
+
const fullBuffer = Buffer.concat(rawChunks);
|
|
253
|
+
return gbDecoder.decode(fullBuffer);
|
|
254
|
+
}
|
|
255
|
+
if (detectedEncoding === null && !options?.stream) {
|
|
256
|
+
detectedEncoding = "utf-8";
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
178
261
|
}
|
|
179
262
|
/**
|
|
180
263
|
* 解码终端输出 chunk;end=true 时刷新解码器内部剩余字节。
|
|
@@ -395,7 +478,7 @@ async function executeTerminalCommand(sessionId, commandValue) {
|
|
|
395
478
|
const stderrDecoder = createTerminalOutputDecoder();
|
|
396
479
|
const usesAcodeCommandOutputPolicy = entry.providerId === "acode";
|
|
397
480
|
const outputSpilloverState = {
|
|
398
|
-
|
|
481
|
+
agentId: entry.agentId,
|
|
399
482
|
startedAt: new Date(),
|
|
400
483
|
chunks: [],
|
|
401
484
|
spilled: false,
|
|
@@ -807,6 +890,24 @@ function displayStringList(value) {
|
|
|
807
890
|
.filter(Boolean)
|
|
808
891
|
.map((item) => redactTransientClipboardPastePaths(item));
|
|
809
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* 格式化工具调用输入参数为 JSON 字符串,用于时间线展示。
|
|
895
|
+
* 对敏感内容(如剪贴板临时路径)进行脱敏处理。
|
|
896
|
+
*/
|
|
897
|
+
function formatToolInput(toolInput) {
|
|
898
|
+
if (!toolInput || Object.keys(toolInput).length === 0)
|
|
899
|
+
return undefined;
|
|
900
|
+
try {
|
|
901
|
+
const sanitized = redactTransientClipboardPastePaths(JSON.stringify(toolInput, null, 2));
|
|
902
|
+
// 限制输入参数长度,避免超大参数撑爆时间线
|
|
903
|
+
return sanitized.length > 8000
|
|
904
|
+
? `${sanitized.slice(0, 8000)}\n...(输入参数过长,已截断)`
|
|
905
|
+
: sanitized;
|
|
906
|
+
}
|
|
907
|
+
catch {
|
|
908
|
+
return undefined;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
810
911
|
function formatAiRequestDetail(data) {
|
|
811
912
|
const lines = [
|
|
812
913
|
data.model ? `模型: ${data.model}` : undefined,
|
|
@@ -879,6 +980,7 @@ export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatu
|
|
|
879
980
|
if (event.type === "tool_use_start") {
|
|
880
981
|
const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
|
|
881
982
|
const actionDetail = resolveSdkToolLogDetail(event, inferredAction);
|
|
983
|
+
const actionInput = formatToolInput(event.data.toolInput);
|
|
882
984
|
if (event.data.actionType === "idle" ||
|
|
883
985
|
inferredAction.actionType === "idle") {
|
|
884
986
|
return {
|
|
@@ -890,6 +992,7 @@ export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatu
|
|
|
890
992
|
event.data.actionDetail ||
|
|
891
993
|
inferredAction.actionDetail ||
|
|
892
994
|
event.data.toolName,
|
|
995
|
+
actionInput,
|
|
893
996
|
actionId: event.data.actionId || event.data.toolUseId,
|
|
894
997
|
},
|
|
895
998
|
};
|
|
@@ -903,24 +1006,17 @@ export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatu
|
|
|
903
1006
|
event.data.actionDetail ||
|
|
904
1007
|
inferredAction.actionDetail ||
|
|
905
1008
|
event.data.toolName,
|
|
1009
|
+
actionInput,
|
|
906
1010
|
actionId: event.data.actionId || event.data.toolUseId,
|
|
907
1011
|
},
|
|
908
1012
|
};
|
|
909
1013
|
}
|
|
910
1014
|
if (event.type === "turn_complete") {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
? {
|
|
914
|
-
actionType: "ai_request",
|
|
915
|
-
actionLabel: "AI响应",
|
|
916
|
-
actionResult: event.data.responseBody,
|
|
917
|
-
usage: event.data.usage,
|
|
918
|
-
}
|
|
919
|
-
: undefined;
|
|
1015
|
+
// 方案 A:不设置 "AI响应" action 信息,避免状态卡在 action 展示
|
|
1016
|
+
// 只返回干净的 waiting_input 状态,usage 信息保留用于 token 统计
|
|
920
1017
|
return {
|
|
921
1018
|
status: "waiting_input",
|
|
922
|
-
actionInfo:
|
|
923
|
-
(event.data.usage ? { usage: event.data.usage } : undefined),
|
|
1019
|
+
actionInfo: event.data.usage ? { usage: event.data.usage } : undefined,
|
|
924
1020
|
};
|
|
925
1021
|
}
|
|
926
1022
|
if (event.type === "error") {
|
|
@@ -928,15 +1024,111 @@ export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatu
|
|
|
928
1024
|
}
|
|
929
1025
|
return undefined;
|
|
930
1026
|
}
|
|
931
|
-
|
|
1027
|
+
// Per-session 输出队列:确保 sendOutput HTTP POST 按事件顺序串行到达 Server,
|
|
1028
|
+
// 避免并发 POST 因网络时序导致流式文本 chunk 乱序。
|
|
1029
|
+
const sdkOutputQueueBySession = new Map();
|
|
1030
|
+
/** 小 chunk 积攒缓存:sessionId:sourceEventType → { buffer, seq, timer, sourceEventType, entry },不足 20 字符时积攒最多 500ms 再推送 */
|
|
1031
|
+
const sdkOutputSmallChunkAccumulators = new Map();
|
|
1032
|
+
/** 小 chunk 积攒阈值:字符数少于此值的 output 事件积攒发送 */
|
|
1033
|
+
const SMALL_CHUNK_CHAR_THRESHOLD = 20;
|
|
1034
|
+
/** 小 chunk 最大积攒时间(毫秒) */
|
|
1035
|
+
const SMALL_CHUNK_MAX_ACCUMULATE_MS = 500;
|
|
1036
|
+
function flushSmallChunkAccumulator(sessionId, sourceEventType) {
|
|
1037
|
+
const key = sourceEventType
|
|
1038
|
+
? `${sessionId}:${sourceEventType}`
|
|
1039
|
+
: undefined;
|
|
1040
|
+
// 如果指定了 key 则精确刷新;否则回退到按 sessionId 前缀查找并刷新所有(兼容大 chunk 触发的全量刷新)
|
|
1041
|
+
let acc;
|
|
1042
|
+
let accKey;
|
|
1043
|
+
if (key) {
|
|
1044
|
+
acc = sdkOutputSmallChunkAccumulators.get(key);
|
|
1045
|
+
accKey = key;
|
|
1046
|
+
}
|
|
1047
|
+
else {
|
|
1048
|
+
// 刷新该 sessionId 下的所有 accumulator(可能有多种 sourceEventType)
|
|
1049
|
+
for (const [k, v] of sdkOutputSmallChunkAccumulators) {
|
|
1050
|
+
if (k.startsWith(sessionId + ":") && v.buffer) {
|
|
1051
|
+
flushSmallChunkAccumulator(sessionId, v.sourceEventType);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
if (!acc || !acc.buffer)
|
|
1057
|
+
return;
|
|
1058
|
+
acc.timer = null;
|
|
1059
|
+
const output = acc.buffer;
|
|
1060
|
+
const seq = acc.seq;
|
|
1061
|
+
const entry = acc.entry;
|
|
1062
|
+
const eventType = acc.sourceEventType;
|
|
1063
|
+
acc.buffer = "";
|
|
1064
|
+
if (accKey)
|
|
1065
|
+
sdkOutputSmallChunkAccumulators.delete(accKey);
|
|
1066
|
+
const metadata = {
|
|
1067
|
+
sourceEventType: eventType,
|
|
1068
|
+
logCategory: "acode",
|
|
1069
|
+
};
|
|
1070
|
+
const authOptions = {
|
|
1071
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1072
|
+
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
1073
|
+
};
|
|
1074
|
+
const prev = sdkOutputQueueBySession.get(sessionId) ?? Promise.resolve();
|
|
1075
|
+
const next = prev.then(() => sendOutput(entry.agentId, output, sessionId, seq, metadata, authOptions), () => sendOutput(entry.agentId, output, sessionId, seq, metadata, authOptions));
|
|
1076
|
+
sdkOutputQueueBySession.set(sessionId, next);
|
|
1077
|
+
void next.finally(() => {
|
|
1078
|
+
if (sdkOutputQueueBySession.get(sessionId) === next) {
|
|
1079
|
+
sdkOutputQueueBySession.delete(sessionId);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
function enqueueSdkOutputEvent(event, entry) {
|
|
932
1084
|
const output = formatSdkOutputEvent(event);
|
|
933
1085
|
if (!output) {
|
|
934
1086
|
return;
|
|
935
1087
|
}
|
|
936
1088
|
entry.seq += 1;
|
|
937
|
-
|
|
1089
|
+
const seq = entry.seq;
|
|
1090
|
+
const metadata = buildSdkOutputMetadata(event);
|
|
1091
|
+
const authOptions = {
|
|
938
1092
|
runtimeAccessToken: entry.runtimeAccessToken,
|
|
939
1093
|
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
1094
|
+
};
|
|
1095
|
+
// 小 chunk 积攒优化:命令输出和 AI 文本增量等小片段不足阈值时积攒再推送
|
|
1096
|
+
if ((event.type === "command_output" ||
|
|
1097
|
+
event.type === "text_delta" ||
|
|
1098
|
+
event.type === "thinking") &&
|
|
1099
|
+
output.length < SMALL_CHUNK_CHAR_THRESHOLD) {
|
|
1100
|
+
const accKey = `${event.sessionId}:${event.type}`;
|
|
1101
|
+
let acc = sdkOutputSmallChunkAccumulators.get(accKey);
|
|
1102
|
+
if (!acc) {
|
|
1103
|
+
acc = {
|
|
1104
|
+
buffer: "",
|
|
1105
|
+
seq: 0,
|
|
1106
|
+
timer: null,
|
|
1107
|
+
entry,
|
|
1108
|
+
sourceEventType: event.type,
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
acc.buffer += output;
|
|
1112
|
+
acc.seq = seq;
|
|
1113
|
+
acc.entry = entry;
|
|
1114
|
+
if (!acc.timer) {
|
|
1115
|
+
const capturedSessionId = event.sessionId;
|
|
1116
|
+
const capturedType = event.type;
|
|
1117
|
+
acc.timer = setTimeout(() => flushSmallChunkAccumulator(capturedSessionId, capturedType), SMALL_CHUNK_MAX_ACCUMULATE_MS);
|
|
1118
|
+
}
|
|
1119
|
+
sdkOutputSmallChunkAccumulators.set(accKey, acc);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
// 大 chunk 直接发送(先刷新可能存在的积攒缓存)
|
|
1123
|
+
flushSmallChunkAccumulator(event.sessionId);
|
|
1124
|
+
const prev = sdkOutputQueueBySession.get(event.sessionId) ?? Promise.resolve();
|
|
1125
|
+
const next = prev.then(() => sendOutput(entry.agentId, output, event.sessionId, seq, metadata, authOptions), () => sendOutput(entry.agentId, output, event.sessionId, seq, metadata, authOptions));
|
|
1126
|
+
sdkOutputQueueBySession.set(event.sessionId, next);
|
|
1127
|
+
// 队列空闲时自动清理,避免内存泄漏
|
|
1128
|
+
void next.finally(() => {
|
|
1129
|
+
if (sdkOutputQueueBySession.get(event.sessionId) === next) {
|
|
1130
|
+
sdkOutputQueueBySession.delete(event.sessionId);
|
|
1131
|
+
}
|
|
940
1132
|
});
|
|
941
1133
|
}
|
|
942
1134
|
export function resolveSdkProviderId(command) {
|
|
@@ -948,6 +1140,8 @@ function ensureAdapterInitialized() {
|
|
|
948
1140
|
if (adapterInitialized)
|
|
949
1141
|
return;
|
|
950
1142
|
registerSdkProviders(adapterRegistry, wireSdkAdapterEvents);
|
|
1143
|
+
// 需求 4.2.1 #2:启动全局心跳调度器(每 30s 推送空包检测 Server 连接存活)
|
|
1144
|
+
startHeartbeatScheduler();
|
|
951
1145
|
adapterInitialized = true;
|
|
952
1146
|
}
|
|
953
1147
|
function wireSdkAdapterEvents(adapter, definition) {
|
|
@@ -955,20 +1149,36 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
955
1149
|
const entry = sdkSessions.get(event.sessionId);
|
|
956
1150
|
if (entry) {
|
|
957
1151
|
console.log(`[${definition.displayName} Adapter] Event: ${event.type} for session ${event.sessionId}`);
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1152
|
+
// Skip sendStatus for thinking/text_delta/tool_use_start events — the status-change
|
|
1153
|
+
// listener already emits the authoritative status with restored actionInfo,
|
|
1154
|
+
// avoiding duplicate timeline entries in agentEventStore.
|
|
1155
|
+
const isDeferredStatusEvent = event.type === "thinking" || event.type === "text_delta" || event.type === "tool_use_start";
|
|
1156
|
+
if (!isDeferredStatusEvent) {
|
|
1157
|
+
const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event, entry.currentRuntimeStatus);
|
|
1158
|
+
if (runtimeStatus) {
|
|
1159
|
+
void sendStatus(entry.agentId, event.sessionId, runtimeStatus.status, runtimeStatus.actionInfo, resolveStatusChangeUsage(runtimeStatus.actionInfo), {
|
|
1160
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1161
|
+
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
1162
|
+
},
|
|
1163
|
+
// Pass original timestamp from ACode runtime for correct timeline ordering
|
|
1164
|
+
// Convert ISO string timestamp to epoch milliseconds
|
|
1165
|
+
event.timestamp ? new Date(event.timestamp).getTime() : undefined,
|
|
1166
|
+
// Pass original event type for frontend gray-zone rendering (thinking vs text_delta)
|
|
1167
|
+
event.type);
|
|
1168
|
+
}
|
|
964
1169
|
}
|
|
965
|
-
|
|
1170
|
+
enqueueSdkOutputEvent(event, entry);
|
|
966
1171
|
const toolResultActionInfo = buildToolResultTimelineActionInfo(event);
|
|
967
1172
|
if (toolResultActionInfo) {
|
|
968
1173
|
void sendStatus(entry.agentId, event.sessionId, "tool_result", toolResultActionInfo, undefined, {
|
|
969
1174
|
runtimeAccessToken: entry.runtimeAccessToken,
|
|
970
1175
|
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
971
|
-
}
|
|
1176
|
+
},
|
|
1177
|
+
// Pass original timestamp from ACode runtime for correct timeline ordering
|
|
1178
|
+
// Convert ISO string timestamp to epoch milliseconds
|
|
1179
|
+
event.timestamp ? new Date(event.timestamp).getTime() : undefined,
|
|
1180
|
+
// Pass original event type (tool_use_end) for timeline sourceEventType
|
|
1181
|
+
event.type);
|
|
972
1182
|
}
|
|
973
1183
|
}
|
|
974
1184
|
});
|
|
@@ -977,18 +1187,48 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
977
1187
|
if (entry) {
|
|
978
1188
|
const runtimeState = resolveStatusChangeRuntimeState(status, actionInfo);
|
|
979
1189
|
entry.currentRuntimeStatus = runtimeState.status;
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1190
|
+
// 当新状态未携带动作且进入 thinking 时,保留上一个 idle 类动作(如 poll_message),
|
|
1191
|
+
// 避免页面刷新后丢失"监听消息"状态而错误显示"思考中"。
|
|
1192
|
+
// 注意:MCP client 发送的 actionType 是 "mcp" 而非 "idle",需要通过 actionDetail 判断。
|
|
1193
|
+
// ★ 关键修复: turn_complete 后进入 waiting_input 时,如果携带的是 ai_request 动作,
|
|
1194
|
+
// 不应该保留之前的 tool_using 动作,避免永久卡在工具调用状态。
|
|
1195
|
+
const prevActionDetail = entry.currentRuntimeActionDetail ?? "";
|
|
1196
|
+
const isPrevPollMessage = prevActionDetail.includes("poll_message") || prevActionDetail.includes("get_message");
|
|
1197
|
+
const isNewAiRequestAction = runtimeState.actionInfo?.actionType === "ai_request";
|
|
1198
|
+
const shouldPreservePrevAction = !isNewAiRequestAction && // 新动作不是 AI 响应
|
|
1199
|
+
(runtimeState.status === "thinking" || runtimeState.status === "waiting_input") && // 状态是 thinking 或 waiting_input
|
|
1200
|
+
(entry.currentRuntimeActionType === "idle" || entry.currentRuntimeActionType === "mcp") && // 之前是 idle/mcp 动作
|
|
1201
|
+
isPrevPollMessage; // 且之前是 poll_message
|
|
1202
|
+
if (runtimeState.actionInfo?.actionType || // 新状态携带了动作
|
|
1203
|
+
!shouldPreservePrevAction // 不满足保留条件
|
|
1204
|
+
) {
|
|
1205
|
+
entry.currentRuntimeActionType = runtimeState.actionInfo?.actionType;
|
|
1206
|
+
entry.currentRuntimeActionLabel = runtimeState.actionInfo?.actionLabel;
|
|
1207
|
+
entry.currentRuntimeActionDetail =
|
|
1208
|
+
runtimeState.actionInfo?.actionDetail;
|
|
1209
|
+
entry.currentRuntimeActionInput =
|
|
1210
|
+
runtimeState.actionInfo?.actionInput;
|
|
1211
|
+
}
|
|
984
1212
|
console.log(`[${definition.displayName} Adapter] Status changed: ${runtimeState.status} for agent ${entry.agentId}`, runtimeState.actionInfo || "");
|
|
985
1213
|
void updatePersistedSessionRuntimeState(sessionId, {
|
|
986
1214
|
runtimeStatus: runtimeState.status,
|
|
987
|
-
runtimeActionType:
|
|
988
|
-
runtimeActionLabel:
|
|
989
|
-
runtimeActionDetail:
|
|
1215
|
+
runtimeActionType: entry.currentRuntimeActionType,
|
|
1216
|
+
runtimeActionLabel: entry.currentRuntimeActionLabel,
|
|
1217
|
+
runtimeActionDetail: entry.currentRuntimeActionDetail,
|
|
990
1218
|
});
|
|
991
|
-
|
|
1219
|
+
// thinking + poll_message 动作保留时,派生恢复用 actionInfo 传给 sendStatus
|
|
1220
|
+
// 支持 actionType 为 "idle" 或 "mcp",通过 actionDetail 中的 poll_message/get_message 判断
|
|
1221
|
+
const restoredActionInfo = runtimeState.actionInfo?.actionType
|
|
1222
|
+
? runtimeState.actionInfo
|
|
1223
|
+
: entry.currentRuntimeActionType
|
|
1224
|
+
? {
|
|
1225
|
+
actionType: entry.currentRuntimeActionType,
|
|
1226
|
+
actionLabel: entry.currentRuntimeActionLabel ?? "",
|
|
1227
|
+
actionDetail: entry.currentRuntimeActionDetail,
|
|
1228
|
+
actionInput: entry.currentRuntimeActionInput,
|
|
1229
|
+
}
|
|
1230
|
+
: undefined;
|
|
1231
|
+
void sendStatus(entry.agentId, sessionId, runtimeState.status, restoredActionInfo, resolveStatusChangeUsage(runtimeState.actionInfo), {
|
|
992
1232
|
runtimeAccessToken: entry.runtimeAccessToken,
|
|
993
1233
|
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
994
1234
|
});
|
|
@@ -1008,6 +1248,17 @@ function wireSdkAdapterEvents(adapter, definition) {
|
|
|
1008
1248
|
const entry = sdkSessions.get(data.sessionId);
|
|
1009
1249
|
if (entry) {
|
|
1010
1250
|
console.log(`[${definition.displayName} Adapter] Permission request: ${data.toolName} for agent ${entry.agentId}`);
|
|
1251
|
+
const actionDetail = data.toolInput
|
|
1252
|
+
? redactTransientClipboardPastePaths(JSON.stringify(data.toolInput).slice(0, 400))
|
|
1253
|
+
: undefined;
|
|
1254
|
+
void sendStatus(entry.agentId, data.sessionId, "waiting_input", {
|
|
1255
|
+
actionType: "other",
|
|
1256
|
+
actionLabel: "权限确认",
|
|
1257
|
+
actionDetail: actionDetail || data.toolName,
|
|
1258
|
+
}, undefined, {
|
|
1259
|
+
runtimeAccessToken: entry.runtimeAccessToken,
|
|
1260
|
+
schedulerBaseUrl: entry.schedulerBaseUrl,
|
|
1261
|
+
});
|
|
1011
1262
|
}
|
|
1012
1263
|
});
|
|
1013
1264
|
}
|
|
@@ -1049,7 +1300,24 @@ async function startSdkSession(req, res) {
|
|
|
1049
1300
|
// MCP 配置 - 加载 aws-client-agent-mcp
|
|
1050
1301
|
mcpConfigPath, extraMcpServers, envOverrides,
|
|
1051
1302
|
// 空闲命令配置
|
|
1052
|
-
idleInputAutoCommand, nonInputAutoCommand,
|
|
1303
|
+
idleInputAutoCommand, nonInputAutoCommand,
|
|
1304
|
+
// Per-agent model override(优先于全局配置)
|
|
1305
|
+
model: perAgentModel,
|
|
1306
|
+
// Per-instance AI provider 覆盖(仅 ACode 消费;优先级:api > env > file > global)
|
|
1307
|
+
instanceAiConfig: perInstanceAiConfig, } = req.body || {};
|
|
1308
|
+
const _dbg = getInstanceAiConfigDebugLogger();
|
|
1309
|
+
void _dbg.log({
|
|
1310
|
+
level: "info",
|
|
1311
|
+
category: "launch",
|
|
1312
|
+
message: "startSdkSession: 收到启动请求",
|
|
1313
|
+
source: "bridge",
|
|
1314
|
+
agentId: String(agentId || "").trim() || undefined,
|
|
1315
|
+
details: {
|
|
1316
|
+
reqBodyKeys: Object.keys(req.body || {}),
|
|
1317
|
+
perInstanceAiConfig: perInstanceAiConfig ? redactForLog(perInstanceAiConfig) : null,
|
|
1318
|
+
perAgentModel: perAgentModel ?? null,
|
|
1319
|
+
},
|
|
1320
|
+
});
|
|
1053
1321
|
let sessionId = null;
|
|
1054
1322
|
const normalizedAgentId = String(agentId || "").trim();
|
|
1055
1323
|
const runtimeAccessToken = String(req.body?.runtimeAccessToken || "").trim() || undefined;
|
|
@@ -1074,9 +1342,14 @@ async function startSdkSession(req, res) {
|
|
|
1074
1342
|
await removePersistedSession(persistedSession.sessionId);
|
|
1075
1343
|
}
|
|
1076
1344
|
else {
|
|
1345
|
+
const displayStatus = resolveSdkSessionDisplayStatus(persistedSession.runtimeStatus, persistedSession.runtimeStatus, persistedSession.runtimeActionType);
|
|
1077
1346
|
res.json({
|
|
1078
1347
|
sessionId: persistedSession.sessionId,
|
|
1079
|
-
status:
|
|
1348
|
+
status: displayStatus,
|
|
1349
|
+
runtimeStatus: displayStatus,
|
|
1350
|
+
runtimeActionType: persistedSession.runtimeActionType,
|
|
1351
|
+
runtimeActionLabel: persistedSession.runtimeActionLabel,
|
|
1352
|
+
runtimeActionDetail: persistedSession.runtimeActionDetail,
|
|
1080
1353
|
agentId: persistedSession.agentId,
|
|
1081
1354
|
workspacePath: persistedSession.workspacePath,
|
|
1082
1355
|
command: persistedSession.command,
|
|
@@ -1098,7 +1371,6 @@ async function startSdkSession(req, res) {
|
|
|
1098
1371
|
// 根据命令选择对应的 adapter
|
|
1099
1372
|
const normalizedCommand = (command || "acode").toLowerCase();
|
|
1100
1373
|
const providerId = resolveSdkProviderId(command);
|
|
1101
|
-
console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
|
|
1102
1374
|
const adapter = adapterRegistry.get(providerId);
|
|
1103
1375
|
sessionId = uuidv4();
|
|
1104
1376
|
const savedClaudeConfig = providerId === "claude-code"
|
|
@@ -1107,18 +1379,108 @@ async function startSdkSession(req, res) {
|
|
|
1107
1379
|
const claudeLaunchConfig = providerId === "claude-code"
|
|
1108
1380
|
? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
|
|
1109
1381
|
: buildClaudeCodeLaunchConfig(null, envOverrides);
|
|
1382
|
+
// 模型解析优先级(ACode 与非 ACode 不同):
|
|
1383
|
+
//
|
|
1384
|
+
// ACode:模型仲裁收敛到 AcodeSdkAdapter.resolveAcodeProvider → resolveAcodeProviderConfig 纯函数
|
|
1385
|
+
// 优先级:perSessionModel(request) > instanceAiConfig(instance) > acode.json(global) > default
|
|
1386
|
+
// 这里不预解析,将原始输入传递给 adapter
|
|
1387
|
+
//
|
|
1388
|
+
// 非 ACode (claude-code/codex/opencode):
|
|
1389
|
+
// 优先级:perAgentModel(request) > claudeLaunchConfig.model(global)
|
|
1390
|
+
// 通过 ANTHROPIC_MODEL/CODEX_MODEL 环境变量注入
|
|
1391
|
+
const resolvedModel = providerId === "acode"
|
|
1392
|
+
? (perAgentModel ? String(perAgentModel).trim() : undefined)
|
|
1393
|
+
: (() => {
|
|
1394
|
+
const requestModel = perAgentModel ? String(perAgentModel).trim() : undefined;
|
|
1395
|
+
const globalModel = claudeLaunchConfig.model ? String(claudeLaunchConfig.model).trim() : undefined;
|
|
1396
|
+
return requestModel || globalModel;
|
|
1397
|
+
})();
|
|
1398
|
+
void _dbg.log({
|
|
1399
|
+
level: "info",
|
|
1400
|
+
category: "resolve",
|
|
1401
|
+
message: "模型解析结果",
|
|
1402
|
+
source: "bridge",
|
|
1403
|
+
agentId: normalizedAgentId,
|
|
1404
|
+
sessionId: sessionId ?? undefined,
|
|
1405
|
+
details: {
|
|
1406
|
+
perAgentModel: perAgentModel ?? null,
|
|
1407
|
+
globalModel: claudeLaunchConfig.model ?? null,
|
|
1408
|
+
resolvedModel: resolvedModel ?? null,
|
|
1409
|
+
providerId,
|
|
1410
|
+
note: providerId === "acode" ? "adapter handles full priority" : "request > global",
|
|
1411
|
+
},
|
|
1412
|
+
});
|
|
1413
|
+
// 构建 effectiveEnvOverrides:将 resolvedModel 注入各 provider 对应的环境变量
|
|
1414
|
+
const effectiveEnvOverrides = { ...claudeLaunchConfig.envOverrides };
|
|
1415
|
+
if (resolvedModel) {
|
|
1416
|
+
// Claude Code 使用 ANTHROPIC_MODEL
|
|
1417
|
+
effectiveEnvOverrides.ANTHROPIC_MODEL = resolvedModel;
|
|
1418
|
+
// Codex 使用 CODEX_MODEL
|
|
1419
|
+
effectiveEnvOverrides.CODEX_MODEL = resolvedModel;
|
|
1420
|
+
// ACode / OpenCode: 不通过 env var 传递模型
|
|
1421
|
+
}
|
|
1422
|
+
// ACode 实例自配置:解析 api > env > file,deep-merge 全局后写入 per-instance acode.json
|
|
1423
|
+
// 其它 provider 不消费 instanceAiConfig,保持向后兼容
|
|
1424
|
+
// 模型仲裁由 AcodeSdkAdapter.resolveAcodeProvider 统一处理
|
|
1425
|
+
let resolvedInstanceAiConfig;
|
|
1426
|
+
let resolvedMcpConfigPath = mcpConfigPath;
|
|
1427
|
+
if (providerId === "acode") {
|
|
1428
|
+
const mergedInstanceConfig = perInstanceAiConfig || {};
|
|
1429
|
+
const prepared = await prepareAcodeInstanceConfig({
|
|
1430
|
+
apiOverrides: sanitizeInstanceAiConfig(mergedInstanceConfig) ?? undefined,
|
|
1431
|
+
agentId: normalizedAgentId,
|
|
1432
|
+
sessionId: sessionId ?? undefined,
|
|
1433
|
+
runtimeBridgeBaseUrl: requestedSchedulerBaseUrl,
|
|
1434
|
+
});
|
|
1435
|
+
resolvedInstanceAiConfig = prepared.instanceConfig.config;
|
|
1436
|
+
resolvedMcpConfigPath = prepared.perInstanceConfigPath;
|
|
1437
|
+
void _dbg.log({
|
|
1438
|
+
level: "info",
|
|
1439
|
+
category: "apply",
|
|
1440
|
+
message: "prepareAcodeInstanceConfig 完成",
|
|
1441
|
+
source: "bridge",
|
|
1442
|
+
agentId: normalizedAgentId,
|
|
1443
|
+
sessionId: sessionId ?? undefined,
|
|
1444
|
+
details: {
|
|
1445
|
+
sanitizedApiOverrides: sanitizeInstanceAiConfig(mergedInstanceConfig) ? redactForLog(sanitizeInstanceAiConfig(mergedInstanceConfig)) : null,
|
|
1446
|
+
resolvedInstanceAiConfig: resolvedInstanceAiConfig ? redactForLog(resolvedInstanceAiConfig) : null,
|
|
1447
|
+
perInstanceConfigPath: resolvedMcpConfigPath,
|
|
1448
|
+
instanceConfigSources: prepared.instanceConfig.source ?? null,
|
|
1449
|
+
},
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1110
1452
|
// 构建SDK配置
|
|
1111
1453
|
const config = {
|
|
1112
1454
|
command: command || "acode",
|
|
1113
1455
|
workingDirectory: workspacePath,
|
|
1456
|
+
agentId: normalizedAgentId,
|
|
1114
1457
|
autoAccept: Boolean(autoAccept),
|
|
1115
1458
|
initialPrompt,
|
|
1116
|
-
model:
|
|
1459
|
+
model: resolvedModel,
|
|
1117
1460
|
// MCP 配置:不再默认注入 aws-mcp;由用户在面板中安装/配置
|
|
1118
|
-
|
|
1461
|
+
// ACode 场景下,per-instance acode.json 已由 instance-ai-config-service 写入
|
|
1462
|
+
mcpConfigPath: resolvedMcpConfigPath,
|
|
1463
|
+
// ACode 实例自配置:来源与值会写入 d:/Instance-ai-configuration-debug.log
|
|
1464
|
+
...(resolvedInstanceAiConfig
|
|
1465
|
+
? { instanceAiConfig: resolvedInstanceAiConfig }
|
|
1466
|
+
: {}),
|
|
1119
1467
|
extraMcpServers: extraMcpServers || {},
|
|
1120
|
-
envOverrides: buildRuntimeEnv(String(agentId), String(workspacePath), process.env,
|
|
1468
|
+
envOverrides: buildRuntimeEnv(String(agentId), String(workspacePath), process.env, effectiveEnvOverrides),
|
|
1121
1469
|
};
|
|
1470
|
+
void _dbg.log({
|
|
1471
|
+
level: "info",
|
|
1472
|
+
category: "launch",
|
|
1473
|
+
message: "传递给 Adapter 的完整 AdapterSessionConfig",
|
|
1474
|
+
source: "bridge",
|
|
1475
|
+
agentId: normalizedAgentId,
|
|
1476
|
+
sessionId,
|
|
1477
|
+
details: {
|
|
1478
|
+
adapterModel: config.model ?? null,
|
|
1479
|
+
adapterInstanceAiConfig: config.instanceAiConfig ? redactForLog(config.instanceAiConfig) : null,
|
|
1480
|
+
adapterMcpConfigPath: config.mcpConfigPath ?? null,
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
1483
|
+
await _dbg.flush();
|
|
1122
1484
|
// 存储会话信息
|
|
1123
1485
|
sdkSessions.set(sessionId, {
|
|
1124
1486
|
agentId: normalizedAgentId,
|
|
@@ -1784,3 +2146,58 @@ terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
|
|
|
1784
2146
|
errors: errors.length > 0 ? errors : undefined,
|
|
1785
2147
|
});
|
|
1786
2148
|
});
|
|
2149
|
+
// ============ 桥接侧事件查询 ============
|
|
2150
|
+
/**
|
|
2151
|
+
* 查询最近 N 条桥接侧本地事件。
|
|
2152
|
+
*
|
|
2153
|
+
* 用于前端面板或调试工具获取最近的状态/输出事件快照。
|
|
2154
|
+
* GET /runtime/events/last/:n
|
|
2155
|
+
*/
|
|
2156
|
+
terminalRouter.get("/events/last/:n", validateToken, (req, res) => {
|
|
2157
|
+
const nParam = parseInt(req.params.n, 10);
|
|
2158
|
+
if (!Number.isFinite(nParam) || nParam < 1) {
|
|
2159
|
+
res.status(400).json({ error: ":n must be a positive integer" });
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const entries = bridgeEventBuffer.getLastN(nParam);
|
|
2163
|
+
res.json({
|
|
2164
|
+
ok: true,
|
|
2165
|
+
count: entries.length,
|
|
2166
|
+
entries,
|
|
2167
|
+
});
|
|
2168
|
+
});
|
|
2169
|
+
/**
|
|
2170
|
+
* 查询自指定 index(不含)之后的桥接侧本地事件。
|
|
2171
|
+
*
|
|
2172
|
+
* 增量轮询端点:客户端保存上一次看到的 index,
|
|
2173
|
+
* 下次传入该 index 仅获取增量事件,适用于实时刷新场景。
|
|
2174
|
+
* GET /runtime/events/since/:index
|
|
2175
|
+
*/
|
|
2176
|
+
terminalRouter.get("/events/since/:index", validateToken, (req, res) => {
|
|
2177
|
+
const indexParam = parseInt(req.params.index, 10);
|
|
2178
|
+
if (!Number.isFinite(indexParam) || indexParam < 0) {
|
|
2179
|
+
res.status(400).json({ error: ":index must be a non-negative integer" });
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const slice = bridgeEventBuffer.getSinceIndex(indexParam);
|
|
2183
|
+
res.json({
|
|
2184
|
+
ok: true,
|
|
2185
|
+
entries: slice.entries,
|
|
2186
|
+
nextIndex: slice.nextIndex,
|
|
2187
|
+
oldestIndex: slice.oldestIndex,
|
|
2188
|
+
dataLoss: slice.oldestIndex > indexParam + 1,
|
|
2189
|
+
});
|
|
2190
|
+
});
|
|
2191
|
+
/**
|
|
2192
|
+
* 获取 Bridge 侧事件推送统计(需求 4.2.1 #3)。
|
|
2193
|
+
* 返回每个 Agent 的待推送数、累计成功/失败、连续失败次数、最近延迟等。
|
|
2194
|
+
* GET /runtime/events/push-stats
|
|
2195
|
+
*/
|
|
2196
|
+
terminalRouter.get("/events/push-stats", validateToken, (_req, res) => {
|
|
2197
|
+
const stats = getAllPushStats();
|
|
2198
|
+
res.json({
|
|
2199
|
+
ok: true,
|
|
2200
|
+
count: stats.length,
|
|
2201
|
+
agents: stats,
|
|
2202
|
+
});
|
|
2203
|
+
});
|