aws-runtime-bridge 1.7.50 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +30 -3
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +427 -105
  4. package/dist/adapter/AcodeSdkAdapter.test.js +1093 -86
  5. package/dist/adapter/ClaudeSdkAdapter.test.js +31 -8
  6. package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
  7. package/dist/adapter/CodexSdkAdapter.js +2 -0
  8. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
  9. package/dist/adapter/OpencodeSdkAdapter.js +2 -0
  10. package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
  11. package/dist/adapter/adapter.test.js +2 -2
  12. package/dist/adapter/idle-poll-loop.d.ts +4 -4
  13. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  14. package/dist/adapter/idle-poll-loop.js +3 -3
  15. package/dist/adapter/idle-poll-loop.test.js +15 -1
  16. package/dist/adapter/types.d.ts +25 -6
  17. package/dist/adapter/types.d.ts.map +1 -1
  18. package/dist/adapter/types.js +221 -110
  19. package/dist/adapter/types.test.js +10 -3
  20. package/dist/routes/instance.d.ts.map +1 -1
  21. package/dist/routes/instance.js +6 -7
  22. package/dist/routes/runtime-binding.js +2 -2
  23. package/dist/routes/terminal.d.ts +20 -5
  24. package/dist/routes/terminal.d.ts.map +1 -1
  25. package/dist/routes/terminal.js +420 -170
  26. package/dist/routes/terminal.test.js +380 -208
  27. package/dist/services/mcp-launch-binding-queue.d.ts +1 -0
  28. package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
  29. package/dist/services/mcp-launch-binding-queue.js +8 -2
  30. package/dist/services/mcp-launch-binding-queue.test.js +47 -4
  31. package/dist/services/session-output.d.ts +10 -6
  32. package/dist/services/session-output.d.ts.map +1 -1
  33. package/dist/services/session-output.js +29 -14
  34. package/dist/services/session-output.test.js +93 -39
  35. package/package/acode/dist/built-in-file-tools.d.ts +1 -1
  36. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  37. package/package/acode/dist/built-in-file-tools.js +388 -50
  38. package/package/acode/dist/runtime.d.ts +11 -3
  39. package/package/acode/dist/runtime.d.ts.map +1 -1
  40. package/package/acode/dist/runtime.js +902 -190
  41. package/package/acode/dist/types.d.ts +18 -5
  42. package/package/acode/dist/types.d.ts.map +1 -1
  43. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  44. package/package/aws-client-agent-mcp/dist/mcp-server.js +3 -2
  45. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  46. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +11 -9
  47. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  48. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
  49. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +1 -0
  50. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
  51. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +2 -0
  52. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  53. package/package/aws-client-agent-mcp/dist/status-reporter.js +31 -3
  54. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  55. package/package/aws-client-agent-mcp/dist/types.d.ts +1 -1
  56. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  57. package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
  58. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
  59. package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -0
  60. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
  61. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -0
  62. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
  63. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { promises as fs } from 'node:fs';
3
- import path from 'node:path';
1
+ import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import { promises as fs } from "node:fs";
4
+ import path from "node:path";
4
5
  function toDisplayPath(targetPath) {
5
- return targetPath.replace(/\\/g, '/');
6
+ return targetPath.replace(/\\/g, "/");
6
7
  }
