@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.
Files changed (54) hide show
  1. package/dist/chunk-236QJATI.js +678 -0
  2. package/dist/{chunk-VZWMPUSS.js → chunk-2DIXRINW.js} +1 -1
  3. package/dist/chunk-A6FUAM64.js +11 -0
  4. package/dist/{chunk-3JCXABUK.js → chunk-F7UOI3YR.js} +19 -11
  5. package/dist/chunk-FI4YPDFE.js +38 -0
  6. package/dist/chunk-GI5XI4HN.js +11 -0
  7. package/dist/{chunk-QNZMO55X.js → chunk-J7O24FJB.js} +19 -11
  8. package/dist/chunk-PKMIDA5A.js +6 -0
  9. package/dist/chunk-SPAD4DZS.js +319 -0
  10. package/dist/chunk-WEWXKRKL.js +6 -0
  11. package/dist/chunk-XG6HNLY6.js +166 -0
  12. package/dist/chunk-YCO6C7KF.js +6 -0
  13. package/dist/index.d.ts +105 -3
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1404 -28
  16. package/dist/tools/Bash/BashTool.d.ts +1 -1
  17. package/dist/tools/Bash/BashTool.js +2 -2
  18. package/dist/tools/Bash/artifact-checker.d.ts +33 -0
  19. package/dist/tools/Bash/artifact-checker.d.ts.map +1 -0
  20. package/dist/tools/Bash/artifact-checker.js +6 -0
  21. package/dist/tools/Bash/binary-helper.d.ts +7 -0
  22. package/dist/tools/Bash/binary-helper.d.ts.map +1 -0
  23. package/dist/tools/Bash/binary-helper.js +6 -0
  24. package/dist/tools/Bash/command-semantics.d.ts +11 -0
  25. package/dist/tools/Bash/command-semantics.d.ts.map +1 -0
  26. package/dist/tools/Bash/command-semantics.js +6 -0
  27. package/dist/tools/Bash/prompt.d.ts +1 -1
  28. package/dist/tools/Bash/prompt.d.ts.map +1 -1
  29. package/dist/tools/Bash/prompt.js +1 -1
  30. package/dist/tools/Bash/service-kernel.d.ts +75 -0
  31. package/dist/tools/Bash/service-kernel.d.ts.map +1 -0
  32. package/dist/tools/Bash/service-kernel.js +8 -0
  33. package/dist/tools/Glob/GlobTool.js +2 -2
  34. package/dist/tools/ReadSnippets/ReadSnippetsTool.d.ts +6 -0
  35. package/dist/tools/ReadSnippets/ReadSnippetsTool.d.ts.map +1 -0
  36. package/dist/tools/ReadSnippets/ReadSnippetsTool.js +7 -0
  37. package/dist/tools/ReadSnippets/prompt.d.ts +2 -0
  38. package/dist/tools/ReadSnippets/prompt.d.ts.map +1 -0
  39. package/dist/tools/ReadSnippets/prompt.js +6 -0
  40. package/dist/tools/SourcePack/SourcePackTool.d.ts +6 -0
  41. package/dist/tools/SourcePack/SourcePackTool.d.ts.map +1 -0
  42. package/dist/tools/SourcePack/SourcePackTool.js +7 -0
  43. package/dist/tools/SourcePack/prompt.d.ts +2 -0
  44. package/dist/tools/SourcePack/prompt.d.ts.map +1 -0
  45. package/dist/tools/SourcePack/prompt.js +6 -0
  46. package/dist/tools/Todo/TodoTool.js +2 -2
  47. package/dist/tools/prompts.d.ts.map +1 -1
  48. package/dist/tools/prompts.js +20 -16
  49. package/dist/tools/ui.d.ts.map +1 -1
  50. package/dist/tools/ui.js +20 -16
  51. package/package.json +4 -1
  52. package/dist/chunk-E75PYM7V.js +0 -6
  53. package/dist/{chunk-MFHY7PBM.js → chunk-4567YAXM.js} +3 -3
  54. 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
- formatFetchOutput,
8
- webFetch
9
- } from "./chunk-4UHQWBFA.js";
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-3JCXABUK.js";
19
+ } from "./chunk-F7UOI3YR.js";
16
20
  import {
17
21
  toolUserFacingNames
18
- } from "./chunk-QNZMO55X.js";
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-DN3KJZRV.js";
35
- import "./chunk-2U4BZXAI.js";
36
- import "./chunk-ITBZHMZQ.js";
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-HNIVNZIS.js";
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-VZWMPUSS.js";
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
- patchSummaries: {}
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
- command: readString(record, "command", "Bash"),
478
- timeoutMs: readOptionalPositiveInteger(record, "timeoutMs", "Bash")
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 fullText = [
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
- sanitizeSecrets(result.output)
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: adapted.adapter === "native" ? { exitCode: result.exitCode, outcome: result.outcome } : { exitCode: result.exitCode, outcome: result.outcome, adapter: adapted.adapter },
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
  };