@linghun/tools 0.1.2 → 0.1.4
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/{chunk-VZWMPUSS.js → chunk-2DIXRINW.js} +1 -1
- package/dist/{chunk-2R75FXZG.js → chunk-6BBM4UX3.js} +13 -13
- package/dist/chunk-FI4YPDFE.js +38 -0
- package/dist/chunk-PKMIDA5A.js +6 -0
- package/dist/{chunk-DWXJBDPQ.js → chunk-ZGLZZKO6.js} +13 -13
- package/dist/index.d.ts +76 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +793 -23
- package/dist/tools/Bash/BashTool.d.ts +1 -1
- package/dist/tools/Bash/BashTool.js +2 -2
- package/dist/tools/Bash/command-semantics.d.ts +11 -0
- package/dist/tools/Bash/command-semantics.d.ts.map +1 -0
- package/dist/tools/Bash/command-semantics.js +6 -0
- package/dist/tools/Bash/prompt.d.ts +1 -1
- package/dist/tools/Bash/prompt.d.ts.map +1 -1
- package/dist/tools/Bash/prompt.js +1 -1
- package/dist/tools/MultiEdit/MultiEditTool.js +2 -2
- package/dist/tools/Todo/TodoTool.js +2 -2
- package/dist/tools/prompts.js +16 -16
- package/dist/tools/ui.js +16 -16
- package/package.json +6 -7
- package/LICENSE +0 -190
- package/dist/chunk-E75PYM7V.js +0 -6
- package/dist/{chunk-VJDLOFCF.js → chunk-3NZ3IOPM.js} +3 -3
- package/dist/{chunk-MFHY7PBM.js → chunk-4567YAXM.js} +3 -3
package/dist/index.js
CHANGED
|
@@ -12,51 +12,55 @@ import {
|
|
|
12
12
|
} from "./chunk-MMJX44V6.js";
|
|
13
13
|
import {
|
|
14
14
|
toolPrompts
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ZGLZZKO6.js";
|
|
16
16
|
import {
|
|
17
17
|
toolUserFacingNames
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-6BBM4UX3.js";
|
|
19
|
+
import "./chunk-4EZHIDZ6.js";
|
|
19
20
|
import "./chunk-I3Z4OKMS.js";
|
|
20
21
|
import "./chunk-PS2ZOFZ6.js";
|
|
21
22
|
import "./chunk-NPKLGVDQ.js";
|
|
22
23
|
import "./chunk-LGZOG7IQ.js";
|
|
23
24
|
import "./chunk-ZYAEUABC.js";
|
|
24
25
|
import "./chunk-SWK6XRZQ.js";
|
|
25
|
-
import "./chunk-4EZHIDZ6.js";
|
|
26
26
|
import "./chunk-YR4HWOR2.js";
|
|
27
27
|
import "./chunk-VAFW24RN.js";
|
|
28
|
+
import "./chunk-TZ6QK52L.js";
|
|
29
|
+
import "./chunk-K3YDFDLP.js";
|
|
28
30
|
import "./chunk-GI5XI4HN.js";
|
|
29
31
|
import "./chunk-WEWXKRKL.js";
|
|
30
32
|
import "./chunk-A6FUAM64.js";
|
|
31
33
|
import "./chunk-YCO6C7KF.js";
|
|
32
|
-
import "./chunk-
|
|
33
|
-
import "./chunk-SZFZPQGL.js";
|
|
34
|
+
import "./chunk-4567YAXM.js";
|
|
34
35
|
import "./chunk-XZWSO5CM.js";
|
|
36
|
+
import "./chunk-SZFZPQGL.js";
|
|
35
37
|
import "./chunk-AZCQL24J.js";
|
|
36
38
|
import "./chunk-HUIZOB4A.js";
|
|
37
39
|
import "./chunk-MK3YP2VR.js";
|
|
38
|
-
import "./chunk-VJDLOFCF.js";
|
|
39
|
-
import "./chunk-3RP665TH.js";
|
|
40
|
-
import "./chunk-IOC3BSPT.js";
|
|
41
|
-
import "./chunk-TZ6QK52L.js";
|
|
42
|
-
import "./chunk-K3YDFDLP.js";
|
|
43
|
-
import "./chunk-HNIVNZIS.js";
|
|
44
|
-
import "./chunk-DN3KJZRV.js";
|
|
45
|
-
import "./chunk-2U4BZXAI.js";
|
|
46
|
-
import "./chunk-ITBZHMZQ.js";
|
|
47
40
|
import "./chunk-WLQ7DGMG.js";
|
|
48
41
|
import "./chunk-SQP6SJRP.js";
|
|
49
42
|
import "./chunk-BRG64AEC.js";
|
|
50
|
-
import "./chunk-
|
|
51
|
-
import "./chunk-E75PYM7V.js";
|
|
52
|
-
import "./chunk-GAWMEMXB.js";
|
|
43
|
+
import "./chunk-HNIVNZIS.js";
|
|
53
44
|
import "./chunk-7ZOADACP.js";
|
|
54
45
|
import "./chunk-H5OOD2OS.js";
|
|
55
46
|
import "./chunk-IQP3XE32.js";
|
|
47
|
+
import "./chunk-DN3KJZRV.js";
|
|
48
|
+
import "./chunk-2U4BZXAI.js";
|
|
49
|
+
import "./chunk-ITBZHMZQ.js";
|
|
50
|
+
import "./chunk-3NZ3IOPM.js";
|
|
51
|
+
import "./chunk-IOC3BSPT.js";
|
|
52
|
+
import "./chunk-3RP665TH.js";
|
|
53
|
+
import "./chunk-2DIXRINW.js";
|
|
54
|
+
import {
|
|
55
|
+
interpretCommandResult
|
|
56
|
+
} from "./chunk-FI4YPDFE.js";
|
|
57
|
+
import "./chunk-PKMIDA5A.js";
|
|
58
|
+
import "./chunk-GAWMEMXB.js";
|
|
56
59
|
|
|
57
60
|
// src/index.ts
|
|
58
61
|
import { spawn } from "child_process";
|
|
59
62
|
import { createHash, randomUUID } from "crypto";
|
|
63
|
+
import { closeSync, createWriteStream, openSync } from "fs";
|
|
60
64
|
import { mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
|
|
61
65
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
62
66
|
var toolRegistryStatus = "ready";
|
|
@@ -84,7 +88,8 @@ function createToolContext(workspaceRoot = process.cwd(), options = {}) {
|
|
|
84
88
|
todos: [],
|
|
85
89
|
readSnapshots: {},
|
|
86
90
|
sourcePackCandidates: options.sourcePackCandidates,
|
|
87
|
-
patchSummaries: {}
|
|
91
|
+
patchSummaries: {},
|
|
92
|
+
recentDiagnostics: []
|
|
88
93
|
};
|
|
89
94
|
}
|
|
90
95
|
async function runTool(name, input, context) {
|
|
@@ -571,9 +576,16 @@ function validateGlobInput(input) {
|
|
|
571
576
|
}
|
|
572
577
|
function validateBashInput(input) {
|
|
573
578
|
const record = validateRecord(input, "Bash");
|
|
579
|
+
const command = readOptionalString(record, "command", "Bash");
|
|
580
|
+
if (command === void 0) {
|
|
581
|
+
throw new Error("Bash.command \u5FC5\u987B\u63D0\u4F9B\u3002");
|
|
582
|
+
}
|
|
583
|
+
const runInBackground = record.runInBackground === true || record.run_in_background === true;
|
|
574
584
|
return {
|
|
575
|
-
command
|
|
576
|
-
|
|
585
|
+
command,
|
|
586
|
+
description: readOptionalString(record, "description", "Bash"),
|
|
587
|
+
timeoutMs: readOptionalPositiveInteger(record, "timeoutMs", "Bash"),
|
|
588
|
+
...runInBackground ? { runInBackground: true } : {}
|
|
577
589
|
};
|
|
578
590
|
}
|
|
579
591
|
function validateTodoInput(input) {
|
|
@@ -1009,11 +1021,41 @@ function sanitizeSecrets(text) {
|
|
|
1009
1021
|
return sanitized;
|
|
1010
1022
|
}
|
|
1011
1023
|
async function bashTool(input, context) {
|
|
1024
|
+
if (!input.command) {
|
|
1025
|
+
throw new Error("Bash.command \u5FC5\u987B\u63D0\u4F9B\u3002");
|
|
1026
|
+
}
|
|
1012
1027
|
const logRoot = context.logRoot ?? join(context.workspaceRoot, ".linghun", "logs", "tools");
|
|
1013
1028
|
await mkdir(logRoot, { recursive: true });
|
|
1014
1029
|
const fullOutputPath = join(logRoot, `bash-${Date.now()}-${randomUUID()}.log`);
|
|
1015
1030
|
const timeoutMs = input.timeoutMs ?? BASH_TIMEOUT_MS;
|
|
1016
1031
|
const adapted = adaptShellCommand(input.command);
|
|
1032
|
+
if (input.runInBackground === true) {
|
|
1033
|
+
const taskId = randomUUID();
|
|
1034
|
+
void runBackgroundBash({
|
|
1035
|
+
taskId,
|
|
1036
|
+
command: adapted.command,
|
|
1037
|
+
originalCommand: input.command,
|
|
1038
|
+
cwd: context.workspaceRoot,
|
|
1039
|
+
timeoutMs,
|
|
1040
|
+
fullOutputPath,
|
|
1041
|
+
adapter: adapted.adapter,
|
|
1042
|
+
logCommand: adapted.logCommand,
|
|
1043
|
+
retainAfterReturn: isHeadlessBenchContext(context),
|
|
1044
|
+
abortSignal: context.abortSignal,
|
|
1045
|
+
trackChildProcess: context.trackChildProcess,
|
|
1046
|
+
onComplete: context.onBackgroundBashComplete
|
|
1047
|
+
});
|
|
1048
|
+
return {
|
|
1049
|
+
text: `\u547D\u4EE4\u5DF2\u5728\u540E\u53F0\u542F\u52A8\u3002
|
|
1050
|
+
taskId: ${taskId}
|
|
1051
|
+
outputPath: ${fullOutputPath}`,
|
|
1052
|
+
data: {
|
|
1053
|
+
backgroundTaskId: taskId,
|
|
1054
|
+
outputPath: fullOutputPath,
|
|
1055
|
+
command: sanitizeSecrets(input.command)
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1017
1059
|
const result = await runShell(
|
|
1018
1060
|
adapted.command,
|
|
1019
1061
|
context.workspaceRoot,
|
|
@@ -1027,23 +1069,35 @@ async function bashTool(input, context) {
|
|
|
1027
1069
|
`original command ${summarizeOriginalShellCommand(input.command)}`
|
|
1028
1070
|
];
|
|
1029
1071
|
const commandForLog = adapted.logCommand ?? adapted.command;
|
|
1030
|
-
const
|
|
1072
|
+
const sanitizedShellOutput = sanitizeSecrets(result.output);
|
|
1073
|
+
const cmdInterpretation = interpretCommandResult(input.command, result.exitCode);
|
|
1074
|
+
const rawFullText = [
|
|
1031
1075
|
`$ ${sanitizeSecrets(commandForLog)}`,
|
|
1032
1076
|
...adapterLines,
|
|
1033
1077
|
`exit code ${result.exitCode}`,
|
|
1034
1078
|
`outcome ${result.outcome}`,
|
|
1079
|
+
...cmdInterpretation.message ? [`returnCodeInterpretation: ${cmdInterpretation.message}`] : [],
|
|
1035
1080
|
"",
|
|
1036
|
-
|
|
1081
|
+
sanitizedShellOutput
|
|
1037
1082
|
].join("\n");
|
|
1083
|
+
const diagnostics = createBashOutcomeDiagnostics(result.outcome);
|
|
1084
|
+
const fullText = rawFullText;
|
|
1038
1085
|
await writeFile(fullOutputPath, fullText, "utf8");
|
|
1039
1086
|
const truncated = fullText.length > BASH_PREVIEW_LIMIT;
|
|
1040
1087
|
const preview = truncated ? `${fullText.slice(0, BASH_PREVIEW_LIMIT)}
|
|
1041
1088
|
...\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u65E5\u5FD7\u89C1 fullOutputPath\uFF09` : fullText;
|
|
1042
1089
|
const details = createBashDetails(fullOutputPath, fullText);
|
|
1090
|
+
const data = adapted.adapter === "native" ? { exitCode: result.exitCode, outcome: result.outcome } : { exitCode: result.exitCode, outcome: result.outcome, adapter: adapted.adapter };
|
|
1091
|
+
const outputData = {
|
|
1092
|
+
...data,
|
|
1093
|
+
...cmdInterpretation.message ? { returnCodeInterpretation: cmdInterpretation.message } : {},
|
|
1094
|
+
...cmdInterpretation.isError === false && result.exitCode !== 0 ? { isError: false } : {},
|
|
1095
|
+
...diagnostics.length > 0 ? { diagnostics } : {}
|
|
1096
|
+
};
|
|
1043
1097
|
return {
|
|
1044
1098
|
text: preview,
|
|
1045
1099
|
details,
|
|
1046
|
-
data:
|
|
1100
|
+
data: outputData,
|
|
1047
1101
|
truncated,
|
|
1048
1102
|
fullOutputPath
|
|
1049
1103
|
};
|
|
@@ -1057,6 +1111,505 @@ function createBashDetails(fullOutputPath, fullText) {
|
|
|
1057
1111
|
...tailLines(fullText, 80)
|
|
1058
1112
|
].join("\n");
|
|
1059
1113
|
}
|
|
1114
|
+
var BINARY_HINT_EXTENSIONS = [
|
|
1115
|
+
".bin",
|
|
1116
|
+
".elf",
|
|
1117
|
+
".o",
|
|
1118
|
+
".so",
|
|
1119
|
+
".a",
|
|
1120
|
+
".exe",
|
|
1121
|
+
".dll",
|
|
1122
|
+
".7z",
|
|
1123
|
+
".zip",
|
|
1124
|
+
".gz",
|
|
1125
|
+
".png",
|
|
1126
|
+
".jpg",
|
|
1127
|
+
".jpeg",
|
|
1128
|
+
".pdf",
|
|
1129
|
+
".sqlite",
|
|
1130
|
+
".db"
|
|
1131
|
+
];
|
|
1132
|
+
var ARTIFACT_HINT_EXTENSIONS = [
|
|
1133
|
+
".txt",
|
|
1134
|
+
".json",
|
|
1135
|
+
".out",
|
|
1136
|
+
".log",
|
|
1137
|
+
".py",
|
|
1138
|
+
".html",
|
|
1139
|
+
".htm",
|
|
1140
|
+
".csv",
|
|
1141
|
+
".yaml",
|
|
1142
|
+
".yml",
|
|
1143
|
+
".toml",
|
|
1144
|
+
".md",
|
|
1145
|
+
".sh",
|
|
1146
|
+
".bin",
|
|
1147
|
+
".elf",
|
|
1148
|
+
".zip",
|
|
1149
|
+
".7z",
|
|
1150
|
+
".png",
|
|
1151
|
+
".pdf",
|
|
1152
|
+
".sqlite",
|
|
1153
|
+
".db"
|
|
1154
|
+
];
|
|
1155
|
+
function parseBashCommandIntent(command, _context) {
|
|
1156
|
+
const tokens = tokenizeShellLike(command);
|
|
1157
|
+
const segments = splitCommandSegments(tokens);
|
|
1158
|
+
const commandNames = [];
|
|
1159
|
+
const parsedSegments = [];
|
|
1160
|
+
const binaryCandidates = /* @__PURE__ */ new Set();
|
|
1161
|
+
const artifactCandidates = /* @__PURE__ */ new Set();
|
|
1162
|
+
const serviceCandidates = [];
|
|
1163
|
+
for (const segment of segments) {
|
|
1164
|
+
const parsed = parseCommandSegment(segment.tokens);
|
|
1165
|
+
if (!parsed) continue;
|
|
1166
|
+
commandNames.push(parsed.commandName);
|
|
1167
|
+
parsedSegments.push({
|
|
1168
|
+
commandName: parsed.commandName,
|
|
1169
|
+
argv: parsed.argv,
|
|
1170
|
+
background: segment.background || parsed.background,
|
|
1171
|
+
redirections: collectSegmentRedirections(parsed.argv)
|
|
1172
|
+
});
|
|
1173
|
+
collectSegmentBinaryCandidates(parsed, binaryCandidates);
|
|
1174
|
+
collectSegmentArtifactCandidates(parsed, artifactCandidates);
|
|
1175
|
+
serviceCandidates.push(...collectSegmentServiceCandidates(parsed));
|
|
1176
|
+
}
|
|
1177
|
+
return {
|
|
1178
|
+
binaryCandidates: [...binaryCandidates],
|
|
1179
|
+
serviceCandidates: dedupeServiceCandidates(serviceCandidates),
|
|
1180
|
+
artifactCandidates: [...artifactCandidates],
|
|
1181
|
+
backgroundLikely: isBackgroundLikely(segments, serviceCandidates),
|
|
1182
|
+
commandNames: unique(commandNames.filter(Boolean)),
|
|
1183
|
+
segments: parsedSegments,
|
|
1184
|
+
hasShellControl: segments.length !== 1 || tokens.some((token) => isCommandSeparator(token))
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
var BINARY_COMMAND_NAMES = /* @__PURE__ */ new Set(["file", "xxd", "readelf", "objdump", "hexdump", "strings", "7z", "7za", "unzip", "tar"]);
|
|
1188
|
+
var SHELL_RESERVED_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
1189
|
+
"if",
|
|
1190
|
+
"then",
|
|
1191
|
+
"else",
|
|
1192
|
+
"elif",
|
|
1193
|
+
"fi",
|
|
1194
|
+
"for",
|
|
1195
|
+
"while",
|
|
1196
|
+
"until",
|
|
1197
|
+
"do",
|
|
1198
|
+
"done",
|
|
1199
|
+
"case",
|
|
1200
|
+
"esac",
|
|
1201
|
+
"in",
|
|
1202
|
+
"function",
|
|
1203
|
+
"coproc"
|
|
1204
|
+
]);
|
|
1205
|
+
var SHELL_SYNTAX_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
1206
|
+
"[",
|
|
1207
|
+
"[[",
|
|
1208
|
+
"test",
|
|
1209
|
+
"{",
|
|
1210
|
+
"}",
|
|
1211
|
+
"(",
|
|
1212
|
+
")",
|
|
1213
|
+
"!",
|
|
1214
|
+
":",
|
|
1215
|
+
"true",
|
|
1216
|
+
"false"
|
|
1217
|
+
]);
|
|
1218
|
+
var ARTIFACT_PRODUCING_COMMAND_NAMES = /* @__PURE__ */ new Set(["touch", "tee"]);
|
|
1219
|
+
var ARTIFACT_PRODUCING_TOKENS = /* @__PURE__ */ new Set(["write", "wrote", "written", "create", "created", "generate", "generated", "build", "built", "output"]);
|
|
1220
|
+
function canSafelyAliasPythonCommand(intent) {
|
|
1221
|
+
const segment = intent.segments[0];
|
|
1222
|
+
return intent.segments.length === 1 && !intent.hasShellControl && segment?.commandName === "python" && !segment.background && segment.redirections.length === 0;
|
|
1223
|
+
}
|
|
1224
|
+
function tokenizeShellLike(command) {
|
|
1225
|
+
const tokens = [];
|
|
1226
|
+
let current = "";
|
|
1227
|
+
let quote;
|
|
1228
|
+
let escaped = false;
|
|
1229
|
+
const push = () => {
|
|
1230
|
+
if (current.length > 0) tokens.push(current);
|
|
1231
|
+
current = "";
|
|
1232
|
+
};
|
|
1233
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
1234
|
+
const char = command[index] ?? "";
|
|
1235
|
+
if (escaped) {
|
|
1236
|
+
current += char;
|
|
1237
|
+
escaped = false;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (char === "\\") {
|
|
1241
|
+
escaped = true;
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
if (quote) {
|
|
1245
|
+
if (char === quote) quote = void 0;
|
|
1246
|
+
else current += char;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
if (char === "'" || char === '"') {
|
|
1250
|
+
quote = char;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (/\s/u.test(char)) {
|
|
1254
|
+
push();
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (char === ">" && /^\d+$/u.test(current)) {
|
|
1258
|
+
const prefix = current;
|
|
1259
|
+
current = "";
|
|
1260
|
+
let redirect = `${prefix}>`;
|
|
1261
|
+
if (command[index + 1] === ">") {
|
|
1262
|
+
redirect += ">";
|
|
1263
|
+
index += 1;
|
|
1264
|
+
}
|
|
1265
|
+
if (command[index + 1] === "&") {
|
|
1266
|
+
redirect += "&";
|
|
1267
|
+
index += 1;
|
|
1268
|
+
while (index + 1 < command.length && /[A-Za-z0-9_/-]/u.test(command[index + 1] ?? "")) {
|
|
1269
|
+
redirect += command[index + 1];
|
|
1270
|
+
index += 1;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
tokens.push(redirect);
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (char === "(" && current.length === 0) {
|
|
1277
|
+
push();
|
|
1278
|
+
tokens.push(char);
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
if ((char === "{" || char === "}") && current.length === 0) {
|
|
1282
|
+
push();
|
|
1283
|
+
tokens.push(char);
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (char === ")" && !current.includes("$(")) {
|
|
1287
|
+
push();
|
|
1288
|
+
tokens.push(char);
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (char === "|" || char === ";" || char === "&" || char === ">") {
|
|
1292
|
+
push();
|
|
1293
|
+
const next = command[index + 1];
|
|
1294
|
+
if (char === "&" && next === "&" || char === ">" && next === ">") {
|
|
1295
|
+
tokens.push(`${char}${next}`);
|
|
1296
|
+
index += 1;
|
|
1297
|
+
} else {
|
|
1298
|
+
tokens.push(char);
|
|
1299
|
+
}
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
current += char;
|
|
1303
|
+
}
|
|
1304
|
+
push();
|
|
1305
|
+
return tokens;
|
|
1306
|
+
}
|
|
1307
|
+
function isCommandSeparator(token) {
|
|
1308
|
+
return token === "|" || token === "&&" || token === ";" || token === "&";
|
|
1309
|
+
}
|
|
1310
|
+
function splitCommandSegments(tokens) {
|
|
1311
|
+
const segments = [];
|
|
1312
|
+
let current = [];
|
|
1313
|
+
for (const token of tokens) {
|
|
1314
|
+
if (!isCommandSeparator(token)) {
|
|
1315
|
+
current.push(token);
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
if (current.length > 0) {
|
|
1319
|
+
segments.push({ tokens: current, background: token === "&" });
|
|
1320
|
+
current = [];
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
if (current.length > 0) segments.push({ tokens: current, background: false });
|
|
1324
|
+
return segments;
|
|
1325
|
+
}
|
|
1326
|
+
function parseCommandSegment(tokens) {
|
|
1327
|
+
const env = /* @__PURE__ */ new Map();
|
|
1328
|
+
let index = 0;
|
|
1329
|
+
while (index < tokens.length && isShellGroupBoundaryToken(tokens[index] ?? "")) {
|
|
1330
|
+
index += 1;
|
|
1331
|
+
}
|
|
1332
|
+
while (index < tokens.length && isEnvironmentAssignment(tokens[index] ?? "")) {
|
|
1333
|
+
const [name, ...rest] = (tokens[index] ?? "").split("=");
|
|
1334
|
+
if (name) env.set(name.toUpperCase(), rest.join("="));
|
|
1335
|
+
index += 1;
|
|
1336
|
+
}
|
|
1337
|
+
if (index >= tokens.length) return void 0;
|
|
1338
|
+
let commandToken = tokens[index] ?? "";
|
|
1339
|
+
if (commandToken === "nohup" || commandToken === "setsid" || commandToken === "time") {
|
|
1340
|
+
index += 1;
|
|
1341
|
+
commandToken = tokens[index] ?? "";
|
|
1342
|
+
}
|
|
1343
|
+
if (isShellReservedCommandToken(commandToken)) return void 0;
|
|
1344
|
+
if (isShellSyntaxCommandToken(commandToken)) return void 0;
|
|
1345
|
+
if (!commandToken) return void 0;
|
|
1346
|
+
const commandName = stripExecutableToken(commandToken);
|
|
1347
|
+
if (!commandName || isShellReservedCommandToken(commandName) || isShellSyntaxCommandToken(commandName)) {
|
|
1348
|
+
return void 0;
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
commandName,
|
|
1352
|
+
argv: tokens.slice(index + 1).filter((token) => !isShellGroupBoundaryToken(token)),
|
|
1353
|
+
env,
|
|
1354
|
+
background: tokens.includes("disown")
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
function collectSegmentBinaryCandidates(segment, target) {
|
|
1358
|
+
if (BINARY_COMMAND_NAMES.has(segment.commandName)) {
|
|
1359
|
+
for (const token of segment.argv) {
|
|
1360
|
+
if (!isOptionToken(token)) addPathCandidate(target, token);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
for (const token of segment.argv) {
|
|
1364
|
+
if (isBinarySuffix(token)) addPathCandidate(target, token);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
function collectSegmentArtifactCandidates(segment, target) {
|
|
1368
|
+
for (let index = 0; index < segment.argv.length; index += 1) {
|
|
1369
|
+
const token = segment.argv[index] ?? "";
|
|
1370
|
+
if (token === ">" || token === ">>") {
|
|
1371
|
+
addPathCandidate(target, segment.argv[index + 1] ?? "");
|
|
1372
|
+
index += 1;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (isOutputFlag(token)) {
|
|
1376
|
+
addPathCandidate(target, segment.argv[index + 1] ?? "");
|
|
1377
|
+
index += 1;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
if (isArtifactSuffix(token) && isArtifactProducingCommand(segment.commandName, segment.argv)) {
|
|
1381
|
+
addPathCandidate(target, token);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function collectSegmentRedirections(argv) {
|
|
1386
|
+
const redirections = [];
|
|
1387
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1388
|
+
const token = argv[index] ?? "";
|
|
1389
|
+
if (token !== ">" && token !== ">>") continue;
|
|
1390
|
+
const target = argv[index + 1] ?? "";
|
|
1391
|
+
if (target) redirections.push({ operator: token, target });
|
|
1392
|
+
index += 1;
|
|
1393
|
+
}
|
|
1394
|
+
return redirections;
|
|
1395
|
+
}
|
|
1396
|
+
function collectSegmentServiceCandidates(segment) {
|
|
1397
|
+
const candidates = [];
|
|
1398
|
+
const envPort = parsePort(segment.env.get("PORT"));
|
|
1399
|
+
const explicit = findExplicitServiceTarget(segment.argv, envPort);
|
|
1400
|
+
if (isPythonHttpServer(segment)) {
|
|
1401
|
+
const port = findFirstPortToken(segment.argv) ?? envPort ?? 8e3;
|
|
1402
|
+
candidates.push(createServiceIntent("http-server", port, "high", "python -m http.server"));
|
|
1403
|
+
return candidates;
|
|
1404
|
+
}
|
|
1405
|
+
if (segment.commandName === "uvicorn") {
|
|
1406
|
+
if (explicit) candidates.push(createServiceIntent("framework-server", explicit.port, "high", explicit.evidence, explicit.host));
|
|
1407
|
+
return candidates;
|
|
1408
|
+
}
|
|
1409
|
+
if (segment.commandName === "flask" && segment.argv[0]?.toLowerCase() === "run") {
|
|
1410
|
+
if (explicit) candidates.push(createServiceIntent("framework-server", explicit.port, "high", explicit.evidence, explicit.host));
|
|
1411
|
+
return candidates;
|
|
1412
|
+
}
|
|
1413
|
+
if (segment.commandName === "gunicorn") {
|
|
1414
|
+
const bind = findGunicornBindTarget(segment.argv);
|
|
1415
|
+
if (bind) candidates.push(createServiceIntent("framework-server", bind.port, "high", bind.evidence, bind.host));
|
|
1416
|
+
return candidates;
|
|
1417
|
+
}
|
|
1418
|
+
if (isPackageScriptCommand(segment)) {
|
|
1419
|
+
if (explicit) candidates.push(createServiceIntent("package-script", explicit.port, "medium", explicit.evidence, explicit.host));
|
|
1420
|
+
return candidates;
|
|
1421
|
+
}
|
|
1422
|
+
if (isJsRuntimeCommand(segment.commandName)) {
|
|
1423
|
+
if (explicit) candidates.push(createServiceIntent("explicit-port", explicit.port, "medium", explicit.evidence, explicit.host));
|
|
1424
|
+
return candidates;
|
|
1425
|
+
}
|
|
1426
|
+
if (explicit && isKnownServiceCommand(segment.commandName)) {
|
|
1427
|
+
candidates.push(createServiceIntent("explicit-port", explicit.port, "medium", explicit.evidence, explicit.host));
|
|
1428
|
+
}
|
|
1429
|
+
return candidates;
|
|
1430
|
+
}
|
|
1431
|
+
function createServiceIntent(kind, port, confidence, evidence, host = "127.0.0.1") {
|
|
1432
|
+
return { kind, host: normalizeServiceHost(host), port, confidence, evidence };
|
|
1433
|
+
}
|
|
1434
|
+
function isPythonHttpServer(segment) {
|
|
1435
|
+
return (segment.commandName === "python" || segment.commandName === "python3") && hasAdjacentTokens(segment.argv, "-m", "http.server");
|
|
1436
|
+
}
|
|
1437
|
+
function findExplicitServiceTarget(argv, envPort) {
|
|
1438
|
+
let host = "127.0.0.1";
|
|
1439
|
+
if (envPort) return { host, port: envPort, evidence: "PORT=" };
|
|
1440
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1441
|
+
const token = argv[index] ?? "";
|
|
1442
|
+
if (token === "--host") host = normalizeServiceHost(argv[index + 1] ?? host);
|
|
1443
|
+
if (token === "--port" || token === "-p") {
|
|
1444
|
+
const port = parsePort(argv[index + 1]);
|
|
1445
|
+
if (port) return { host, port, evidence: token };
|
|
1446
|
+
}
|
|
1447
|
+
const inlinePort = parseInlineOptionPort(token);
|
|
1448
|
+
if (inlinePort) return { host, port: inlinePort, evidence: token.split("=")[0] ?? token };
|
|
1449
|
+
const target = parseHostPortToken(token);
|
|
1450
|
+
if (target?.port) return { host: target.host ?? host, port: target.port, evidence: "host:port" };
|
|
1451
|
+
}
|
|
1452
|
+
return void 0;
|
|
1453
|
+
}
|
|
1454
|
+
function findGunicornBindTarget(argv) {
|
|
1455
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1456
|
+
const token = argv[index] ?? "";
|
|
1457
|
+
if (token !== "-b" && token !== "--bind") continue;
|
|
1458
|
+
const target = parseHostPortToken(argv[index + 1] ?? "");
|
|
1459
|
+
if (target?.port) return { host: target.host ?? "127.0.0.1", port: target.port, evidence: token };
|
|
1460
|
+
}
|
|
1461
|
+
return void 0;
|
|
1462
|
+
}
|
|
1463
|
+
function findFirstPortToken(argv) {
|
|
1464
|
+
for (const token of argv) {
|
|
1465
|
+
const port = parsePort(token);
|
|
1466
|
+
if (port) return port;
|
|
1467
|
+
}
|
|
1468
|
+
return void 0;
|
|
1469
|
+
}
|
|
1470
|
+
function parseInlineOptionPort(token) {
|
|
1471
|
+
if (!token.startsWith("--port=") && !token.startsWith("-p=")) return void 0;
|
|
1472
|
+
return parsePort(token.slice(token.indexOf("=") + 1));
|
|
1473
|
+
}
|
|
1474
|
+
function parsePort(value) {
|
|
1475
|
+
if (!value || !/^\d{1,5}$/u.test(value)) return void 0;
|
|
1476
|
+
return normalizePort(Number(value));
|
|
1477
|
+
}
|
|
1478
|
+
function isPackageScriptCommand(segment) {
|
|
1479
|
+
if (segment.commandName !== "npm" && segment.commandName !== "pnpm" && segment.commandName !== "yarn") return false;
|
|
1480
|
+
const command = segment.argv[0]?.toLowerCase();
|
|
1481
|
+
const script = segment.argv[1]?.toLowerCase();
|
|
1482
|
+
return command === "start" || command === "dev" || command === "test-server" || command === "run" && (script === "start" || script === "dev" || script === "test-server");
|
|
1483
|
+
}
|
|
1484
|
+
function isJsRuntimeCommand(commandName) {
|
|
1485
|
+
return commandName === "node" || commandName === "deno" || commandName === "bun";
|
|
1486
|
+
}
|
|
1487
|
+
function isKnownServiceCommand(commandName) {
|
|
1488
|
+
return commandName === "grpc";
|
|
1489
|
+
}
|
|
1490
|
+
function normalizeServiceHost(host) {
|
|
1491
|
+
return "127.0.0.1";
|
|
1492
|
+
}
|
|
1493
|
+
function isBackgroundLikely(segments, serviceCandidates) {
|
|
1494
|
+
return segments.some(
|
|
1495
|
+
(segment) => segment.background || segment.tokens[0] === "nohup" || segment.tokens[0] === "setsid" || segment.tokens.includes("disown")
|
|
1496
|
+
) || serviceCandidates.some(
|
|
1497
|
+
(candidate) => candidate.kind === "http-server" || candidate.kind === "framework-server" || candidate.kind === "package-script"
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
function stripExecutableToken(token) {
|
|
1501
|
+
const normalized = token.replace(/^\(+/u, "").replace(/\)+$/u, "");
|
|
1502
|
+
if (normalized.includes("/") || normalized.includes("\\")) {
|
|
1503
|
+
return normalized.replace(/\.(?:exe|cmd|bat)$/iu, "");
|
|
1504
|
+
}
|
|
1505
|
+
return basename(normalized).replace(/\.(?:exe|cmd|bat)$/iu, "").toLowerCase();
|
|
1506
|
+
}
|
|
1507
|
+
function addPathCandidate(target, token) {
|
|
1508
|
+
if (!isStaticPathCandidate(token)) return;
|
|
1509
|
+
target.add(token.replaceAll("\\", "/"));
|
|
1510
|
+
}
|
|
1511
|
+
function isOptionToken(token) {
|
|
1512
|
+
return token.startsWith("-") && !/^-?\d+$/u.test(token);
|
|
1513
|
+
}
|
|
1514
|
+
function isOutputFlag(token) {
|
|
1515
|
+
return token === "-o" || token === "--output" || token === "--out" || token === "--dest" || token === "--file";
|
|
1516
|
+
}
|
|
1517
|
+
function isEnvironmentAssignment(token) {
|
|
1518
|
+
return /^[A-Za-z_][A-Za-z0-9_]*=.*$/u.test(token);
|
|
1519
|
+
}
|
|
1520
|
+
function isShellReservedCommandToken(token) {
|
|
1521
|
+
return SHELL_RESERVED_COMMAND_NAMES.has(token.toLowerCase());
|
|
1522
|
+
}
|
|
1523
|
+
function isShellSyntaxCommandToken(token) {
|
|
1524
|
+
return SHELL_SYNTAX_COMMAND_NAMES.has(token.toLowerCase());
|
|
1525
|
+
}
|
|
1526
|
+
function isShellGroupBoundaryToken(token) {
|
|
1527
|
+
return token === "(" || token === ")" || token === "{" || token === "}";
|
|
1528
|
+
}
|
|
1529
|
+
function isStaticPathCandidate(token) {
|
|
1530
|
+
if (!token || isOptionToken(token) || token.includes("://")) return false;
|
|
1531
|
+
if (token.includes("$") || token.includes("`")) return false;
|
|
1532
|
+
if (/^(?:\d+)?>{1,2}(?:&\d+)?$/u.test(token)) return false;
|
|
1533
|
+
if (token === "|" || token === ";" || token === "&" || token === "<") return false;
|
|
1534
|
+
if (isShellGroupBoundaryToken(token) || isShellSyntaxCommandToken(token)) return false;
|
|
1535
|
+
if (/^\d+$/u.test(token)) return false;
|
|
1536
|
+
if (token === "/dev/null" || token === "/dev/stdout" || token === "/dev/stderr") return false;
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
function isBinarySuffix(token) {
|
|
1540
|
+
return BINARY_HINT_EXTENSIONS.some((extension) => token.toLowerCase().endsWith(extension));
|
|
1541
|
+
}
|
|
1542
|
+
function isArtifactSuffix(token) {
|
|
1543
|
+
return ARTIFACT_HINT_EXTENSIONS.some((extension) => token.toLowerCase().endsWith(extension));
|
|
1544
|
+
}
|
|
1545
|
+
function isArtifactProducingCommand(commandName, tokens) {
|
|
1546
|
+
if (!commandName) return false;
|
|
1547
|
+
if (ARTIFACT_PRODUCING_COMMAND_NAMES.has(commandName)) return true;
|
|
1548
|
+
return tokens.some((token) => ARTIFACT_PRODUCING_TOKENS.has(token.toLowerCase()));
|
|
1549
|
+
}
|
|
1550
|
+
function parseHostPortToken(token) {
|
|
1551
|
+
const url = tryParseLocalUrl(token);
|
|
1552
|
+
if (url) return url;
|
|
1553
|
+
const separator = token.lastIndexOf(":");
|
|
1554
|
+
if (separator <= 0) return void 0;
|
|
1555
|
+
const host = token.slice(0, separator).replace(/^\[(.*)\]$/u, "$1");
|
|
1556
|
+
const port = normalizePort(Number(token.slice(separator + 1)));
|
|
1557
|
+
if (!port || !isLocalHost(host)) return void 0;
|
|
1558
|
+
return normalizeHostPort(host, port);
|
|
1559
|
+
}
|
|
1560
|
+
function tryParseLocalUrl(token) {
|
|
1561
|
+
try {
|
|
1562
|
+
const url = new URL(token);
|
|
1563
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
1564
|
+
if (!isLocalHost(url.hostname)) return void 0;
|
|
1565
|
+
const port = normalizePort(Number(url.port));
|
|
1566
|
+
if (!port) return void 0;
|
|
1567
|
+
return normalizeHostPort(url.hostname, port);
|
|
1568
|
+
} catch {
|
|
1569
|
+
return void 0;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function isLocalHost(host) {
|
|
1573
|
+
return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1";
|
|
1574
|
+
}
|
|
1575
|
+
function hasAdjacentTokens(tokens, first, second) {
|
|
1576
|
+
return tokens.some(
|
|
1577
|
+
(token, index) => token.toLowerCase() === first && tokens[index + 1]?.toLowerCase() === second
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
function normalizePort(value) {
|
|
1581
|
+
return Number.isInteger(value) && value > 0 && value <= 65535 ? value : void 0;
|
|
1582
|
+
}
|
|
1583
|
+
function dedupeServiceCandidates(candidates) {
|
|
1584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1585
|
+
return candidates.filter((candidate) => {
|
|
1586
|
+
const key = `${candidate.host}:${candidate.port}:${candidate.kind}`;
|
|
1587
|
+
if (seen.has(key)) return false;
|
|
1588
|
+
seen.add(key);
|
|
1589
|
+
return true;
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
function normalizeHostPort(host, port) {
|
|
1593
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) return void 0;
|
|
1594
|
+
return { host: host === "0.0.0.0" || host === "localhost" ? "127.0.0.1" : host.replace(/^\[(.*)\]$/u, "$1"), port };
|
|
1595
|
+
}
|
|
1596
|
+
function isHeadlessBenchContext(context) {
|
|
1597
|
+
const record = context;
|
|
1598
|
+
return record.headlessBench?.enabled === true || record.isHeadlessBench === true;
|
|
1599
|
+
}
|
|
1600
|
+
function createBashOutcomeDiagnostics(outcome) {
|
|
1601
|
+
if (outcome === "timeout") {
|
|
1602
|
+
return [
|
|
1603
|
+
{
|
|
1604
|
+
type: "timeout",
|
|
1605
|
+
severity: "recoverable",
|
|
1606
|
+
evidence: "Bash outcome timeout",
|
|
1607
|
+
suggestion: "Run the smallest focused check first and avoid repeating long blind commands."
|
|
1608
|
+
}
|
|
1609
|
+
];
|
|
1610
|
+
}
|
|
1611
|
+
return [];
|
|
1612
|
+
}
|
|
1060
1613
|
function tailLines(text, limit) {
|
|
1061
1614
|
const lines = text.split(/\r?\n/u);
|
|
1062
1615
|
return lines.slice(Math.max(0, lines.length - limit));
|
|
@@ -1073,6 +1626,8 @@ function adaptShellCommandForPlatform(command, platform) {
|
|
|
1073
1626
|
"Unsupported multi-line Unix shell syntax on Windows PowerShell; use PowerShell-safe commands or Node one-liners."
|
|
1074
1627
|
);
|
|
1075
1628
|
}
|
|
1629
|
+
const nativePowerShell = convertNativePowerShellCommand(command);
|
|
1630
|
+
if (nativePowerShell) return nativePowerShell;
|
|
1076
1631
|
const converted = convertUnixPipelineForPowerShell(command);
|
|
1077
1632
|
if (converted) return { command: converted, adapter: "powershell-adapted" };
|
|
1078
1633
|
const readOnlyCommand = convertUnixReadOnlyCommandForPowerShell(command);
|
|
@@ -1085,6 +1640,22 @@ function adaptShellCommandForPlatform(command, platform) {
|
|
|
1085
1640
|
if (unsupportedReadOnlyCommand) return unsupportedReadOnlyCommand;
|
|
1086
1641
|
return { command, adapter: "native" };
|
|
1087
1642
|
}
|
|
1643
|
+
function convertNativePowerShellCommand(command) {
|
|
1644
|
+
const normalized = command.trim();
|
|
1645
|
+
if (!normalized) return void 0;
|
|
1646
|
+
if (/^(?:powershell(?:\.exe)?|pwsh(?:\.exe)?)\b/iu.test(normalized)) return void 0;
|
|
1647
|
+
if (!/^(?:Get|Set|New|Remove|Select|ForEach|Where|Test|Resolve|Join|Split|Copy|Move|Write|Out|Measure|Compare|Sort|Format)-[A-Za-z]+\b/u.test(normalized)) {
|
|
1648
|
+
return void 0;
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
command: [
|
|
1652
|
+
"powershell.exe -NoProfile -NonInteractive -Command",
|
|
1653
|
+
quoteCmdArg(`$ErrorActionPreference='Stop'; ${normalized}`)
|
|
1654
|
+
].join(" "),
|
|
1655
|
+
adapter: "powershell-adapted",
|
|
1656
|
+
logCommand: `powershell.exe -NoProfile -NonInteractive -Command <powershell cmdlet>`
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1088
1659
|
function convertNodeHereDocForPowerShell(command) {
|
|
1089
1660
|
const normalized = command.replace(/\r\n/gu, "\n");
|
|
1090
1661
|
const match = normalized.match(
|
|
@@ -2063,6 +2634,198 @@ function runShell(command, cwd, timeoutMs, signal, onProgress, trackChildProcess
|
|
|
2063
2634
|
});
|
|
2064
2635
|
});
|
|
2065
2636
|
}
|
|
2637
|
+
var STALL_THRESHOLD_MS = 45e3;
|
|
2638
|
+
var PROMPT_PATTERNS = [
|
|
2639
|
+
/\(y\/n\)\s*$/i,
|
|
2640
|
+
/\[y\/n\]\s*$/i,
|
|
2641
|
+
/\(yes\/no\)\s*$/i,
|
|
2642
|
+
/Press Enter/i,
|
|
2643
|
+
/Continue\?/i,
|
|
2644
|
+
/Overwrite\?/i,
|
|
2645
|
+
/password[:\s]*$/i,
|
|
2646
|
+
/passphrase[:\s]*$/i,
|
|
2647
|
+
/\?\s*$/
|
|
2648
|
+
];
|
|
2649
|
+
function looksLikePrompt(tail) {
|
|
2650
|
+
const lastLine = tail.split(/\r?\n/).filter(Boolean).pop() ?? "";
|
|
2651
|
+
return PROMPT_PATTERNS.some((re) => re.test(lastLine));
|
|
2652
|
+
}
|
|
2653
|
+
async function runBackgroundBash(opts) {
|
|
2654
|
+
const {
|
|
2655
|
+
taskId,
|
|
2656
|
+
command,
|
|
2657
|
+
originalCommand,
|
|
2658
|
+
cwd,
|
|
2659
|
+
timeoutMs,
|
|
2660
|
+
fullOutputPath,
|
|
2661
|
+
adapter,
|
|
2662
|
+
logCommand,
|
|
2663
|
+
retainAfterReturn,
|
|
2664
|
+
abortSignal,
|
|
2665
|
+
onProgress,
|
|
2666
|
+
trackChildProcess,
|
|
2667
|
+
onComplete
|
|
2668
|
+
} = opts;
|
|
2669
|
+
await mkdir(dirname(fullOutputPath), { recursive: true }).catch(() => {
|
|
2670
|
+
});
|
|
2671
|
+
const fileStream = createWriteStream(fullOutputPath, { encoding: "utf8" });
|
|
2672
|
+
const commandForLog = logCommand ?? command;
|
|
2673
|
+
const header = [
|
|
2674
|
+
`$ ${sanitizeSecrets(commandForLog)}`,
|
|
2675
|
+
...adapter !== "native" ? [`adapter ${adapter}`, `original command ${summarizeOriginalShellCommand(originalCommand)}`] : [],
|
|
2676
|
+
""
|
|
2677
|
+
].join("\n");
|
|
2678
|
+
fileStream.write(header);
|
|
2679
|
+
const detached = process.platform !== "win32";
|
|
2680
|
+
if (retainAfterReturn) {
|
|
2681
|
+
fileStream.write("\n[background] retained process started\n");
|
|
2682
|
+
await new Promise((resolveEnd) => fileStream.end(resolveEnd));
|
|
2683
|
+
const outFd = openSync(fullOutputPath, "a");
|
|
2684
|
+
const errFd = openSync(fullOutputPath, "a");
|
|
2685
|
+
const child2 = spawn(command, {
|
|
2686
|
+
cwd,
|
|
2687
|
+
shell: true,
|
|
2688
|
+
windowsHide: true,
|
|
2689
|
+
detached,
|
|
2690
|
+
stdio: ["ignore", outFd, errFd]
|
|
2691
|
+
});
|
|
2692
|
+
closeSync(outFd);
|
|
2693
|
+
closeSync(errFd);
|
|
2694
|
+
child2.unref();
|
|
2695
|
+
trackChildProcess?.(child2, {
|
|
2696
|
+
detached,
|
|
2697
|
+
cwd,
|
|
2698
|
+
label: `BashBg:${command.slice(0, 80)}`,
|
|
2699
|
+
retainAfterExit: true
|
|
2700
|
+
});
|
|
2701
|
+
onComplete?.({
|
|
2702
|
+
taskId,
|
|
2703
|
+
exitCode: 0,
|
|
2704
|
+
outcome: "completed",
|
|
2705
|
+
outputPath: fullOutputPath,
|
|
2706
|
+
command: sanitizeSecrets(originalCommand)
|
|
2707
|
+
});
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const child = spawn(command, { cwd, shell: true, windowsHide: true, detached });
|
|
2711
|
+
trackChildProcess?.(child, {
|
|
2712
|
+
detached,
|
|
2713
|
+
cwd,
|
|
2714
|
+
label: `BashBg:${command.slice(0, 80)}`,
|
|
2715
|
+
retainAfterExit: detached
|
|
2716
|
+
});
|
|
2717
|
+
let tailBuffer = "";
|
|
2718
|
+
let lastOutputTime = Date.now();
|
|
2719
|
+
let stallTimer;
|
|
2720
|
+
let settled = false;
|
|
2721
|
+
let outcome = "completed";
|
|
2722
|
+
const resetStallTimer = () => {
|
|
2723
|
+
lastOutputTime = Date.now();
|
|
2724
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
2725
|
+
stallTimer = setTimeout(checkStall, STALL_THRESHOLD_MS);
|
|
2726
|
+
};
|
|
2727
|
+
const checkStall = () => {
|
|
2728
|
+
if (Date.now() - lastOutputTime >= STALL_THRESHOLD_MS && looksLikePrompt(tailBuffer)) {
|
|
2729
|
+
const msg = "\n[stall watchdog] \u68C0\u6D4B\u5230\u4EA4\u4E92\u5F0F\u63D0\u793A\uFF0C\u53EF\u80FD\u9700\u8981\u7528\u6237\u8F93\u5165\u3002\n";
|
|
2730
|
+
fileStream.write(msg);
|
|
2731
|
+
onProgress?.("system", msg);
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
const finish = (exitCode) => {
|
|
2735
|
+
if (settled) return;
|
|
2736
|
+
settled = true;
|
|
2737
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
2738
|
+
clearTimeout(timer);
|
|
2739
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
2740
|
+
const footer = `
|
|
2741
|
+
exit code ${exitCode}
|
|
2742
|
+
outcome ${outcome}
|
|
2743
|
+
`;
|
|
2744
|
+
fileStream.write(footer);
|
|
2745
|
+
fileStream.end();
|
|
2746
|
+
onComplete?.({
|
|
2747
|
+
taskId,
|
|
2748
|
+
exitCode,
|
|
2749
|
+
outcome,
|
|
2750
|
+
outputPath: fullOutputPath,
|
|
2751
|
+
command: sanitizeSecrets(originalCommand)
|
|
2752
|
+
});
|
|
2753
|
+
};
|
|
2754
|
+
const requestStop = async (force) => {
|
|
2755
|
+
if (process.platform === "win32" && child.pid) {
|
|
2756
|
+
await stopWindowsProcessTree(child.pid, cwd);
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
const sig = force ? "SIGKILL" : "SIGTERM";
|
|
2760
|
+
if (detached && child.pid) {
|
|
2761
|
+
try {
|
|
2762
|
+
process.kill(-child.pid, sig);
|
|
2763
|
+
} catch {
|
|
2764
|
+
child.kill(sig);
|
|
2765
|
+
}
|
|
2766
|
+
} else {
|
|
2767
|
+
child.kill(sig);
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
const FORCE_KILL_WAIT_MS = 3e3;
|
|
2771
|
+
const waitForCloseOrForceKill = () => {
|
|
2772
|
+
const forceTimer = setTimeout(() => {
|
|
2773
|
+
void requestStop(true);
|
|
2774
|
+
setTimeout(() => finish(1), 500);
|
|
2775
|
+
}, FORCE_KILL_WAIT_MS);
|
|
2776
|
+
child.once("close", () => {
|
|
2777
|
+
clearTimeout(forceTimer);
|
|
2778
|
+
});
|
|
2779
|
+
};
|
|
2780
|
+
const onAbort = () => {
|
|
2781
|
+
if (settled) return;
|
|
2782
|
+
outcome = "cancelled";
|
|
2783
|
+
const msg = "\n[cancelled] \u5DE5\u5177\u8C03\u7528\u5DF2\u53D6\u6D88\uFF0C\u6B63\u5728\u7EC8\u6B62\u5B50\u8FDB\u7A0B\u3002\n";
|
|
2784
|
+
fileStream.write(msg);
|
|
2785
|
+
onProgress?.("system", msg);
|
|
2786
|
+
void requestStop(false);
|
|
2787
|
+
waitForCloseOrForceKill();
|
|
2788
|
+
};
|
|
2789
|
+
const timer = setTimeout(() => {
|
|
2790
|
+
if (settled) return;
|
|
2791
|
+
outcome = "timeout";
|
|
2792
|
+
const msg = `
|
|
2793
|
+
[timeout] \u547D\u4EE4\u8D85\u65F6\uFF1A\u8D85\u8FC7 ${timeoutMs}ms\uFF0C\u5DF2\u7EC8\u6B62\u3002
|
|
2794
|
+
`;
|
|
2795
|
+
fileStream.write(msg);
|
|
2796
|
+
onProgress?.("system", msg);
|
|
2797
|
+
void requestStop(false);
|
|
2798
|
+
waitForCloseOrForceKill();
|
|
2799
|
+
}, timeoutMs);
|
|
2800
|
+
if (abortSignal?.aborted) {
|
|
2801
|
+
onAbort();
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2805
|
+
child.stdout.on("data", (chunk) => {
|
|
2806
|
+
const text = decodeShellChunk(chunk);
|
|
2807
|
+
fileStream.write(text);
|
|
2808
|
+
tailBuffer = (tailBuffer + text).slice(-2e3);
|
|
2809
|
+
resetStallTimer();
|
|
2810
|
+
onProgress?.("stdout", text);
|
|
2811
|
+
});
|
|
2812
|
+
child.stderr.on("data", (chunk) => {
|
|
2813
|
+
const text = decodeShellChunk(chunk);
|
|
2814
|
+
fileStream.write(text);
|
|
2815
|
+
tailBuffer = (tailBuffer + text).slice(-2e3);
|
|
2816
|
+
resetStallTimer();
|
|
2817
|
+
onProgress?.("stderr", text);
|
|
2818
|
+
});
|
|
2819
|
+
child.on("close", (code) => {
|
|
2820
|
+
finish(code ?? 1);
|
|
2821
|
+
});
|
|
2822
|
+
child.on("error", (err) => {
|
|
2823
|
+
fileStream.write(`
|
|
2824
|
+
[error] ${err.message}
|
|
2825
|
+
`);
|
|
2826
|
+
finish(1);
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2066
2829
|
function decodeShellChunk(chunk) {
|
|
2067
2830
|
const utf8 = chunk.toString("utf8");
|
|
2068
2831
|
if (process.platform !== "win32") return utf8;
|
|
@@ -2190,14 +2953,21 @@ function unique(items) {
|
|
|
2190
2953
|
}
|
|
2191
2954
|
var __testGlobToRegExp = globToRegExp;
|
|
2192
2955
|
var __testDecodeShellChunk = decodeShellChunk;
|
|
2956
|
+
var __testParseBashCommandIntent = parseBashCommandIntent;
|
|
2957
|
+
var __testCanSafelyAliasPythonCommand = canSafelyAliasPythonCommand;
|
|
2958
|
+
var __testRunBackgroundBash = runBackgroundBash;
|
|
2193
2959
|
export {
|
|
2960
|
+
__testCanSafelyAliasPythonCommand,
|
|
2194
2961
|
__testDecodeShellChunk,
|
|
2195
2962
|
__testGlobToRegExp,
|
|
2963
|
+
__testParseBashCommandIntent,
|
|
2964
|
+
__testRunBackgroundBash,
|
|
2196
2965
|
adaptShellCommand,
|
|
2197
2966
|
adaptShellCommandForPlatform,
|
|
2198
2967
|
builtInTools,
|
|
2199
2968
|
createTool,
|
|
2200
2969
|
createToolContext,
|
|
2970
|
+
interpretCommandResult,
|
|
2201
2971
|
runTool,
|
|
2202
2972
|
toolRegistryStatus
|
|
2203
2973
|
};
|