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.
Files changed (121) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +40 -0
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +353 -7
  4. package/dist/adapter/AcodeSdkAdapter.test.js +155 -8
  5. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  6. package/dist/adapter/OpencodeSdkAdapter.js +36 -3
  7. package/dist/adapter/instance-ai-config-resolver.d.ts +49 -0
  8. package/dist/adapter/instance-ai-config-resolver.d.ts.map +1 -0
  9. package/dist/adapter/instance-ai-config-resolver.js +46 -0
  10. package/dist/adapter/types.d.ts +27 -2
  11. package/dist/adapter/types.d.ts.map +1 -1
  12. package/dist/index.js +5 -1
  13. package/dist/routes/dashboard.d.ts.map +1 -1
  14. package/dist/routes/dashboard.js +749 -15
  15. package/dist/routes/events.d.ts +7 -0
  16. package/dist/routes/events.d.ts.map +1 -0
  17. package/dist/routes/events.js +22 -0
  18. package/dist/routes/file-browser.d.ts +1 -1
  19. package/dist/routes/file-browser.d.ts.map +1 -1
  20. package/dist/routes/file-browser.js +25 -9
  21. package/dist/routes/file-snapshot.d.ts +10 -0
  22. package/dist/routes/file-snapshot.d.ts.map +1 -0
  23. package/dist/routes/file-snapshot.js +156 -0
  24. package/dist/routes/git.d.ts +10 -0
  25. package/dist/routes/git.d.ts.map +1 -1
  26. package/dist/routes/git.js +305 -2
  27. package/dist/routes/git.test.js +39 -2
  28. package/dist/routes/instance.js +3 -1
  29. package/dist/routes/memory.d.ts +3 -0
  30. package/dist/routes/memory.d.ts.map +1 -1
  31. package/dist/routes/memory.js +3 -0
  32. package/dist/routes/terminal.d.ts +2 -1
  33. package/dist/routes/terminal.d.ts.map +1 -1
  34. package/dist/routes/terminal.js +378 -41
  35. package/dist/routes/terminal.test.js +33 -7
  36. package/dist/services/agent-event-store.d.ts +59 -0
  37. package/dist/services/agent-event-store.d.ts.map +1 -0
  38. package/dist/services/agent-event-store.js +68 -0
  39. package/dist/services/event-delta-pusher.d.ts +40 -0
  40. package/dist/services/event-delta-pusher.d.ts.map +1 -0
  41. package/dist/services/event-delta-pusher.js +95 -0
  42. package/dist/services/fileSnapshotStore.d.ts +96 -0
  43. package/dist/services/fileSnapshotStore.d.ts.map +1 -0
  44. package/dist/services/fileSnapshotStore.js +500 -0
  45. package/dist/services/instance-ai-config-service.d.ts +49 -0
  46. package/dist/services/instance-ai-config-service.d.ts.map +1 -0
  47. package/dist/services/instance-ai-config-service.js +174 -0
  48. package/dist/services/panel-auth.d.ts +8 -0
  49. package/dist/services/panel-auth.d.ts.map +1 -1
  50. package/dist/services/panel-auth.js +8 -0
  51. package/dist/services/ring-buffer.d.ts +67 -0
  52. package/dist/services/ring-buffer.d.ts.map +1 -0
  53. package/dist/services/ring-buffer.js +103 -0
  54. package/dist/services/session-output.d.ts +47 -3
  55. package/dist/services/session-output.d.ts.map +1 -1
  56. package/dist/services/session-output.js +112 -2
  57. package/dist/utils/instance-ai-config-debug-logger.d.ts +37 -0
  58. package/dist/utils/instance-ai-config-debug-logger.d.ts.map +1 -0
  59. package/dist/utils/instance-ai-config-debug-logger.js +63 -0
  60. package/dist/utils/logger.d.ts +18 -0
  61. package/dist/utils/logger.d.ts.map +1 -1
  62. package/dist/utils/logger.js +48 -4
  63. package/package/acode/dist/built-in-file-tools.d.ts +1 -1
  64. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  65. package/package/acode/dist/built-in-file-tools.js +5 -3
  66. package/package/acode/dist/builtins/commandArtifacts.d.ts +8 -1
  67. package/package/acode/dist/builtins/commandArtifacts.d.ts.map +1 -1
  68. package/package/acode/dist/builtins/commandArtifacts.js +24 -4
  69. package/package/acode/dist/builtins/shellCommandResult.d.ts +1 -0
  70. package/package/acode/dist/builtins/shellCommandResult.d.ts.map +1 -1
  71. package/package/acode/dist/builtins/shellCommandResult.js +3 -3
  72. package/package/acode/dist/builtins/shellCommandRunner.d.ts +1 -0
  73. package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +1 -1
  74. package/package/acode/dist/builtins/shellCommandRunner.js +1 -0
  75. package/package/acode/dist/cli.js +2 -0
  76. package/package/acode/dist/runtime.d.ts.map +1 -1
  77. package/package/acode/dist/runtime.js +156 -5
  78. package/package/acode/dist/types.d.ts +1 -0
  79. package/package/acode/dist/types.d.ts.map +1 -1
  80. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +5 -0
  81. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +1 -1
  82. package/package/aws-client-agent-mcp/dist/agent-client.js +16 -0
  83. package/package/aws-client-agent-mcp/dist/agent-client.js.map +1 -1
  84. package/package/aws-client-agent-mcp/dist/context-manager.d.ts +136 -0
  85. package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +1 -0
  86. package/package/aws-client-agent-mcp/dist/context-manager.js +473 -0
  87. package/package/aws-client-agent-mcp/dist/context-manager.js.map +1 -0
  88. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +6 -0
  89. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +1 -0
  90. package/package/aws-client-agent-mcp/dist/context-manager.test.js +307 -0
  91. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -0
  92. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +170 -2
  93. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  94. package/package/aws-client-agent-mcp/dist/mcp-server.js +947 -9
  95. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  96. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +103 -1
  97. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  98. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +15 -1
  99. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  100. package/package/aws-client-agent-mcp/dist/mcp-tools.js +62 -2
  101. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  102. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +46 -1
  103. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
  104. package/package/aws-client-agent-mcp/dist/memory-store.js +164 -12
  105. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
  106. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +8 -0
  107. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
  108. package/package/aws-client-agent-mcp/dist/memory-tools.js +88 -1
  109. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
  110. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +7 -1
  111. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
  112. package/package/aws-client-agent-mcp/dist/server-memory-store.js +18 -0
  113. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
  114. package/package/aws-client-agent-mcp/dist/types.d.ts +103 -2
  115. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  116. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  117. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +27 -2
  118. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  119. package/package/aws-client-agent-mcp/dist/websocket-client.js +122 -5
  120. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  121. 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":"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"}
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
- async createRuntime(sessionId, config, runtimeHome) {
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: config.mcpConfigPath || getAcodeConfigFile(runtimeHome),
535
+ configPath,
289
536
  skillsDir: getAcodeSkillsDir(runtimeHome),
290
- model: config.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
- this.emit("status-change", event.sessionId, status);
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
  }