@linghun/tools 0.1.1 → 0.1.3
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-236QJATI.js +678 -0
- package/dist/{chunk-VZWMPUSS.js → chunk-2DIXRINW.js} +1 -1
- package/dist/chunk-A6FUAM64.js +11 -0
- package/dist/{chunk-3JCXABUK.js → chunk-F7UOI3YR.js} +19 -11
- package/dist/chunk-FI4YPDFE.js +38 -0
- package/dist/chunk-GI5XI4HN.js +11 -0
- package/dist/{chunk-QNZMO55X.js → chunk-J7O24FJB.js} +19 -11
- package/dist/chunk-PKMIDA5A.js +6 -0
- package/dist/chunk-SPAD4DZS.js +319 -0
- package/dist/chunk-WEWXKRKL.js +6 -0
- package/dist/chunk-XG6HNLY6.js +166 -0
- package/dist/chunk-YCO6C7KF.js +6 -0
- package/dist/index.d.ts +105 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1404 -28
- package/dist/tools/Bash/BashTool.d.ts +1 -1
- package/dist/tools/Bash/BashTool.js +2 -2
- package/dist/tools/Bash/artifact-checker.d.ts +33 -0
- package/dist/tools/Bash/artifact-checker.d.ts.map +1 -0
- package/dist/tools/Bash/artifact-checker.js +6 -0
- package/dist/tools/Bash/binary-helper.d.ts +7 -0
- package/dist/tools/Bash/binary-helper.d.ts.map +1 -0
- package/dist/tools/Bash/binary-helper.js +6 -0
- 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/Bash/service-kernel.d.ts +75 -0
- package/dist/tools/Bash/service-kernel.d.ts.map +1 -0
- package/dist/tools/Bash/service-kernel.js +8 -0
- package/dist/tools/Glob/GlobTool.js +2 -2
- package/dist/tools/ReadSnippets/ReadSnippetsTool.d.ts +6 -0
- package/dist/tools/ReadSnippets/ReadSnippetsTool.d.ts.map +1 -0
- package/dist/tools/ReadSnippets/ReadSnippetsTool.js +7 -0
- package/dist/tools/ReadSnippets/prompt.d.ts +2 -0
- package/dist/tools/ReadSnippets/prompt.d.ts.map +1 -0
- package/dist/tools/ReadSnippets/prompt.js +6 -0
- package/dist/tools/SourcePack/SourcePackTool.d.ts +6 -0
- package/dist/tools/SourcePack/SourcePackTool.d.ts.map +1 -0
- package/dist/tools/SourcePack/SourcePackTool.js +7 -0
- package/dist/tools/SourcePack/prompt.d.ts +2 -0
- package/dist/tools/SourcePack/prompt.d.ts.map +1 -0
- package/dist/tools/SourcePack/prompt.js +6 -0
- package/dist/tools/Todo/TodoTool.js +2 -2
- package/dist/tools/prompts.d.ts.map +1 -1
- package/dist/tools/prompts.js +20 -16
- package/dist/tools/ui.d.ts.map +1 -1
- package/dist/tools/ui.js +20 -16
- package/package.json +4 -1
- package/dist/chunk-E75PYM7V.js +0 -6
- package/dist/{chunk-MFHY7PBM.js → chunk-4567YAXM.js} +3 -3
- package/dist/{chunk-DN3KJZRV.js → chunk-Z3H6SZBS.js} +3 -3
package/dist/index.js
CHANGED
|
@@ -1,64 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatFetchOutput,
|
|
3
|
+
webFetch
|
|
4
|
+
} from "./chunk-4UHQWBFA.js";
|
|
1
5
|
import {
|
|
2
6
|
applyDomainFilter,
|
|
3
7
|
bingSearch,
|
|
4
8
|
formatSearchOutput
|
|
5
9
|
} from "./chunk-EPIB4FSE.js";
|
|
6
10
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
runBashServiceLifecycleAction,
|
|
12
|
+
startBashService
|
|
13
|
+
} from "./chunk-236QJATI.js";
|
|
10
14
|
import {
|
|
11
15
|
createTool
|
|
12
16
|
} from "./chunk-MMJX44V6.js";
|
|
13
17
|
import {
|
|
14
18
|
toolPrompts
|
|
15
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-F7UOI3YR.js";
|
|
16
20
|
import {
|
|
17
21
|
toolUserFacingNames
|
|
18
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-J7O24FJB.js";
|
|
19
23
|
import "./chunk-4EZHIDZ6.js";
|
|
20
|
-
import "./chunk-YR4HWOR2.js";
|
|
21
|
-
import "./chunk-VAFW24RN.js";
|
|
22
24
|
import "./chunk-I3Z4OKMS.js";
|
|
23
25
|
import "./chunk-PS2ZOFZ6.js";
|
|
24
26
|
import "./chunk-NPKLGVDQ.js";
|
|
25
|
-
import "./chunk-MFHY7PBM.js";
|
|
26
|
-
import "./chunk-SZFZPQGL.js";
|
|
27
|
-
import "./chunk-XZWSO5CM.js";
|
|
28
|
-
import "./chunk-LGZOG7IQ.js";
|
|
29
|
-
import "./chunk-ZYAEUABC.js";
|
|
30
|
-
import "./chunk-SWK6XRZQ.js";
|
|
31
27
|
import "./chunk-VJDLOFCF.js";
|
|
32
28
|
import "./chunk-3RP665TH.js";
|
|
33
29
|
import "./chunk-IOC3BSPT.js";
|
|
34
|
-
import "./chunk-
|
|
35
|
-
import "./chunk-
|
|
36
|
-
import "./chunk-
|
|
30
|
+
import "./chunk-LGZOG7IQ.js";
|
|
31
|
+
import "./chunk-ZYAEUABC.js";
|
|
32
|
+
import "./chunk-SWK6XRZQ.js";
|
|
33
|
+
import "./chunk-YR4HWOR2.js";
|
|
34
|
+
import "./chunk-VAFW24RN.js";
|
|
37
35
|
import "./chunk-TZ6QK52L.js";
|
|
38
36
|
import "./chunk-K3YDFDLP.js";
|
|
39
|
-
import "./chunk-
|
|
37
|
+
import "./chunk-GI5XI4HN.js";
|
|
38
|
+
import "./chunk-WEWXKRKL.js";
|
|
39
|
+
import "./chunk-A6FUAM64.js";
|
|
40
|
+
import "./chunk-YCO6C7KF.js";
|
|
41
|
+
import "./chunk-4567YAXM.js";
|
|
42
|
+
import "./chunk-XZWSO5CM.js";
|
|
43
|
+
import "./chunk-SZFZPQGL.js";
|
|
40
44
|
import "./chunk-WLQ7DGMG.js";
|
|
41
45
|
import "./chunk-SQP6SJRP.js";
|
|
42
46
|
import "./chunk-BRG64AEC.js";
|
|
43
47
|
import "./chunk-AZCQL24J.js";
|
|
44
48
|
import "./chunk-HUIZOB4A.js";
|
|
45
49
|
import "./chunk-MK3YP2VR.js";
|
|
46
|
-
import "./chunk-
|
|
47
|
-
import "./chunk-E75PYM7V.js";
|
|
48
|
-
import "./chunk-GAWMEMXB.js";
|
|
50
|
+
import "./chunk-HNIVNZIS.js";
|
|
49
51
|
import "./chunk-7ZOADACP.js";
|
|
50
52
|
import "./chunk-H5OOD2OS.js";
|
|
51
53
|
import "./chunk-IQP3XE32.js";
|
|
54
|
+
import "./chunk-Z3H6SZBS.js";
|
|
55
|
+
import "./chunk-ITBZHMZQ.js";
|
|
56
|
+
import "./chunk-2U4BZXAI.js";
|
|
57
|
+
import {
|
|
58
|
+
checkArtifact
|
|
59
|
+
} from "./chunk-SPAD4DZS.js";
|
|
60
|
+
import "./chunk-2DIXRINW.js";
|
|
61
|
+
import "./chunk-PKMIDA5A.js";
|
|
62
|
+
import "./chunk-GAWMEMXB.js";
|
|
63
|
+
import {
|
|
64
|
+
inspectBinaryFile
|
|
65
|
+
} from "./chunk-XG6HNLY6.js";
|
|
66
|
+
import {
|
|
67
|
+
interpretCommandResult
|
|
68
|
+
} from "./chunk-FI4YPDFE.js";
|
|
52
69
|
|
|
53
70
|
// src/index.ts
|
|
54
71
|
import { spawn } from "child_process";
|
|
55
72
|
import { createHash, randomUUID } from "crypto";
|
|
73
|
+
import { closeSync, createWriteStream, openSync } from "fs";
|
|
56
74
|
import { mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
|
|
57
75
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
|
|
58
76
|
var toolRegistryStatus = "ready";
|
|
59
77
|
var DEFAULT_LIMIT = 200;
|
|
60
78
|
var DEFAULT_SEARCH_LIMIT = 100;
|
|
61
79
|
var DEFAULT_TOOL_TEXT_LIMIT = 5e4;
|
|
80
|
+
var MAX_READ_SNIPPET_RANGES = 20;
|
|
81
|
+
var MAX_READ_SNIPPET_LINES = 120;
|
|
82
|
+
var MAX_READ_SNIPPET_OUTPUT_CHARS = 2e5;
|
|
83
|
+
var SOURCE_PACK_DEFAULT_LIMIT = 6;
|
|
84
|
+
var SOURCE_PACK_MAX_LIMIT = 12;
|
|
85
|
+
var SOURCE_PACK_CONTEXT_LINES = 8;
|
|
86
|
+
var SOURCE_PACK_MAX_TERMS = 6;
|
|
62
87
|
var BASH_PREVIEW_LIMIT = 3e4;
|
|
63
88
|
var BASH_TIMEOUT_MS = 12e4;
|
|
64
89
|
var MAX_TODO_ITEMS = 100;
|
|
@@ -66,13 +91,16 @@ var SEARCH_EXCLUDED_DIR_NAMES = ["node_modules", "dist", ".git", ".codebase-memo
|
|
|
66
91
|
var SEARCH_EXCLUDED_PATH_PREFIXES = [".linghun/logs", ".linghun/agent-runs", ".linghun/failures"];
|
|
67
92
|
var SEARCH_EXCLUDED_FILE_SUFFIXES = [".tsbuildinfo"];
|
|
68
93
|
var RG_TIMEOUT_MS = 3e4;
|
|
69
|
-
function createToolContext(workspaceRoot = process.cwd()) {
|
|
94
|
+
function createToolContext(workspaceRoot = process.cwd(), options = {}) {
|
|
70
95
|
return {
|
|
71
96
|
workspaceRoot: resolve(workspaceRoot),
|
|
72
97
|
changedFiles: [],
|
|
73
98
|
todos: [],
|
|
74
99
|
readSnapshots: {},
|
|
75
|
-
|
|
100
|
+
sourcePackCandidates: options.sourcePackCandidates,
|
|
101
|
+
patchSummaries: {},
|
|
102
|
+
recentDiagnostics: [],
|
|
103
|
+
services: []
|
|
76
104
|
};
|
|
77
105
|
}
|
|
78
106
|
async function runTool(name, input, context) {
|
|
@@ -116,6 +144,52 @@ var toolDefinitions = {
|
|
|
116
144
|
getToolUseSummary: (input) => `Read ${input.path}`,
|
|
117
145
|
getActivityDescription: (input) => `Reading ${input.path}`
|
|
118
146
|
}),
|
|
147
|
+
ReadSnippets: defineTool({
|
|
148
|
+
name: "ReadSnippets",
|
|
149
|
+
title: "\u8BFB\u53D6\u4EE3\u7801\u7247\u6BB5",
|
|
150
|
+
description: "\u4E00\u6B21\u8BFB\u53D6\u591A\u4E2A\u5DE5\u4F5C\u533A\u6587\u4EF6\u8303\u56F4\u3002",
|
|
151
|
+
permission: {
|
|
152
|
+
risk: "low",
|
|
153
|
+
scope: "workspace",
|
|
154
|
+
reason: "\u53EA\u8BFB\u67E5\u770B\u591A\u4E2A\u6587\u4EF6\u7247\u6BB5\u3002",
|
|
155
|
+
phase06Mode: "metadata-only"
|
|
156
|
+
},
|
|
157
|
+
isReadOnly: true,
|
|
158
|
+
isConcurrencySafe: true,
|
|
159
|
+
lifecycle: {
|
|
160
|
+
...readOnlyLifecycle(),
|
|
161
|
+
maxResultSizeChars: MAX_READ_SNIPPET_OUTPUT_CHARS + 2e3
|
|
162
|
+
},
|
|
163
|
+
validateInput: validateReadSnippetsInput,
|
|
164
|
+
call: readSnippetsTool,
|
|
165
|
+
prompt: () => toolPrompts.ReadSnippets,
|
|
166
|
+
userFacingName: () => toolUserFacingNames.ReadSnippets,
|
|
167
|
+
getToolUseSummary: (input) => `ReadSnippets ${input.ranges.length} ranges`,
|
|
168
|
+
getActivityDescription: (input) => `Reading ${input.ranges.length} snippets`
|
|
169
|
+
}),
|
|
170
|
+
SourcePack: defineTool({
|
|
171
|
+
name: "SourcePack",
|
|
172
|
+
title: "\u5B9A\u4F4D\u4EE3\u7801\u7247\u6BB5",
|
|
173
|
+
description: "\u6309\u67E5\u8BE2\u5B9A\u4F4D\u5E76\u8FD4\u56DE\u5019\u9009\u4EE3\u7801\u7247\u6BB5\u3002",
|
|
174
|
+
permission: {
|
|
175
|
+
risk: "low",
|
|
176
|
+
scope: "workspace",
|
|
177
|
+
reason: "\u53EA\u8BFB\u641C\u7D22\u5E76\u8FD4\u56DE\u5019\u9009\u4EE3\u7801\u7247\u6BB5\u3002",
|
|
178
|
+
phase06Mode: "metadata-only"
|
|
179
|
+
},
|
|
180
|
+
isReadOnly: true,
|
|
181
|
+
isConcurrencySafe: true,
|
|
182
|
+
lifecycle: {
|
|
183
|
+
...readOnlyLifecycle(),
|
|
184
|
+
maxResultSizeChars: MAX_READ_SNIPPET_OUTPUT_CHARS + 2e3
|
|
185
|
+
},
|
|
186
|
+
validateInput: validateSourcePackInput,
|
|
187
|
+
call: sourcePackTool,
|
|
188
|
+
prompt: () => toolPrompts.SourcePack,
|
|
189
|
+
userFacingName: () => toolUserFacingNames.SourcePack,
|
|
190
|
+
getToolUseSummary: (input) => `SourcePack ${input.query}`,
|
|
191
|
+
getActivityDescription: (input) => `Locating snippets for ${input.query}`
|
|
192
|
+
}),
|
|
119
193
|
Write: defineTool({
|
|
120
194
|
name: "Write",
|
|
121
195
|
title: "\u5199\u5165\u6587\u4EF6",
|
|
@@ -234,7 +308,7 @@ var toolDefinitions = {
|
|
|
234
308
|
call: bashTool,
|
|
235
309
|
prompt: () => toolPrompts.Bash,
|
|
236
310
|
userFacingName: () => toolUserFacingNames.Bash,
|
|
237
|
-
getToolUseSummary: (input) => `Bash ${input.command}`,
|
|
311
|
+
getToolUseSummary: (input) => input.artifact ? `Bash artifact ${input.artifact.path}` : input.binary ? `Bash binary ${input.binary.path}` : `Bash ${input.command}`,
|
|
238
312
|
getActivityDescription: () => "Running command"
|
|
239
313
|
}),
|
|
240
314
|
Todo: defineTool({
|
|
@@ -420,6 +494,46 @@ function validateReadInput(input) {
|
|
|
420
494
|
limit: readOptionalPositiveInteger(record, "limit", "Read")
|
|
421
495
|
};
|
|
422
496
|
}
|
|
497
|
+
function validateReadSnippetsInput(input) {
|
|
498
|
+
const record = validateRecord(input, "ReadSnippets");
|
|
499
|
+
const ranges = record.ranges;
|
|
500
|
+
if (!Array.isArray(ranges) || ranges.length === 0) {
|
|
501
|
+
throw new Error("ReadSnippets.ranges \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4\u3002");
|
|
502
|
+
}
|
|
503
|
+
if (ranges.length > MAX_READ_SNIPPET_RANGES) {
|
|
504
|
+
throw new Error(`ReadSnippets.ranges \u6700\u591A ${MAX_READ_SNIPPET_RANGES} \u4E2A\u8303\u56F4\u3002`);
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
ranges: ranges.map((item, index) => {
|
|
508
|
+
const range = validateRecord(item, "ReadSnippets");
|
|
509
|
+
const start = readPositiveLineNumber(range, "start", "ReadSnippets");
|
|
510
|
+
const end = readPositiveLineNumber(range, "end", "ReadSnippets");
|
|
511
|
+
if (end < start) {
|
|
512
|
+
throw new Error(`ReadSnippets.ranges[${index}].end \u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E start\u3002`);
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
path: readString(range, "path", "ReadSnippets"),
|
|
516
|
+
start,
|
|
517
|
+
end
|
|
518
|
+
};
|
|
519
|
+
})
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function validateSourcePackInput(input) {
|
|
523
|
+
const record = validateRecord(input, "SourcePack");
|
|
524
|
+
const limit = readOptionalPositiveInteger(record, "limit", "SourcePack");
|
|
525
|
+
return {
|
|
526
|
+
query: readString(record, "query", "SourcePack"),
|
|
527
|
+
limit: limit === void 0 ? void 0 : Math.min(Math.max(limit, 1), SOURCE_PACK_MAX_LIMIT)
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function readPositiveLineNumber(record, key, toolName) {
|
|
531
|
+
const value = record[key];
|
|
532
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
533
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F 1-based \u6B63\u6574\u6570\u884C\u53F7\u3002`);
|
|
534
|
+
}
|
|
535
|
+
return value;
|
|
536
|
+
}
|
|
423
537
|
function validateWriteInput(input) {
|
|
424
538
|
const record = validateRecord(input, "Write");
|
|
425
539
|
return {
|
|
@@ -472,12 +586,122 @@ function validateGlobInput(input) {
|
|
|
472
586
|
};
|
|
473
587
|
}
|
|
474
588
|
function validateBashInput(input) {
|
|
589
|
+
const record = validateRecord(input, "Bash");
|
|
590
|
+
const command = readOptionalString(record, "command", "Bash");
|
|
591
|
+
const binary = validateOptionalBashBinaryInspect(record.binary);
|
|
592
|
+
const artifact = validateOptionalBashArtifactCheck(record.artifact);
|
|
593
|
+
const service = validateOptionalBashServiceInput(record.service);
|
|
594
|
+
const serviceAction = isBashServiceLifecycleAction(service);
|
|
595
|
+
if (command === void 0 && binary === void 0 && artifact === void 0 && !serviceAction) {
|
|
596
|
+
throw new Error("Bash.command\u3001Bash.binary\u3001Bash.artifact \u6216 Bash.service action \u5FC5\u987B\u63D0\u4F9B\u4E00\u4E2A\u3002");
|
|
597
|
+
}
|
|
598
|
+
const modeCount = [
|
|
599
|
+
command !== void 0,
|
|
600
|
+
binary !== void 0,
|
|
601
|
+
artifact !== void 0,
|
|
602
|
+
serviceAction
|
|
603
|
+
].filter(Boolean).length;
|
|
604
|
+
if (modeCount > 1 || (binary !== void 0 || artifact !== void 0) && service !== void 0) {
|
|
605
|
+
throw new Error("Bash.command\u3001Bash.binary\u3001Bash.artifact \u548C Bash.service \u4E0D\u80FD\u6DF7\u7528\u3002");
|
|
606
|
+
}
|
|
607
|
+
if (service !== void 0 && !serviceAction && command === void 0) {
|
|
608
|
+
throw new Error("Bash.service readiness \u5FC5\u987B\u548C Bash.command \u4E00\u8D77\u4F7F\u7528\u3002");
|
|
609
|
+
}
|
|
610
|
+
const runInBackground = record.runInBackground === true || record.run_in_background === true;
|
|
611
|
+
return {
|
|
612
|
+
command,
|
|
613
|
+
timeoutMs: readOptionalPositiveInteger(record, "timeoutMs", "Bash"),
|
|
614
|
+
service,
|
|
615
|
+
binary,
|
|
616
|
+
artifact,
|
|
617
|
+
...runInBackground ? { runInBackground: true } : {}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function validateOptionalBashBinaryInspect(input) {
|
|
621
|
+
if (input === void 0) return void 0;
|
|
622
|
+
if (isEmptyOptionalObject(input)) return void 0;
|
|
623
|
+
const record = validateRecord(input, "Bash");
|
|
624
|
+
return {
|
|
625
|
+
path: readString(record, "path", "Bash"),
|
|
626
|
+
previewBytes: readOptionalPositiveInteger(record, "previewBytes", "Bash")
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function validateOptionalBashArtifactCheck(input) {
|
|
630
|
+
if (input === void 0) return void 0;
|
|
631
|
+
if (isEmptyOptionalObject(input)) return void 0;
|
|
475
632
|
const record = validateRecord(input, "Bash");
|
|
476
633
|
return {
|
|
477
|
-
|
|
478
|
-
|
|
634
|
+
path: readString(record, "path", "Bash"),
|
|
635
|
+
expectHeader: readOptionalString(record, "expectHeader", "Bash"),
|
|
636
|
+
expectMagic: readOptionalString(record, "expectMagic", "Bash"),
|
|
637
|
+
json: readOptionalBoolean(record, "json", "Bash"),
|
|
638
|
+
executable: readOptionalBoolean(record, "executable", "Bash"),
|
|
639
|
+
protectPaths: readOptionalStringArray(record, "protectPaths", "Bash"),
|
|
640
|
+
text: validateOptionalBashArtifactTextCheck(record.text),
|
|
641
|
+
preserve: validateOptionalBashArtifactPreserveCheck(record.preserve)
|
|
479
642
|
};
|
|
480
643
|
}
|
|
644
|
+
function validateOptionalBashServiceInput(input) {
|
|
645
|
+
if (input === void 0) return void 0;
|
|
646
|
+
if (isEmptyOptionalObject(input)) return void 0;
|
|
647
|
+
const record = validateRecord(input, "Bash");
|
|
648
|
+
const action = readOptionalString(record, "action", "Bash");
|
|
649
|
+
if (action !== void 0) {
|
|
650
|
+
if (action === "fetch") {
|
|
651
|
+
const url = readString(record, "url", "Bash");
|
|
652
|
+
validateHttpUrl(url);
|
|
653
|
+
return {
|
|
654
|
+
action,
|
|
655
|
+
url,
|
|
656
|
+
expectStatus: readOptionalPositiveInteger(record, "expectStatus", "Bash"),
|
|
657
|
+
bodyContains: readOptionalStringOrStringArray(record, "bodyContains", "Bash"),
|
|
658
|
+
timeoutMs: readOptionalPositiveInteger(record, "timeoutMs", "Bash"),
|
|
659
|
+
retry: readOptionalPositiveInteger(record, "retry", "Bash"),
|
|
660
|
+
intervalMs: readOptionalPositiveInteger(record, "intervalMs", "Bash")
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
if (action === "status" || action === "probe" || action === "logs" || action === "stop") {
|
|
664
|
+
return {
|
|
665
|
+
action,
|
|
666
|
+
serviceId: readString(record, "serviceId", "Bash"),
|
|
667
|
+
...action === "logs" ? { tailBytes: readOptionalPositiveInteger(record, "tailBytes", "Bash") } : {}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
throw new Error("Bash.service.action \u5FC5\u987B\u662F status/probe/logs/stop/fetch\u3002");
|
|
671
|
+
}
|
|
672
|
+
const type = readString(record, "type", "Bash");
|
|
673
|
+
const timeoutMs = readOptionalPositiveInteger(record, "timeoutMs", "Bash");
|
|
674
|
+
const intervalMs = readOptionalPositiveInteger(record, "intervalMs", "Bash");
|
|
675
|
+
if (type === "tcp") {
|
|
676
|
+
const rawPort = record.port;
|
|
677
|
+
if (!Number.isInteger(rawPort) || rawPort <= 0) {
|
|
678
|
+
throw new Error("Bash.service.port \u5FC5\u987B\u662F\u6B63\u6574\u6570\u3002");
|
|
679
|
+
}
|
|
680
|
+
const port = rawPort;
|
|
681
|
+
if (port > 65535) {
|
|
682
|
+
throw new Error("Bash.service.port \u5FC5\u987B\u5728 1-65535 \u8303\u56F4\u5185\u3002");
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
type,
|
|
686
|
+
port,
|
|
687
|
+
host: readOptionalString(record, "host", "Bash"),
|
|
688
|
+
timeoutMs,
|
|
689
|
+
intervalMs
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
if (type === "http") {
|
|
693
|
+
const url = readString(record, "url", "Bash");
|
|
694
|
+
validateHttpUrl(url);
|
|
695
|
+
return { type, url, timeoutMs, intervalMs };
|
|
696
|
+
}
|
|
697
|
+
throw new Error("Bash.service.type \u5FC5\u987B\u662F tcp \u6216 http\u3002");
|
|
698
|
+
}
|
|
699
|
+
function isEmptyOptionalObject(input) {
|
|
700
|
+
return input === null || typeof input === "object" && !Array.isArray(input) && Object.keys(input).length === 0;
|
|
701
|
+
}
|
|
702
|
+
function isBashServiceLifecycleAction(input) {
|
|
703
|
+
return Boolean(input && "action" in input);
|
|
704
|
+
}
|
|
481
705
|
function validateTodoInput(input) {
|
|
482
706
|
const record = validateRecord(input, "Todo");
|
|
483
707
|
const action = readString(record, "action", "Todo");
|
|
@@ -535,6 +759,53 @@ function readOptionalStringArray(record, key, toolName) {
|
|
|
535
759
|
}
|
|
536
760
|
return value;
|
|
537
761
|
}
|
|
762
|
+
function readOptionalBoolean(record, key, toolName) {
|
|
763
|
+
const value = record[key];
|
|
764
|
+
if (value === void 0) return void 0;
|
|
765
|
+
if (typeof value !== "boolean") {
|
|
766
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F boolean \u6216 undefined\u3002`);
|
|
767
|
+
}
|
|
768
|
+
return value;
|
|
769
|
+
}
|
|
770
|
+
function readOptionalStringOrStringArray(record, key, toolName) {
|
|
771
|
+
const value = record[key];
|
|
772
|
+
if (value === void 0) return void 0;
|
|
773
|
+
if (typeof value === "string") return value;
|
|
774
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "string")) return value;
|
|
775
|
+
throw new Error(`${toolName}.${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3001\u5B57\u7B26\u4E32\u6570\u7EC4\u6216 undefined\u3002`);
|
|
776
|
+
}
|
|
777
|
+
function validateOptionalBashArtifactTextCheck(input) {
|
|
778
|
+
if (input === void 0) return void 0;
|
|
779
|
+
const record = validateRecord(input, "Bash");
|
|
780
|
+
return {
|
|
781
|
+
exact: readOptionalString(record, "exact", "Bash"),
|
|
782
|
+
contains: readOptionalStringOrStringArray(record, "contains", "Bash"),
|
|
783
|
+
lineSet: readOptionalStringArray(record, "lineSet", "Bash")
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function validateOptionalBashArtifactPreserveCheck(input) {
|
|
787
|
+
if (input === void 0) return void 0;
|
|
788
|
+
const record = validateRecord(input, "Bash");
|
|
789
|
+
const mode = readString(record, "mode", "Bash");
|
|
790
|
+
if (mode !== "rawPreserve" && mode !== "compareNormalizedHtml") {
|
|
791
|
+
throw new Error("Bash.artifact.preserve.mode \u5FC5\u987B\u662F rawPreserve \u6216 compareNormalizedHtml\u3002");
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
mode,
|
|
795
|
+
expectedPath: readOptionalString(record, "expectedPath", "Bash"),
|
|
796
|
+
expectedText: readOptionalString(record, "expectedText", "Bash")
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function validateHttpUrl(url) {
|
|
800
|
+
try {
|
|
801
|
+
const parsed = new URL(url);
|
|
802
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
803
|
+
throw new Error("protocol");
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
throw new Error("Bash.service.url \u5FC5\u987B\u662F http/https URL\u3002");
|
|
807
|
+
}
|
|
808
|
+
}
|
|
538
809
|
async function readTool(input, context) {
|
|
539
810
|
const filePath = resolveWorkspacePath(context.workspaceRoot, input.path);
|
|
540
811
|
const content = await readFile(filePath, "utf8");
|
|
@@ -566,6 +837,183 @@ async function readTool(input, context) {
|
|
|
566
837
|
truncated
|
|
567
838
|
};
|
|
568
839
|
}
|
|
840
|
+
async function readSnippetsTool(input, context) {
|
|
841
|
+
const ranges = [];
|
|
842
|
+
let remainingChars = MAX_READ_SNIPPET_OUTPUT_CHARS;
|
|
843
|
+
let safetyTruncated = false;
|
|
844
|
+
for (const range of input.ranges) {
|
|
845
|
+
const requestedEnd = range.end;
|
|
846
|
+
const boundedEnd = Math.min(range.end, range.start + MAX_READ_SNIPPET_LINES - 1);
|
|
847
|
+
const rangeLineTruncated = boundedEnd < requestedEnd;
|
|
848
|
+
try {
|
|
849
|
+
const filePath = resolveWorkspacePath(context.workspaceRoot, range.path);
|
|
850
|
+
const content = await readFile(filePath, "utf8");
|
|
851
|
+
const info = await stat(filePath);
|
|
852
|
+
rememberReadSnapshot(context, filePath, content, info);
|
|
853
|
+
const lines = splitContentLines(content);
|
|
854
|
+
const selected = lines.slice(range.start - 1, boundedEnd);
|
|
855
|
+
const actualEnd = selected.length > 0 ? range.start + selected.length - 1 : range.start - 1;
|
|
856
|
+
const numbered = selected.map((line, index) => `${range.start + index} ${line}`).join("\n");
|
|
857
|
+
const capped = applySnippetSafetyCap(numbered, remainingChars);
|
|
858
|
+
remainingChars -= capped.usedChars;
|
|
859
|
+
safetyTruncated ||= capped.truncated;
|
|
860
|
+
ranges.push({
|
|
861
|
+
path: relativePath(context.workspaceRoot, filePath),
|
|
862
|
+
start: range.start,
|
|
863
|
+
end: actualEnd,
|
|
864
|
+
content: capped.content,
|
|
865
|
+
requestedStart: range.start,
|
|
866
|
+
requestedEnd,
|
|
867
|
+
totalLines: lines.length,
|
|
868
|
+
truncated: rangeLineTruncated || capped.truncated
|
|
869
|
+
});
|
|
870
|
+
if (capped.truncated) {
|
|
871
|
+
remainingChars = 0;
|
|
872
|
+
}
|
|
873
|
+
} catch (error) {
|
|
874
|
+
ranges.push({
|
|
875
|
+
path: range.path,
|
|
876
|
+
start: range.start,
|
|
877
|
+
end: boundedEnd,
|
|
878
|
+
content: "",
|
|
879
|
+
requestedStart: range.start,
|
|
880
|
+
requestedEnd,
|
|
881
|
+
truncated: rangeLineTruncated,
|
|
882
|
+
error: formatDiagnosticError(error)
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const text = formatReadSnippetsOutput(ranges, safetyTruncated);
|
|
887
|
+
const okCount = ranges.filter((range) => !range.error).length;
|
|
888
|
+
return {
|
|
889
|
+
text,
|
|
890
|
+
summary: `ReadSnippets: ${okCount}/${ranges.length} ranges`,
|
|
891
|
+
data: {
|
|
892
|
+
ranges,
|
|
893
|
+
count: okCount,
|
|
894
|
+
requestedRanges: input.ranges.length,
|
|
895
|
+
safetyTruncated
|
|
896
|
+
},
|
|
897
|
+
truncated: ranges.some((range) => range.truncated) || safetyTruncated
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function applySnippetSafetyCap(content, remainingChars) {
|
|
901
|
+
if (remainingChars <= 0) {
|
|
902
|
+
return {
|
|
903
|
+
content: "...\uFF08\u7ED3\u679C\u5DF2\u622A\u65AD\uFF0C\u540E\u7EED\u5185\u5BB9\u7701\u7565\uFF1B\u5982\u9700\u7CBE\u8BFB\u8BF7\u6307\u5B9A\u66F4\u5C0F\u8303\u56F4\u3002\uFF09",
|
|
904
|
+
usedChars: 0,
|
|
905
|
+
truncated: true
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
if (content.length <= remainingChars) {
|
|
909
|
+
return { content, usedChars: content.length, truncated: false };
|
|
910
|
+
}
|
|
911
|
+
const suffix = "\n...\uFF08\u7ED3\u679C\u5DF2\u622A\u65AD\uFF0C\u540E\u7EED\u5185\u5BB9\u7701\u7565\uFF1B\u5982\u9700\u7CBE\u8BFB\u8BF7\u6307\u5B9A\u66F4\u5C0F\u8303\u56F4\u3002\uFF09";
|
|
912
|
+
const sliceLength = Math.max(0, remainingChars - suffix.length);
|
|
913
|
+
return {
|
|
914
|
+
content: `${content.slice(0, sliceLength)}${suffix}`,
|
|
915
|
+
usedChars: remainingChars,
|
|
916
|
+
truncated: true
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function formatReadSnippetsOutput(ranges, safetyTruncated) {
|
|
920
|
+
const lines = [];
|
|
921
|
+
for (const range of ranges) {
|
|
922
|
+
const header = `${range.path}:${range.start}-${range.end}`;
|
|
923
|
+
if (range.error) {
|
|
924
|
+
lines.push(`${header}
|
|
925
|
+
ERROR: ${range.error}`);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
lines.push(`${header}
|
|
929
|
+
${range.content}`);
|
|
930
|
+
if (range.truncated) {
|
|
931
|
+
lines.push("...\uFF08\u8BE5\u8303\u56F4\u5DF2\u622A\u65AD\uFF1B\u53EF\u7EE7\u7EED\u8BFB\u53D6\u540E\u7EED\u8303\u56F4\u3002\uFF09");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
if (safetyTruncated) {
|
|
935
|
+
lines.push("...\uFF08\u7ED3\u679C\u5DF2\u622A\u65AD\uFF0C\u540E\u7EED\u5185\u5BB9\u7701\u7565\uFF1B\u5982\u9700\u7CBE\u8BFB\u8BF7\u6307\u5B9A\u66F4\u5C0F\u8303\u56F4\u3002\uFF09");
|
|
936
|
+
}
|
|
937
|
+
return lines.join("\n\n");
|
|
938
|
+
}
|
|
939
|
+
async function sourcePackTool(input, context) {
|
|
940
|
+
const limit = input.limit ?? SOURCE_PACK_DEFAULT_LIMIT;
|
|
941
|
+
const terms = extractSourcePackTerms(input.query);
|
|
942
|
+
if (terms.length === 0) {
|
|
943
|
+
return {
|
|
944
|
+
text: "empty: query did not contain searchable terms.",
|
|
945
|
+
summary: "SourcePack: 0 snippets",
|
|
946
|
+
data: { query: input.query, snippets: [], count: 0, empty: true }
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
const matchResult = await findSourcePackMatches(input.query, terms, context, limit * 4);
|
|
950
|
+
const snippets = [];
|
|
951
|
+
const seen = /* @__PURE__ */ new Set();
|
|
952
|
+
let remainingChars = MAX_READ_SNIPPET_OUTPUT_CHARS;
|
|
953
|
+
let safetyTruncated = false;
|
|
954
|
+
for (const match of matchResult.matches) {
|
|
955
|
+
if (snippets.length >= limit) break;
|
|
956
|
+
const start = match.start ?? Math.max(1, match.line - SOURCE_PACK_CONTEXT_LINES);
|
|
957
|
+
const end = match.end ?? match.line + SOURCE_PACK_CONTEXT_LINES;
|
|
958
|
+
const key = `${match.path}:${start}:${end}`;
|
|
959
|
+
if (seen.has(key)) continue;
|
|
960
|
+
seen.add(key);
|
|
961
|
+
try {
|
|
962
|
+
const filePath = resolveWorkspacePath(context.workspaceRoot, match.path);
|
|
963
|
+
const content = await readFile(filePath, "utf8");
|
|
964
|
+
const info = await stat(filePath);
|
|
965
|
+
rememberReadSnapshot(context, filePath, content, info);
|
|
966
|
+
const lines = splitContentLines(content);
|
|
967
|
+
const boundedEnd = Math.min(end, lines.length);
|
|
968
|
+
const selected = lines.slice(start - 1, boundedEnd);
|
|
969
|
+
const numbered = selected.map((line, index) => `${start + index} ${line}`).join("\n");
|
|
970
|
+
const capped = applySnippetSafetyCap(numbered, remainingChars);
|
|
971
|
+
remainingChars -= capped.usedChars;
|
|
972
|
+
safetyTruncated ||= capped.truncated;
|
|
973
|
+
snippets.push({
|
|
974
|
+
path: relativePath(context.workspaceRoot, filePath),
|
|
975
|
+
start,
|
|
976
|
+
end: boundedEnd,
|
|
977
|
+
content: capped.content,
|
|
978
|
+
requestedStart: start,
|
|
979
|
+
requestedEnd: end,
|
|
980
|
+
totalLines: lines.length,
|
|
981
|
+
truncated: capped.truncated,
|
|
982
|
+
reason: match.reason ?? `matched "${match.term}" at line ${match.line}`,
|
|
983
|
+
confidence: estimateSourcePackConfidence(input.query, match),
|
|
984
|
+
source: match.source
|
|
985
|
+
});
|
|
986
|
+
if (capped.truncated) {
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
} catch {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
if (snippets.length === 0) {
|
|
994
|
+
return {
|
|
995
|
+
text: "empty: no matching source snippets found.",
|
|
996
|
+
summary: "SourcePack: 0 snippets",
|
|
997
|
+
data: { query: input.query, snippets: [], count: 0, empty: true }
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
const text = formatSourcePackOutput(snippets, safetyTruncated);
|
|
1001
|
+
return {
|
|
1002
|
+
text,
|
|
1003
|
+
summary: `SourcePack: ${snippets.length} snippets`,
|
|
1004
|
+
data: {
|
|
1005
|
+
query: input.query,
|
|
1006
|
+
snippets,
|
|
1007
|
+
count: snippets.length,
|
|
1008
|
+
searchedTerms: terms,
|
|
1009
|
+
source: matchResult.source,
|
|
1010
|
+
fallback: matchResult.source !== "index",
|
|
1011
|
+
candidatePaths: unique(snippets.map((snippet) => snippet.path)),
|
|
1012
|
+
safetyTruncated
|
|
1013
|
+
},
|
|
1014
|
+
truncated: safetyTruncated || snippets.some((snippet) => snippet.truncated)
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
569
1017
|
function splitContentLines(content) {
|
|
570
1018
|
if (content.length === 0) {
|
|
571
1019
|
return [];
|
|
@@ -734,11 +1182,70 @@ function sanitizeSecrets(text) {
|
|
|
734
1182
|
return sanitized;
|
|
735
1183
|
}
|
|
736
1184
|
async function bashTool(input, context) {
|
|
1185
|
+
if (input.artifact) {
|
|
1186
|
+
return checkArtifact({
|
|
1187
|
+
input: input.artifact,
|
|
1188
|
+
target: resolveArtifactPath(context, input.artifact.path),
|
|
1189
|
+
protectPaths: (input.artifact.protectPaths ?? []).map((path) => resolveArtifactPath(context, path)),
|
|
1190
|
+
context
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
if (input.binary) {
|
|
1194
|
+
return inspectBinaryFile(input.binary, resolveWorkspacePath(context.workspaceRoot, input.binary.path));
|
|
1195
|
+
}
|
|
1196
|
+
if (!input.command) {
|
|
1197
|
+
if (isBashServiceLifecycleAction(input.service)) {
|
|
1198
|
+
return runBashServiceLifecycleAction(input.service, context);
|
|
1199
|
+
}
|
|
1200
|
+
throw new Error("Bash.command\u3001Bash.binary \u6216 Bash.service action \u5FC5\u987B\u63D0\u4F9B\u4E00\u4E2A\u3002");
|
|
1201
|
+
}
|
|
737
1202
|
const logRoot = context.logRoot ?? join(context.workspaceRoot, ".linghun", "logs", "tools");
|
|
738
1203
|
await mkdir(logRoot, { recursive: true });
|
|
739
1204
|
const fullOutputPath = join(logRoot, `bash-${Date.now()}-${randomUUID()}.log`);
|
|
740
1205
|
const timeoutMs = input.timeoutMs ?? BASH_TIMEOUT_MS;
|
|
741
1206
|
const adapted = adaptShellCommand(input.command);
|
|
1207
|
+
if (input.service && !isBashServiceLifecycleAction(input.service)) {
|
|
1208
|
+
const readiness = withDefaultServiceReadiness(input.service, context);
|
|
1209
|
+
return startBashService({
|
|
1210
|
+
command: adapted.command,
|
|
1211
|
+
logCommand: adapted.logCommand ?? adapted.command,
|
|
1212
|
+
cwd: context.workspaceRoot,
|
|
1213
|
+
fullOutputPath,
|
|
1214
|
+
readiness,
|
|
1215
|
+
context,
|
|
1216
|
+
abortSignal: context.abortSignal,
|
|
1217
|
+
onProgress: (stream, text) => void context.onProgress?.({ toolName: "Bash", stream, text }),
|
|
1218
|
+
trackChildProcess: context.trackChildProcess,
|
|
1219
|
+
sanitizeText: sanitizeSecrets
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
if (input.runInBackground === true) {
|
|
1223
|
+
const taskId = randomUUID();
|
|
1224
|
+
void runBackgroundBash({
|
|
1225
|
+
taskId,
|
|
1226
|
+
command: adapted.command,
|
|
1227
|
+
originalCommand: input.command,
|
|
1228
|
+
cwd: context.workspaceRoot,
|
|
1229
|
+
timeoutMs,
|
|
1230
|
+
fullOutputPath,
|
|
1231
|
+
adapter: adapted.adapter,
|
|
1232
|
+
logCommand: adapted.logCommand,
|
|
1233
|
+
retainAfterReturn: isHeadlessBenchContext(context),
|
|
1234
|
+
abortSignal: context.abortSignal,
|
|
1235
|
+
trackChildProcess: context.trackChildProcess,
|
|
1236
|
+
onComplete: context.onBackgroundBashComplete
|
|
1237
|
+
});
|
|
1238
|
+
return {
|
|
1239
|
+
text: `\u547D\u4EE4\u5DF2\u5728\u540E\u53F0\u542F\u52A8\u3002
|
|
1240
|
+
taskId: ${taskId}
|
|
1241
|
+
outputPath: ${fullOutputPath}`,
|
|
1242
|
+
data: {
|
|
1243
|
+
backgroundTaskId: taskId,
|
|
1244
|
+
outputPath: fullOutputPath,
|
|
1245
|
+
command: sanitizeSecrets(input.command)
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
742
1249
|
const result = await runShell(
|
|
743
1250
|
adapted.command,
|
|
744
1251
|
context.workspaceRoot,
|
|
@@ -752,23 +1259,35 @@ async function bashTool(input, context) {
|
|
|
752
1259
|
`original command ${summarizeOriginalShellCommand(input.command)}`
|
|
753
1260
|
];
|
|
754
1261
|
const commandForLog = adapted.logCommand ?? adapted.command;
|
|
755
|
-
const
|
|
1262
|
+
const sanitizedShellOutput = sanitizeSecrets(result.output);
|
|
1263
|
+
const cmdInterpretation = interpretCommandResult(input.command, result.exitCode);
|
|
1264
|
+
const rawFullText = [
|
|
756
1265
|
`$ ${sanitizeSecrets(commandForLog)}`,
|
|
757
1266
|
...adapterLines,
|
|
758
1267
|
`exit code ${result.exitCode}`,
|
|
759
1268
|
`outcome ${result.outcome}`,
|
|
1269
|
+
...cmdInterpretation.message ? [`returnCodeInterpretation: ${cmdInterpretation.message}`] : [],
|
|
760
1270
|
"",
|
|
761
|
-
|
|
1271
|
+
sanitizedShellOutput
|
|
762
1272
|
].join("\n");
|
|
1273
|
+
const diagnostics = createBashOutcomeDiagnostics(result.outcome);
|
|
1274
|
+
const fullText = rawFullText;
|
|
763
1275
|
await writeFile(fullOutputPath, fullText, "utf8");
|
|
764
1276
|
const truncated = fullText.length > BASH_PREVIEW_LIMIT;
|
|
765
1277
|
const preview = truncated ? `${fullText.slice(0, BASH_PREVIEW_LIMIT)}
|
|
766
1278
|
...\uFF08\u8F93\u51FA\u5DF2\u622A\u65AD\uFF0C\u5B8C\u6574\u65E5\u5FD7\u89C1 fullOutputPath\uFF09` : fullText;
|
|
767
1279
|
const details = createBashDetails(fullOutputPath, fullText);
|
|
1280
|
+
const data = adapted.adapter === "native" ? { exitCode: result.exitCode, outcome: result.outcome } : { exitCode: result.exitCode, outcome: result.outcome, adapter: adapted.adapter };
|
|
1281
|
+
const outputData = {
|
|
1282
|
+
...data,
|
|
1283
|
+
...cmdInterpretation.message ? { returnCodeInterpretation: cmdInterpretation.message } : {},
|
|
1284
|
+
...cmdInterpretation.isError === false && result.exitCode !== 0 ? { isError: false } : {},
|
|
1285
|
+
...diagnostics.length > 0 ? { diagnostics } : {}
|
|
1286
|
+
};
|
|
768
1287
|
return {
|
|
769
1288
|
text: preview,
|
|
770
1289
|
details,
|
|
771
|
-
data:
|
|
1290
|
+
data: outputData,
|
|
772
1291
|
truncated,
|
|
773
1292
|
fullOutputPath
|
|
774
1293
|
};
|
|
@@ -782,6 +1301,513 @@ function createBashDetails(fullOutputPath, fullText) {
|
|
|
782
1301
|
...tailLines(fullText, 80)
|
|
783
1302
|
].join("\n");
|
|
784
1303
|
}
|
|
1304
|
+
var BINARY_HINT_EXTENSIONS = [
|
|
1305
|
+
".bin",
|
|
1306
|
+
".elf",
|
|
1307
|
+
".o",
|
|
1308
|
+
".so",
|
|
1309
|
+
".a",
|
|
1310
|
+
".exe",
|
|
1311
|
+
".dll",
|
|
1312
|
+
".7z",
|
|
1313
|
+
".zip",
|
|
1314
|
+
".gz",
|
|
1315
|
+
".png",
|
|
1316
|
+
".jpg",
|
|
1317
|
+
".jpeg",
|
|
1318
|
+
".pdf",
|
|
1319
|
+
".sqlite",
|
|
1320
|
+
".db"
|
|
1321
|
+
];
|
|
1322
|
+
var ARTIFACT_HINT_EXTENSIONS = [
|
|
1323
|
+
".txt",
|
|
1324
|
+
".json",
|
|
1325
|
+
".out",
|
|
1326
|
+
".log",
|
|
1327
|
+
".py",
|
|
1328
|
+
".html",
|
|
1329
|
+
".htm",
|
|
1330
|
+
".csv",
|
|
1331
|
+
".yaml",
|
|
1332
|
+
".yml",
|
|
1333
|
+
".toml",
|
|
1334
|
+
".md",
|
|
1335
|
+
".sh",
|
|
1336
|
+
".bin",
|
|
1337
|
+
".elf",
|
|
1338
|
+
".zip",
|
|
1339
|
+
".7z",
|
|
1340
|
+
".png",
|
|
1341
|
+
".pdf",
|
|
1342
|
+
".sqlite",
|
|
1343
|
+
".db"
|
|
1344
|
+
];
|
|
1345
|
+
function parseBashCommandIntent(command, _context) {
|
|
1346
|
+
const tokens = tokenizeShellLike(command);
|
|
1347
|
+
const segments = splitCommandSegments(tokens);
|
|
1348
|
+
const commandNames = [];
|
|
1349
|
+
const parsedSegments = [];
|
|
1350
|
+
const binaryCandidates = /* @__PURE__ */ new Set();
|
|
1351
|
+
const artifactCandidates = /* @__PURE__ */ new Set();
|
|
1352
|
+
const serviceCandidates = [];
|
|
1353
|
+
for (const segment of segments) {
|
|
1354
|
+
const parsed = parseCommandSegment(segment.tokens);
|
|
1355
|
+
if (!parsed) continue;
|
|
1356
|
+
commandNames.push(parsed.commandName);
|
|
1357
|
+
parsedSegments.push({
|
|
1358
|
+
commandName: parsed.commandName,
|
|
1359
|
+
argv: parsed.argv,
|
|
1360
|
+
background: segment.background || parsed.background,
|
|
1361
|
+
redirections: collectSegmentRedirections(parsed.argv)
|
|
1362
|
+
});
|
|
1363
|
+
collectSegmentBinaryCandidates(parsed, binaryCandidates);
|
|
1364
|
+
collectSegmentArtifactCandidates(parsed, artifactCandidates);
|
|
1365
|
+
serviceCandidates.push(...collectSegmentServiceCandidates(parsed));
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
binaryCandidates: [...binaryCandidates],
|
|
1369
|
+
serviceCandidates: dedupeServiceCandidates(serviceCandidates),
|
|
1370
|
+
artifactCandidates: [...artifactCandidates],
|
|
1371
|
+
backgroundLikely: isBackgroundLikely(segments, serviceCandidates),
|
|
1372
|
+
commandNames: unique(commandNames.filter(Boolean)),
|
|
1373
|
+
segments: parsedSegments,
|
|
1374
|
+
hasShellControl: segments.length !== 1 || tokens.some((token) => isCommandSeparator(token))
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
var BINARY_COMMAND_NAMES = /* @__PURE__ */ new Set(["file", "xxd", "readelf", "objdump", "hexdump", "strings", "7z", "7za", "unzip", "tar"]);
|
|
1378
|
+
var SHELL_RESERVED_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
1379
|
+
"if",
|
|
1380
|
+
"then",
|
|
1381
|
+
"else",
|
|
1382
|
+
"elif",
|
|
1383
|
+
"fi",
|
|
1384
|
+
"for",
|
|
1385
|
+
"while",
|
|
1386
|
+
"until",
|
|
1387
|
+
"do",
|
|
1388
|
+
"done",
|
|
1389
|
+
"case",
|
|
1390
|
+
"esac",
|
|
1391
|
+
"in",
|
|
1392
|
+
"function",
|
|
1393
|
+
"coproc"
|
|
1394
|
+
]);
|
|
1395
|
+
var SHELL_SYNTAX_COMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
1396
|
+
"[",
|
|
1397
|
+
"[[",
|
|
1398
|
+
"test",
|
|
1399
|
+
"{",
|
|
1400
|
+
"}",
|
|
1401
|
+
"(",
|
|
1402
|
+
")",
|
|
1403
|
+
"!",
|
|
1404
|
+
":",
|
|
1405
|
+
"true",
|
|
1406
|
+
"false"
|
|
1407
|
+
]);
|
|
1408
|
+
var ARTIFACT_PRODUCING_COMMAND_NAMES = /* @__PURE__ */ new Set(["touch", "tee"]);
|
|
1409
|
+
var ARTIFACT_PRODUCING_TOKENS = /* @__PURE__ */ new Set(["write", "wrote", "written", "create", "created", "generate", "generated", "build", "built", "output"]);
|
|
1410
|
+
function canSafelyAliasPythonCommand(intent) {
|
|
1411
|
+
const segment = intent.segments[0];
|
|
1412
|
+
return intent.segments.length === 1 && !intent.hasShellControl && segment?.commandName === "python" && !segment.background && segment.redirections.length === 0;
|
|
1413
|
+
}
|
|
1414
|
+
function tokenizeShellLike(command) {
|
|
1415
|
+
const tokens = [];
|
|
1416
|
+
let current = "";
|
|
1417
|
+
let quote;
|
|
1418
|
+
let escaped = false;
|
|
1419
|
+
const push = () => {
|
|
1420
|
+
if (current.length > 0) tokens.push(current);
|
|
1421
|
+
current = "";
|
|
1422
|
+
};
|
|
1423
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
1424
|
+
const char = command[index] ?? "";
|
|
1425
|
+
if (escaped) {
|
|
1426
|
+
current += char;
|
|
1427
|
+
escaped = false;
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
if (char === "\\") {
|
|
1431
|
+
escaped = true;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
if (quote) {
|
|
1435
|
+
if (char === quote) quote = void 0;
|
|
1436
|
+
else current += char;
|
|
1437
|
+
continue;
|
|
1438
|
+
}
|
|
1439
|
+
if (char === "'" || char === '"') {
|
|
1440
|
+
quote = char;
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (/\s/u.test(char)) {
|
|
1444
|
+
push();
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
if (char === ">" && /^\d+$/u.test(current)) {
|
|
1448
|
+
const prefix = current;
|
|
1449
|
+
current = "";
|
|
1450
|
+
let redirect = `${prefix}>`;
|
|
1451
|
+
if (command[index + 1] === ">") {
|
|
1452
|
+
redirect += ">";
|
|
1453
|
+
index += 1;
|
|
1454
|
+
}
|
|
1455
|
+
if (command[index + 1] === "&") {
|
|
1456
|
+
redirect += "&";
|
|
1457
|
+
index += 1;
|
|
1458
|
+
while (index + 1 < command.length && /[A-Za-z0-9_/-]/u.test(command[index + 1] ?? "")) {
|
|
1459
|
+
redirect += command[index + 1];
|
|
1460
|
+
index += 1;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
tokens.push(redirect);
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (char === "(" && current.length === 0) {
|
|
1467
|
+
push();
|
|
1468
|
+
tokens.push(char);
|
|
1469
|
+
continue;
|
|
1470
|
+
}
|
|
1471
|
+
if ((char === "{" || char === "}") && current.length === 0) {
|
|
1472
|
+
push();
|
|
1473
|
+
tokens.push(char);
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
if (char === ")" && !current.includes("$(")) {
|
|
1477
|
+
push();
|
|
1478
|
+
tokens.push(char);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (char === "|" || char === ";" || char === "&" || char === ">") {
|
|
1482
|
+
push();
|
|
1483
|
+
const next = command[index + 1];
|
|
1484
|
+
if (char === "&" && next === "&" || char === ">" && next === ">") {
|
|
1485
|
+
tokens.push(`${char}${next}`);
|
|
1486
|
+
index += 1;
|
|
1487
|
+
} else {
|
|
1488
|
+
tokens.push(char);
|
|
1489
|
+
}
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
current += char;
|
|
1493
|
+
}
|
|
1494
|
+
push();
|
|
1495
|
+
return tokens;
|
|
1496
|
+
}
|
|
1497
|
+
function isCommandSeparator(token) {
|
|
1498
|
+
return token === "|" || token === "&&" || token === ";" || token === "&";
|
|
1499
|
+
}
|
|
1500
|
+
function splitCommandSegments(tokens) {
|
|
1501
|
+
const segments = [];
|
|
1502
|
+
let current = [];
|
|
1503
|
+
for (const token of tokens) {
|
|
1504
|
+
if (!isCommandSeparator(token)) {
|
|
1505
|
+
current.push(token);
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
if (current.length > 0) {
|
|
1509
|
+
segments.push({ tokens: current, background: token === "&" });
|
|
1510
|
+
current = [];
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (current.length > 0) segments.push({ tokens: current, background: false });
|
|
1514
|
+
return segments;
|
|
1515
|
+
}
|
|
1516
|
+
function parseCommandSegment(tokens) {
|
|
1517
|
+
const env = /* @__PURE__ */ new Map();
|
|
1518
|
+
let index = 0;
|
|
1519
|
+
while (index < tokens.length && isShellGroupBoundaryToken(tokens[index] ?? "")) {
|
|
1520
|
+
index += 1;
|
|
1521
|
+
}
|
|
1522
|
+
while (index < tokens.length && isEnvironmentAssignment(tokens[index] ?? "")) {
|
|
1523
|
+
const [name, ...rest] = (tokens[index] ?? "").split("=");
|
|
1524
|
+
if (name) env.set(name.toUpperCase(), rest.join("="));
|
|
1525
|
+
index += 1;
|
|
1526
|
+
}
|
|
1527
|
+
if (index >= tokens.length) return void 0;
|
|
1528
|
+
let commandToken = tokens[index] ?? "";
|
|
1529
|
+
if (commandToken === "nohup" || commandToken === "setsid" || commandToken === "time") {
|
|
1530
|
+
index += 1;
|
|
1531
|
+
commandToken = tokens[index] ?? "";
|
|
1532
|
+
}
|
|
1533
|
+
if (isShellReservedCommandToken(commandToken)) return void 0;
|
|
1534
|
+
if (isShellSyntaxCommandToken(commandToken)) return void 0;
|
|
1535
|
+
if (!commandToken) return void 0;
|
|
1536
|
+
const commandName = stripExecutableToken(commandToken);
|
|
1537
|
+
if (!commandName || isShellReservedCommandToken(commandName) || isShellSyntaxCommandToken(commandName)) {
|
|
1538
|
+
return void 0;
|
|
1539
|
+
}
|
|
1540
|
+
return {
|
|
1541
|
+
commandName,
|
|
1542
|
+
argv: tokens.slice(index + 1).filter((token) => !isShellGroupBoundaryToken(token)),
|
|
1543
|
+
env,
|
|
1544
|
+
background: tokens.includes("disown")
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function collectSegmentBinaryCandidates(segment, target) {
|
|
1548
|
+
if (BINARY_COMMAND_NAMES.has(segment.commandName)) {
|
|
1549
|
+
for (const token of segment.argv) {
|
|
1550
|
+
if (!isOptionToken(token)) addPathCandidate(target, token);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
for (const token of segment.argv) {
|
|
1554
|
+
if (isBinarySuffix(token)) addPathCandidate(target, token);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
function collectSegmentArtifactCandidates(segment, target) {
|
|
1558
|
+
for (let index = 0; index < segment.argv.length; index += 1) {
|
|
1559
|
+
const token = segment.argv[index] ?? "";
|
|
1560
|
+
if (token === ">" || token === ">>") {
|
|
1561
|
+
addPathCandidate(target, segment.argv[index + 1] ?? "");
|
|
1562
|
+
index += 1;
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
if (isOutputFlag(token)) {
|
|
1566
|
+
addPathCandidate(target, segment.argv[index + 1] ?? "");
|
|
1567
|
+
index += 1;
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (isArtifactSuffix(token) && isArtifactProducingCommand(segment.commandName, segment.argv)) {
|
|
1571
|
+
addPathCandidate(target, token);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function collectSegmentRedirections(argv) {
|
|
1576
|
+
const redirections = [];
|
|
1577
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1578
|
+
const token = argv[index] ?? "";
|
|
1579
|
+
if (token !== ">" && token !== ">>") continue;
|
|
1580
|
+
const target = argv[index + 1] ?? "";
|
|
1581
|
+
if (target) redirections.push({ operator: token, target });
|
|
1582
|
+
index += 1;
|
|
1583
|
+
}
|
|
1584
|
+
return redirections;
|
|
1585
|
+
}
|
|
1586
|
+
function collectSegmentServiceCandidates(segment) {
|
|
1587
|
+
const candidates = [];
|
|
1588
|
+
const envPort = parsePort(segment.env.get("PORT"));
|
|
1589
|
+
const explicit = findExplicitServiceTarget(segment.argv, envPort);
|
|
1590
|
+
if (isPythonHttpServer(segment)) {
|
|
1591
|
+
const port = findFirstPortToken(segment.argv) ?? envPort ?? 8e3;
|
|
1592
|
+
candidates.push(createServiceIntent("http-server", port, "high", "python -m http.server"));
|
|
1593
|
+
return candidates;
|
|
1594
|
+
}
|
|
1595
|
+
if (segment.commandName === "uvicorn") {
|
|
1596
|
+
if (explicit) candidates.push(createServiceIntent("framework-server", explicit.port, "high", explicit.evidence, explicit.host));
|
|
1597
|
+
return candidates;
|
|
1598
|
+
}
|
|
1599
|
+
if (segment.commandName === "flask" && segment.argv[0]?.toLowerCase() === "run") {
|
|
1600
|
+
if (explicit) candidates.push(createServiceIntent("framework-server", explicit.port, "high", explicit.evidence, explicit.host));
|
|
1601
|
+
return candidates;
|
|
1602
|
+
}
|
|
1603
|
+
if (segment.commandName === "gunicorn") {
|
|
1604
|
+
const bind = findGunicornBindTarget(segment.argv);
|
|
1605
|
+
if (bind) candidates.push(createServiceIntent("framework-server", bind.port, "high", bind.evidence, bind.host));
|
|
1606
|
+
return candidates;
|
|
1607
|
+
}
|
|
1608
|
+
if (isPackageScriptCommand(segment)) {
|
|
1609
|
+
if (explicit) candidates.push(createServiceIntent("package-script", explicit.port, "medium", explicit.evidence, explicit.host));
|
|
1610
|
+
return candidates;
|
|
1611
|
+
}
|
|
1612
|
+
if (isJsRuntimeCommand(segment.commandName)) {
|
|
1613
|
+
if (explicit) candidates.push(createServiceIntent("explicit-port", explicit.port, "medium", explicit.evidence, explicit.host));
|
|
1614
|
+
return candidates;
|
|
1615
|
+
}
|
|
1616
|
+
if (explicit && isKnownServiceCommand(segment.commandName)) {
|
|
1617
|
+
candidates.push(createServiceIntent("explicit-port", explicit.port, "medium", explicit.evidence, explicit.host));
|
|
1618
|
+
}
|
|
1619
|
+
return candidates;
|
|
1620
|
+
}
|
|
1621
|
+
function createServiceIntent(kind, port, confidence, evidence, host = "127.0.0.1") {
|
|
1622
|
+
return { kind, host: normalizeServiceHost(host), port, confidence, evidence };
|
|
1623
|
+
}
|
|
1624
|
+
function isPythonHttpServer(segment) {
|
|
1625
|
+
return (segment.commandName === "python" || segment.commandName === "python3") && hasAdjacentTokens(segment.argv, "-m", "http.server");
|
|
1626
|
+
}
|
|
1627
|
+
function findExplicitServiceTarget(argv, envPort) {
|
|
1628
|
+
let host = "127.0.0.1";
|
|
1629
|
+
if (envPort) return { host, port: envPort, evidence: "PORT=" };
|
|
1630
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1631
|
+
const token = argv[index] ?? "";
|
|
1632
|
+
if (token === "--host") host = normalizeServiceHost(argv[index + 1] ?? host);
|
|
1633
|
+
if (token === "--port" || token === "-p") {
|
|
1634
|
+
const port = parsePort(argv[index + 1]);
|
|
1635
|
+
if (port) return { host, port, evidence: token };
|
|
1636
|
+
}
|
|
1637
|
+
const inlinePort = parseInlineOptionPort(token);
|
|
1638
|
+
if (inlinePort) return { host, port: inlinePort, evidence: token.split("=")[0] ?? token };
|
|
1639
|
+
const target = parseHostPortToken(token);
|
|
1640
|
+
if (target?.port) return { host: target.host ?? host, port: target.port, evidence: "host:port" };
|
|
1641
|
+
}
|
|
1642
|
+
return void 0;
|
|
1643
|
+
}
|
|
1644
|
+
function findGunicornBindTarget(argv) {
|
|
1645
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1646
|
+
const token = argv[index] ?? "";
|
|
1647
|
+
if (token !== "-b" && token !== "--bind") continue;
|
|
1648
|
+
const target = parseHostPortToken(argv[index + 1] ?? "");
|
|
1649
|
+
if (target?.port) return { host: target.host ?? "127.0.0.1", port: target.port, evidence: token };
|
|
1650
|
+
}
|
|
1651
|
+
return void 0;
|
|
1652
|
+
}
|
|
1653
|
+
function findFirstPortToken(argv) {
|
|
1654
|
+
for (const token of argv) {
|
|
1655
|
+
const port = parsePort(token);
|
|
1656
|
+
if (port) return port;
|
|
1657
|
+
}
|
|
1658
|
+
return void 0;
|
|
1659
|
+
}
|
|
1660
|
+
function parseInlineOptionPort(token) {
|
|
1661
|
+
if (!token.startsWith("--port=") && !token.startsWith("-p=")) return void 0;
|
|
1662
|
+
return parsePort(token.slice(token.indexOf("=") + 1));
|
|
1663
|
+
}
|
|
1664
|
+
function parsePort(value) {
|
|
1665
|
+
if (!value || !/^\d{1,5}$/u.test(value)) return void 0;
|
|
1666
|
+
return normalizePort(Number(value));
|
|
1667
|
+
}
|
|
1668
|
+
function isPackageScriptCommand(segment) {
|
|
1669
|
+
if (segment.commandName !== "npm" && segment.commandName !== "pnpm" && segment.commandName !== "yarn") return false;
|
|
1670
|
+
const command = segment.argv[0]?.toLowerCase();
|
|
1671
|
+
const script = segment.argv[1]?.toLowerCase();
|
|
1672
|
+
return command === "start" || command === "dev" || command === "test-server" || command === "run" && (script === "start" || script === "dev" || script === "test-server");
|
|
1673
|
+
}
|
|
1674
|
+
function isJsRuntimeCommand(commandName) {
|
|
1675
|
+
return commandName === "node" || commandName === "deno" || commandName === "bun";
|
|
1676
|
+
}
|
|
1677
|
+
function isKnownServiceCommand(commandName) {
|
|
1678
|
+
return commandName === "grpc";
|
|
1679
|
+
}
|
|
1680
|
+
function normalizeServiceHost(host) {
|
|
1681
|
+
return "127.0.0.1";
|
|
1682
|
+
}
|
|
1683
|
+
function isBackgroundLikely(segments, serviceCandidates) {
|
|
1684
|
+
return segments.some(
|
|
1685
|
+
(segment) => segment.background || segment.tokens[0] === "nohup" || segment.tokens[0] === "setsid" || segment.tokens.includes("disown")
|
|
1686
|
+
) || serviceCandidates.some(
|
|
1687
|
+
(candidate) => candidate.kind === "http-server" || candidate.kind === "framework-server" || candidate.kind === "package-script"
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
function stripExecutableToken(token) {
|
|
1691
|
+
const normalized = token.replace(/^\(+/u, "").replace(/\)+$/u, "");
|
|
1692
|
+
if (normalized.includes("/") || normalized.includes("\\")) {
|
|
1693
|
+
return normalized.replace(/\.(?:exe|cmd|bat)$/iu, "");
|
|
1694
|
+
}
|
|
1695
|
+
return basename(normalized).replace(/\.(?:exe|cmd|bat)$/iu, "").toLowerCase();
|
|
1696
|
+
}
|
|
1697
|
+
function addPathCandidate(target, token) {
|
|
1698
|
+
if (!isStaticPathCandidate(token)) return;
|
|
1699
|
+
target.add(token.replaceAll("\\", "/"));
|
|
1700
|
+
}
|
|
1701
|
+
function isOptionToken(token) {
|
|
1702
|
+
return token.startsWith("-") && !/^-?\d+$/u.test(token);
|
|
1703
|
+
}
|
|
1704
|
+
function isOutputFlag(token) {
|
|
1705
|
+
return token === "-o" || token === "--output" || token === "--out" || token === "--dest" || token === "--file";
|
|
1706
|
+
}
|
|
1707
|
+
function isEnvironmentAssignment(token) {
|
|
1708
|
+
return /^[A-Za-z_][A-Za-z0-9_]*=.*$/u.test(token);
|
|
1709
|
+
}
|
|
1710
|
+
function isShellReservedCommandToken(token) {
|
|
1711
|
+
return SHELL_RESERVED_COMMAND_NAMES.has(token.toLowerCase());
|
|
1712
|
+
}
|
|
1713
|
+
function isShellSyntaxCommandToken(token) {
|
|
1714
|
+
return SHELL_SYNTAX_COMMAND_NAMES.has(token.toLowerCase());
|
|
1715
|
+
}
|
|
1716
|
+
function isShellGroupBoundaryToken(token) {
|
|
1717
|
+
return token === "(" || token === ")" || token === "{" || token === "}";
|
|
1718
|
+
}
|
|
1719
|
+
function isStaticPathCandidate(token) {
|
|
1720
|
+
if (!token || isOptionToken(token) || token.includes("://")) return false;
|
|
1721
|
+
if (token.includes("$") || token.includes("`")) return false;
|
|
1722
|
+
if (/^(?:\d+)?>{1,2}(?:&\d+)?$/u.test(token)) return false;
|
|
1723
|
+
if (token === "|" || token === ";" || token === "&" || token === "<") return false;
|
|
1724
|
+
if (isShellGroupBoundaryToken(token) || isShellSyntaxCommandToken(token)) return false;
|
|
1725
|
+
if (/^\d+$/u.test(token)) return false;
|
|
1726
|
+
if (token === "/dev/null" || token === "/dev/stdout" || token === "/dev/stderr") return false;
|
|
1727
|
+
return true;
|
|
1728
|
+
}
|
|
1729
|
+
function isBinarySuffix(token) {
|
|
1730
|
+
return BINARY_HINT_EXTENSIONS.some((extension) => token.toLowerCase().endsWith(extension));
|
|
1731
|
+
}
|
|
1732
|
+
function isArtifactSuffix(token) {
|
|
1733
|
+
return ARTIFACT_HINT_EXTENSIONS.some((extension) => token.toLowerCase().endsWith(extension));
|
|
1734
|
+
}
|
|
1735
|
+
function isArtifactProducingCommand(commandName, tokens) {
|
|
1736
|
+
if (!commandName) return false;
|
|
1737
|
+
if (ARTIFACT_PRODUCING_COMMAND_NAMES.has(commandName)) return true;
|
|
1738
|
+
return tokens.some((token) => ARTIFACT_PRODUCING_TOKENS.has(token.toLowerCase()));
|
|
1739
|
+
}
|
|
1740
|
+
function parseHostPortToken(token) {
|
|
1741
|
+
const url = tryParseLocalUrl(token);
|
|
1742
|
+
if (url) return url;
|
|
1743
|
+
const separator = token.lastIndexOf(":");
|
|
1744
|
+
if (separator <= 0) return void 0;
|
|
1745
|
+
const host = token.slice(0, separator).replace(/^\[(.*)\]$/u, "$1");
|
|
1746
|
+
const port = normalizePort(Number(token.slice(separator + 1)));
|
|
1747
|
+
if (!port || !isLocalHost(host)) return void 0;
|
|
1748
|
+
return normalizeHostPort(host, port);
|
|
1749
|
+
}
|
|
1750
|
+
function tryParseLocalUrl(token) {
|
|
1751
|
+
try {
|
|
1752
|
+
const url = new URL(token);
|
|
1753
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
1754
|
+
if (!isLocalHost(url.hostname)) return void 0;
|
|
1755
|
+
const port = normalizePort(Number(url.port));
|
|
1756
|
+
if (!port) return void 0;
|
|
1757
|
+
return normalizeHostPort(url.hostname, port);
|
|
1758
|
+
} catch {
|
|
1759
|
+
return void 0;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
function isLocalHost(host) {
|
|
1763
|
+
return host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host === "::1";
|
|
1764
|
+
}
|
|
1765
|
+
function hasAdjacentTokens(tokens, first, second) {
|
|
1766
|
+
return tokens.some(
|
|
1767
|
+
(token, index) => token.toLowerCase() === first && tokens[index + 1]?.toLowerCase() === second
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
function normalizePort(value) {
|
|
1771
|
+
return Number.isInteger(value) && value > 0 && value <= 65535 ? value : void 0;
|
|
1772
|
+
}
|
|
1773
|
+
function dedupeServiceCandidates(candidates) {
|
|
1774
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1775
|
+
return candidates.filter((candidate) => {
|
|
1776
|
+
const key = `${candidate.host}:${candidate.port}:${candidate.kind}`;
|
|
1777
|
+
if (seen.has(key)) return false;
|
|
1778
|
+
seen.add(key);
|
|
1779
|
+
return true;
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
function normalizeHostPort(host, port) {
|
|
1783
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) return void 0;
|
|
1784
|
+
return { host: host === "0.0.0.0" || host === "localhost" ? "127.0.0.1" : host.replace(/^\[(.*)\]$/u, "$1"), port };
|
|
1785
|
+
}
|
|
1786
|
+
function isHeadlessBenchContext(context) {
|
|
1787
|
+
const record = context;
|
|
1788
|
+
return record.headlessBench?.enabled === true || record.isHeadlessBench === true;
|
|
1789
|
+
}
|
|
1790
|
+
function withDefaultServiceReadiness(readiness, context) {
|
|
1791
|
+
if (readiness.timeoutMs !== void 0) return readiness;
|
|
1792
|
+
const hasReadinessRisk = (context.recentDiagnostics ?? []).some(
|
|
1793
|
+
(diagnostic) => diagnostic.type === "service_readiness" || diagnostic.type === "timeout"
|
|
1794
|
+
);
|
|
1795
|
+
const timeoutMs = hasReadinessRisk ? 15e3 : isHeadlessBenchContext(context) ? 1e4 : 5e3;
|
|
1796
|
+
return { ...readiness, timeoutMs };
|
|
1797
|
+
}
|
|
1798
|
+
function createBashOutcomeDiagnostics(outcome) {
|
|
1799
|
+
if (outcome === "timeout") {
|
|
1800
|
+
return [
|
|
1801
|
+
{
|
|
1802
|
+
type: "timeout",
|
|
1803
|
+
severity: "recoverable",
|
|
1804
|
+
evidence: "Bash outcome timeout",
|
|
1805
|
+
suggestion: "Run the smallest focused check first and avoid repeating long blind commands."
|
|
1806
|
+
}
|
|
1807
|
+
];
|
|
1808
|
+
}
|
|
1809
|
+
return [];
|
|
1810
|
+
}
|
|
785
1811
|
function tailLines(text, limit) {
|
|
786
1812
|
const lines = text.split(/\r?\n/u);
|
|
787
1813
|
return lines.slice(Math.max(0, lines.length - limit));
|
|
@@ -1156,6 +2182,14 @@ function resolveWorkspacePath(workspaceRoot, inputPath) {
|
|
|
1156
2182
|
function relativePath(workspaceRoot, filePath) {
|
|
1157
2183
|
return relative(workspaceRoot, filePath).replaceAll("\\", "/") || ".";
|
|
1158
2184
|
}
|
|
2185
|
+
function resolveArtifactPath(context, inputPath) {
|
|
2186
|
+
const absolute = resolveWorkspacePath(context.workspaceRoot, inputPath);
|
|
2187
|
+
return {
|
|
2188
|
+
input: inputPath,
|
|
2189
|
+
absolute,
|
|
2190
|
+
relative: relativePath(context.workspaceRoot, absolute)
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
1159
2193
|
function ensureUnique(content, oldText) {
|
|
1160
2194
|
if (!oldText) {
|
|
1161
2195
|
throw new Error("oldText \u4E0D\u80FD\u4E3A\u7A7A\u3002\u5EFA\u8BAE\uFF1A\u63D0\u4F9B\u8DB3\u591F\u4E0A\u4E0B\u6587\u7684\u552F\u4E00\u7247\u6BB5\u3002");
|
|
@@ -1407,6 +2441,149 @@ async function tryRipgrepFiles(input, context, root, limit) {
|
|
|
1407
2441
|
if (!result) return null;
|
|
1408
2442
|
return { matches: result.lines.slice(0, limit), truncated: result.truncated };
|
|
1409
2443
|
}
|
|
2444
|
+
async function findSourcePackMatches(query, terms, context, limit) {
|
|
2445
|
+
const indexMatches = sourcePackMatchesFromCandidates(query, context.sourcePackCandidates, limit);
|
|
2446
|
+
if (indexMatches.length > 0) {
|
|
2447
|
+
return { source: "index", matches: indexMatches };
|
|
2448
|
+
}
|
|
2449
|
+
const rgMatches = await tryRipgrepSourcePack(terms, context, limit);
|
|
2450
|
+
if (rgMatches && rgMatches.length > 0) {
|
|
2451
|
+
return { source: "rg", matches: rgMatches };
|
|
2452
|
+
}
|
|
2453
|
+
const matches = [];
|
|
2454
|
+
for await (const filePath of listFiles(context.workspaceRoot, () => matches.length >= limit)) {
|
|
2455
|
+
const content = await safeReadText(filePath, context);
|
|
2456
|
+
if (content === null) continue;
|
|
2457
|
+
const rel = relativePath(context.workspaceRoot, filePath);
|
|
2458
|
+
const pathTerm = terms.find((item) => rel.toLowerCase().includes(item.toLowerCase()));
|
|
2459
|
+
if (pathTerm) {
|
|
2460
|
+
matches.push({
|
|
2461
|
+
path: rel,
|
|
2462
|
+
line: 1,
|
|
2463
|
+
text: rel,
|
|
2464
|
+
term: pathTerm,
|
|
2465
|
+
source: "file_name",
|
|
2466
|
+
confidence: 0.35,
|
|
2467
|
+
reason: `fallback file name matched "${pathTerm}"`
|
|
2468
|
+
});
|
|
2469
|
+
if (matches.length >= limit) break;
|
|
2470
|
+
}
|
|
2471
|
+
const lines = content.split(/\r?\n/u);
|
|
2472
|
+
for (const [index, line] of lines.entries()) {
|
|
2473
|
+
const lower = line.toLowerCase();
|
|
2474
|
+
const term = terms.find((item) => lower.includes(item.toLowerCase()));
|
|
2475
|
+
if (!term) continue;
|
|
2476
|
+
matches.push({
|
|
2477
|
+
path: rel,
|
|
2478
|
+
line: index + 1,
|
|
2479
|
+
text: line,
|
|
2480
|
+
term,
|
|
2481
|
+
source: "local_scan",
|
|
2482
|
+
confidence: 0.4,
|
|
2483
|
+
reason: `fallback local scan matched "${term}" at line ${index + 1}`
|
|
2484
|
+
});
|
|
2485
|
+
if (matches.length >= limit) break;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
return { source: matches[0]?.source ?? "local_scan", matches };
|
|
2489
|
+
}
|
|
2490
|
+
async function tryRipgrepSourcePack(terms, context, limit) {
|
|
2491
|
+
const args = [
|
|
2492
|
+
"--line-number",
|
|
2493
|
+
"--no-heading",
|
|
2494
|
+
"--color",
|
|
2495
|
+
"never",
|
|
2496
|
+
"--hidden",
|
|
2497
|
+
"--no-ignore",
|
|
2498
|
+
"--max-columns",
|
|
2499
|
+
"500",
|
|
2500
|
+
"-i",
|
|
2501
|
+
"-F",
|
|
2502
|
+
...createRgExcludeArgs(context.workspaceRoot, context.workspaceRoot)
|
|
2503
|
+
];
|
|
2504
|
+
for (const term of terms) {
|
|
2505
|
+
args.push("-e", term);
|
|
2506
|
+
}
|
|
2507
|
+
args.push(".");
|
|
2508
|
+
const result = await runRipgrep(
|
|
2509
|
+
args,
|
|
2510
|
+
context,
|
|
2511
|
+
limit,
|
|
2512
|
+
(line) => normalizeRgGrepLine(line, context.workspaceRoot)
|
|
2513
|
+
);
|
|
2514
|
+
if (!result) return null;
|
|
2515
|
+
return result.lines.flatMap((line) => parseSourcePackRgLine(line, terms));
|
|
2516
|
+
}
|
|
2517
|
+
function parseSourcePackRgLine(line, terms) {
|
|
2518
|
+
const firstColon = line.indexOf(":");
|
|
2519
|
+
const secondColon = firstColon < 0 ? -1 : line.indexOf(":", firstColon + 1);
|
|
2520
|
+
if (firstColon < 0 || secondColon < 0) return [];
|
|
2521
|
+
const lineNumber = Number.parseInt(line.slice(firstColon + 1, secondColon), 10);
|
|
2522
|
+
if (!Number.isInteger(lineNumber)) return [];
|
|
2523
|
+
const text = line.slice(secondColon + 1).trim();
|
|
2524
|
+
const lower = text.toLowerCase();
|
|
2525
|
+
const term = terms.find((item) => lower.includes(item.toLowerCase())) ?? terms[0];
|
|
2526
|
+
return [
|
|
2527
|
+
{
|
|
2528
|
+
path: line.slice(0, firstColon),
|
|
2529
|
+
line: lineNumber,
|
|
2530
|
+
text,
|
|
2531
|
+
term,
|
|
2532
|
+
source: "rg",
|
|
2533
|
+
confidence: 0.6,
|
|
2534
|
+
reason: `rg matched "${term}" at line ${lineNumber}`
|
|
2535
|
+
}
|
|
2536
|
+
];
|
|
2537
|
+
}
|
|
2538
|
+
function sourcePackMatchesFromCandidates(query, candidates, limit) {
|
|
2539
|
+
if (!candidates || candidates.length === 0) return [];
|
|
2540
|
+
const terms = extractSourcePackTerms(query);
|
|
2541
|
+
return candidates.slice(0, limit).map((candidate) => {
|
|
2542
|
+
const start = Math.max(1, candidate.start);
|
|
2543
|
+
const end = Math.max(start, candidate.end);
|
|
2544
|
+
const term = terms[0] ?? query;
|
|
2545
|
+
return {
|
|
2546
|
+
path: candidate.path,
|
|
2547
|
+
line: start,
|
|
2548
|
+
text: candidate.reason ?? candidate.path,
|
|
2549
|
+
term,
|
|
2550
|
+
source: "index",
|
|
2551
|
+
start,
|
|
2552
|
+
end,
|
|
2553
|
+
confidence: candidate.confidence ?? 0.85,
|
|
2554
|
+
reason: candidate.reason ?? `index candidate for "${query}"`
|
|
2555
|
+
};
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
function extractSourcePackTerms(query) {
|
|
2559
|
+
const terms = query.split(/[^\p{L}\p{N}_.$/@-]+/u).map((term) => term.trim()).filter((term) => term.length >= 2 && !/^\d+$/u.test(term));
|
|
2560
|
+
return unique(terms).slice(0, SOURCE_PACK_MAX_TERMS);
|
|
2561
|
+
}
|
|
2562
|
+
function estimateSourcePackConfidence(query, match) {
|
|
2563
|
+
if (match.confidence !== void 0) return match.confidence;
|
|
2564
|
+
const queryTerms = extractSourcePackTerms(query);
|
|
2565
|
+
const haystack = `${match.path} ${match.text}`.toLowerCase();
|
|
2566
|
+
const hitCount = queryTerms.filter((term) => haystack.includes(term.toLowerCase())).length;
|
|
2567
|
+
const base = match.source === "index" ? 0.7 : match.source === "rg" ? 0.45 : 0.25;
|
|
2568
|
+
const cap = match.source === "index" ? 0.95 : match.source === "rg" ? 0.75 : 0.55;
|
|
2569
|
+
return Math.min(cap, base + hitCount * 0.15);
|
|
2570
|
+
}
|
|
2571
|
+
function formatSourcePackOutput(snippets, safetyTruncated) {
|
|
2572
|
+
const lines = [];
|
|
2573
|
+
for (const snippet of snippets) {
|
|
2574
|
+
lines.push(
|
|
2575
|
+
[
|
|
2576
|
+
`${snippet.path}:${snippet.start}-${snippet.end}`,
|
|
2577
|
+
`source: ${snippet.source}; reason: ${snippet.reason}; confidence: ${snippet.confidence.toFixed(2)}`,
|
|
2578
|
+
snippet.content
|
|
2579
|
+
].join("\n")
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
if (safetyTruncated) {
|
|
2583
|
+
lines.push("...\uFF08\u7ED3\u679C\u5DF2\u622A\u65AD\uFF0C\u540E\u7EED\u5185\u5BB9\u7701\u7565\uFF1B\u5982\u9700\u7CBE\u8BFB\u8BF7\u6307\u5B9A\u66F4\u5C0F\u8303\u56F4\u3002\uFF09");
|
|
2584
|
+
}
|
|
2585
|
+
return lines.join("\n\n");
|
|
2586
|
+
}
|
|
1410
2587
|
function createRgExcludeArgs(root, workspaceRoot) {
|
|
1411
2588
|
const searchRootRel = stripCurrentDirectoryPrefix(
|
|
1412
2589
|
relative(workspaceRoot, root).replaceAll("\\", "/")
|
|
@@ -1645,6 +2822,198 @@ function runShell(command, cwd, timeoutMs, signal, onProgress, trackChildProcess
|
|
|
1645
2822
|
});
|
|
1646
2823
|
});
|
|
1647
2824
|
}
|
|
2825
|
+
var STALL_THRESHOLD_MS = 45e3;
|
|
2826
|
+
var PROMPT_PATTERNS = [
|
|
2827
|
+
/\(y\/n\)\s*$/i,
|
|
2828
|
+
/\[y\/n\]\s*$/i,
|
|
2829
|
+
/\(yes\/no\)\s*$/i,
|
|
2830
|
+
/Press Enter/i,
|
|
2831
|
+
/Continue\?/i,
|
|
2832
|
+
/Overwrite\?/i,
|
|
2833
|
+
/password[:\s]*$/i,
|
|
2834
|
+
/passphrase[:\s]*$/i,
|
|
2835
|
+
/\?\s*$/
|
|
2836
|
+
];
|
|
2837
|
+
function looksLikePrompt(tail) {
|
|
2838
|
+
const lastLine = tail.split(/\r?\n/).filter(Boolean).pop() ?? "";
|
|
2839
|
+
return PROMPT_PATTERNS.some((re) => re.test(lastLine));
|
|
2840
|
+
}
|
|
2841
|
+
async function runBackgroundBash(opts) {
|
|
2842
|
+
const {
|
|
2843
|
+
taskId,
|
|
2844
|
+
command,
|
|
2845
|
+
originalCommand,
|
|
2846
|
+
cwd,
|
|
2847
|
+
timeoutMs,
|
|
2848
|
+
fullOutputPath,
|
|
2849
|
+
adapter,
|
|
2850
|
+
logCommand,
|
|
2851
|
+
retainAfterReturn,
|
|
2852
|
+
abortSignal,
|
|
2853
|
+
onProgress,
|
|
2854
|
+
trackChildProcess,
|
|
2855
|
+
onComplete
|
|
2856
|
+
} = opts;
|
|
2857
|
+
await mkdir(dirname(fullOutputPath), { recursive: true }).catch(() => {
|
|
2858
|
+
});
|
|
2859
|
+
const fileStream = createWriteStream(fullOutputPath, { encoding: "utf8" });
|
|
2860
|
+
const commandForLog = logCommand ?? command;
|
|
2861
|
+
const header = [
|
|
2862
|
+
`$ ${sanitizeSecrets(commandForLog)}`,
|
|
2863
|
+
...adapter !== "native" ? [`adapter ${adapter}`, `original command ${summarizeOriginalShellCommand(originalCommand)}`] : [],
|
|
2864
|
+
""
|
|
2865
|
+
].join("\n");
|
|
2866
|
+
fileStream.write(header);
|
|
2867
|
+
const detached = process.platform !== "win32";
|
|
2868
|
+
if (retainAfterReturn) {
|
|
2869
|
+
fileStream.write("\n[background] retained process started\n");
|
|
2870
|
+
await new Promise((resolveEnd) => fileStream.end(resolveEnd));
|
|
2871
|
+
const outFd = openSync(fullOutputPath, "a");
|
|
2872
|
+
const errFd = openSync(fullOutputPath, "a");
|
|
2873
|
+
const child2 = spawn(command, {
|
|
2874
|
+
cwd,
|
|
2875
|
+
shell: true,
|
|
2876
|
+
windowsHide: true,
|
|
2877
|
+
detached,
|
|
2878
|
+
stdio: ["ignore", outFd, errFd]
|
|
2879
|
+
});
|
|
2880
|
+
closeSync(outFd);
|
|
2881
|
+
closeSync(errFd);
|
|
2882
|
+
child2.unref();
|
|
2883
|
+
trackChildProcess?.(child2, {
|
|
2884
|
+
detached,
|
|
2885
|
+
cwd,
|
|
2886
|
+
label: `BashBg:${command.slice(0, 80)}`,
|
|
2887
|
+
retainAfterExit: true
|
|
2888
|
+
});
|
|
2889
|
+
onComplete?.({
|
|
2890
|
+
taskId,
|
|
2891
|
+
exitCode: 0,
|
|
2892
|
+
outcome: "completed",
|
|
2893
|
+
outputPath: fullOutputPath,
|
|
2894
|
+
command: sanitizeSecrets(originalCommand)
|
|
2895
|
+
});
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
const child = spawn(command, { cwd, shell: true, windowsHide: true, detached });
|
|
2899
|
+
trackChildProcess?.(child, {
|
|
2900
|
+
detached,
|
|
2901
|
+
cwd,
|
|
2902
|
+
label: `BashBg:${command.slice(0, 80)}`,
|
|
2903
|
+
retainAfterExit: detached
|
|
2904
|
+
});
|
|
2905
|
+
let tailBuffer = "";
|
|
2906
|
+
let lastOutputTime = Date.now();
|
|
2907
|
+
let stallTimer;
|
|
2908
|
+
let settled = false;
|
|
2909
|
+
let outcome = "completed";
|
|
2910
|
+
const resetStallTimer = () => {
|
|
2911
|
+
lastOutputTime = Date.now();
|
|
2912
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
2913
|
+
stallTimer = setTimeout(checkStall, STALL_THRESHOLD_MS);
|
|
2914
|
+
};
|
|
2915
|
+
const checkStall = () => {
|
|
2916
|
+
if (Date.now() - lastOutputTime >= STALL_THRESHOLD_MS && looksLikePrompt(tailBuffer)) {
|
|
2917
|
+
const msg = "\n[stall watchdog] \u68C0\u6D4B\u5230\u4EA4\u4E92\u5F0F\u63D0\u793A\uFF0C\u53EF\u80FD\u9700\u8981\u7528\u6237\u8F93\u5165\u3002\n";
|
|
2918
|
+
fileStream.write(msg);
|
|
2919
|
+
onProgress?.("system", msg);
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
const finish = (exitCode) => {
|
|
2923
|
+
if (settled) return;
|
|
2924
|
+
settled = true;
|
|
2925
|
+
if (stallTimer) clearTimeout(stallTimer);
|
|
2926
|
+
clearTimeout(timer);
|
|
2927
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
2928
|
+
const footer = `
|
|
2929
|
+
exit code ${exitCode}
|
|
2930
|
+
outcome ${outcome}
|
|
2931
|
+
`;
|
|
2932
|
+
fileStream.write(footer);
|
|
2933
|
+
fileStream.end();
|
|
2934
|
+
onComplete?.({
|
|
2935
|
+
taskId,
|
|
2936
|
+
exitCode,
|
|
2937
|
+
outcome,
|
|
2938
|
+
outputPath: fullOutputPath,
|
|
2939
|
+
command: sanitizeSecrets(originalCommand)
|
|
2940
|
+
});
|
|
2941
|
+
};
|
|
2942
|
+
const requestStop = async (force) => {
|
|
2943
|
+
if (process.platform === "win32" && child.pid) {
|
|
2944
|
+
await stopWindowsProcessTree(child.pid, cwd);
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
const sig = force ? "SIGKILL" : "SIGTERM";
|
|
2948
|
+
if (detached && child.pid) {
|
|
2949
|
+
try {
|
|
2950
|
+
process.kill(-child.pid, sig);
|
|
2951
|
+
} catch {
|
|
2952
|
+
child.kill(sig);
|
|
2953
|
+
}
|
|
2954
|
+
} else {
|
|
2955
|
+
child.kill(sig);
|
|
2956
|
+
}
|
|
2957
|
+
};
|
|
2958
|
+
const FORCE_KILL_WAIT_MS = 3e3;
|
|
2959
|
+
const waitForCloseOrForceKill = () => {
|
|
2960
|
+
const forceTimer = setTimeout(() => {
|
|
2961
|
+
void requestStop(true);
|
|
2962
|
+
setTimeout(() => finish(1), 500);
|
|
2963
|
+
}, FORCE_KILL_WAIT_MS);
|
|
2964
|
+
child.once("close", () => {
|
|
2965
|
+
clearTimeout(forceTimer);
|
|
2966
|
+
});
|
|
2967
|
+
};
|
|
2968
|
+
const onAbort = () => {
|
|
2969
|
+
if (settled) return;
|
|
2970
|
+
outcome = "cancelled";
|
|
2971
|
+
const msg = "\n[cancelled] \u5DE5\u5177\u8C03\u7528\u5DF2\u53D6\u6D88\uFF0C\u6B63\u5728\u7EC8\u6B62\u5B50\u8FDB\u7A0B\u3002\n";
|
|
2972
|
+
fileStream.write(msg);
|
|
2973
|
+
onProgress?.("system", msg);
|
|
2974
|
+
void requestStop(false);
|
|
2975
|
+
waitForCloseOrForceKill();
|
|
2976
|
+
};
|
|
2977
|
+
const timer = setTimeout(() => {
|
|
2978
|
+
if (settled) return;
|
|
2979
|
+
outcome = "timeout";
|
|
2980
|
+
const msg = `
|
|
2981
|
+
[timeout] \u547D\u4EE4\u8D85\u65F6\uFF1A\u8D85\u8FC7 ${timeoutMs}ms\uFF0C\u5DF2\u7EC8\u6B62\u3002
|
|
2982
|
+
`;
|
|
2983
|
+
fileStream.write(msg);
|
|
2984
|
+
onProgress?.("system", msg);
|
|
2985
|
+
void requestStop(false);
|
|
2986
|
+
waitForCloseOrForceKill();
|
|
2987
|
+
}, timeoutMs);
|
|
2988
|
+
if (abortSignal?.aborted) {
|
|
2989
|
+
onAbort();
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2993
|
+
child.stdout.on("data", (chunk) => {
|
|
2994
|
+
const text = decodeShellChunk(chunk);
|
|
2995
|
+
fileStream.write(text);
|
|
2996
|
+
tailBuffer = (tailBuffer + text).slice(-2e3);
|
|
2997
|
+
resetStallTimer();
|
|
2998
|
+
onProgress?.("stdout", text);
|
|
2999
|
+
});
|
|
3000
|
+
child.stderr.on("data", (chunk) => {
|
|
3001
|
+
const text = decodeShellChunk(chunk);
|
|
3002
|
+
fileStream.write(text);
|
|
3003
|
+
tailBuffer = (tailBuffer + text).slice(-2e3);
|
|
3004
|
+
resetStallTimer();
|
|
3005
|
+
onProgress?.("stderr", text);
|
|
3006
|
+
});
|
|
3007
|
+
child.on("close", (code) => {
|
|
3008
|
+
finish(code ?? 1);
|
|
3009
|
+
});
|
|
3010
|
+
child.on("error", (err) => {
|
|
3011
|
+
fileStream.write(`
|
|
3012
|
+
[error] ${err.message}
|
|
3013
|
+
`);
|
|
3014
|
+
finish(1);
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
1648
3017
|
function decodeShellChunk(chunk) {
|
|
1649
3018
|
const utf8 = chunk.toString("utf8");
|
|
1650
3019
|
if (process.platform !== "win32") return utf8;
|
|
@@ -1772,14 +3141,21 @@ function unique(items) {
|
|
|
1772
3141
|
}
|
|
1773
3142
|
var __testGlobToRegExp = globToRegExp;
|
|
1774
3143
|
var __testDecodeShellChunk = decodeShellChunk;
|
|
3144
|
+
var __testParseBashCommandIntent = parseBashCommandIntent;
|
|
3145
|
+
var __testCanSafelyAliasPythonCommand = canSafelyAliasPythonCommand;
|
|
3146
|
+
var __testRunBackgroundBash = runBackgroundBash;
|
|
1775
3147
|
export {
|
|
3148
|
+
__testCanSafelyAliasPythonCommand,
|
|
1776
3149
|
__testDecodeShellChunk,
|
|
1777
3150
|
__testGlobToRegExp,
|
|
3151
|
+
__testParseBashCommandIntent,
|
|
3152
|
+
__testRunBackgroundBash,
|
|
1778
3153
|
adaptShellCommand,
|
|
1779
3154
|
adaptShellCommandForPlatform,
|
|
1780
3155
|
builtInTools,
|
|
1781
3156
|
createTool,
|
|
1782
3157
|
createToolContext,
|
|
3158
|
+
interpretCommandResult,
|
|
1783
3159
|
runTool,
|
|
1784
3160
|
toolRegistryStatus
|
|
1785
3161
|
};
|