aws-runtime-bridge 1.9.10 → 1.9.20
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 +353 -7
- package/dist/adapter/AcodeSdkAdapter.test.js +155 -8
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +36 -3
- 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 +27 -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 +305 -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 +2 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +378 -41
- package/dist/routes/terminal.test.js +33 -7
- package/dist/services/agent-event-store.d.ts +59 -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 +47 -3
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +112 -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/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 +5 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.js +16 -0
- 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/mcp-server.d.ts +170 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +947 -9
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +103 -1
- 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 +46 -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 +164 -12
- 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 +88 -1
- 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 +122 -5
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 主流程:直接加载随 aws-runtime-bridge 内置发布的 ACode headless runtime,
|
|
5
5
|
* 不依赖外部 CLI 安装;将 ACode 的结构化事件映射为 runtime-bridge 统一事件流。
|
|
6
|
+
*
|
|
7
|
+
* 模型/baseURL/apiKey 解析(核心):
|
|
8
|
+
* - 调用 instance-ai-config-resolver 按 "instance > global > default" 三层仲裁
|
|
9
|
+
* - "instance" 包含 per-session config.model 与 per-instance config.instanceAiConfig
|
|
10
|
+
* - "global" 来自 configPath 指向的 acode.json(per-instance 文件已 deep-merge 全局)
|
|
11
|
+
* - 解析结果会写入 d:/Instance-ai-configuration-debug.log,bridge + 后端共享同一份
|
|
6
12
|
*/
|
|
7
13
|
import { EventEmitter } from "node:events";
|
|
8
14
|
import type { AdapterSessionConfig, BaseProviderAdapter, ConversationMessage, SessionStatus } from "./types.js";
|
|
@@ -22,6 +28,7 @@ type ACodeRuntimeInstance = {
|
|
|
22
28
|
};
|
|
23
29
|
type ACodeRuntimeConstructor = new (config: {
|
|
24
30
|
sessionId: string;
|
|
31
|
+
agentId?: string;
|
|
25
32
|
workingDirectory: string;
|
|
26
33
|
configPath?: string;
|
|
27
34
|
skillsDir?: string;
|
|
@@ -58,6 +65,28 @@ export declare class AcodeSdkAdapter extends EventEmitter implements BaseProvide
|
|
|
58
65
|
}): void;
|
|
59
66
|
/** Injects a runtime constructor for adapter tests without loading the packaged ACode module. */
|
|
60
67
|
setRuntimeConstructorForTest(runtimeConstructor: ACodeRuntimeConstructor): void;
|
|
68
|
+
/**
|
|
69
|
+
* 加载 ACode acode.json 顶层 provider 字段,提取 model/baseURL/apiKey,
|
|
70
|
+
* 以及顶层 contextLimit(用户自定义上下文窗口大小)。
|
|
71
|
+
* 主流程:先尝试 per-instance 文件(由 instance-ai-config-service 写入,含 deep-merge 结果),
|
|
72
|
+
* 失败回退到 global acode.json;解析失败返回空对象。
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* 从 acode.json 中读取 provider.default 信息及模型配置字典。
|
|
76
|
+
* 返回包含 model/baseURL/apiKey 的仲裁配置,以及:
|
|
77
|
+
* - contextLimit: 顶层 contextLimit(优先)
|
|
78
|
+
* - modelContextLimits: 各模型的 contextLimit 字典,供 resolveAcodeProvider 回退查找
|
|
79
|
+
*/
|
|
80
|
+
private loadAcodeProviderSection;
|
|
81
|
+
/**
|
|
82
|
+
* 解析 ACode provider 配置:按 "instance > global > default" 优先级逐字段仲裁。
|
|
83
|
+
* 入口在 createRuntime;该方法独立出来便于测试。
|
|
84
|
+
*
|
|
85
|
+
* @param config AdapterSessionConfig(per-session model 与 per-instance instanceAiConfig)
|
|
86
|
+
* @param configPath 实际作为 ACodeRuntime.configPath 的 acode.json 路径
|
|
87
|
+
* (可能为 per-instance 文件或全局文件)
|
|
88
|
+
*/
|
|
89
|
+
private resolveAcodeProvider;
|
|
61
90
|
private createRuntime;
|
|
62
91
|
private loadRuntimeConstructor;
|
|
63
92
|
private requireSession;
|
|
@@ -101,6 +130,17 @@ export declare class AcodeSdkAdapter extends EventEmitter implements BaseProvide
|
|
|
101
130
|
* look permanently stuck. Recover to waiting_input and arm the listening loop.
|
|
102
131
|
*/
|
|
103
132
|
private recoverResolvedTurnWithoutTerminalEvent;
|
|
133
|
+
/**
|
|
134
|
+
* 启动 submit 超时检测(治本修复:防止状态永久卡住)
|
|
135
|
+
*
|
|
136
|
+
* 如果 runtime.submit() 在指定时间内未返回,强制触发恢复机制,
|
|
137
|
+
* 避免会话永久卡在 thinking/tool_using 状态。
|
|
138
|
+
*/
|
|
139
|
+
private startSubmitTimeoutDetection;
|
|
140
|
+
/**
|
|
141
|
+
* 取消 submit 超时检测
|
|
142
|
+
*/
|
|
143
|
+
private cancelSubmitTimeoutDetection;
|
|
104
144
|
}
|
|
105
145
|
export {};
|
|
106
146
|
//# sourceMappingURL=AcodeSdkAdapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AcodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/AcodeSdkAdapter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"AcodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/AcodeSdkAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 主流程:直接加载随 aws-runtime-bridge 内置发布的 ACode headless runtime,
|
|
5
5
|
* 不依赖外部 CLI 安装;将 ACode 的结构化事件映射为 runtime-bridge 统一事件流。
|
|
6
|
+
*
|
|
7
|
+
* 模型/baseURL/apiKey 解析(核心):
|
|
8
|
+
* - 调用 instance-ai-config-resolver 按 "instance > global > default" 三层仲裁
|
|
9
|
+
* - "instance" 包含 per-session config.model 与 per-instance config.instanceAiConfig
|
|
10
|
+
* - "global" 来自 configPath 指向的 acode.json(per-instance 文件已 deep-merge 全局)
|
|
11
|
+
* - 解析结果会写入 d:/Instance-ai-configuration-debug.log,bridge + 后端共享同一份
|
|
6
12
|
*/
|
|
7
13
|
import { EventEmitter } from "node:events";
|
|
8
14
|
import path from "node:path";
|
|
15
|
+
import fs from "node:fs/promises";
|
|
9
16
|
import { pathToFileURL } from "node:url";
|
|
10
17
|
import { v4 as uuidv4 } from "uuid";
|
|
11
18
|
import { getAcodeConfigFile, getAcodeSkillsDir, getRuntimeHomeDir, } from "../config.js";
|
|
12
19
|
import { getReleasedAcodeEntryPath } from "../services/acode-package.js";
|
|
20
|
+
import { getInstanceAiConfigDebugLogger } from "../utils/instance-ai-config-debug-logger.js";
|
|
21
|
+
import { redactForLog } from "../services/instance-ai-config-service.js";
|
|
22
|
+
import { resolveAcodeProviderConfig, } from "./instance-ai-config-resolver.js";
|
|
13
23
|
import { isMessagePollingIdleCommand, isPollMessageToolDescriptor, } from "./acode-tool-names.js";
|
|
14
24
|
import { IdlePollLoop } from "./idle-poll-loop.js";
|
|
15
25
|
import { getCanonicalMcpDetail, getToolActionInfo } from "./types.js";
|
|
@@ -24,6 +34,9 @@ const ACODE_AUTO_POLL_DELAY_MS = 250;
|
|
|
24
34
|
// model 5xx, provider unreachable). Larger than ACODE_AUTO_POLL_DELAY_MS to
|
|
25
35
|
// avoid busy-looping when the same failure recurs on every retry.
|
|
26
36
|
const ACODE_AUTO_POLL_FAILURE_DELAY_MS = 2000;
|
|
37
|
+
// 新增:submit 超时检测配置(治本修复:防止状态永久卡住)
|
|
38
|
+
// 当 submit 超过此时间未返回,强制触发恢复机制
|
|
39
|
+
const ACODE_SUBMIT_TIMEOUT_MS = 120000; // 120秒(2分钟)
|
|
27
40
|
const ACODE_MESSAGE_POLLING_IDLE_PROMPT = "系统提醒:当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒;收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理;完成任务后调用 submit_task_result 提交结果,然后继续 poll_message。";
|
|
28
41
|
function getAcodePackageEntryPath() {
|
|
29
42
|
return path.resolve(getReleasedAcodeEntryPath());
|
|
@@ -31,6 +44,63 @@ function getAcodePackageEntryPath() {
|
|
|
31
44
|
export function toAcodeEsmImportSpecifier(modulePath) {
|
|
32
45
|
return pathToFileURL(path.resolve(modulePath)).href;
|
|
33
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Known model context window limits lookup.
|
|
49
|
+
* Maps model name prefixes to { contextLimit, maxOutput }.
|
|
50
|
+
* Falls back to DEFAULT_CONTEXT_LIMIT (128K) when no match is found.
|
|
51
|
+
* This is aligned with aws-client-agent-mcp's context-manager.ts MODEL_CONTEXT_LIMITS.
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_CONTEXT_LIMIT = 128_000;
|
|
54
|
+
const MODEL_CONTEXT_LIMITS = {
|
|
55
|
+
"claude-4": { contextLimit: 200_000, maxOutput: 8_192 },
|
|
56
|
+
"claude-sonnet-4": { contextLimit: 200_000, maxOutput: 8_192 },
|
|
57
|
+
"claude-3-5-sonnet": { contextLimit: 200_000, maxOutput: 8_192 },
|
|
58
|
+
"claude-3-opus": { contextLimit: 200_000, maxOutput: 8_192 },
|
|
59
|
+
"claude-3-haiku": { contextLimit: 200_000, maxOutput: 8_192 },
|
|
60
|
+
"claude-2": { contextLimit: 100_000, maxOutput: 4_096 },
|
|
61
|
+
"gpt-4o": { contextLimit: 128_000, maxOutput: 16_384 },
|
|
62
|
+
"gpt-4-turbo": { contextLimit: 128_000, maxOutput: 4_096 },
|
|
63
|
+
"gpt-4": { contextLimit: 8_192, maxOutput: 4_096 },
|
|
64
|
+
"gpt-3.5-turbo": { contextLimit: 16_384, maxOutput: 4_096 },
|
|
65
|
+
"o1": { contextLimit: 200_000, maxOutput: 100_000 },
|
|
66
|
+
"o3-mini": { contextLimit: 200_000, maxOutput: 100_000 },
|
|
67
|
+
"gemini-2.0-flash": { contextLimit: 1_048_576, maxOutput: 8_192 },
|
|
68
|
+
"gemini-2.5-pro": { contextLimit: 1_048_576, maxOutput: 8_192 },
|
|
69
|
+
"gemini-1.5-pro": { contextLimit: 2_097_152, maxOutput: 8_192 },
|
|
70
|
+
"gemini-1.5-flash": { contextLimit: 1_048_576, maxOutput: 8_192 },
|
|
71
|
+
"deepseek": { contextLimit: 64_000, maxOutput: 8_192 },
|
|
72
|
+
"deepseek-chat": { contextLimit: 64_000, maxOutput: 8_192 },
|
|
73
|
+
"deepseek-reasoner": { contextLimit: 64_000, maxOutput: 8_192 },
|
|
74
|
+
"qwen-max": { contextLimit: 32_000, maxOutput: 8_192 },
|
|
75
|
+
"qwen-plus": { contextLimit: 128_000, maxOutput: 8_192 },
|
|
76
|
+
"qwen-turbo": { contextLimit: 1_000_000, maxOutput: 8_192 },
|
|
77
|
+
"glm-4": { contextLimit: 128_000, maxOutput: 4_096 },
|
|
78
|
+
"yi-": { contextLimit: 200_000, maxOutput: 4_096 },
|
|
79
|
+
"llama-3": { contextLimit: 8_192, maxOutput: 4_096 },
|
|
80
|
+
"llama-2": { contextLimit: 4_096, maxOutput: 4_096 },
|
|
81
|
+
"mistral": { contextLimit: 32_000, maxOutput: 4_096 },
|
|
82
|
+
"mixtral": { contextLimit: 32_000, maxOutput: 4_096 },
|
|
83
|
+
"codestral": { contextLimit: 256_000, maxOutput: 8_192 },
|
|
84
|
+
"grok": { contextLimit: 131_072, maxOutput: 4_096 },
|
|
85
|
+
"nova-": { contextLimit: 200_000, maxOutput: 8_192 },
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Look up model context window limit by model name.
|
|
89
|
+
* Exact match first, then prefix match (longest prefix wins).
|
|
90
|
+
*/
|
|
91
|
+
function lookupModelContextLimit(modelName) {
|
|
92
|
+
if (!modelName)
|
|
93
|
+
return DEFAULT_CONTEXT_LIMIT;
|
|
94
|
+
const key = modelName.toLowerCase().trim();
|
|
95
|
+
if (MODEL_CONTEXT_LIMITS[key])
|
|
96
|
+
return MODEL_CONTEXT_LIMITS[key].contextLimit;
|
|
97
|
+
const matches = Object.keys(MODEL_CONTEXT_LIMITS)
|
|
98
|
+
.filter((prefix) => key.startsWith(prefix))
|
|
99
|
+
.sort((a, b) => b.length - a.length);
|
|
100
|
+
if (matches.length > 0)
|
|
101
|
+
return MODEL_CONTEXT_LIMITS[matches[0]].contextLimit;
|
|
102
|
+
return DEFAULT_CONTEXT_LIMIT;
|
|
103
|
+
}
|
|
34
104
|
function toUsage(value) {
|
|
35
105
|
if (!value || typeof value !== "object") {
|
|
36
106
|
return undefined;
|
|
@@ -132,17 +202,24 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
132
202
|
}
|
|
133
203
|
async startSession(sessionId, config) {
|
|
134
204
|
const runtimeHome = getRuntimeHomeDir();
|
|
205
|
+
// Resolve provider config first to get the resolved model and context limit,
|
|
206
|
+
// so totalUsage can carry contextLimit from the start.
|
|
207
|
+
const configPath = config.mcpConfigPath || getAcodeConfigFile(runtimeHome);
|
|
208
|
+
const { resolved, configContextLimit } = await this.resolveAcodeProvider(config, configPath);
|
|
209
|
+
const resolvedModel = resolved.model;
|
|
210
|
+
// priority: acode.json top-level contextLimit > model lookup table > 128K default
|
|
211
|
+
const contextLimit = configContextLimit ?? lookupModelContextLimit(resolvedModel);
|
|
135
212
|
const session = {
|
|
136
213
|
sessionId,
|
|
137
214
|
status: "starting",
|
|
138
215
|
messages: [],
|
|
139
216
|
createdAt: new Date().toISOString(),
|
|
140
|
-
totalUsage: { inputTokens: 0, outputTokens: 0 },
|
|
217
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, contextLimit },
|
|
141
218
|
};
|
|
142
219
|
this.sessions.set(sessionId, {
|
|
143
220
|
adapterSession: session,
|
|
144
221
|
config,
|
|
145
|
-
runtime: await this.createRuntime(sessionId, config, runtimeHome),
|
|
222
|
+
runtime: await this.createRuntime(sessionId, resolvedModel, config, runtimeHome),
|
|
146
223
|
autoPollActive: false,
|
|
147
224
|
});
|
|
148
225
|
this.emit("status-change", sessionId, "starting");
|
|
@@ -177,7 +254,15 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
177
254
|
}
|
|
178
255
|
}
|
|
179
256
|
async sendMessage(sessionId, message) {
|
|
257
|
+
const sendStartTime = Date.now();
|
|
258
|
+
console.log(`[ACode] ═══════════════════════════════════════════════════`);
|
|
259
|
+
console.log(`[ACode] ▶️ sendMessage STARTED`);
|
|
260
|
+
console.log(`[ACode] Session: ${sessionId}`);
|
|
261
|
+
console.log(`[ACode] Message: ${message.slice(0, 100)}${message.length > 100 ? '...' : ''}`);
|
|
262
|
+
console.log(`[ACode] ════════════════════════════════════════════════════`);
|
|
180
263
|
const entry = this.requireSession(sessionId);
|
|
264
|
+
const previousStatus = entry.adapterSession.status;
|
|
265
|
+
console.log(`[ACode] Previous status: ${previousStatus}`);
|
|
181
266
|
this.cancelScheduledAutoPoll(entry);
|
|
182
267
|
entry.failedTurnRecoveryScheduled = false;
|
|
183
268
|
entry.idlePollLoop?.stop();
|
|
@@ -190,14 +275,36 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
190
275
|
};
|
|
191
276
|
entry.adapterSession.messages.push(userMessage);
|
|
192
277
|
this.emit("conversation-message", sessionId, userMessage);
|
|
278
|
+
console.log(`[ACode] Changing status: ${previousStatus} → thinking`);
|
|
193
279
|
entry.adapterSession.status = "thinking";
|
|
194
280
|
this.emit("status-change", sessionId, "thinking");
|
|
281
|
+
// 启动超时检测
|
|
282
|
+
this.startSubmitTimeoutDetection(sessionId, entry, "sendMessage");
|
|
195
283
|
try {
|
|
284
|
+
console.log(`[ACode] ▶️ Calling runtime.submit()...`);
|
|
285
|
+
const submitStartTime = Date.now();
|
|
196
286
|
await entry.runtime.submit(message);
|
|
287
|
+
const submitDuration = Date.now() - submitStartTime;
|
|
288
|
+
// submit 成功返回,取消超时检测
|
|
289
|
+
this.cancelSubmitTimeoutDetection(entry);
|
|
290
|
+
console.log(`[ACode] ✅ runtime.submit() completed in ${submitDuration}ms`);
|
|
291
|
+
console.log(`[ACode] Current status after submit: ${entry.adapterSession.status}`);
|
|
292
|
+
console.log(`[ACode] ▶️ Calling recoverResolvedTurnWithoutTerminalEvent()...`);
|
|
197
293
|
this.recoverResolvedTurnWithoutTerminalEvent(sessionId);
|
|
294
|
+
console.log(`[ACode] Status after recovery: ${entry.adapterSession.status}`);
|
|
295
|
+
const totalDuration = Date.now() - sendStartTime;
|
|
296
|
+
console.log(`[ACode] ════════════════════════════════════════════════════`);
|
|
297
|
+
console.log(`[ACode] ✅ sendMessage COMPLETED in ${totalDuration}ms`);
|
|
298
|
+
console.log(`[ACode] ═══════════════════════════════════════════════════`);
|
|
198
299
|
}
|
|
199
300
|
catch (error) {
|
|
301
|
+
const errorDuration = Date.now() - sendStartTime;
|
|
302
|
+
console.error(`[ACode] ❌ sendMessage FAILED after ${errorDuration}ms:`, error instanceof Error ? error.message : String(error));
|
|
303
|
+
// submit 抛出异常,取消超时检测
|
|
304
|
+
this.cancelSubmitTimeoutDetection(entry);
|
|
305
|
+
console.log(`[ACode] ▶️ Calling emitRuntimeError with recover=true...`);
|
|
200
306
|
this.emitRuntimeError(sessionId, error, { recover: true });
|
|
307
|
+
console.log(`[ACode] Status after error recovery: ${entry.adapterSession.status}`);
|
|
201
308
|
}
|
|
202
309
|
}
|
|
203
310
|
async sendConfirmation(_sessionId, _accept) { }
|
|
@@ -218,7 +325,7 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
218
325
|
}
|
|
219
326
|
await entry.runtime.resetContext();
|
|
220
327
|
entry.adapterSession.messages = [];
|
|
221
|
-
entry.adapterSession.totalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
328
|
+
entry.adapterSession.totalUsage = { inputTokens: 0, outputTokens: 0, contextLimit: entry.adapterSession.totalUsage.contextLimit };
|
|
222
329
|
entry.adapterSession.status = "waiting_input";
|
|
223
330
|
this.emit("status-change", sessionId, "waiting_input", {
|
|
224
331
|
usage: entry.adapterSession.totalUsage,
|
|
@@ -280,19 +387,178 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
280
387
|
setRuntimeConstructorForTest(runtimeConstructor) {
|
|
281
388
|
this.runtimeConstructor = runtimeConstructor;
|
|
282
389
|
}
|
|
283
|
-
|
|
390
|
+
/**
|
|
391
|
+
* 加载 ACode acode.json 顶层 provider 字段,提取 model/baseURL/apiKey,
|
|
392
|
+
* 以及顶层 contextLimit(用户自定义上下文窗口大小)。
|
|
393
|
+
* 主流程:先尝试 per-instance 文件(由 instance-ai-config-service 写入,含 deep-merge 结果),
|
|
394
|
+
* 失败回退到 global acode.json;解析失败返回空对象。
|
|
395
|
+
*/
|
|
396
|
+
/**
|
|
397
|
+
* 从 acode.json 中读取 provider.default 信息及模型配置字典。
|
|
398
|
+
* 返回包含 model/baseURL/apiKey 的仲裁配置,以及:
|
|
399
|
+
* - contextLimit: 顶层 contextLimit(优先)
|
|
400
|
+
* - modelContextLimits: 各模型的 contextLimit 字典,供 resolveAcodeProvider 回退查找
|
|
401
|
+
*/
|
|
402
|
+
async loadAcodeProviderSection(configPath) {
|
|
403
|
+
try {
|
|
404
|
+
const raw = await fs.readFile(configPath, "utf-8");
|
|
405
|
+
const parsed = JSON.parse(raw);
|
|
406
|
+
const provider = parsed.provider;
|
|
407
|
+
const defaultProvider = provider?.default;
|
|
408
|
+
const options = defaultProvider?.options;
|
|
409
|
+
// 读取顶层 contextLimit(用户可在 acode.json 中配置自定义上下文窗口)
|
|
410
|
+
const contextLimit = toFiniteTokenCount(parsed.contextLimit);
|
|
411
|
+
// 从 provider.default.models 中提取各模型的 contextLimit 字典
|
|
412
|
+
const modelContextLimits = {};
|
|
413
|
+
const models = defaultProvider?.models;
|
|
414
|
+
if (models && typeof models === "object") {
|
|
415
|
+
for (const [modelName, modelConfig] of Object.entries(models)) {
|
|
416
|
+
if (modelConfig && typeof modelConfig === "object") {
|
|
417
|
+
const limit = toFiniteTokenCount(modelConfig.contextLimit);
|
|
418
|
+
if (limit > 0) {
|
|
419
|
+
modelContextLimits[modelName.trim()] = limit;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
...(defaultProvider?.model ? { model: defaultProvider.model } : {}),
|
|
426
|
+
...(options?.baseURL || options?.baseUrl
|
|
427
|
+
? { baseURL: options?.baseURL || options?.baseUrl }
|
|
428
|
+
: {}),
|
|
429
|
+
...(options?.apiKey ? { apiKey: options.apiKey } : {}),
|
|
430
|
+
...(contextLimit > 0 ? { contextLimit } : {}),
|
|
431
|
+
...(Object.keys(modelContextLimits).length > 0 ? { modelContextLimits } : {}),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
436
|
+
const dbg = getInstanceAiConfigDebugLogger();
|
|
437
|
+
void dbg.log({
|
|
438
|
+
level: "warn",
|
|
439
|
+
category: "load",
|
|
440
|
+
message: `loadAcodeProviderSection failed: ${configPath}: ${message}`,
|
|
441
|
+
source: "bridge",
|
|
442
|
+
});
|
|
443
|
+
return {};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 解析 ACode provider 配置:按 "instance > global > default" 优先级逐字段仲裁。
|
|
448
|
+
* 入口在 createRuntime;该方法独立出来便于测试。
|
|
449
|
+
*
|
|
450
|
+
* @param config AdapterSessionConfig(per-session model 与 per-instance instanceAiConfig)
|
|
451
|
+
* @param configPath 实际作为 ACodeRuntime.configPath 的 acode.json 路径
|
|
452
|
+
* (可能为 per-instance 文件或全局文件)
|
|
453
|
+
*/
|
|
454
|
+
async resolveAcodeProvider(config, configPath) {
|
|
455
|
+
// 实例层 = per-session model (config.model) + per-instance instanceAiConfig
|
|
456
|
+
// 注意:priority 上 per-session model 高于 per-instance instanceAiConfig.model
|
|
457
|
+
// per-session model 是请求级最具体的覆盖,应拥有最高优先级
|
|
458
|
+
const perSessionModel = typeof config.model === "string" && config.model.trim()
|
|
459
|
+
? config.model.trim()
|
|
460
|
+
: undefined;
|
|
461
|
+
const _dbg = getInstanceAiConfigDebugLogger();
|
|
462
|
+
void _dbg.log({
|
|
463
|
+
level: "info",
|
|
464
|
+
category: "resolve",
|
|
465
|
+
message: "resolveAcodeProvider 输入",
|
|
466
|
+
source: "bridge",
|
|
467
|
+
details: {
|
|
468
|
+
configModel: config.model ?? null,
|
|
469
|
+
configInstanceAiConfig: config.instanceAiConfig ? redactForLog(config.instanceAiConfig) : null,
|
|
470
|
+
configPath,
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
// 优先级仲裁:perSessionModel(request) > instanceAiConfig(instance) > global > default
|
|
474
|
+
// perSessionModel 是请求级最具体的覆盖,拥有最高优先级
|
|
475
|
+
const instanceConfig = {
|
|
476
|
+
...(config.instanceAiConfig?.model
|
|
477
|
+
? { model: config.instanceAiConfig.model }
|
|
478
|
+
: {}),
|
|
479
|
+
...(config.instanceAiConfig?.baseURL
|
|
480
|
+
? { baseURL: config.instanceAiConfig.baseURL }
|
|
481
|
+
: {}),
|
|
482
|
+
...(config.instanceAiConfig?.apiKey
|
|
483
|
+
? { apiKey: config.instanceAiConfig.apiKey }
|
|
484
|
+
: {}),
|
|
485
|
+
...(config.instanceAiConfig?.effort
|
|
486
|
+
? { effort: config.instanceAiConfig.effort }
|
|
487
|
+
: {}),
|
|
488
|
+
// perSessionModel 最后设置,确保优先级最高
|
|
489
|
+
...(perSessionModel ? { model: perSessionModel } : {}),
|
|
490
|
+
};
|
|
491
|
+
// 全局层 = acode.json 顶层 provider.default(含 deep-merge 后的 per-instance 文件)
|
|
492
|
+
const globalConfig = await this.loadAcodeProviderSection(configPath);
|
|
493
|
+
// 顶层 contextLimit(用户自定义上下文窗口大小)
|
|
494
|
+
// 回退:从 provider.default.models[resolvedModel].contextLimit 读取
|
|
495
|
+
let configContextLimit = globalConfig.contextLimit;
|
|
496
|
+
if (!configContextLimit && globalConfig.modelContextLimits) {
|
|
497
|
+
// 优先使用 perSessionModel,其次 instanceAiConfig 中的 model
|
|
498
|
+
const fallbackModel = perSessionModel ||
|
|
499
|
+
config.instanceAiConfig?.model ||
|
|
500
|
+
globalConfig.model;
|
|
501
|
+
if (fallbackModel && globalConfig.modelContextLimits[fallbackModel]) {
|
|
502
|
+
configContextLimit = globalConfig.modelContextLimits[fallbackModel];
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const resolved = resolveAcodeProviderConfig(instanceConfig, globalConfig, "acode-default");
|
|
506
|
+
void _dbg.log({
|
|
507
|
+
level: "info",
|
|
508
|
+
category: "resolve",
|
|
509
|
+
message: "resolveAcodeProvider 解析结果",
|
|
510
|
+
source: "bridge",
|
|
511
|
+
details: {
|
|
512
|
+
instanceConfig: redactForLog(instanceConfig),
|
|
513
|
+
globalConfigModel: globalConfig?.model ?? null,
|
|
514
|
+
resolvedModel: resolved.model,
|
|
515
|
+
resolvedBaseURL: resolved.baseURL ?? null,
|
|
516
|
+
resolvedApiKey: resolved.apiKey ? "[set]" : null,
|
|
517
|
+
configContextLimit,
|
|
518
|
+
resolvedSource: resolved.source,
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
return {
|
|
522
|
+
resolved,
|
|
523
|
+
configPath,
|
|
524
|
+
perSessionModel,
|
|
525
|
+
configContextLimit,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
async createRuntime(sessionId, resolvedModel, config, runtimeHome) {
|
|
284
529
|
const ACodeRuntime = await this.loadRuntimeConstructor();
|
|
530
|
+
const configPath = config.mcpConfigPath || getAcodeConfigFile(runtimeHome);
|
|
285
531
|
const runtime = new ACodeRuntime({
|
|
286
532
|
sessionId,
|
|
533
|
+
agentId: config.agentId,
|
|
287
534
|
workingDirectory: config.workingDirectory,
|
|
288
|
-
configPath
|
|
535
|
+
configPath,
|
|
289
536
|
skillsDir: getAcodeSkillsDir(runtimeHome),
|
|
290
|
-
model:
|
|
537
|
+
model: resolvedModel,
|
|
291
538
|
systemPrompt: typeof config.systemPrompt === "string"
|
|
292
539
|
? config.systemPrompt
|
|
293
540
|
: undefined,
|
|
294
541
|
env: config.envOverrides,
|
|
295
542
|
});
|
|
543
|
+
const debugLogger = getInstanceAiConfigDebugLogger();
|
|
544
|
+
void debugLogger.log({
|
|
545
|
+
level: "info",
|
|
546
|
+
category: "resolve",
|
|
547
|
+
message: "ACode provider resolved",
|
|
548
|
+
source: "bridge",
|
|
549
|
+
sessionId,
|
|
550
|
+
details: {
|
|
551
|
+
resolvedModel,
|
|
552
|
+
perInstanceModel: config.instanceAiConfig?.model || null,
|
|
553
|
+
perInstanceBaseURL: config.instanceAiConfig?.baseURL
|
|
554
|
+
? "[set]"
|
|
555
|
+
: null,
|
|
556
|
+
perInstanceApiKey: config.instanceAiConfig?.apiKey
|
|
557
|
+
? "[set]"
|
|
558
|
+
: null,
|
|
559
|
+
configPath,
|
|
560
|
+
},
|
|
561
|
+
});
|
|
296
562
|
runtime.on("event", (event) => this.forwardAcodeEvent(event));
|
|
297
563
|
return runtime;
|
|
298
564
|
}
|
|
@@ -327,9 +593,13 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
327
593
|
}
|
|
328
594
|
entry.adapterSession.status = "thinking";
|
|
329
595
|
this.emit("status-change", sessionId, "thinking");
|
|
596
|
+
// 启动超时检测
|
|
597
|
+
this.startSubmitTimeoutDetection(sessionId, entry, "startInitialPromptTurn");
|
|
330
598
|
void entry.runtime
|
|
331
599
|
.submit(initialPrompt)
|
|
332
600
|
.then(() => {
|
|
601
|
+
// submit 成功返回,取消超时检测
|
|
602
|
+
this.cancelSubmitTimeoutDetection(entry);
|
|
333
603
|
this.recoverResolvedTurnWithoutTerminalEvent(sessionId);
|
|
334
604
|
})
|
|
335
605
|
.catch((error) => {
|
|
@@ -337,6 +607,8 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
337
607
|
if (!currentEntry) {
|
|
338
608
|
return;
|
|
339
609
|
}
|
|
610
|
+
// submit 抛出异常,取消超时检测
|
|
611
|
+
this.cancelSubmitTimeoutDetection(currentEntry);
|
|
340
612
|
// R4 fix: a failed initial prompt turn used to leave the session
|
|
341
613
|
// permanently in 'error' because autoPollActive was false at this
|
|
342
614
|
// point (only set by submitAutoPollPrompt), so the R2 recovery in
|
|
@@ -433,6 +705,11 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
433
705
|
entry.adapterSession.totalUsage.inputTokens += usage.inputTokens;
|
|
434
706
|
entry.adapterSession.totalUsage.outputTokens += usage.outputTokens;
|
|
435
707
|
}
|
|
708
|
+
// 关键日志:记录 turn_complete 事件
|
|
709
|
+
const previousStatus = entry.adapterSession.status;
|
|
710
|
+
console.log(`[ACode] turn_complete event received (session: ${event.sessionId}), ` +
|
|
711
|
+
`previous status: ${previousStatus}, failed: ${event.data.failed === true}, ` +
|
|
712
|
+
`aborted: ${event.data.aborted === true}`);
|
|
436
713
|
if (isFailedAcodeTurnComplete(event.data)) {
|
|
437
714
|
// R2 fix: a failed polling-driven turn (e.g., 60s MCP tool timeout,
|
|
438
715
|
// model 5xx, provider unreachable) used to leave the session parked
|
|
@@ -446,6 +723,7 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
446
723
|
// initial prompt turn failed, autoPollActive was false and the
|
|
447
724
|
// session got permanently stuck in 'error'. Now we always recover
|
|
448
725
|
// for non-terminal sessions so the idle loop can retry.
|
|
726
|
+
console.log(`[ACode] Recovering from failed turn_complete (session: ${event.sessionId})`);
|
|
449
727
|
this.recoverTurnFailure(event.sessionId, entry);
|
|
450
728
|
return;
|
|
451
729
|
}
|
|
@@ -462,6 +740,7 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
462
740
|
this.emit("status-change", event.sessionId, "waiting_input", {
|
|
463
741
|
usage: entry.adapterSession.totalUsage,
|
|
464
742
|
});
|
|
743
|
+
console.log(`[ACode] Status changed to waiting_input after turn_complete (session: ${event.sessionId})`);
|
|
465
744
|
if (entry.autoPollActive &&
|
|
466
745
|
entry.autoPollObservedAssistantText &&
|
|
467
746
|
!entry.autoPollObservedPollingTool) {
|
|
@@ -565,7 +844,10 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
565
844
|
? "waiting_input"
|
|
566
845
|
: "thinking";
|
|
567
846
|
entry.adapterSession.status = status;
|
|
568
|
-
|
|
847
|
+
// 关键修复:
|
|
848
|
+
// - thinking 状态:不传递 actionInfo,让前端清除旧的 tool_using action
|
|
849
|
+
// - waiting_input 状态:传递 actionInfo,保留 poll_message 的"监听消息"显示
|
|
850
|
+
this.emit("status-change", event.sessionId, status, status === "thinking" ? undefined : eventData);
|
|
569
851
|
return;
|
|
570
852
|
}
|
|
571
853
|
const status = actionInfo.actionType === "idle" ? "waiting_input" : "tool_using";
|
|
@@ -585,6 +867,24 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
585
867
|
}
|
|
586
868
|
return;
|
|
587
869
|
}
|
|
870
|
+
if (event.type === "exit_plan_mode") {
|
|
871
|
+
// 需求 4.1.1:exit_plan_mode 映射为 status 事件,等待用户审批计划
|
|
872
|
+
entry.adapterSession.status = "waiting_input";
|
|
873
|
+
const planText = typeof event.data.plan === "string"
|
|
874
|
+
? event.data.plan
|
|
875
|
+
: undefined;
|
|
876
|
+
this.emitProviderEvent(event, "exit_plan_mode", {
|
|
877
|
+
toolInput: event.data.toolInput && typeof event.data.toolInput === "object"
|
|
878
|
+
? event.data.toolInput
|
|
879
|
+
: undefined,
|
|
880
|
+
text: planText || "退出计划模式",
|
|
881
|
+
});
|
|
882
|
+
this.emit("status-change", event.sessionId, "waiting_input", {
|
|
883
|
+
actionLabel: "审批计划",
|
|
884
|
+
actionDetail: planText || "等待用户审批退出计划模式",
|
|
885
|
+
});
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
588
888
|
this.emitProviderEvent(event, "thinking", {
|
|
589
889
|
text: JSON.stringify(event.data),
|
|
590
890
|
});
|
|
@@ -639,6 +939,8 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
639
939
|
entry.adapterSession.status = "thinking";
|
|
640
940
|
this.emit("status-change", sessionId, "thinking");
|
|
641
941
|
}
|
|
942
|
+
// 启动 submit 超时检测,防止 auto-poll 请求永久卡住
|
|
943
|
+
this.startSubmitTimeoutDetection(sessionId, entry, "submitAutoPollPrompt");
|
|
642
944
|
void entry.runtime
|
|
643
945
|
.submit(prompt)
|
|
644
946
|
.catch((error) => {
|
|
@@ -648,6 +950,8 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
648
950
|
const latestEntry = this.sessions.get(sessionId);
|
|
649
951
|
if (!latestEntry)
|
|
650
952
|
return;
|
|
953
|
+
// 无论 submit 是正常返回还是超时恢复,都需要取消超时检测(超时回调里已设置 submitTimeoutTimer=undefined,再调 cancel 是安全的)
|
|
954
|
+
this.cancelSubmitTimeoutDetection(latestEntry);
|
|
651
955
|
latestEntry.autoPollActive = false;
|
|
652
956
|
// If the R2 recovery path already scheduled a delayed re-arm for a
|
|
653
957
|
// failed polling-driven turn, skip armIdleLoopOrAutoPoll so we do
|
|
@@ -787,16 +1091,58 @@ export class AcodeSdkAdapter extends EventEmitter {
|
|
|
787
1091
|
* look permanently stuck. Recover to waiting_input and arm the listening loop.
|
|
788
1092
|
*/
|
|
789
1093
|
recoverResolvedTurnWithoutTerminalEvent(sessionId) {
|
|
1094
|
+
console.log(`[ACode] ▶️ recoverResolvedTurnWithoutTerminalEvent called for session ${sessionId}`);
|
|
790
1095
|
const entry = this.sessions.get(sessionId);
|
|
791
1096
|
if (!entry || this.isTerminalStatus(entry.adapterSession.status)) {
|
|
1097
|
+
console.log(`[ACode] Skipping recovery: entry=${!!entry}, isTerminal=${entry ? this.isTerminalStatus(entry.adapterSession.status) : 'N/A'}`);
|
|
792
1098
|
return;
|
|
793
1099
|
}
|
|
1100
|
+
console.log(`[ACode] Current status: ${entry.adapterSession.status}`);
|
|
794
1101
|
if (entry.adapterSession.status !== "thinking" &&
|
|
795
1102
|
entry.adapterSession.status !== "tool_using") {
|
|
1103
|
+
console.log(`[ACode] Skipping recovery: status is ${entry.adapterSession.status} (not thinking/tool_using)`);
|
|
796
1104
|
return;
|
|
797
1105
|
}
|
|
1106
|
+
console.log(`[ACode] ⚠️ Detected stuck state: ${entry.adapterSession.status} after submit resolved!`);
|
|
1107
|
+
console.log(`[ACode] Recovering to waiting_input...`);
|
|
798
1108
|
entry.adapterSession.status = "waiting_input";
|
|
799
1109
|
this.emit("status-change", sessionId, "waiting_input");
|
|
1110
|
+
console.log(`[ACode] ✅ Recovery complete, status now: ${entry.adapterSession.status}`);
|
|
1111
|
+
console.log(`[ACode] ▶️ Arming idle loop or auto-poll...`);
|
|
800
1112
|
this.armIdleLoopOrAutoPoll(sessionId);
|
|
1113
|
+
console.log(`[ACode] ✅ Idle loop armed, recovery process complete`);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* 启动 submit 超时检测(治本修复:防止状态永久卡住)
|
|
1117
|
+
*
|
|
1118
|
+
* 如果 runtime.submit() 在指定时间内未返回,强制触发恢复机制,
|
|
1119
|
+
* 避免会话永久卡在 thinking/tool_using 状态。
|
|
1120
|
+
*/
|
|
1121
|
+
startSubmitTimeoutDetection(sessionId, entry, source) {
|
|
1122
|
+
// 清除之前的超时检测
|
|
1123
|
+
this.cancelSubmitTimeoutDetection(entry);
|
|
1124
|
+
entry.submitStartTime = Date.now();
|
|
1125
|
+
entry.submitTimeoutTimer = setTimeout(() => {
|
|
1126
|
+
const currentEntry = this.sessions.get(sessionId);
|
|
1127
|
+
if (!currentEntry)
|
|
1128
|
+
return;
|
|
1129
|
+
const elapsed = Date.now() - (currentEntry.submitStartTime || 0);
|
|
1130
|
+
console.warn(`[ACode] CRITICAL: submit timeout detected after ${Math.round(elapsed / 1000)}s ` +
|
|
1131
|
+
`(source: ${source}, session: ${sessionId}, status: ${currentEntry.adapterSession.status}). ` +
|
|
1132
|
+
`Forcing recovery to prevent permanent state stuck.`);
|
|
1133
|
+
// 强制恢复到 waiting_input 状态
|
|
1134
|
+
currentEntry.submitTimeoutTimer = undefined;
|
|
1135
|
+
this.recoverTurnFailure(sessionId, currentEntry);
|
|
1136
|
+
}, ACODE_SUBMIT_TIMEOUT_MS);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* 取消 submit 超时检测
|
|
1140
|
+
*/
|
|
1141
|
+
cancelSubmitTimeoutDetection(entry) {
|
|
1142
|
+
if (entry.submitTimeoutTimer) {
|
|
1143
|
+
clearTimeout(entry.submitTimeoutTimer);
|
|
1144
|
+
entry.submitTimeoutTimer = undefined;
|
|
1145
|
+
}
|
|
1146
|
+
entry.submitStartTime = undefined;
|
|
801
1147
|
}
|
|
802
1148
|
}
|