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.
- package/dist/adapter/AcodeSdkAdapter.d.ts +30 -3
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +427 -105
- package/dist/adapter/AcodeSdkAdapter.test.js +1093 -86
- package/dist/adapter/ClaudeSdkAdapter.test.js +31 -8
- package/dist/adapter/CodexSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/CodexSdkAdapter.js +2 -0
- package/dist/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +2 -0
- package/dist/adapter/SdkProviderSpi.d.ts.map +1 -1
- package/dist/adapter/adapter.test.js +2 -2
- package/dist/adapter/idle-poll-loop.d.ts +4 -4
- package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
- package/dist/adapter/idle-poll-loop.js +3 -3
- package/dist/adapter/idle-poll-loop.test.js +15 -1
- package/dist/adapter/types.d.ts +25 -6
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +221 -110
- package/dist/adapter/types.test.js +10 -3
- package/dist/routes/instance.d.ts.map +1 -1
- package/dist/routes/instance.js +6 -7
- package/dist/routes/runtime-binding.js +2 -2
- package/dist/routes/terminal.d.ts +20 -5
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +420 -170
- package/dist/routes/terminal.test.js +380 -208
- package/dist/services/mcp-launch-binding-queue.d.ts +1 -0
- package/dist/services/mcp-launch-binding-queue.d.ts.map +1 -1
- package/dist/services/mcp-launch-binding-queue.js +8 -2
- package/dist/services/mcp-launch-binding-queue.test.js +47 -4
- package/dist/services/session-output.d.ts +10 -6
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +29 -14
- package/dist/services/session-output.test.js +93 -39
- package/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 +388 -50
- package/package/acode/dist/runtime.d.ts +11 -3
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +902 -190
- package/package/acode/dist/types.d.ts +18 -5
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +3 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +11 -9
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +1 -0
- package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +31 -3
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +43 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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 ===
|
|
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(
|
|
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 ===
|
|
56
|
-
|
|
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 ||
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
131
|
-
toolName:
|
|
132
|
-
exposedName:
|
|
133
|
-
description:
|
|
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:
|
|
390
|
+
type: "object",
|
|
136
391
|
properties: {
|
|
137
|
-
file_path: {
|
|
138
|
-
|
|
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: [
|
|
401
|
+
required: ["file_path", "content"],
|
|
141
402
|
additionalProperties: false,
|
|
142
403
|
},
|
|
143
404
|
async execute(args) {
|
|
144
|
-
const filePath = readStringArg(args, [
|
|
145
|
-
const content = readStringArg(args, [
|
|
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:
|
|
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:
|
|
161
|
-
toolName:
|
|
162
|
-
exposedName:
|
|
163
|
-
description:
|
|
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:
|
|
426
|
+
type: "object",
|
|
166
427
|
properties: {
|
|
167
|
-
file_path: {
|
|
168
|
-
|
|
169
|
-
|
|
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: [
|
|
438
|
+
required: ["file_path", "old_string", "new_string"],
|
|
172
439
|
additionalProperties: false,
|
|
173
440
|
},
|
|
174
441
|
async execute(args) {
|
|
175
|
-
const filePath = readStringArg(args, [
|
|
176
|
-
const oldString = readStringArg(args, [
|
|
177
|
-
const newString = readStringArg(args, [
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
205
|
-
toolName:
|
|
206
|
-
exposedName:
|
|
207
|
-
description:
|
|
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:
|
|
476
|
+
type: "object",
|
|
210
477
|
properties: {
|
|
211
|
-
file_path: {
|
|
478
|
+
file_path: {
|
|
479
|
+
type: "string",
|
|
480
|
+
description: "Workspace-relative file path to read.",
|
|
481
|
+
},
|
|
212
482
|
},
|
|
213
|
-
required: [
|
|
483
|
+
required: ["file_path"],
|
|
214
484
|
additionalProperties: false,
|
|
215
485
|
},
|
|
216
486
|
async execute(args) {
|
|
217
|
-
const filePath = readStringArg(args, [
|
|
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:
|
|
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 [
|
|
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
|
|
2
|
-
import type { ACodeRuntimeConfig } from
|
|
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;
|
|
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"}
|