7
8
  async function pathExists(targetPath) {
8
9
  try {
@@ -28,20 +29,69 @@ function countOccurrences(content, search) {
28
29
  function readStringArg(args, names, label) {
29
30
  for (const name of names) {
30
31
  const value = args[name];
31
- if (typeof value === 'string') {
32
+ if (typeof value === "string") {
32
33
  return value;
33
34
  }
34
35
  }
35
36
  throw new Error(`${label} is required`);
36
37
  }
38
+ function readOptionalStringArg(args, names) {
39
+ for (const name of names) {
40
+ const value = args[name];
41
+ if (typeof value === "string" && value.trim()) {
42
+ return value;
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ function readOptionalPositiveIntegerArg(args, names, fallback) {
48
+ for (const name of names) {
49
+ const value = args[name];
50
+ const numeric = typeof value === "number"
51
+ ? value
52
+ : typeof value === "string"
53
+ ? Number(value)
54
+ : NaN;
55
+ if (Number.isFinite(numeric) && numeric > 0) {
56
+ return Math.floor(numeric);
57
+ }
58
+ }
59
+ return fallback;
60
+ }
37
61
  function ensureInsideWorkspace(workspaceRoot, targetPath) {
38
62
  const resolvedTargetPath = path.resolve(targetPath);
39
63
  const relativePath = path.relative(workspaceRoot, resolvedTargetPath);
40
- if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
64
+ if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
41
65
  throw new Error(`Target path is outside workspace: ${resolvedTargetPath}`);
42
66
  }
43
67
  return resolvedTargetPath;
44
68
  }
69
+ async function resolveWorkspaceDirectory(workingDirectory, rawCwd) {
70
+ const requestedRoot = path.resolve(String(workingDirectory || "").trim());
71
+ const workspaceRoot = await fs.realpath(requestedRoot);
72
+ const workspaceStat = await fs.stat(workspaceRoot);
73
+ if (!workspaceStat.isDirectory()) {
74
+ throw new Error(`Workspace path is not a directory: ${requestedRoot}`);
75
+ }
76
+ if (!rawCwd?.trim()) {
77
+ return { workspaceRoot, cwd: workspaceRoot };
78
+ }
79
+ const requestedCwd = rawCwd.trim();
80
+ const candidatePath = path.isAbsolute(requestedCwd)
81
+ ? path.resolve(requestedCwd)
82
+ : path.resolve(requestedRoot, requestedCwd);
83
+ const requestedRelativePath = path.relative(requestedRoot, candidatePath);
84
+ if (requestedRelativePath.startsWith("..") ||
85
+ path.isAbsolute(requestedRelativePath)) {
86
+ throw new Error(`Command cwd is outside workspace: ${candidatePath}`);
87
+ }
88
+ const realCwd = ensureInsideWorkspace(workspaceRoot, await fs.realpath(candidatePath));
89
+ const cwdStat = await fs.stat(realCwd);
90
+ if (!cwdStat.isDirectory()) {
91
+ throw new Error(`Command cwd is not a directory: ${realCwd}`);
92
+ }
93
+ return { workspaceRoot, cwd: realCwd };
94
+ }
45
95
  async function ensureWritableTarget(workspaceRoot, targetPath) {
46
96
  const realParentPath = await fs.realpath(path.dirname(targetPath));
47
97
  ensureInsideWorkspace(workspaceRoot, realParentPath);
@@ -52,8 +102,10 @@ async function ensureWritableTarget(workspaceRoot, targetPath) {
52
102
  }
53
103
  }
54
104
  catch (error) {
55
- const errorCode = typeof error === 'object' && error !== null ? Reflect.get(error, 'code') : undefined;
56
- if (errorCode !== 'ENOENT') {
105
+ const errorCode = typeof error === "object" && error !== null
106
+ ? Reflect.get(error, "code")
107
+ : undefined;
108
+ if (errorCode !== "ENOENT") {
57
109
  throw error;
58
110
  }
59
111
  }
@@ -63,7 +115,7 @@ async function ensureWritableTarget(workspaceRoot, targetPath) {
63
115
  * 主流程:同时校验请求路径与 realpath 工作区边界,防止绝对路径、`..` 与符号链接目录穿透。
64
116
  */
65
117
  async function resolveWorkspaceTarget(workingDirectory, rawFilePath) {
66
- const requestedRoot = path.resolve(String(workingDirectory || '').trim());
118
+ const requestedRoot = path.resolve(String(workingDirectory || "").trim());
67
119
  const workspaceRoot = await fs.realpath(requestedRoot);
68
120
  const workspaceStat = await fs.stat(workspaceRoot);
69
121
  if (!workspaceStat.isDirectory()) {
@@ -71,13 +123,14 @@ async function resolveWorkspaceTarget(workingDirectory, rawFilePath) {
71
123
  }
72
124
  const normalizedFilePath = rawFilePath.trim();
73
125
  if (!normalizedFilePath) {
74
- throw new Error('file_path is required');
126
+ throw new Error("file_path is required");
75
127
  }
76
128
  const requestedPath = path.isAbsolute(normalizedFilePath)
77
129
  ? path.resolve(normalizedFilePath)
78
130
  : path.resolve(requestedRoot, normalizedFilePath);
79
131
  const requestedRelativePath = path.relative(requestedRoot, requestedPath);
80
- if (requestedRelativePath.startsWith('..') || path.isAbsolute(requestedRelativePath)) {
132
+ if (requestedRelativePath.startsWith("..") ||
133
+ path.isAbsolute(requestedRelativePath)) {
81
134
  throw new Error(`Target path is outside workspace: ${requestedPath}`);
82
135
  }
83
136
  return {
@@ -98,7 +151,7 @@ async function writeFileAtomically(workingDirectory, filePath, content) {
98
151
  await ensureWritableTarget(target.workspaceRoot, target.targetPath);
99
152
  const temporaryPath = path.join(parentPath, `.${path.basename(target.targetPath)}.${process.pid}.${randomUUID()}.tmp`);
100
153
  try {
101
- await fs.writeFile(temporaryPath, content, 'utf-8');
154
+ await fs.writeFile(temporaryPath, content, "utf-8");
102
155
  await fs.rename(temporaryPath, target.targetPath);
103
156
  }
104
157
  catch (error) {
@@ -108,7 +161,7 @@ async function writeFileAtomically(workingDirectory, filePath, content) {
108
161
  return {
109
162
  workspaceRoot: target.workspaceRoot,
110
163
  targetPath: target.targetPath,
111
- bytes: Buffer.byteLength(content, 'utf-8'),
164
+ bytes: Buffer.byteLength(content, "utf-8"),
112
165
  createdParentDirectory,
113
166
  };
114
167
  }
@@ -122,31 +175,239 @@ async function readFileInsideWorkspace(workingDirectory, filePath) {
122
175
  return {
123
176
  workspaceRoot: target.workspaceRoot,
124
177
  targetPath: realTargetPath,
125
- content: await fs.readFile(realTargetPath, 'utf-8'),
178
+ content: await fs.readFile(realTargetPath, "utf-8"),
126
179
  };
127
180
  }
181
+ const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
182
+ const MAX_COMMAND_TIMEOUT_MS = 10 * 60_000;
183
+ const DEFAULT_COMMAND_INACTIVITY_TIMEOUT_MS = 60_000;
184
+ const MAX_COMMAND_INACTIVITY_TIMEOUT_MS = 10 * 60_000;
185
+ const MAX_COMMAND_OUTPUT_BYTES = 128 * 1024;
186
+ const COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT = 100;
187
+ function padDatePart(value) {
188
+ return String(value).padStart(2, "0");
189
+ }
190
+ function countOutputLines(output) {
191
+ if (!output) {
192
+ return 0;
193
+ }
194
+ const normalized = output.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
195
+ const withoutTrailingLineBreak = normalized.endsWith("\n")
196
+ ? normalized.slice(0, -1)
197
+ : normalized;
198
+ return withoutTrailingLineBreak
199
+ ? withoutTrailingLineBreak.split("\n").length
200
+ : 1;
201
+ }
202
+ function buildCommandOutputSpilloverPath(workspaceRoot, now = new Date()) {
203
+ const year = now.getFullYear();
204
+ const month = padDatePart(now.getMonth() + 1);
205
+ const day = padDatePart(now.getDate());
206
+ const hour = padDatePart(now.getHours());
207
+ const minute = padDatePart(now.getMinutes());
208
+ const timestamp = `${year}${month}${day}-${hour}${minute}${padDatePart(now.getSeconds())}-${String(now.getMilliseconds()).padStart(3, "0")}-${process.pid}-${randomUUID().slice(0, 8)}`;
209
+ return path.join(workspaceRoot, ".agentswork", "acode", "cmd-dist", `${year}-${month}-${day}`, hour, minute, `${timestamp}-cmd.out`);
210
+ }
211
+ async function writeCommandOutputSpilloverFile(workspaceRoot, content) {
212
+ const targetPath = buildCommandOutputSpilloverPath(workspaceRoot);
213
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
214
+ await fs.writeFile(targetPath, content, "utf-8");
215
+ return targetPath;
216
+ }
217
+ function buildCommandOutputSpilloverContent(input) {
218
+ return [
219
+ `Command: ${input.commandText}`,
220
+ `CWD: ${input.cwd}`,
221
+ `Started: ${input.startedAt.toISOString()}`,
222
+ `Ended: ${input.endedAt.toISOString()}`,
223
+ `Exit code: ${input.exitCode ?? "null"}`,
224
+ `Signal: ${input.signal ?? "null"}`,
225
+ `Timed out: ${input.timedOut}`,
226
+ `Timed out by inactivity: ${input.inactivityTimedOut}`,
227
+ "",
228
+ "--- stdout ---",
229
+ input.stdout,
230
+ "",
231
+ "--- stderr ---",
232
+ input.stderr,
233
+ ].join("\n");
234
+ }
235
+ function formatCommandOutputSpilloverStdout(outputFilePath, outputLineCount) {
236
+ return [
237
+ `[ACode] 命令输出超过 ${COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT} 行(共 ${outputLineCount} 行),完整输出已写入: ${toDisplayPath(outputFilePath)}`,
238
+ "[ACode] Agent 可自行读取该文件,或在该文件中搜索需要的内容。",
239
+ "",
240
+ ].join("\n");
241
+ }
242
+ function truncateOutput(value) {
243
+ const bytes = Buffer.byteLength(value, "utf-8");
244
+ if (bytes <= MAX_COMMAND_OUTPUT_BYTES) {
245
+ return { text: value, truncated: false };
246
+ }
247
+ const buffer = Buffer.from(value, "utf-8");
248
+ return {
249
+ text: buffer.subarray(0, MAX_COMMAND_OUTPUT_BYTES).toString("utf-8") +
250
+ `\n...[truncated after ${MAX_COMMAND_OUTPUT_BYTES} bytes]`,
251
+ truncated: true,
252
+ };
253
+ }
254
+ function shellExecutable() {
255
+ if (process.platform === "win32") {
256
+ return {
257
+ command: process.env.ComSpec || "cmd.exe",
258
+ argsPrefix: ["/d", "/s", "/c"],
259
+ };
260
+ }
261
+ return { command: process.env.SHELL || "/bin/sh", argsPrefix: ["-lc"] };
262
+ }
263
+ function runShellCommand(commandText, workspaceRoot, cwd, timeoutMs, inactivityTimeoutMs) {
264
+ return new Promise((resolve, reject) => {
265
+ const shell = shellExecutable();
266
+ const startedAt = new Date();
267
+ const child = spawn(shell.command, [...shell.argsPrefix, commandText], {
268
+ cwd,
269
+ env: process.env,
270
+ windowsHide: true,
271
+ });
272
+ let stdout = "";
273
+ let stderr = "";
274
+ let timedOut = false;
275
+ let inactivityTimedOut = false;
276
+ let killEscalationTimer;
277
+ let inactivityTimer;
278
+ const escalateKill = () => {
279
+ killEscalationTimer = setTimeout(() => {
280
+ if (!child.killed) {
281
+ child.kill("SIGKILL");
282
+ }
283
+ }, 1000);
284
+ killEscalationTimer.unref?.();
285
+ };
286
+ const timer = setTimeout(() => {
287
+ timedOut = true;
288
+ child.kill("SIGTERM");
289
+ escalateKill();
290
+ }, timeoutMs);
291
+ timer.unref?.();
292
+ const resetInactivityTimer = () => {
293
+ if (inactivityTimer) {
294
+ clearTimeout(inactivityTimer);
295
+ }
296
+ inactivityTimer = setTimeout(() => {
297
+ inactivityTimedOut = true;
298
+ timedOut = true;
299
+ child.kill("SIGTERM");
300
+ escalateKill();
301
+ }, inactivityTimeoutMs);
302
+ inactivityTimer.unref?.();
303
+ };
304
+ resetInactivityTimer();
305
+ child.stdout?.setEncoding("utf-8");
306
+ child.stderr?.setEncoding("utf-8");
307
+ child.stdout?.on("data", (chunk) => {
308
+ stdout += chunk;
309
+ resetInactivityTimer();
310
+ });
311
+ child.stderr?.on("data", (chunk) => {
312
+ stderr += chunk;
313
+ resetInactivityTimer();
314
+ });
315
+ child.on("error", (error) => {
316
+ clearTimeout(timer);
317
+ if (inactivityTimer) {
318
+ clearTimeout(inactivityTimer);
319
+ }
320
+ if (killEscalationTimer) {
321
+ clearTimeout(killEscalationTimer);
322
+ }
323
+ reject(error);
324
+ });
325
+ child.on("close", async (exitCode, signal) => {
326
+ clearTimeout(timer);
327
+ if (inactivityTimer) {
328
+ clearTimeout(inactivityTimer);
329
+ }
330
+ if (killEscalationTimer) {
331
+ clearTimeout(killEscalationTimer);
332
+ }
333
+ const endedAt = new Date();
334
+ const outputLineCount = countOutputLines(`${stdout}${stderr}`);
335
+ try {
336
+ if (outputLineCount > COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
337
+ const outputFilePath = await writeCommandOutputSpilloverFile(workspaceRoot, buildCommandOutputSpilloverContent({
338
+ commandText,
339
+ cwd,
340
+ startedAt,
341
+ endedAt,
342
+ exitCode,
343
+ signal,
344
+ timedOut,
345
+ inactivityTimedOut,
346
+ stdout,
347
+ stderr,
348
+ }));
349
+ resolve({
350
+ exitCode,
351
+ signal,
352
+ stdout: formatCommandOutputSpilloverStdout(outputFilePath, outputLineCount),
353
+ stderr: "",
354
+ durationMs: endedAt.getTime() - startedAt.getTime(),
355
+ timedOut,
356
+ inactivityTimedOut,
357
+ outputSpilled: true,
358
+ outputFilePath: toDisplayPath(outputFilePath),
359
+ outputLineCount,
360
+ });
361
+ return;
362
+ }
363
+ const truncatedStdout = truncateOutput(stdout);
364
+ const truncatedStderr = truncateOutput(stderr);
365
+ resolve({
366
+ exitCode,
367
+ signal,
368
+ stdout: truncatedStdout.text,
369
+ stderr: truncatedStderr.text,
370
+ durationMs: endedAt.getTime() - startedAt.getTime(),
371
+ timedOut,
372
+ inactivityTimedOut,
373
+ outputSpilled: false,
374
+ outputLineCount,
375
+ });
376
+ }
377
+ catch (error) {
378
+ reject(error);
379
+ }
380
+ });
381
+ });
382
+ }
128
383
  function writeFileTool(workingDirectory) {
129
384
  return {
130
- kind: 'builtin',
131
- toolName: 'write_file',
132
- exposedName: 'write_file',
133
- description: 'Create or overwrite a UTF-8 text file inside the current workspace. Parent directories are created automatically.',
385
+ kind: "builtin",
386
+ toolName: "write_file",
387
+ exposedName: "write_file",
388
+ description: "Create or overwrite a UTF-8 text file inside the current workspace. Parent directories are created automatically.",
134
389
  inputSchema: {
135
- type: 'object',
390
+ type: "object",
136
391
  properties: {
137
- file_path: { type: 'string', description: 'Workspace-relative file path to write.' },
138
- content: { type: 'string', description: 'Complete UTF-8 file content.' },
392
+ file_path: {
393
+ type: "string",
394
+ description: "Workspace-relative file path to write.",
395
+ },
396
+ content: {
397
+ type: "string",
398
+ description: "Complete UTF-8 file content.",
399
+ },
139
400
  },
140
- required: ['file_path', 'content'],
401
+ required: ["file_path", "content"],
141
402
  additionalProperties: false,
142
403
  },
143
404
  async execute(args) {
144
- const filePath = readStringArg(args, ['file_path', 'filePath'], 'file_path');
145
- const content = readStringArg(args, ['content'], 'content');
405
+ const filePath = readStringArg(args, ["file_path", "filePath"], "file_path");
406
+ const content = readStringArg(args, ["content"], "content");
146
407
  const result = await writeFileAtomically(workingDirectory, filePath, content);
147
408
  return {
148
409
  ok: true,
149
- tool: 'write_file',
410
+ tool: "write_file",
150
411
  workspacePath: toDisplayPath(result.workspaceRoot),
151
412
  filePath: toDisplayPath(result.targetPath),
152
413
  bytes: result.bytes,
@@ -157,31 +418,37 @@ function writeFileTool(workingDirectory) {
157
418
  }
158
419
  function editFileTool(workingDirectory) {
159
420
  return {
160
- kind: 'builtin',
161
- toolName: 'edit_file',
162
- exposedName: 'edit_file',
163
- description: 'Replace exactly one occurrence of old_string with new_string in a UTF-8 workspace text file.',
421
+ kind: "builtin",
422
+ toolName: "edit_file",
423
+ exposedName: "edit_file",
424
+ description: "Replace exactly one occurrence of old_string with new_string in a UTF-8 workspace text file.",
164
425
  inputSchema: {
165
- type: 'object',
426
+ type: "object",
166
427
  properties: {
167
- file_path: { type: 'string', description: 'Workspace-relative file path to edit.' },
168
- old_string: { type: 'string', description: 'Existing text to replace. Must occur exactly once.' },
169
- new_string: { type: 'string', description: 'Replacement text.' },
428
+ file_path: {
429
+ type: "string",
430
+ description: "Workspace-relative file path to edit.",
431
+ },
432
+ old_string: {
433
+ type: "string",
434
+ description: "Existing text to replace. Must occur exactly once.",
435
+ },
436
+ new_string: { type: "string", description: "Replacement text." },
170
437
  },
171
- required: ['file_path', 'old_string', 'new_string'],
438
+ required: ["file_path", "old_string", "new_string"],
172
439
  additionalProperties: false,
173
440
  },
174
441
  async execute(args) {
175
- const filePath = readStringArg(args, ['file_path', 'filePath'], 'file_path');
176
- const oldString = readStringArg(args, ['old_string', 'oldString'], 'old_string');
177
- const newString = readStringArg(args, ['new_string', 'newString'], 'new_string');
442
+ const filePath = readStringArg(args, ["file_path", "filePath"], "file_path");
443
+ const oldString = readStringArg(args, ["old_string", "oldString"], "old_string");
444
+ const newString = readStringArg(args, ["new_string", "newString"], "new_string");
178
445
  if (!oldString) {
179
- throw new Error('old_string must not be empty');
446
+ throw new Error("old_string must not be empty");
180
447
  }
181
448
  const current = await readFileInsideWorkspace(workingDirectory, filePath);
182
449
  const occurrences = countOccurrences(current.content, oldString);
183
450
  if (occurrences === 0) {
184
- throw new Error('old_string was not found in file');
451
+ throw new Error("old_string was not found in file");
185
452
  }
186
453
  if (occurrences > 1) {
187
454
  throw new Error(`old_string occurs ${occurrences} times; provide a unique match`);
@@ -190,7 +457,7 @@ function editFileTool(workingDirectory) {
190
457
  const result = await writeFileAtomically(workingDirectory, filePath, updatedContent);
191
458
  return {
192
459
  ok: true,
193
- tool: 'edit_file',
460
+ tool: "edit_file",
194
461
  workspacePath: toDisplayPath(result.workspaceRoot),
195
462
  filePath: toDisplayPath(result.targetPath),
196
463
  replacements: 1,
@@ -201,24 +468,27 @@ function editFileTool(workingDirectory) {
201
468
  }
202
469
  function readFileTool(workingDirectory) {
203
470
  return {
204
- kind: 'builtin',
205
- toolName: 'read_file',
206
- exposedName: 'read_file',
207
- description: 'Read a UTF-8 text file inside the current workspace.',
471
+ kind: "builtin",
472
+ toolName: "read_file",
473
+ exposedName: "read_file",
474
+ description: "Read a UTF-8 text file inside the current workspace.",
208
475
  inputSchema: {
209
- type: 'object',
476
+ type: "object",
210
477
  properties: {
211
- file_path: { type: 'string', description: 'Workspace-relative file path to read.' },
478
+ file_path: {
479
+ type: "string",
480
+ description: "Workspace-relative file path to read.",
481
+ },
212
482
  },
213
- required: ['file_path'],
483
+ required: ["file_path"],
214
484
  additionalProperties: false,
215
485
  },
216
486
  async execute(args) {
217
- const filePath = readStringArg(args, ['file_path', 'filePath'], 'file_path');
487
+ const filePath = readStringArg(args, ["file_path", "filePath"], "file_path");
218
488
  const result = await readFileInsideWorkspace(workingDirectory, filePath);
219
489
  return {
220
490
  ok: true,
221
- tool: 'read_file',
491
+ tool: "read_file",
222
492
  workspacePath: toDisplayPath(result.workspaceRoot),
223
493
  filePath: toDisplayPath(result.targetPath),
224
494
  content: result.content,
@@ -226,6 +496,74 @@ function readFileTool(workingDirectory) {
226
496
  },
227
497
  };
228
498
  }
499
+ function bashTool(workingDirectory) {
500
+ return {
501
+ kind: "builtin",
502
+ toolName: "Bash",
503
+ exposedName: "Bash",
504
+ description: "Execute a shell command in the current workspace. Use for local diagnostics, builds, tests, and OS commands. The command runs with a bounded timeout and returns stdout, stderr, and exit code.",
505
+ inputSchema: {
506
+ type: "object",
507
+ properties: {
508
+ command: { type: "string", description: "Shell command to execute." },
509
+ cwd: {
510
+ type: "string",
511
+ description: "Optional workspace-relative working directory. Defaults to the workspace root.",
512
+ },
513
+ timeout_ms: {
514
+ type: "number",
515
+ description: "Optional timeout in milliseconds. Defaults to 30000 and is capped at 600000.",
516
+ },
517
+ inactivity_timeout_ms: {
518
+ type: "number",
519
+ description: "Optional no-output timeout in milliseconds. Defaults to 60000 and is capped at 600000.",
520
+ },
521
+ description: {
522
+ type: "string",
523
+ description: "Optional short reason for running the command.",
524
+ },
525
+ },
526
+ required: ["command"],
527
+ additionalProperties: false,
528
+ },
529
+ async execute(args) {
530
+ const command = readStringArg(args, ["command"], "command").trim();
531
+ if (!command) {
532
+ throw new Error("command must not be empty");
533
+ }
534
+ const requestedTimeoutMs = readOptionalPositiveIntegerArg(args, ["timeout_ms", "timeoutMs"], DEFAULT_COMMAND_TIMEOUT_MS);
535
+ const timeoutMs = Math.min(requestedTimeoutMs, MAX_COMMAND_TIMEOUT_MS);
536
+ const requestedInactivityTimeoutMs = readOptionalPositiveIntegerArg(args, ["inactivity_timeout_ms", "inactivityTimeoutMs"], DEFAULT_COMMAND_INACTIVITY_TIMEOUT_MS);
537
+ const inactivityTimeoutMs = Math.min(requestedInactivityTimeoutMs, MAX_COMMAND_INACTIVITY_TIMEOUT_MS);
538
+ const cwdArg = readOptionalStringArg(args, ["cwd"]);
539
+ const resolved = await resolveWorkspaceDirectory(workingDirectory, cwdArg);
540
+ const result = await runShellCommand(command, resolved.workspaceRoot, resolved.cwd, timeoutMs, inactivityTimeoutMs);
541
+ return {
542
+ ok: result.exitCode === 0 && !result.timedOut,
543
+ tool: "Bash",
544
+ command,
545
+ cwd: toDisplayPath(resolved.cwd),
546
+ exitCode: result.exitCode,
547
+ signal: result.signal,
548
+ timedOut: result.timedOut,
549
+ inactivityTimedOut: result.inactivityTimedOut,
550
+ timeoutMs,
551
+ inactivityTimeoutMs,
552
+ durationMs: result.durationMs,
553
+ outputSpilled: result.outputSpilled,
554
+ outputFilePath: result.outputFilePath,
555
+ outputLineCount: result.outputLineCount,
556
+ stdout: result.stdout,
557
+ stderr: result.stderr,
558
+ };
559
+ },
560
+ };
561
+ }
229
562
  export function createBuiltInFileTools(workingDirectory) {
230
- return [readFileTool(workingDirectory), writeFileTool(workingDirectory), editFileTool(workingDirectory)];
563
+ return [
564
+ readFileTool(workingDirectory),
565
+ writeFileTool(workingDirectory),
566
+ editFileTool(workingDirectory),
567
+ bashTool(workingDirectory),
568
+ ];
231
569
  }
@@ -1,10 +1,14 @@
1
- import { EventEmitter } from 'node:events';
2
- import type { ACodeRuntimeConfig } from './types.js';
1
+ import { EventEmitter } from "node:events";
2
+ import type { ACodeRuntimeConfig } from "./types.js";
3
3
  export declare class ACodeRuntime extends EventEmitter {
4
4
  private readonly config;
5
5
  private readonly hookBus;
6
6
  private state;
7
7
  private abortController;
8
+ private submitQueue;
9
+ private activeTurn;
10
+ private abortRequested;
11
+ private sessionCompleteEmitted;
8
12
  constructor(config: ACodeRuntimeConfig);
9
13
  private emitAcodeEvent;
10
14
  /**
@@ -19,14 +23,18 @@ export declare class ACodeRuntime extends EventEmitter {
19
23
  * MCP tool call 则执行工具并继续同一轮,最后输出 assistant 文本与 usage。
20
24
  */
21
25
  submit(message: string): Promise<void>;
22
- /** 中止当前轮次;当前骨架无长任务,仅发出完成状态。 */
26
+ private executeSubmit;
27
+ /** 中止当前轮次;由执行轮次统一发出一个终态事件,避免 aborted/failed 双终态。 */
23
28
  abort(): Promise<void>;
24
29
  /** 终止 ACode 会话并发出 session_complete。 */
25
30
  stop(): Promise<void>;
26
31
  private resolveCommandDirs;
27
32
  private runModelTurn;
28
33
  private requestChatCompletion;
34
+ private readChatCompletionStream;
29
35
  private handleToolCalls;
36
+ private appendSyntheticMultimodalUserMessage;
37
+ private appendToolFailureResult;
30
38
  private connectMcpServers;
31
39
  private createMcpTransport;
32
40
  private disconnectMcpServers;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAa3C,OAAO,KAAK,EAOV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA6PpB,qBAAa,YAAa,SAAQ,YAAY;IAmBhC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAlBnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,KAAK,CAYX;IAEF,OAAO,CAAC,eAAe,CAAgC;gBAE1B,MAAM,EAAE,kBAAkB;IAevD,OAAO,CAAC,cAAc;IAUtB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuE5B;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+C5C,+BAA+B;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,uCAAuC;IACjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,OAAO,CAAC,kBAAkB;YASZ,YAAY;YA6BZ,qBAAqB;YA+BrB,eAAe;YAiFf,iBAAiB;IA8B/B,OAAO,CAAC,kBAAkB;YAuBZ,oBAAoB;CAYnC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB3C,OAAO,KAAK,EAOV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AA44BpB,qBAAa,YAAa,SAAQ,YAAY;IAmChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAlCnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,KAAK,CAoBX;IAEF,OAAO,CAAC,eAAe,CAAgC;IAEvD,OAAO,CAAC,WAAW,CAAoC;IAEvD,OAAO,CAAC,UAAU,CAAS;IAE3B,OAAO,CAAC,cAAc,CAAS;IAE/B,OAAO,CAAC,sBAAsB,CAAS;gBAEV,MAAM,EAAE,kBAAkB;IAevD,OAAO,CAAC,cAAc;IAatB;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwF5B;;;;OAIG;IACG,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAM9B,aAAa;IA2E3B,oDAAoD;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,uCAAuC;IACjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B3B,OAAO,CAAC,kBAAkB;YAaZ,YAAY;YA6DZ,qBAAqB;YAiHrB,wBAAwB;YA6ExB,eAAe;YAwJf,oCAAoC;IAkBlD,OAAO,CAAC,uBAAuB;YAuBjB,iBAAiB;IA2C/B,OAAO,CAAC,kBAAkB;YA2BZ,oBAAoB;CAiBnC"}